LINUX.ORG.RU

Perl прямиком из 1987 года

 , , , ,


7

5

Прочитав новость «Код интерпретатора Perl официально перенесён на GitHub» я решил взглянуть на репозиторий Perl 5, который теперь уже находится на GitHub’е.

Удивительно, как трепетно и качественно его перенесли, сохранив не только абсолютно всю 32-летнюю историю проекта, но и багрепорты (Issues), патчи (PRs), релизы и ветки. Надпись «32 years ago» рядом с файлами вызывает улыбку.

Что ещё делать в этот унылейший пятничный вечер, когда на улице неприятно моросит дождь со снегом, а все дорожки погрязли в осенней слякоти? Правильно, красноглазить! Так что я ради эксперимента и интереса решил взять и собрать древний Perl на современной x86_64-машинке с gcc (GCC) 9.2.0 в качестве компилятора. Сможет ли такой старый код пройти проверку временем?

Чтобы было совсем уж аутентичненько и некрофильненько, я развернул виртуальную машину с голыми иксами и twm, который тоже родом из 1987 года. Кто знает, может быть Larry Wall писал свой Perl используя именно twm, так сказать bleeding edge technology того времени. Используемый дистрибутив – Arch Linux. Просто потому что в его репозитории есть некоторые полезные вещи, которые впоследствии мне пригодились.

Итак, поехали!

Подготовка окружения

Сперва устанавливаем на развёрнутую виртуалку весь необходимый для сборки и редактирования исходного кода джентльменский набор утилит и компиляторов: gcc, make, vim, git, gdb и т. д. Некоторые из них уже установлены, а другие доступны в мета-пакете base-devel, его нужно обязательно установить, если он не установлен. После того, как окружение готово к активным действиям, получаем копию исходного кода Perl 32-летней выдержки!

git clone https://github.com/Perl/perl5/ --depth=1 -b perl-1.0

Благодаря особенностям Git’а нам не требуется тянуть кучу файлов, чтобы добраться до самого первого релиза проекта:

* commit 8d063cd8450e59ea1c611a2f4f5a21059a2804f1 (grafted, HEAD, tag: perl-1.0)
  Commit:     Larry Wall <lwall@jpl-devvax.jpl.nasa.gov>
  CommitDate: Fri Dec 18 00:00:00 1987 +0000
  
      a "replacement" for awk and sed

Мы скачиваем лишь небольшой объём данных и в итоге репозиторий с исходным кодом первой версии Perl занимает всего 150 КБ.

В то тёмное и дремучее время не было такой элементарной вещи, как autotools (счастье-то какое!), однако в корне репозитория имеется скрипт Configure. В чём же дело? А дело в том, что Larry Wall и является изобретателем подобных скриптов, которые позволяли сгенерировать Makefile’ы под самые разношёрстные UNIX-машины того времени. Как гласит одноимённая статья про эти скрипты в английской Википедии, Larry Wall ещё три года до написания Perl’а поставлял файл Configure с некоторым своим софтом, например, программой для чтения новостей rn. Впоследствии Perl не стал исключением и для его сборки использовался уже обкатанный на многих машинах скрипт. Позже эту идею подхватили и другие разработчики, например, программисты из компании Trolltech. Они использовали для конфигурации сборки своего фреймворка Qt похожий скрипт, который многие путают с configure из autotools. Именно зоопарк таких вот скриптов от разных разработчиков и послужил толчком к созданию средства для их упрощённой и автоматической генерации.

Конфигурирование исходного кода

Скрипт Configure «старой закалки», что уже видно по его Shebang’у, в котором имеется пробел:

$ cat Configure | head -5
#! /bin/sh
#
# If these # comments don't work, trim them.  Don't worry about any other
# shell scripts, Configure will trim # comments from them for you.
#

Согласно комментарию, оказывается существовали shell’ы, в скриптах которых не было возможности оставлять комментарии! Ситуация с пробелом выглядит непривычно, но когда-то подобное было нормой, см. дополнительную информацию по ссылкам здесь. Самое главное, что для современных shell-интерпретаторов нет никакой разницы, имеется там пробел или нет.

Хватит лирики, переходим к делу! Запускаем скрипт и видим интересное предположение, которое оказывается не совсем верным:

$ ./Configure 
(I see you are using the Korn shell.  Some ksh's blow up on Configure,
especially on exotic machines.  If yours does, try the Bourne shell instead.)
 
Beginning of configuration questions for perl kit.
 
Checking echo to see how to suppress newlines...
...using -n.
Type carriage return to continue.  Your cursor should be here-->

Удивительно, что скрипт является интерактивным и содержит огромную кучу различной справочной информации. Модель взаимодействия с пользователем построена на диалогах, анализируя ответы на которые скрипт меняет свои параметры, по которым он впоследствии будет генерировать Makefile’ы. Меня лично заинтересовала проверка того, все ли команды оболочки находятся на своём месте?

Locating common programs...
expr is in /bin/expr.
sed is in /bin/sed.
echo is in /bin/echo.
cat is in /bin/cat.
rm is in /bin/rm.
mv is in /bin/mv.
cp is in /bin/cp.
tr is in /bin/tr.
mkdir is in /bin/mkdir.
sort is in /bin/sort.
uniq is in /bin/uniq.
grep is in /bin/grep.
 
Don't worry if any of the following aren't found...
test is in /bin/test.
egrep is in /bin/egrep.
I don't see Mcc out there, offhand.

Видимо раньше это было далеко не так. Интересно, а за что отвечает утилита Mcc, которую не удалось найти?

Самое забавное, что этот скрипт в лучших хакерских традициях того времени полон дружелюбного юмора. Сейчас подобное практически не увидишь:

Is your "test" built into sh? [n] (OK to guess) OK
 
Checking compatibility between /bin/echo and builtin echo (if any)...
They are compatible.  In fact, they may be identical.
 
Your C library is in /lib/libc.a.  You're normal.
 
Extracting names from /lib/libc.a for later perusal...done
 
Hmm...  Looks kind of like a USG system, but we'll see...
 
Congratulations.  You aren't running Eunice.
 
It's not Xenix...
 
Nor is it Venix...
 
Checking your sh to see if it knows about # comments...
Your sh handles # comments correctly.
 
Okay, let's see if #! works on this system...
It does.
 
Checking out how to guarantee sh startup...
Let's see if '#!/bin/sh' works...
Yup, it does.

На большинство вопросов я ответил значением по умолчанию, либо тем, что мне предложил скрипт. Особенно порадовал и удивил запрос флажков для компилятора и линкёра:

Any additional cc flags? [none] 
 
Any additional ld flags? [none] 

Туда можно прописать что-нибудь интересное, например, -m32 для 32-битной сборки или библиотеку, которая требуется при линковке.

На последний вопрос скрипта:

Now you need to generate make dependencies by running "make depend".
You might prefer to run it in background: "make depend > makedepend.out &"
It can take a while, so you might not want to run it right now.

Run make depend now? [n] y

Я ответил положительно. Древняя утилита makedepend, судя по Википедии, была создана в самом начале жизни проекта Athena, для облегчения работы с Makefile’ами. Этот проект подарил нам X Window System, Kerberos, Zephyr и повлиял на множество других привычных сегодня вещей. Всё это замечательно, но вот только откуда эта утилита в современном Linux-окружении? Она давно уже никем и нигде не используется. Но если посмотреть внимательно в корень репозитория, оказывается, что Larry Wall написал её скриптовую версию-заменитель, которую нам заботливо распаковал и выполнил конфигурационный скрипт.

Выполнение makedepend завершилось с некоторыми странными ошибками:

./makedepend: command substitution: line 82: unexpected EOF while looking for matching `''
./makedepend: command substitution: line 83: syntax error: unexpected end of file
./makedepend: command substitution: line 82: unexpected EOF while looking for matching `''
./makedepend: command substitution: line 83: syntax error: unexpected end of file

Возможно именно они повлекли за собой проблему из-за которой сгенерированные сборочные файлы Makefile оказались немного пожёванными:

$ make
make: *** No rule to make target '<built-in>', needed by 'arg.o'.  Stop.

Лезть в дебри замысловатой shell-лапши утилиты makedepend мне решительно не хотелось и я решил тщательно присмотреться к Makefile’ам, в которых выявилась странная закономерность:

arg.o: arg.c
arg.o: arg.h
arg.o: array.h
arg.o: <built-in>
arg.o: cmd.h
arg.o: <command-line>
arg.o: config.h
arg.o: EXTERN.h
...
array.o: arg.h
array.o: array.c
array.o: array.h
array.o: <built-in>
array.o: cmd.h
array.o: <command-line>
array.o: config.h
array.o: EXTERN.h
...

Видимо какая-то утилита неправильно вставила свои аргументы в выхлоп. Взяв в руки топор утилиту sed я решил поправить это дело:

$ sed -i '/built-in/d' Makefile
$ sed -i '/command-line/d' Makefile

На удивление трюк сработал и Makefile’ы заработали как надо!

Ошибки файла грамматики yacc

Было бы просто невероятно, если бы 32-летний код взял и собрался без каких-либо проблем. К сожалению, чудес не бывает. Изучая дерево исходного кода я наткнулся на файл perl.y, представляющий собой описания грамматики для утилиты yacc, которая в современных дистрибутивах давно заменена на bison. Скрипт, находящийся по пути /usr/bin/yacc, просто вызывает bison в режиме совместимости c yacc. Вот только эта совместимость не является полной и при обработке этого файла сыпется огромная куча ошибок, исправлять которые я не умею да и не очень хочу, ведь есть альтернативное решение, о котором я узнал совсем недавно.

Буквально год или два назад Helio Chissini de Castro, являющийся разработчиком KDE, занимался похожей работой и адаптировал KDE 1, 2 и Qt 1, 2 под современные окружения и компиляторы. Я заинтересовался его работой, скачал исходные коды проектов, но при сборке наткнулся на такой же подводный камень из-за несовместимости yacc и bison, которые использовались для построения древней версии метакомпилятора moc. Впоследствии мне удалось найти решение этой проблемы в виде замены bison на утилиту byacc (Berkeley Yacc), которая оказалась совместимой со старыми грамматиками для yacc и была доступна во многих дистрибутивах.

Простая замена yacc на byacc в Makefile меня тогда выручила, правда не надолго, поскольку чуть позже в новых версиях byacc всё-таки сломали совместимость с yacc, отломив отладку через yydebug. Поэтому пришлось немного исправлять грамматику.

Итак, стратегия исправления ошибок построения в файле perl.y была предсказана предыдущим опытом: устанавливаем утилиту byacc, меняем yacc на byacc во всех Makefile, затем вырезаем отовсюду yydebug. Эти действия решили все проблемы с этим файлом, ошибки исчезли и компиляция продолжилась.

Ошибки компиляции кода на «C»

Древний код Perl’а пестрил ужасами вроде давным-давно устаревшей и всеми забытой нотации определений функций вида K&R:

format(orec,fcmd)
register struct outrec *orec;
register FCMD *fcmd;
{
    ...
}

STR *
hfetch(tb,key)
register HASH *tb;
char *key;
{
    ...
}

char *
getparen(compex,n)
register COMPEX *compex;
int n;
{
    ...
}

Подобные особенности встречались, например, в коде Microsoft Word 1.1a, который тоже достаточно древний. Стандарт языка программирования «C», под названием «C89» появится лишь через два года. Современные компиляторы умеют работать с таким кодом, а вот некоторые IDE не осиливают разбирать подобные определения и подсвечивают их как синтаксические ошибки, например, раньше таким грешил Qt Creator до того, как парсинг кода в нём перевели на libclang.

Компилятор GCC 9.2.0, изрыгая огромное количество предупреждений, взялся компилировать древний код первой версии Perl. Простыни из предупреждений были настолько велики и для того чтобы добраться до ошибки, приходилось пролистывать несколько страниц выхлопа вверх. На моё удивление, большинство ошибок компиляции были типовыми и в основном связанными с предопределёнными дефайнами, которые играли роль флажков для сборки.

Под дефайном STDSTDIO Larry Wall экспериментировал с какой-то древней и нестандартной библиотекой языка программирования «C», а под дефайном DEBUGGING была отладочная информация с пресловутым yydebug, про который я упоминал выше. По умолчанию эти флажки были включены. Выключив их в файле perl.h и добавив несколько забытых дефайнов мне удалось значительно уменьшить количество ошибок.

Другой тип ошибок – переопределения ныне стандартизированных функций стандартной библиотеки и слоя POSIX. В проекте имеется свой malloc(), setenv() и другие сущности, которые создавали конфликты.

В парочке мест были определены статические функции без объявлений. Компиляторы со временем стали строже относиться к этой проблеме и превратили предупреждение в ошибку. Ну и напоследок парочка забытых хедеров, куда же без них.

На моё удивление патч для кода 32-летней давности получился просто крошечным:

diff --git a/malloc.c b/malloc.c
index 17c3b27..a1dfe9c 100644
--- a/malloc.c
+++ b/malloc.c
@@ -79,6 +79,9 @@ static	u_int nmalloc[NBUCKETS];
 #include <stdio.h>
 #endif
 
+static findbucket(union overhead *freep, int srchlen);
+static morecore(register bucket);
+
 #ifdef debug
 #define	ASSERT(p)   if (!(p)) botch("p"); else
 static
diff --git a/perl.h b/perl.h
index 3ccff10..e98ded5 100644
--- a/perl.h
+++ b/perl.h
@@ -6,16 +6,16 @@
  * 
  */
 
-#define DEBUGGING
-#define STDSTDIO	/* eventually should be in config.h */
+//#define DEBUGGING
+//#define STDSTDIO	/* eventually should be in config.h */
 
 #define VOIDUSED 1
 #include "config.h"
 
-#ifndef BCOPY
-#   define bcopy(s1,s2,l) memcpy(s2,s1,l);
-#   define bzero(s,l) memset(s,0,l);
-#endif
+//#ifndef BCOPY
+//#   define bcopy(s1,s2,l) memcpy(s2,s1,l);
+//#   define bzero(s,l) memset(s,0,l);
+//#endif
 
 #include <stdio.h>
 #include <ctype.h>
@@ -183,11 +183,11 @@ double atof();
 long time();
 struct tm *gmtime(), *localtime();
 
-#ifdef CHARSPRINTF
-    char *sprintf();
-#else
-    int sprintf();
-#endif
+//#ifdef CHARSPRINTF
+//    char *sprintf();
+//#else
+//    int sprintf();
+//#endif
 
 #ifdef EUNICE
 #define UNLINK(f) while (unlink(f) >= 0)
diff --git a/perl.y b/perl.y
index 16f8a9a..1ab769f 100644
--- a/perl.y
+++ b/perl.y
@@ -7,6 +7,7 @@
  */
 
 %{
+#include <stdlib.h>
 #include "handy.h"
 #include "EXTERN.h"
 #include "search.h"
diff --git a/perly.c b/perly.c
index bc32318..fe945eb 100644
--- a/perly.c
+++ b/perly.c
@@ -246,12 +246,14 @@ yylex()
     static bool firstline = TRUE;
 
   retry:
+#ifdef DEBUGGING
 #ifdef YYDEBUG
     if (yydebug)
 	if (index(s,'\n'))
 	    fprintf(stderr,"Tokener at %s",s);
 	else
 	    fprintf(stderr,"Tokener at %s\n",s);
+#endif
 #endif
     switch (*s) {
     default:
diff --git a/stab.c b/stab.c
index b9ef533..9757cfe 100644
--- a/stab.c
+++ b/stab.c
@@ -7,6 +7,7 @@
  */
 
 #include <signal.h>
+#include <errno.h>
 #include "handy.h"
 #include "EXTERN.h"
 #include "search.h"
diff --git a/util.h b/util.h
index 4f92eeb..95cb9bf 100644
--- a/util.h
+++ b/util.h
@@ -28,7 +28,7 @@ void	prexit();
 char	*get_a_line();
 char	*savestr();
 int	makedir();
-void	setenv();
+//void	setenv();
 int	envix();
 void	notincl();
 char	*getval();

Ошибка линковки undefined reference to 'crypt' была исправлена добавлением в Makefile соответствующей библиотеки -lcrypt, после чего я наконец-то получил вожделенный исполнительный файл perl:

$ file perl
perl: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=fd952ceae424613568530b3a2ca88ebd6477e0ae, for GNU/Linux 3.2.0, not stripped

Исправление некоторых ошибок Segmentation fault

После практически беспроблемной компиляции удача отвернулась от меня. Сразу после запуска собранного интерпретатора Perl’а я получил несколько странных ошибок и сегфолт в конце:

$ ./perl -e 'print "Hello World!\n";'
Corrupt malloc ptr 0x2db36040 at 0x2db36000
Corrupt malloc ptr 0x2db36880 at 0x2db36800
Corrupt malloc ptr 0x2db36080 at 0x2db36040
Corrupt malloc ptr 0x2db37020 at 0x2db37000
Segmentation fault (core dumped)

Грепнув исходный текст по фразе Corrupt malloc, оказалось, что вместо системного malloc() вызывается какой-то кастомный аллокатор родом из 1982 года. Интересно, что в одном из строковых литералов прописано Berkeley, а в комментарии рядом – Caltech. Сотрудничество между этими университетами видимо тогда было очень сильно. В общем, я закомментировал этот хакерский аллокатор и пересобрал исходный код. Ошибки порчи памяти исчезли, а сегфолт остался. Значит дело было не в этом и теперь нужно расчехлять отладчик.

Запустив программу под gdb я обнаружил что падение происходит при вызове функции создания временного файла mktemp() из libc:

$ gdb --args ./perl -e 'print "Hello, World!\n";'
(gdb) r                                                                                   
Starting program: /home/exl/perl5/perl -e print\ \"Hello\ World\!\\n\"\;
                                                                                          
Program received signal SIGSEGV, Segmentation fault.                                      
0x00007ffff7cd20c7 in __gen_tempname () from /usr/lib/libc.so.6                           
(gdb) bt                                                                                  
#0  0x00007ffff7cd20c7 in __gen_tempname () from /usr/lib/libc.so.6                       
#1  0x00007ffff7d71577 in mktemp () from /usr/lib/libc.so.6                               
#2  0x000055555556bb08 in main () 

На эту функцию, кстати, ранее ругался линковщик. Не компилятор, а именно линковщик, что меня удивило:

/usr/bin/ld: perl.o: in function `main':
perl.c:(.text+0x978c): warning: the use of `mktemp' is dangerous, better use `mkstemp' or `mkdtemp'

Первая мысль, которая наверняка вам тоже пришла в голову – заменить небезопасную функцию mktemp() на mkstemp(), что я именно и сделал. Предупреждение линковщика исчезло, но сегфолт в этом месте всё равно остался, только теперь он был в функции mkstemp().

Следовательно теперь нужно очень внимательно посмотреть на кусок кода, который сопряжён с этой функцией. Там я обнаружил довольно странную вещь, которая выделена в этом сниппете:

char *e_tmpname = "/tmp/perl-eXXXXXX";

int main(void) {
   mktemp(e_tmpname);
   e_fp = f_open(e_tmpname, "w");
   
   ...
}

Получается mktemp() пытается поменять литерал по маске, который находится в .rodata, что заведомо обречено на провал. Или всё-таки 32 года назад подобное было допустимо, встречалось в коде и даже как-то работало?

Конечно, добавление дополнительного массива-буфера исправило этот сегфолт и я смог получить то, на что убил целый вечер:

$ ./perl -e 'print "Hello World!\n";'
$ Hello, World!
$ ./perl -e '$a = 5; $b = 6.3; $c = $a+$b; print $c."\n";'
$ 11.3000000000000007
$ ./perl -v
$Header: perly.c,v 1.0 87/12/18 15:53:31 root Exp $
Patch level: 0

Исполнение из командной строки мы проверили, а что насчёт файла? Я скачал первый попавшийся «Hello World» из интернета:

################# test.pl
#!/usr/bin/perl
#
# The traditional first program.
 
# Strict and warnings are recommended.
use strict;
use warnings;
 
# Print a message.
print "Hello, World!\n";

Затем попробовал его запустить, но, увы, меня снова ждал сегфолт. На этот раз совершенно в другом месте:

$ gdb --args ./perl test.pl                    
(gdb) r                                                                                   
Starting program: /home/exl/perl5/perl test.pl                                            
                                                                                          
Program received signal SIGSEGV, Segmentation fault.                                      
0x00007ffff7d1da75 in __strcpy_sse2_unaligned () from /usr/lib/libc.so.6                  
(gdb) bt                                                                                  
#0  0x00007ffff7d1da75 in __strcpy_sse2_unaligned () from /usr/lib/libc.so.6              
#1  0x00005555555629ea in yyerror ()                                                      
#2  0x0000555555568dd6 in yyparse ()                                                      
#3  0x000055555556bd4f in main () 

В функции yyerror() обнаружилась следующая интересная ситуация, привожу оригинальный сниппет:

// perl.y
char *tokename[] = {
    "256",
    "word",
    "append",
    ...

// perl.c
yyerror(s)
char *s;
{
     char tmpbuf[128];
     char *tname = tmpbuf;
 
     if (yychar > 256) {
     tname = tokename[yychar-256]; // ???
         if (strEQ(tname,"word"))
             strcpy(tname,tokenbuf); // Oops!
         else if (strEQ(tname,"register"))
             sprintf(tname,"$%s",tokenbuf); // Oops!

     ...

Снова ситуация похожа на ту, про которую я писал выше. Снова модифицируются данные в секции .rodata. Может быть это просто опечатки из-за Copy-Paste и вместо tname хотели написать tmpbuf? Или же действительно за подобным стоит какой-то скрытый смысл? В любом случае, замена tname на tmpbuf убирает сегфолт и Perl говорит нам следующее:

$ ./perl test.pl
syntax error in file test.pl at line 7, next token "word"
Execution aborted due to compilation errors.

Получается, не нравятся ему всякие новомодные use strict, вот что он нам оказывается пытался сказать! Если удалить или закомментировать эти строчки, то программа запускается:

$ ./perl test.pl
Hello, World!

Подведём итоги

Фактически я добился своей цели и заставил древний код родом из 1987 года не только компилироваться, но и работать в современном Linux-окружении. Несомненно, там ещё осталась большая куча различных сегфолтов, возможно связанных с размером указателя на 64-битной архитектуре. Всё это можно вычистить посидев с отладчиком наперевес. Вот только это не слишком приятное и довольно нудное занятие. Ведь изначально этот эксперимент планировался как развлечение на скучный вечер, а не как полноценная работа, которая будет доведена до конца. Имеется ли какая-нибудь практическая польза от проведённых действий? Может быть когда-нибудь какой-нибудь цифровой археолог наткнётся на эту статью и она ему будет полезна. Но в реальном мире даже опыт, извлечённый из подобных изысканий, по моему мнению не слишком уж ценен.

Если кому-либо интересно, выкладываю набор из двух патчей. Первый фиксит ошибки компиляции, а второй – некоторые сегфолты.

http://esxi.z-lab.me:666/~exl_lab/patches/Perl1987-Fix-compilation-errors.patch
http://esxi.z-lab.me:666/~exl_lab/patches/Perl1987-Fix-some-Segmentation-Faults.patch

P.S. Спешу огорчить любителей патча Бармина, здесь он не работает. Возможно версия Perl’а слишком уж старая для подобных развлечений.
P.P.S. Всем добра и приятных выходных.

>>> Просмотр (1605x600, 55 Kb)

★★★★★

Проверено: a1batross ()

Эх, не я один значит люблю попердолиться вечером с пятницы на понедельник

e7z0x1 ★★★★ ()

Хоть и не программист, всегда интересно читать твои статьи, что по линуксу что по гайке. Продолжай в том же духе.

liss21 ★★★ ()

Порой софт пятилетней давности не всегда получается завести, а ты заставил работать то, что в принципе работать не должно.
Похвально.

mord0d ()

Есть те, кто всё прочитал и не умер от этого? Отпишитесь.

Virtuos86 ★★★★★ ()
Ответ на: комментарий от Virtuos86

Есть те, кто всё прочитал и не умер от этого? Отпишитесь.

А смысл оптисываться? Жертвы твиттера один черт не поверят.

dexpl ★★★★★ ()

Отличная статья, но у меня с телефона почему-то кат не работает, вся главная состоит только из этой статьи.

balsoft ()

Спасибо!

Проделана огромная работа, и это вызывает уважение.

Отдельное спасибо за то, что не поленился оформить результаты исследования в виде статьи.

Bass ★★★ ()
Ответ на: комментарий от alexferman

Удваиваю. Видеть такую, пусть и интересную простыню на главной, совсем не кошерно.

ilinsky ★★★★ ()

Уберите подробности под кат. Палец устаёт скроллить вот это всё. a1batross.

meliafaro ★★★★ ()
Последнее исправление: meliafaro (всего исправлений: 1)

Респект! А насчет полезности - опыт ковыряния в различном легаси(пусть и не таком экстремальном) так или иначе нужен.

Pinkbyte ★★★★★ ()

Your C library is in /lib/libc.a. You're normal.

алярма! неинклюзивность!

mos ★★★★☆ ()

Вообще не угадал автора по заголовку! Вот вообще ты у меня не ассоциируешься с перлом.

ChekPuk ()

Взяв в руки топор утилиту sed я решил поправить это дело:

Всё-таки не получилось за 32 года «заменить» sed?

NeXTSTEP ★★ ()

Perl прямиком из 1987 года

Да уж. Лучше бы он там и остался.

rebforce ()

Сдохли хакеры, ну и RIP с ними

Примечательно что у них репа репа, что 32 года назад, что сегодня выглядит как кусок этого самого. Прямо в корне, вперемешку валяются исходники и заголовочные файлы на c, говнокод на самом перле, какие-то сборочные скрипты, пайплайны, readme на корейском. Понятно почему все перешли на более другие альтернативы.

d_a ★★★★★ ()

Конечно, добавление дополнительного массива-буфера исправило этот сегфолт

Палю годноту: надо заменить char *str = ... на char str[] = ...

#include <stdio.h>
#include <string.h>

char data[] = "Hello  world";

int main()
{
	memcpy(data, "Goodbye", 7);
	puts(data);
	return 0;
}

kawaii_neko ★★★ ()
Ответ на: комментарий от Virtuos86

Есть те, кто всё прочитал и не умер от этого? Отпишитесь.

с каких пор на техническом ресурсе умирают от технического чтива? очень даже легко читается. я пролистал и понял, что EXL не только какие-то не очень коменты оставляет, а вполне себе часть сообщества. такая гиковость - это проявление hacker culture. это очень хорошее явление.

crypt ★★★★★ ()
Последнее исправление: crypt (всего исправлений: 1)

Получается mktemp() пытается поменять литерал по маске, который находится в .rodata, что заведомо обречено на провал. Или всё-таки 32 года назад подобное было допустимо, встречалось в коде и даже как-то работало?

когда читал утром, меня лично это сильно заинтересовало, но ни беглый гугл, ни чего попытки сообразить самому результатов не дали. очевидно, это как-то работало раньше.

crypt ★★★★★ ()

Интересная статья. Спасибо.

Odalist ★★★★★ ()
Ответ на: комментарий от crypt

С каких пор на ЛОР юзеры перестали понимать юмор и превратились в технопуритан?

Virtuos86 ★★★★★ ()
Ответ на: комментарий от kawaii_neko

Спасибо, отлично. Мне вот что интересно, располагались ли эти литералы где-то в .rodata или подобном в те древние времена или же структура исполнительных файлов позволяла делать такие вот трюки?

EXL ★★★★★ ()
Последнее исправление: EXL (всего исправлений: 1)

Ух ты, целая статья на лоре. Давайте заделаем раздел, «Статьи» или «Публикации». Где люди будут писать не тупняк типа «Простите бога ради» ака я :D А вот такие Публикации. java -r @maxcom сейчас много экспериментов, может попробовать ну просто как в перспективе не сразу конечно?

LINUX-ORG-RU ()
Ответ на: комментарий от crypt

очевидно, это как-то работало раньше

Раньше был a.out и там data можно записывать.

no-such-file ★★★★★ ()
Ответ на: комментарий от mord0d

а в том и проблема, что проект пятилетней давности уже тащит за собой целый ворох всякого ненужно. а старые версии были относительно независимы и просты, и их собирать проще.

Iron_Bug ★★★★ ()
Ответ на: комментарий от mord0d

кстати, отдельное спасибо надо сказать девелоперам сишечки, которые не плодили стандарты как угорелые.

Iron_Bug ★★★★ ()
Ответ на: комментарий от mos

В смысле неинклюзивность? Видишь, даже про normal отдельно подумали.

t184256 ★★★★★ ()
Ответ на: комментарий от Virtuos86

С каких пор на ЛОР юзеры перестали понимать юмор и превратились в технопуритан?

а, так остальной лор твоих шуток тоже не детектит. ну бывает, смирись.

crypt ★★★★★ ()
Ответ на: комментарий от crypt

Получается mktemp() пытается поменять литерал по маске, который находится в .rodata, что заведомо обречено на провал. Или всё-таки 32 года назад подобное было допустимо, встречалось в коде и даже как-то работало?

когда читал утром, меня лично это сильно заинтересовало, но ни беглый гугл, ни чего попытки сообразить самому результатов не дали.

Видимо, гугл тебя за что-то не любит.

ANSI C rationale http://www.lysator.liu.se/c/rat/c1.html#3-1-4

String literals are specified to be unmodifiable. This specification allows implementations to share copies of strings with identical text, to place string literals in read-only memory, and perform certain optimizations. However, string literals do not have the type array of const char, in order to avoid the problems of pointer type checking, particularly with library functions, since assigning a pointer to const char to a plain pointer to char is not valid. Those members of the Committee who insisted that string literals should be modifiable were content to have this practice designated a common extension (see F.5.5).

Existing code which modifies string literals can be made strictly conforming by replacing the string literal with an initialized static character array. For instance,

        char *p, *make_temp(char *str);
            /* ... */
        p = make_temp("tempXXX");
                /* make_temp overwrites the literal */
                /* with a unique name */

can be changed to:

        char *p, *make_temp(char *str);
            /* ... */
        {
            static char template[ ] = "tempXXX";
            p = make_temp( template );
        }

https://gcc.gnu.org/onlinedocs/gcc-3.3/gcc/Incompatibilities.html

There are several noteworthy incompatibilities between GNU C and K&R (non-ISO) versions of C.

GCC normally makes string constants read-only. If several identical-looking string constants are used, GCC stores only one copy of the string.

One consequence is that you cannot call mktemp with a string constant argument. The function mktemp always alters the string its argument points to.

Another consequence is that sscanf does not work on some systems when passed a string constant as its format control string or input. This is because sscanf incorrectly tries to write into the string constant. Likewise fscanf and scanf.

The best solution to these problems is to change the program to use char-array variables with initialization strings for these purposes instead of string constants. But if this is not possible, you can use the -fwritable-strings flag, which directs GCC to handle string constants the same way most C compilers do.

P.S. Флаг -fwritable-strings deprecated в GCC 3.4 и удален в GCC 4.0.

utf8nowhere ★★ ()
Ответ на: комментарий от Iron_Bug

в том и проблема, что проект пятилетней давности уже тащит за собой целый ворох всякого ненужно

Именно. Софтина требует конкретные версии библиотек, те за собой тащат ещё что-то, и всё это линкуется с конкретной версией libc6 (или нужно лопатить чуть ли не весь код). Но виной тому не софт, а ленивые программисты.

а старые версии были относительно независимы и просты

И писали их люди, которые умели пользоваться серой массой в черепушке. Но, тем не менее, заставить работать софт, который писался на другой архитектуре, которой по большей части и в живых-то нет — это достижение.

кстати, отдельное спасибо надо сказать девелоперам сишечки, которые не плодили стандарты как угорелые.

Сейчас без стандартов обезьянки не могут договориться.

mord0d ()
Ответ на: комментарий от mord0d

Сейчас без стандартов обезьянки не могут договориться.

Что значит «сейчас», ксеноморф (извини, не знаю, к какому виду ты принадлежишь)? Стандартизация —- это такой способ договориться вообще-то. А как договариваются между собой особи вашего вида?

dexpl ★★★★★ ()
Ответ на: комментарий от dexpl

ксеноморф

Ну если на моей аватарке ксеноморф…

Стандартизация —- это такой способ договориться вообще-то.

Оно работает скорее как "делай так, иначе отстрелишь себе заднюю ногу" (посмотри на Go).

А как договариваются между собой особи вашего вида?

"Если ты сможешь прочитать мой код — сможешь его и использовать исправить!"

mord0d ()

Эталонное страдание мамкиных спецыализдов

mos ★★★★☆ ()

Древний код Perl’а пестрил ужасами вроде давным-давно устаревшей и всеми забытой нотации определений функций вида K&R:

Обычный фортрановский синтаксис, ничего страшного.

LongLiveUbuntu ★★★★★ ()

Доктор Франкенштейн вы ли это?

Ramil ★★★ ()

долго скроллил вниз, ОП посту небыло конца

eR ★★★★★ ()
Ответ на: комментарий от utf8nowhere

Большое спасибо за инфу. Действительно, многое проясняется! В своё время кстати очень активно юзал GCC 3.3.6 (использовался для платформы EZX) и GCC 3.4.3 (использовался для платформы MotoMAGX), но о подобном флажке не знал.

EXL ★★★★★ ()
Ответ на: комментарий от VarfolomeyKote4ka

Интересненький грязненький хак!

Как это правильно сделать?

Попробовал objcopy --writable-text perl и что-то ничего, сегфолты всё равно.

EXL ★★★★★ ()
Ответ на: комментарий от utf8nowhere

Видимо, гугл тебя за что-то не любит.

«чтобы правильно задать вопрос, надо знать половину ответа» (с) :)

crypt ★★★★★ ()
Ответ на: комментарий от EXL

С помощью ld, который вызывается в конце самом, где идет компиляция .o, хз как объяснить доступно. Но вот тут есть в конце (Using the default linker script): https://guyonbits.com/from-rodata-to-rwdata-introduction-to-memory-mapping-an...

VarfolomeyKote4ka ()
Последнее исправление: VarfolomeyKote4ka (всего исправлений: 1)
Ответ на: комментарий от VarfolomeyKote4ka

Хаха, я как раз разобрался с этим и ты запостил комментарий. Действительно, работает!

хз как объяснить доступно

Я просто скачал вот этот скрипт по твоей ссылке для линкёра, положил его в директорию проекта, а в Makefile в LDFLAGS добавил следующее:

LDFLAGS = -T rwdata.ld

После пересобрал Perl и сегфолты пропали, а текстовые литералы стали изменяемыми.

Спасибо тебе, Котеечка, за интересное предположение :з

EXL ★★★★★ ()
Ответ на: комментарий от VarfolomeyKote4ka

Теперь буду играться с этой штукой и пугать неокрепших сишников:

$ cat segfault.c 
// segfault.c
#include <stdio.h>

const char *str = "WTF THIS SHIT";

void main(void) {
    *(char *)(str + 11) = 'O';
    fprintf(stderr, "%s\n", str);
}

$ gcc segfault.c
$ ./a.out 
Segmentation fault (core dumped)

$ curl -LOJ https://raw.githubusercontent.com/guye1296/ld_script_elf_blog_post/master/rwdata.ld
$ gcc segfault.c -T rwdata.ld
$ ./a.out 
WTF THIS SHOT
EXL ★★★★★ ()
Ответ на: комментарий от EXL

Этим можно даже сильно окрепших сишников напугать. Потому что получается, что фактически поведение программы еще и от подсунутых опций ленкера зависит.

Вообще, встретив такой сегфолт, я бы долго тупил над исходниками и думал как оно могло у автора работать.

praseodim ★★★★ ()
Закрыто добавление комментариев для недавно зарегистрированных пользователей (со score < 50)