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'environnement2. Configuration du code source3. Erreurs de fichier de grammaire Yacc4. Erreurs de compilation de code sur "C"5. Correction de certaines erreurs Défaut de segmentation6. Pour résumer1. 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 contenu2. 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
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 contenu3. 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 contenu4. 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; { ... } 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 contenu5. 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:
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:
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 contenu6. 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");
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 !
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