Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Construction d'un ELF et désassemblage (gdb - linux - x86)

12 réponses
Avatar
Kevin Denis
Bonjour,

Tout d'abord, je ne suis pas certain d'être dans le bon groupe et m'en excuse
si c'est le cas.

Soit un programme simple en C, que je désassemble:
$ gdb -q a.out
(gdb) list
1 #include <stdio.h>
2
3 int main()
4 {
5 int i;
6 for(i=0; i < 10; i++)
7 {
8 printf("Hello World!\n");
9 }
10 }
(gdb) disas main
Dump of assembler code for function main:
0x08048384 <main+0>: lea ecx,[esp+0x4]
0x08048388 <main+4>: and esp,0xfffffff0
0x0804838b <main+7>: push DWORD PTR [ecx-0x4]
0x0804838e <main+10>: push ebp
0x0804838f <main+11>: mov ebp,esp
0x08048391 <main+13>: push ecx
0x08048392 <main+14>: sub esp,0x14
0x08048395 <main+17>: mov DWORD PTR [ebp-0x8],0x0
0x0804839c <main+24>: jmp 0x80483b1 <main+45>
0x0804839e <main+26>: sub esp,0xc
0x080483a1 <main+29>: push 0x8048480
0x080483a6 <main+34>: call 0x80482b4 <puts@plt>
0x080483ab <main+39>: add esp,0x10
0x080483ae <main+42>: inc DWORD PTR [ebp-0x8]
0x080483b1 <main+45>: cmp DWORD PTR [ebp-0x8],0x9
0x080483b5 <main+49>: jle 0x804839e <main+26>
0x080483b7 <main+51>: mov ecx,DWORD PTR [ebp-0x4]
0x080483ba <main+54>: leave
0x080483bb <main+55>: lea esp,[ecx-0x4]
0x080483be <main+58>: ret
End of assembler dump.
(gdb)

J'ai quelques questions:
Tout d'abord je suis surpris de la structure du programme. <main+17>:
On met EBP-8 à 0. Puis on jump en fin de programme ou la comparaison
est faite. On remonte en <main+26> pour faire la boucle réelle.

Pourquoi n'avons nous pas en fait une mise à 0 de la valeur. Une
comparaison. Si inférieur ou égale, alors on jumpe à l'affiche
d'HelloWorld, puis un jump en début, ou on incrémente etc.. Si
la valeur est = à 10, alors on jump en fin de programme.

--> Est-ce du à une option de compilation de gcc? J'ai seulement
utilise gcc -g prog.c.
gcc -v
Lecture des spécification à partir de
/usr/lib/gcc/i486-slackware-linux/4.3.3/specs
Target: i486-slackware-linux
Configuré avec: ../gcc-4.3.3/configure --prefix=/usr --libdir=/usr/lib
--enable-shared --enable-bootstrap
--enable-languages=ada,c,c++,fortran,java,objc --enable-threads=posix
--enable-checking=release --with-system-zlib --disable-libunwind-exceptions
--enable-__cxa_atexit --enable-libssp --with-gnu-ld --verbose --with-arch=i486
--target=i486-slackware-linux --build=i486-slackware-linux
--host=i486-slackware-linux
Modèle de thread: posix
gcc version 4.3.3 (GCC)


Je ne comprends pas non plus cette série d'actions:
0x0804839e <main+26>: sub esp,0xc
0x080483a1 <main+29>: push 0x8048480
0x080483a6 <main+34>: call 0x80482b4 <puts@plt>
0x080483ab <main+39>: add esp,0x10

Pourquoi est-ce qu'il n'y a pas un simple
mov DWORD PTR [esp],0xXXXXXXX avec XXXXXXX l'adresse contenant ma
chaîne "Hello World" suivi d'un call à la fonction printf ?

Merci
--
Kevin

10 réponses

1 2
Avatar
Yann Renard
Kevin Denis wrote:
Bonjour,

Tout d'abord, je ne suis pas certain d'être dans le bon groupe et m'en excuse
si c'est le cas.

Soit un programme simple en C, que je désassemble:
$ gdb -q a.out
(gdb) list
1 #include <stdio.h>
2
3 int main()
4 {
5 int i;
6 for(i=0; i < 10; i++)
7 {
8 printf("Hello World!n");
9 }
10 }
(gdb) disas main
Dump of assembler code for function main:
0x08048384 <main+0>: lea ecx,[esp+0x4]
0x08048388 <main+4>: and esp,0xfffffff0
0x0804838b <main+7>: push DWORD PTR [ecx-0x4]
0x0804838e <main+10>: push ebp
0x0804838f <main+11>: mov ebp,esp
0x08048391 <main+13>: push ecx
0x08048392 <main+14>: sub esp,0x14
0x08048395 <main+17>: mov DWORD PTR [ebp-0x8],0x0
0x0804839c <main+24>: jmp 0x80483b1 <main+45>
0x0804839e <main+26>: sub esp,0xc
0x080483a1 <main+29>: push 0x8048480
0x080483a6 <main+34>: call 0x80482b4
0x080483ab <main+39>: add esp,0x10
0x080483ae <main+42>: inc DWORD PTR [ebp-0x8]
0x080483b1 <main+45>: cmp DWORD PTR [ebp-0x8],0x9
0x080483b5 <main+49>: jle 0x804839e <main+26>
0x080483b7 <main+51>: mov ecx,DWORD PTR [ebp-0x4]
0x080483ba <main+54>: leave
0x080483bb <main+55>: lea esp,[ecx-0x4]
0x080483be <main+58>: ret
End of assembler dump.
(gdb)

J'ai quelques questions:
Tout d'abord je suis surpris de la structure du programme. <main+17>:
On met EBP-8 à 0. Puis on jump en fin de programme ou la comparaison
est faite. On remonte en <main+26> pour faire la boucle réelle.

Pourquoi n'avons nous pas en fait une mise à 0 de la valeur. Une
comparaison. Si inférieur ou égale, alors on jumpe à l'affiche
d'HelloWorld, puis un jump en début, ou on incrémente etc.. Si
la valeur est = à 10, alors on jump en fin de programme.

--> Est-ce du à une option de compilation de gcc? J'ai seulement
utilise gcc -g prog.c.
gcc -v
Lecture des spécification à partir de
/usr/lib/gcc/i486-slackware-linux/4.3.3/specs
Target: i486-slackware-linux
Configuré avec: ../gcc-4.3.3/configure --prefix=/usr --libdir=/usr/lib
--enable-shared --enable-bootstrap
--enable-languages­a,c,c++,fortran,java,objc --enable-threads=posix
--enable-checking=release --with-system-zlib --disable-libunwind-exceptions
--enable-__cxa_atexit --enable-libssp --with-gnu-ld --verbose --with-arch=i486
--target=i486-slackware-linux --build=i486-slackware-linux
--host=i486-slackware-linux
Modèle de thread: posix
gcc version 4.3.3 (GCC)


Je ne comprends pas non plus cette série d'actions:
0x0804839e <main+26>: sub esp,0xc
0x080483a1 <main+29>: push 0x8048480
0x080483a6 <main+34>: call 0x80482b4
0x080483ab <main+39>: add esp,0x10

Pourquoi est-ce qu'il n'y a pas un simple
mov DWORD PTR [esp],0xXXXXXXX avec XXXXXXX l'adresse contenant ma
chaîne "Hello World" suivi d'un call à la fonction printf ?

Merci



Kevin,

je ne peux pas répondre à ta question, mais je te recommande de la
traduire et poster sur gnu.gcc.help

Bonne journée,
Yann
Avatar
Alexandre Bacquart
Kevin Denis wrote:
Bonjour,

Tout d'abord, je ne suis pas certain d'être dans le bon groupe et m'en excuse
si c'est le cas.

Soit un programme simple en C, que je désassemble:
$ gdb -q a.out
(gdb) list
1 #include <stdio.h>
2
3 int main()
4 {
5 int i;
6 for(i=0; i < 10; i++)
7 {
8 printf("Hello World!n");
9 }
10 }
(gdb) disas main
Dump of assembler code for function main:
0x08048384 <main+0>: lea ecx,[esp+0x4]
0x08048388 <main+4>: and esp,0xfffffff0
0x0804838b <main+7>: push DWORD PTR [ecx-0x4]
0x0804838e <main+10>: push ebp
0x0804838f <main+11>: mov ebp,esp
0x08048391 <main+13>: push ecx
0x08048392 <main+14>: sub esp,0x14
0x08048395 <main+17>: mov DWORD PTR [ebp-0x8],0x0
0x0804839c <main+24>: jmp 0x80483b1 <main+45>
0x0804839e <main+26>: sub esp,0xc
0x080483a1 <main+29>: push 0x8048480
0x080483a6 <main+34>: call 0x80482b4
0x080483ab <main+39>: add esp,0x10
0x080483ae <main+42>: inc DWORD PTR [ebp-0x8]
0x080483b1 <main+45>: cmp DWORD PTR [ebp-0x8],0x9
0x080483b5 <main+49>: jle 0x804839e <main+26>
0x080483b7 <main+51>: mov ecx,DWORD PTR [ebp-0x4]
0x080483ba <main+54>: leave
0x080483bb <main+55>: lea esp,[ecx-0x4]
0x080483be <main+58>: ret
End of assembler dump.
(gdb)

J'ai quelques questions:
Tout d'abord je suis surpris de la structure du programme. <main+17>:
On met EBP-8 à 0. Puis on jump en fin de programme ou la comparaison
est faite. On remonte en <main+26> pour faire la boucle réelle.

Pourquoi n'avons nous pas en fait une mise à 0 de la valeur. Une
comparaison. Si inférieur ou égale, alors on jumpe à l'affiche
d'HelloWorld, puis un jump en début, ou on incrémente etc.. Si
la valeur est = à 10, alors on jump en fin de programme.



Ca ferait 2 sauts par boucle en moyenne, alors qu'avec le code généré
par le compilo, 1 saut par boucle en moyenne. C'est évidemment plus
performant.

--> Est-ce du à une option de compilation de gcc? J'ai seulement
utilise gcc -g prog.c.
gcc -v
Lecture des spécification à partir de
/usr/lib/gcc/i486-slackware-linux/4.3.3/specs
Target: i486-slackware-linux
Configuré avec: ../gcc-4.3.3/configure --prefix=/usr --libdir=/usr/lib
--enable-shared --enable-bootstrap
--enable-languages­a,c,c++,fortran,java,objc --enable-threads=posix
--enable-checking=release --with-system-zlib --disable-libunwind-exceptions
--enable-__cxa_atexit --enable-libssp --with-gnu-ld --verbose --with-arch=i486
--target=i486-slackware-linux --build=i486-slackware-linux
--host=i486-slackware-linux
Modèle de thread: posix
gcc version 4.3.3 (GCC)


Je ne comprends pas non plus cette série d'actions:
0x0804839e <main+26>: sub esp,0xc



Réserve 12 octets sur la pile. Convention d'appel et/ou alignement ?...

0x080483a1 <main+29>: push 0x8048480



Pousse l'adresse de la chaîne sur la pile (4 octets en l'occurrence).

0x080483a6 <main+34>: call 0x80482b4



Appel de printf() (en fait, optimisé avec un puts() plus rapide car
l'opération est simple).

0x080483ab <main+39>: add esp,0x10



Restauration de la pile de 16 octets (les 12 réservés + les 4 de la chaîne).

Pourquoi est-ce qu'il n'y a pas un simple
mov DWORD PTR [esp],0xXXXXXXX avec XXXXXXX l'adresse contenant ma
chaîne "Hello World" suivi d'un call à la fonction printf ?



La convention d'appel du puts() en question qui doit sûrement faire le
minimum (l'appelant devant gérer lui-même la pile, ces conventions
d'appels ont un nom, genre __cdecl ou autres, c'est un peu vieux pour
moi désolé). Sans doute également qu'à ce moment, la pile est alignée
sur 16 octets, et le code veille à ce que ça reste le cas pour la
performance.

Se plonger un peu dans les docs du CPU cible devrait répondre à toutes
ces questions. Il faut reconnaître que de nos jours, le code généré par
les compilos et beaucoup plus obscur qu'avant, mais il y a souvent une
bonne raison derrière (surtout pour un exemple aussi simple).



--
Alex
Avatar
Antoine Leca
Kevin Denis écrivit :
J'ai quelques questions:
Tout d'abord je suis surpris de la structure du programme. <main+17>:
On met EBP-8 à 0.



Dans ton programme il y a i=0 comme première instruction, ici aussi.

Puis on jump en fin de programme ou la comparaison
est faite. On remonte en <main+26> pour faire la boucle réelle.



C'est comme cela qu'est compilé la structure de contrôle while (ou for);
c'est même la manière standard de le faire (avec des analyseurs
descendants). Entre autres, cela permet de traiter «correctement» le cas
for(i ; i<10; i--)


Pourquoi n'avons nous pas en fait une mise à 0 de la valeur.



? cf. supra.


Pourquoi n'avons nous pas en fait une mise à 0 de la valeur.
Une comparaison. Si inférieur ou égale, alors on jumpe à l'affiche
d'HelloWorld, puis un jump en début, ou on incrémente etc.. Si
la valeur est = à 10, alors on jump en fin de programme.



Je ne vois pas le problème (surtout que tu as bien compris la nécessité
de la première comparaison, ce qui est le point le plus difficile).
Dans le programme que tu présentes, on a 11 instructions jmp, dont dix
sauts : c'est mieux, non ?


Je ne comprends pas non plus cette série d'actions:
0x0804839e <main+26>: sub esp,0xc
0x080483a1 <main+29>: push 0x8048480
0x080483a6 <main+34>: call 0x80482b4
0x080483ab <main+39>: add esp,0x10

Pourquoi est-ce qu'il n'y a pas un simple
mov DWORD PTR [esp],0xXXXXXXX avec XXXXXXX l'adresse contenant ma
chaîne "Hello World" suivi d'un call à la fonction printf ?



L'instruction push a deux effets : faire une place en haut de la pile,
et copier une valeur dans cette place créée; l'instruction mov[esp],...
ne fait que la seconde partie.

Tu remarqueras que le "sub" et le "add" ci-dessus ne sont pas
équilibrés: la différence (4, la taille en octets d'un mot) est
justement là pour balancer le 1er effet de l'instruction push.


printf est remplacé par puts, car GCC croît savoir que c'est mieux.


Antoine
Avatar
Kevin Denis
Le 11-03-2010, Antoine Leca a écrit :
Puis on jump en fin de programme ou la comparaison
est faite. On remonte en <main+26> pour faire la boucle réelle.



C'est comme cela qu'est compilé la structure de contrôle while (ou for);
c'est même la manière standard de le faire (avec des analyseurs
descendants). Entre autres, cela permet de traiter «correctement» le cas
for(i ; i<10; i--)



En fait, j'ai repris un exemple d'un bouquin qui n'affiche pas un code
compilé de la même manière (alors que le code est le même)

Je ne comprends pas non plus cette série d'actions:
0x0804839e <main+26>: sub esp,0xc
0x080483a1 <main+29>: push 0x8048480
0x080483a6 <main+34>: call 0x80482b4
0x080483ab <main+39>: add esp,0x10

Pourquoi est-ce qu'il n'y a pas un simple
mov DWORD PTR [esp],0xXXXXXXX avec XXXXXXX l'adresse contenant ma
chaîne "Hello World" suivi d'un call à la fonction printf ?



L'instruction push a deux effets : faire une place en haut de la pile,
et copier une valeur dans cette place créée; l'instruction mov[esp],...
ne fait que la seconde partie.



Ok. De plus, je retrouve en mémoire mon texte:
(gdb) x/x 0x8048480
0x8048480: 0x6c6c6548
(gdb)
On retrouve donc 48="H", 56="e", 6c="l", 6c="l", etc..
(gdb) x/4x 0x8048480
0x8048480: 0x6c6c6548 0x6f57206f 0x21646c72 Cannot
access memory at address 0x804848c
(gdb)

Tu remarqueras que le "sub" et le "add" ci-dessus ne sont pas
équilibrés: la différence (4, la taille en octets d'un mot) est
justement là pour balancer le 1er effet de l'instruction push.



C'est ce que j'ai du mal à comprendre, justement.
J'aurais mis l'adresse de ma chaine de caractère sur la pile, appel à
la fonction printf qui parcourt la mémoire jusqu'au nul (depuis l'adresse
fournie par la pile), et retour à l'incrémentation de EBP-8 ?

printf est remplacé par puts, car GCC croît savoir que c'est mieux.



Et moi qui ne comprenait pas ou était passé mon printf, c'est gcc
qui réécrit le code?

Merci
--
Kevin
Avatar
Kevin Denis
Le 11-03-2010, Alexandre Bacquart a écrit :
Pourquoi n'avons nous pas en fait une mise à 0 de la valeur. Une
comparaison. Si inférieur ou égale, alors on jumpe à l'affiche
d'HelloWorld, puis un jump en début, ou on incrémente etc.. Si
la valeur est = à 10, alors on jump en fin de programme.



Ca ferait 2 sauts par boucle en moyenne, alors qu'avec le code généré
par le compilo, 1 saut par boucle en moyenne. C'est évidemment plus
performant.



Ok.

Je ne comprends pas non plus cette série d'actions:
0x0804839e <main+26>: sub esp,0xc



Réserve 12 octets sur la pile. Convention d'appel et/ou alignement ?...



$ echo -n 'Hello World!' | wc -c
12

Sans doute ces douze octets?

0x080483a1 <main+29>: push 0x8048480


Pousse l'adresse de la chaîne sur la pile (4 octets en l'occurrence).



Ok

0x080483a6 <main+34>: call 0x80482b4



Appel de printf() (en fait, optimisé avec un puts() plus rapide car
l'opération est simple).



ok. Il est possible de demander à gcc de ne pas faire ce genre
d'optimisations?

0x080483ab <main+39>: add esp,0x10



Restauration de la pile de 16 octets (les 12 réservés + les 4 de la chaîne).

Pourquoi est-ce qu'il n'y a pas un simple
mov DWORD PTR [esp],0xXXXXXXX avec XXXXXXX l'adresse contenant ma
chaîne "Hello World" suivi d'un call à la fonction printf ?



La convention d'appel du puts() en question qui doit sûrement faire le
minimum (l'appelant devant gérer lui-même la pile, ces conventions
d'appels ont un nom, genre __cdecl ou autres, c'est un peu vieux pour
moi désolé). Sans doute également qu'à ce moment, la pile est alignée
sur 16 octets, et le code veille à ce que ça reste le cas pour la
performance.



Ok, merci.

Se plonger un peu dans les docs du CPU cible devrait répondre à toutes
ces questions.



Il existe des docs abordables? (Genre pas le pavé de 5000pages de chez
intel)

Il faut reconnaître que de nos jours, le code généré par
les compilos et beaucoup plus obscur qu'avant, mais il y a souvent une
bonne raison derrière (surtout pour un exemple aussi simple).



Merci
--
Kevin
Avatar
Mickaël Wolff
Kevin Denis a écrit :

printf est remplacé par puts, car GCC croît savoir que c'est mieux.



Et moi qui ne comprenait pas ou était passé mon printf, c'est gcc
qui réécrit le code?

Merci



Essaye de changer le niveau 'optimisation, GCC devrait ne pas
remplacer printf par puts.

L'idée est que GCC analyse le premier paramètre de printf. Ceci
permet de détecter les bogues de format évidents, mais aussi d'optimiser
lorsqu'on utilise printf alors que puts serait plus pertinent (printf
analyse la chaîne à chaque exécution, puts non). Bien sur GCC ne fait
pas de miracle, et n'optimise que lorsqu'à la compilation l'information
est disponible.

--
Mickaël Wolff aka Lupus Michaelis
http://lupusmic.org
Avatar
Alexandre Bacquart
Kevin Denis wrote:
Le 11-03-2010, Alexandre Bacquart a écrit :
Je ne comprends pas non plus cette série d'actions:
0x0804839e <main+26>: sub esp,0xc


Réserve 12 octets sur la pile. Convention d'appel et/ou alignement ?...



$ echo -n 'Hello World!' | wc -c
12

Sans doute ces douze octets?



Nan, rien à voir (l'adresse de la chaîne est placée sur la pile avec le
push qui suit). Je pensais plutôt à une convention d'appel du genre
puts() qui requiert qu'on lui réserve 12 octets sur la pile en plus de
l'adresse de la chaîne. Ca m'étonne un poil quand-même, mais pourquoi
pas. Ce sont des choses qu'on a vraiment pas besoin de savoir quand on
n'appelle pas des fonctions de bibliothèque C en assembleur.

0x080483a1 <main+29>: push 0x8048480


Pousse l'adresse de la chaîne sur la pile (4 octets en l'occurrence).



Ok

0x080483a6 <main+34>: call 0x80482b4


Appel de printf() (en fait, optimisé avec un puts() plus rapide car
l'opération est simple).



ok. Il est possible de demander à gcc de ne pas faire ce genre
d'optimisations?



Oui sans doute. De tête, -O0 désactive toutes les optimisations, mais
attends toi à plus de code "inutile" généré (encore que dans ton
exemple, ça ne devrait pas changer grand chose). Il y a moyen d'être
plus explicite :

http://gcc.gnu.org/onlinedocs/gcc-4.4.3/gcc/Optimize-Options.html#Optimize-Options" target="_blank" class="text-blue hover:opacity-90 " style="word-break: break-all;" rel="noopener nofollow">http://gcc.gnu.org/onlinedocs/gcc-4.4.3/gcc/Optimize-Options.html#Optimize-Options

Note que c'est la doc de gcc 4.4.3, et non pas le 4.3.3 que tu utilises
qui n'est d'ailleurs pas dispo sur le site de gnu :

http://gcc.gnu.org/onlinedocs/

Tu auras sans doute quelque-chose de plus proche avec la 4.3.4.

Mais rassure-toi, si gcc a décidé de remplacer printf() par puts(),
c'est qu'il sait ce qu'il fait. Tu auras exactement le même
comportement. Cette optimisation de gcc est ultra-connue.

0x080483ab <main+39>: add esp,0x10


Restauration de la pile de 16 octets (les 12 réservés + les 4 de la chaîne).

Pourquoi est-ce qu'il n'y a pas un simple
mov DWORD PTR [esp],0xXXXXXXX avec XXXXXXX l'adresse contenant ma
chaîne "Hello World" suivi d'un call à la fonction printf ?


La convention d'appel du puts() en question qui doit sûrement faire le
minimum (l'appelant devant gérer lui-même la pile, ces conventions
d'appels ont un nom, genre __cdecl ou autres, c'est un peu vieux pour
moi désolé). Sans doute également qu'à ce moment, la pile est alignée
sur 16 octets, et le code veille à ce que ça reste le cas pour la
performance.



Ok, merci.



Petite précision : je ne pense pas finalement que l'alignement y soit
pour quelque-chose, car la mnémonique call fait un push implicite de
l'adresse de la prochaine mnémonique (ce qui fait donc un total de 20
octets de pile). J'opterais plutôt pour une simple convention d'appel de
puts().

Se plonger un peu dans les docs du CPU cible devrait répondre à toutes
ces questions.



Il existe des docs abordables? (Genre pas le pavé de 5000pages de chez
intel)



Pas que je sache (c'est bien fini l'époque des 68000, snif). Maintenant,
les caches, les pipelines, prévisions de sauts & co. fichent un boxon
pas possible pour avoir du code CPU efficace. C'est vraiment devenu un
boulot de compilateur. Et c'est quelqu'un qui n'a juré que par
l'assembleur pendant bien trop longtemps qui te le dis. Je suis rentré
dans les rang depuis belle lurette :)

Bref, tout ceci reste assez loin du C... tu auras sans doute des
réponses plus précises (et exactes !) sur gnu.gcc.help, comme le
suggérait Yann.


--
Alex
Avatar
Samuel DEVULDER
Alexandre Bacquart a écrit :


Je ne comprends pas non plus cette série d'actions:
0x0804839e <main+26>: sub esp,0xc



Réserve 12 octets sur la pile. Convention d'appel et/ou alignement ?...



Probablement aligment sur 16 de la pile pour le call. L'abi xp64 bits a
besoin de cela pour des questions de perfs:
http://gcc.gnu.org/ml/gcc/2008-01/msg00282.html

Different source code and optimizations requires different stack
alignment,
as in following table:
Feature Alignment (bytes)
i386_ABI 4
x86_64_ABI 16

Info MS sur l'aligment de pile dans son ABI:
http://msdn.microsoft.com/en-us/library/aa290049%28VS.71%29.aspx

sam.
Avatar
Antoine Leca
Kevin Denis écrivit :
Le 11-03-2010, Antoine Leca a écrit :
Je ne comprends pas non plus cette série d'actions:
0x0804839e <main+26>: sub esp,0xc
0x080483a1 <main+29>: push 0x8048480
0x080483a6 <main+34>: call 0x80482b4
0x080483ab <main+39>: add esp,0x10



Tu remarqueras que le "sub" et le "add" ci-dessus ne sont pas
équilibrés: la différence (4, la taille en octets d'un mot) est
justement là pour balancer le 1er effet de l'instruction push.



C'est ce que j'ai du mal à comprendre, justement.



Si tu supprimes le sub (qui n'est là que pour des questions d'alignement
de la pile, il n'a pas d'effet nécessaire), tu obtiendrais
::: 0x080483a1 <main+29>: push 0x8048480
::: 0x080483a6 <main+34>: call 0x80482b4
::: 0x080483ab <main+39>: add esp,4

Le push empile l'adresse de la chaîne, et comme sur x86 la pile s'étend
vers le bas, le niveau du haut de la pile descend de la taille d'un mot,
soit 4 octets.
Le call est neutre de ce point de vue, c'est la convention d'appel i386
qui le dit.

Donc après le call, tu te retrouves avec l'adresse qui est restée
perchée en haut de la pile... il faut donc ajuster, et c'est exactement
l'effet de l'opération add.


Antoine
Avatar
Jean-Marc Desperrier
Mickaël Wolff wrote:
L'idée est que GCC analyse le premier paramètre de printf. Ceci permet
de détecter les bogues de format évidents, mais aussi d'optimiser
lorsqu'on utilise printf



Ah. De mon point de vue, il pourrait remplacer systématiquement lorsque
l'appel n'a qu'un seul argument. Comportement indéfini, je fais ce que
je veux, tout cela ...
1 2