Mettre Perl directement depuis 1987

Après avoir lu la nouvelle " Code d'interpréteur Perl officiellement porté sur GitHub " sur LINUX.ORG.RU, j'ai décidé de jeter un œil au référentiel Perl 5, qui est déjà sur GitHub.

Il est incroyable de voir à quel point ils l'ont transféré de manière considérable et qualitative, en préservant non seulement absolument l'intégralité de l'histoire du projet pendant 32 ans, mais aussi des rapports de bogues (entrés dans les problèmes), des correctifs (entrés dans les PR), des versions et des branches. L'inscription " il y a 32 ans " à côté des dossiers provoque un sourire involontaire.

Que faire d'autre en ce vendredi soir terne, quand la pluie et la neige bruissent désagréablement dans la rue et que tous les chemins sont embourbés dans la neige fondante d'automne? C'est vrai, les yeux rouges! Donc, pour des raisons d'expérience et d'intérêt, j'ai décidé de prendre et d'assembler l'ancien Perl sur une machine x86_64 moderne avec la dernière version de GCC 9.2.0 en tant que compilateur. Un tel ancien code peut-il passer l'épreuve du temps?


Démonstration de twm , l'un des premiers gestionnaires de fenêtres pour le système X Window, sur une distribution Arch Linux moderne.

Pour être complètement authentique et nekromantnenko, j'ai déployé une machine virtuelle avec bare X et gestionnaire de fenêtres twm , qui date également de 1987. Qui sait, peut-être que Larry Wall a écrit son Perl en utilisant exactement twm , pour ainsi dire la technologie de pointe de l' époque. La distribution utilisée est Arch Linux. Tout simplement parce qu'il y a des choses utiles dans son référentiel qui ont été utiles plus tard. Alors allons-y!

Contenu:


1. Préparation de l'environnement
2. Configuration du code source
3. Erreurs de fichier de grammaire Yacc
4. Erreurs de compilation de code sur "C"
5. Correction de certaines erreurs Défaut de segmentation
6. Pour résumer

1. Préparation de l'environnement


Tout d'abord, nous installons sur un système d'exploitation déployé dans une machine virtuelle tous les utilitaires et compilateurs nécessaires pour assembler et éditer le code source: gcc , make , vim , git , gdb , etc. Certains d'entre eux sont déjà installés, tandis que d'autres sont disponibles dans le méta-package base-devel , il doit être installé s'il n'est pas installé. Une fois l'environnement prêt à l'action, nous obtenons une copie du code source Perl de 32 ans!

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

Grâce aux fonctionnalités de Git, nous n'avons pas besoin de faire glisser un tas de fichiers pour accéder à la toute première version du projet:

 * 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 

Nous ne téléchargeons qu'une petite quantité de données et, par conséquent, le référentiel avec le code source de la première version de Perl ne prend que 150 Ko.

À cette époque sombre et dense, il n'y avait pas de choses élémentaires comme les outils automatiques ( quelle bénédiction! ), Cependant, il y a un script Configure à la racine du référentiel. Quelle est la question? Mais le fait est que Larry Wall est l'inventeur de ces scripts qui ont permis de générer des Makefiles pour les machines UNIX les plus variées de l'époque. Comme l'indique l' article Wikipédia sur les scripts du même nom , Larry Wall a fourni le fichier Configure avec certains de ses logiciels, par exemple, un lecteur de news rn , trois ans avant d'écrire Perl. Par la suite, Perl n'a pas fait exception et un script déjà exécuté sur de nombreuses machines a été utilisé pour le construire. Plus tard, d'autres développeurs, par exemple des programmeurs de Trolltech, ont également repris cette idée. Ils ont utilisé un script similaire pour configurer la construction de leur framework Qt, que beaucoup de gens confondent avec configure depuis les autotools . C'est le zoo de ces scripts de différents développeurs qui a donné l'impulsion à la création d'outils pour leur génération simplifiée et automatique.

<< Passer au contenu

2. Configuration du code source


Le script Configure de la «vieille école», qui est déjà évident à partir de son Shebang ', qui a un espace:

 $ 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. # 

Selon le commentaire, il s'avère qu'il y avait des obus dans les scripts dont il n'était pas possible de laisser de commentaires! La situation spatiale semble inhabituelle, mais une fois que c'était la norme, consultez le lien pour plus d'informations ici . Plus important encore, il n'y a pas de différence pour les interpréteurs de shell modernes, qu'il y ait ou non un espace.

Assez de paroles, passons aux choses sérieuses! Nous commençons le script et voyons une hypothèse intéressante, qui s'avère ne pas être entièrement vraie:

 $ ./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--> 

Étonnamment, le script est interactif et contient une énorme quantité d'informations contextuelles diverses. Le modèle d'interaction utilisateur est construit sur des dialogues, analysant les réponses auxquelles le script modifie ses paramètres, selon lesquelles il générera ensuite des Makefiles. Je voulais personnellement vérifier si toutes les commandes shell sont en place?

 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. 

Apparemment avant, c'était loin d'être le cas. Je me demande de quoi l'utilitaire Mcc est responsable, lequel est introuvable? Le plus drôle, c'est que ce script dans les meilleures traditions des hackers de l'époque est plein d'humour amical. Maintenant, vous verrez à peine ceci:

 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. 

J'ai répondu à la plupart des questions avec la valeur par défaut ou avec ce que le script m'a proposé. La demande de drapeaux pour le compilateur et l'éditeur de liens a été particulièrement satisfaite et surprise:

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

Vous pouvez y écrire quelque chose d'intéressant, par exemple, -m32 pour créer un fichier exécutable 32 bits ou une bibliothèque, qui est nécessaire lors de la liaison. À la dernière question du script:

 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 

J'ai répondu positivement. A en juger par sa page Wikipedia, l'ancien utilitaire makedepend a été créé au tout début de la vie du projet Athena pour faciliter le travail avec Makefiles. Ce projet nous a donné le système X Window, Kerberos, Zephyr et a influencé beaucoup d'autres choses qui nous sont familières aujourd'hui. Tout cela est merveilleux, mais d'où vient cet utilitaire dans un environnement Linux moderne? Il n'a longtemps été utilisé par personne et nulle part. Mais si vous regardez attentivement la racine du référentiel, il s'avère que Larry Wall a écrit sa version de script de remplacement, que nous avons soigneusement décompressée et exécuté le script de configuration.

Makedepend terminé avec quelques erreurs étranges:

 ./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 

Ce sont peut-être eux qui ont causé le problème à cause duquel les Makefiles générés ont été un peu mâchés:

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

Je ne voulais absolument pas entrer dans la jungle des nouilles de coquille complexes de l'utilitaire makedepend et j'ai décidé de regarder attentivement les Makefiles, dans lesquels un motif étrange est apparu:

 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 ... 

Apparemment, un utilitaire a incorrectement inséré ses arguments dans l'échappement. En reprenant l'utilitaire de hache sed, j'ai décidé de corriger légèrement cette chose:

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

Étonnamment, l'astuce a fonctionné et les Makefiles ont fonctionné comme ils le devraient!

<< Passer au contenu

3. Erreurs de fichier de grammaire Yacc


Ce serait incroyable si le code vieux de 32 ans prenait et assemblait sans aucun problème. Malheureusement, les miracles ne se produisent pas. En étudiant l'arbre source, je suis tombé sur un fichier perl.y , qui est une description grammaticale de l'utilitaire yacc , qui a longtemps été remplacé par bison dans les distributions modernes. Le script situé sur le chemin / usr / bin / yacc appelle simplement bison en mode de compatibilité avec yacc . C'est juste que cette compatibilité n'est pas complète et lors du traitement de ce fichier, une énorme quantité d'erreurs affluent, que je ne sais pas comment corriger et que je ne veux pas vraiment, car il existe une solution alternative que j'ai apprise récemment.

Il y a à peine un an ou deux, Helio Chissini de Castro, qui est le développeur de KDE, a effectué un travail similaire et adapté KDE 1, 2 et Qt 1, 2 aux environnements et compilateurs modernes. Je me suis intéressé à son travail, j'ai téléchargé les codes sources des projets, mais lors de l'assemblage, je suis tombé sur un piège similaire en raison de l'incompatibilité du yacc et du bison , qui étaient utilisés pour construire l'ancienne version du métacompilateur moc. Par la suite, j'ai réussi à trouver une solution à ce problème sous la forme du remplacement du bison par l'utilitaire byacc (Berkeley Yacc), qui s'est avéré compatible avec les anciennes grammaires pour yacc et était disponible dans de nombreuses distributions Linux.

Un simple remplacement de yacc par byacc dans le système de construction m'a alors aidé, mais pas pour longtemps, car un peu plus tard dans les nouvelles versions de byacc, ils ont encore rompu la compatibilité avec yacc , interrompant le débogage associé à l'entité yydebug . Par conséquent, j'ai dû légèrement corriger la grammaire de l' utilitaire.

Ainsi, la stratégie de correction des erreurs de construction dans le fichier perl.y a été prédite par l'expérience précédente: installez l'utilitaire byacc , changez yacc en byacc dans tous les Makefiles, puis coupez yydebug de partout. Ces actions ont résolu tous les problèmes avec ce fichier, les erreurs ont disparu et la compilation s'est poursuivie.

<< Passer au contenu

4. Erreurs de compilation de code sur "C"


L'ancien code de Perl était plein d'horreurs, comme la notation depuis longtemps obsolète et oubliée des définitions de fonction du type K&R:

 format(orec,fcmd) register struct outrec *orec; register FCMD *fcmd; { ... } STR * hfetch(tb,key) register HASH *tb; char *key; { ... } /*VARARGS1*/ fatal(pat,a1,a2,a3,a4) char *pat; { fprintf(stderr,pat,a1,a2,a3,a4); exit(1); } 

Des fonctionnalités similaires ont été trouvées, par exemple, dans le code Microsoft Word 1.1a , qui est également assez ancien. Le premier standard du langage de programmation «C», appelé «C89», n'apparaîtra que dans deux ans. Les compilateurs modernes sont capables de travailler avec un tel code, mais certains IDE ne facilitent pas l'analyse de telles définitions et les mettent en évidence comme des erreurs de syntaxe, par exemple, Qt Creator a péché avant avant d'analyser le code qu'il contient dans la bibliothèque libclang .

Le compilateur GCC 9.2.0, générant un grand nombre d'avertissements, s'est engagé à compiler l'ancien code de la première version de Perl. Les feuilles des avertissements étaient si grandes que pour arriver à l'erreur, nous avons dû faire défiler plusieurs pages de l'échappement vers le haut. À ma grande surprise, la plupart des erreurs de compilation étaient typiques et principalement liées à des définitions prédéfinies, qui jouaient le rôle de drapeaux pour l'assemblage.


Le travail du compilateur GCC 9.2.0 moderne et du débogueur GDB 8.3.1 dans le gestionnaire de fenêtres twm et l'émulateur de terminal xterm .

Sous STDSTDIO , Larry Wall a expérimenté une bibliothèque de langage de programmation ancienne et non standard «C», et sous DEBUGGING, il y avait des informations de débogage avec le fameux yydebug , que j'ai mentionné ci-dessus. Par défaut, ces indicateurs ont été activés. En les désactivant dans le fichier perl.h et en ajoutant des définitions oubliées, j'ai pu réduire considérablement le nombre d'erreurs.

Un autre type d'erreur remplace les fonctions désormais normalisées de la bibliothèque standard et de la couche POSIX. Le projet possède ses propres malloc () , setenv () et autres entités qui ont créé des conflits.

Quelques endroits ont défini des fonctions statiques sans déclarations. Au fil du temps, les compilateurs ont commencé à adopter une approche plus stricte de ce problème et ont transformé l'avertissement en une erreur . Et enfin, quelques en-têtes oubliés, où iriez-vous sans eux.

À ma grande surprise, le correctif pour le code de 32 ans s'est avéré si minuscule qu'il peut être entièrement cité ici:

 diff --git a/malloc.cb/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.hb/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.yb/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.cb/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.cb/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.hb/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(); 

Excellent résultat pour un code de 32 ans! La référence non définie au bogue de liaison `crypt 'a été corrigée en ajoutant la directive -lcrypt au Makefile avec la bibliothèque libcrypt appropriée, après quoi j'ai finalement obtenu l'exécutable d'interpréteur Perl souhaité:

 $ 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 

<< Passer au contenu

5. Correction de certaines erreurs Défaut de segmentation


Après une compilation presque sans tracas, la chance m'a tourné le dos. Immédiatement après le démarrage de l'interpréteur Perl assemblé, j'ai eu quelques erreurs étranges et un défaut de segmentation à la fin:

 $ ./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) 

Après avoir rongé le texte source de la phrase Corrupt malloc , il s'est avéré qu'au lieu du système malloc () une sorte d'allocateur personnalisé a été appelée à partir de 1982. Fait intéressant, Berkeley est écrit dans l'un des littéraux de chaîne de son code source et Caltech dans un commentaire à côté. La collaboration entre ces universités était alors évidente très forte. En général, j'ai commenté cet allocateur de hacker et reconstruit le code source. Les erreurs de corruption de mémoire ont disparu, mais l'erreur de segmentation est restée. Ce n'était donc pas le point, et maintenant nous devons découvrir le débogueur.

En exécutant le programme sous gdb, j'ai constaté que le crash se produit lorsque la fonction de création d'un fichier temporaire mktemp () à partir de libc est appelée:

 $ 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 () 

Soit dit en passant, l'éditeur de liens jurait auparavant à cette fonction. Pas un compilateur, mais un éditeur de liens, ce qui m'a surpris:

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

La première pensée qui vous est probablement venue à l'esprit était également de remplacer la fonction dangereuse mktemp () par mkstemp () , ce que j'ai fait. L'avertissement de l'éditeur de liens a disparu, mais la faute de segmentation est restée à cet endroit de toute façon, seulement maintenant, elle était dans la fonction mkstemp () .

Par conséquent, vous devez maintenant regarder très attentivement le morceau de code associé à cette fonction. Là, j'ai découvert une chose assez étrange qui est mise en évidence dans cet extrait:

 char *e_tmpname = "/tmp/perl-eXXXXXX"; int main(void) { mktemp(e_tmpname); e_fp = f_open(e_tmpname, "w"); ... } 

Il s'avère que mktemp () essaie de changer le littéral du masque, qui se trouve dans la section .rodata , qui est évidemment voué à l'échec. Ou, après tout, il y a 32 ans, c'était acceptable, respecté dans le code, et même d'une manière ou d'une autre fonctionné?

Bien sûr, le remplacement de char * e_tmpname par char e_tmpname [] a corrigé cette erreur de segmentation et j'ai pu obtenir ce que j'ai tué pour toute la soirée:

 $ ./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 

Nous avons vérifié l'exécution à partir de la ligne de commande, mais qu'en est-il du fichier? J'ai téléchargé le premier «Hello World» pour le langage de programmation Perl sur Internet:

 ################# 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"; 

Ensuite, j'ai essayé de l'exécuter, mais, hélas, l'erreur de segmentation m'attendait à nouveau. Cette fois dans un endroit complètement différent:

 $ 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 () 

Le point intéressant suivant a été trouvé dans la fonction yyerror () , je cite l'extrait d'origine:

 // 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! ... 

Encore une fois, la situation est similaire à celle dont j'ai parlé plus haut. Les données de la section .rodata sont à nouveau modifiées . Peut-être que c'est juste des fautes de frappe à cause de Copy-Paste et au lieu de tname, ils voulaient écrire tmpbuf ? Ou y a-t-il vraiment une sorte de signification cachée derrière cela? Dans tous les cas, le remplacement de char * tokename [] par char tokename [] [32] supprime l'erreur de panne de segmentation et Perl nous dit ce qui suit:

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

Il s'avère qu'il n'aime pas toutes sortes d' utilisations strictes , c'est ce qu'il essaie de nous dire! Si vous supprimez ou commentez ces lignes dans le fichier, le programme démarre:

 $ ./perl test.pl Hello, World! 

<< Passer au contenu

6. Pour résumer


En fait, j'ai atteint mon objectif et fait que l'ancien code de 1987 non seulement compile, mais fonctionne également dans un environnement Linux moderne. Sans aucun doute, il reste encore une grande pile d'erreurs de défauts de segmentation diverses, probablement liées à la taille du pointeur sur une architecture 64 bits. Tout cela peut être nettoyé après quelques soirées avec le débogueur prêt. Mais ce n'est pas une tâche très agréable et plutôt fastidieuse. Après tout, cette expérience était initialement conçue comme un divertissement pour une soirée ennuyeuse, et non comme une œuvre à part entière, qui prendra fin. Y a-t-il un avantage pratique des actions entreprises? Peut-être qu'un jour, un archéologue numérique rencontrera cet article et il lui sera utile. Mais dans le monde réel, même l'expérience tirée de ces recherches, à mon avis, n'est pas trop précieuse.

Si quelqu'un est intéressé, je poste un ensemble de deux patchs. Le premier corrige les erreurs de compilation et le second corrige certaines erreurs de segmentation.


PS Je m'empresse de bouleverser les fans de joueurs destructeurs sur une seule ligne , cela ne fonctionne pas ici. La version de Perl est peut-être trop ancienne pour un tel divertissement.
PPS Tout va bien et passez un bon week-end. Merci à kawaii_neko pour une petite correction .

Mise à jour du 28 octobre 2019: un utilisateur du forum LINUX.ORG.RU, utilisant le surnom utf8nowhere , a fourni des liens très intéressants dans son commentaire sur cet article, dont les informations clarifient non seulement la situation avec les littéraux de chaîne mutables, mais prennent même en compte le problème d'utilisation décrit ci-dessus. Fonctions mktemp () ! Permettez-moi de citer ces sources, qui décrivent diverses incompatibilités entre le K&R C non normalisé et le GNU C:
Incompatibilités de GCC
Il existe plusieurs incompatibilités notables entre les versions GNU C et K&R (non ISO) de C.

GCC fait normalement des constantes de chaîne en lecture seule. Si plusieurs constantes de chaîne d'aspect identique sont utilisées, GCC ne stocke qu'une seule copie de la chaîne.
Une conséquence est que vous ne pouvez pas appeler mktemp avec un argument de chaîne constante. La fonction mktemp modifie toujours la chaîne vers laquelle pointe son argument.

Une autre conséquence est que sscanf ne fonctionne pas sur certains systèmes lorsqu'il reçoit une constante de chaîne comme chaîne ou entrée de contrôle de format. Cela est dû au fait que sscanf essaie incorrectement d'écrire dans la constante de chaîne.De même fscanf et scanf .

La meilleure solution à ces problèmes consiste à modifier le programme pour utiliser des variables char -array avec des chaînes d'initialisation à ces fins au lieu de constantes de chaîne. Mais si cela n'est pas possible, vous pouvez utiliser l' indicateur -fwritable-strings , qui indique à GCC de gérer les constantes de chaîne de la même manière que la plupart des compilateurs C.

Source: Utilisation du manuel officiel de GNU Compiler Collection (GCC 3.3) .
L' indicateur de compilateur -fwritable-strings a été déconseillé dans GCC 3.4 et définitivement supprimé dans GCC 4.0.
ANSI C rationale | String literals
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 ); } 

: Rationale for American National Standard for Information Systems, Programming Language C .
L'utilisateur VarfolomeyKote4ka a proposé un hack sale intéressant qui vous permet de contourner les erreurs de segmentation lors des tentatives de modification des données dans la section .rodata en les convertissant en section .rwdata . Il n'y a pas si longtemps, un article très intéressant est apparu sur Internet, «Du .rodata au .rwdata - introduction au mappage de la mémoire et aux scripts LD» par le programmeur guye1296 , qui explique comment faire cette astuce. Pour faciliter l'obtention du résultat souhaité, l'auteur de l'article a préparé un script assez volumineux pour l'éditeur de liens standard ld - rwdata.ld. Il suffit de télécharger ce script, de le placer à la racine du répertoire source Perl, de corriger l'indicateur LDFLAGS comme suit: LDFLAGS = -T rwdata.ld , puis reconstruire le projet. En conséquence, nous avons les éléments suivants:

 $ make clean && make -j1 $ mv perl perl_rodata $ curl -LOJ https://raw.githubusercontent.com/guye1296/ld_script_elf_blog_post/master/rwdata.ld $ sed -i 's/LDFLAGS =/LDFLAGS = -T rwdata.ld/' Makefile $ make clean && make -j1 $ mv perl perl_rwdata $ objdump -s -j .rodata perl_rodata | grep tmp -2 19da0 21233f5e 7e3d2d25 30313233 34353637 !#?^~=-%01234567 19db0 38392e2b 262a2829 2c5c2f5b 7c002400 89.+&*(),\/[|.$. 19dc0 73746465 7272002f 746d702f 7065726c stderr./tmp/perl 19dd0 2d655858 58585858 00323536 00617070 -eXXXXXX.256.app 19de0 656e6400 6c6f6f70 63746c00 66756e63 end.loopctl.func $ objdump -s -j .rwdata perl_rodata | grep tmp -2 objdump: section '.rwdata' mentioned in a -j option, but not found in any input file $ objdump -s -j .rwdata perl_rwdata | grep tmp -2 41d9c0 21233f5e 7e3d2d25 30313233 34353637 !#?^~=-%01234567 41d9d0 38392e2b 262a2829 2c5c2f5b 7c002400 89.+&*(),\/[|.$. 41d9e0 73746465 7272002f 746d702f 7065726c stderr./tmp/perl 41d9f0 2d655858 58585858 00323536 00617070 -eXXXXXX.256.app 41da00 656e6400 6c6f6f70 63746c00 66756e63 end.loopctl.func $ objdump -s -j .rodata perl_rwdata | grep tmp -2 objdump: section '.rodata' mentioned in a -j option, but not found in any input file $ ./perl_rodata -e 'print "Hello, World!\n";' Segmentation fault (core dumped) $ ./perl_rwdata -e 'print "Hello, World!\n";' Hello, World! 

Il s'avère que grâce à ce hack, presque toutes les modifications du deuxième patch peuvent être simplement omises! Bien que, bien sûr, amener le code à un aspect qui ne viole pas les normes est toujours préférable.

<< Passer au contenu

Source: https://habr.com/ru/post/fr473228/


All Articles