Quelle est la signification de chaque ligne de l'assemblée de la sortie d'un C bonjour à tout le monde?
J'ai couru gcc-S au cours de cette:
int main()
{
printf ("Hello world!");
}
et j'ai eu cette assemblée de code:
.file "test.c"
.section .rodata
.LC0:
.string "Hello world!"
.text
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $20, %esp
movl $.LC0, (%esp)
call printf
addl $20, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.ident "GCC: (GNU) 4.3.0 20080428 (Red Hat 4.3.0-8)"
.section .note.GNU-stack,"",@progbits
Je suis curieux de comprendre cette sortie. Quelqu'un peut-il partager quelques conseils dans la compréhension de cette sortie, ou si quelqu'un pourrait marquer des commentaires à l'encontre de chacune de ces lignes/groupe de lignes pour expliquer ce qu'il fait, il serait grand.
Vous devez vous connecter pour publier un commentaire.
Ici comment ça se passe:
Le fichier source d'origine (nom utilisé par les débogueurs).
Un zéro à terminaison de chaîne est incluse dans la section ".rodata" ("ro" signifie "lecture seule": l'application sera en mesure de lire les données, mais toute tentative d'écriture en elle déclenchera une exception).
Maintenant, nous écrivons des choses dans le "."texte de l'article, qui est l'endroit où le code va.
Nous définissons une fonction appelée "main" et le monde visible (les autres fichiers de l'objet sera en mesure de l'invoquer).
Nous stockons dans le registre
%ecx
la valeur4+%esp
(%esp
est le pointeur de pile).%esp
est légèrement modifié, de sorte qu'il devient un multiple de 16. Pour certains types de données (floating point format correspondant à Cdouble
etlong double
), les performances sont meilleures lorsque les accès à la mémoire sont à des adresses multiples de 16. Ce n'est pas vraiment nécessaire ici, mais lorsqu'il est utilisé sans l'option d'optimisation (-O2
...), le compilateur a tendance à produire beaucoup de génériques de code inutile (c'est à dire le code qui pourrait être utile dans certains cas, mais pas ici).Celui-ci est un peu bizarre: à ce stade, le mot à l'adresse
-4(%ecx)
est le mot qui était sur le haut de la pile avant leandl
. Le code récupère ce mot (qui doit être l'adresse de retour, par la voie) et le pousse de nouveau. Cette sorte d'émule ce qui serait obtenue avec un appel à partir d'une fonction qui a un 16 octets aligné pile. Ma conjecture est que cettepush
est un vestige d'un argument-la copie de la séquence. Puisque la fonction a ajusté le pointeur de pile, il doit copier les arguments de la fonction, qui sont accessibles grâce à l'ancienne valeur du pointeur de pile. Ici, il n'y a pas d'argument, à l'exception de la fonction de l'adresse de retour. Notez que ce mot ne sera pas utilisé (encore une fois, c'est le code sans optimisation).C'est la norme de la fonction prologue: nous sauver
%ebp
(puisque nous sommes sur le point de le modifier), puis mis en%ebp
à point pour le frame de pile. Par la suite,%ebp
sera utilisé pour accéder à la fonction d'arguments, en faisant%esp
de nouveau libre. (Oui, il n'y a pas d'argument, donc c'est inutile pour cette fonction.)Nous sauver
%ecx
(nous en aurons besoin à la sortie de la fonction, afin de restaurer%esp
à la valeur qu'il avait avant laandl
).Nous nous réservons 32 octets sur la pile (rappelez-vous que la pile de pousse "vers le bas"). Cet espace sera utilisé pour storea les arguments pour
printf()
(c'est exagéré, car il n'y a qu'un seul argument, qui utilise 4 octets [c'est un pointeur]).Nous "pousser" l'argument de
printf()
(c'est à dire, nous nous assurons que%esp
points pour un mot qui contient l'argument, ici$.LC0
, qui est l'adresse de la chaîne constante dans le rodata section). Ensuite, nous appelonsprintf()
.Quand
printf()
retourne, on enlève de l'espace alloué pour les arguments. Cetteaddl
annule ce que lesubl
ci-dessus n'.Nous récupérer
%ecx
(poussé ci-dessus);printf()
peut avoir modifié (les conventions d'appel décrire inscrire, une fonction de modifier sans restauration à la sortie;%ecx
est un tel registre).Fonction épilogue: il restaure
%ebp
(correspondant à lapushl %ebp
ci-dessus).Nous restaurer
%esp
à sa valeur initiale. L'effet de cet opcode est de stocker dans%esp
la valeur%ecx-4
.%ecx
a été mis dans la première fonction de l'opcode. Cela annule toute modification de%esp
, y compris laandl
.La sortie de la fonction.
Cela définit la taille de la
main()
fonction: à tout moment durant l'assemblée, ".
" est un alias de "l'adresse à laquelle nous ajoutons de choses en ce moment". Si une autre instruction a été ajouté ici, ce serait aller à l'adresse indiquée par ".
". Ainsi, ".-main
", ici, est la taille exacte du code de la fonctionmain()
. Le.size
directive indique à l'assembleur pour écrire ces informations dans le fichier d'objet.GCC aime juste pour laisser une trace de son action. Cette chaîne se termine comme une sorte de commentaire dans le fichier de l'objet. L'éditeur de liens se retirer.
Une section spéciale où GCC écrit que le code peut accueillir un non-pile exécutable. C'est le cas normal. Exécutable piles sont nécessaires pour certains usages (pas en C standard). Sur les processeurs modernes, le noyau peut faire un non-exécutable de la pile (une pile, ce qui déclenche une exception si quelqu'un essaie d'exécuter du code des données qui est sur la pile); ce qui est considéré par certains comme un "dispositif de sécurité" parce que mettre du code sur la pile est une commune de la manière d'exploiter les dépassements de tampon. Avec cette section, l'exécutable sera marqué comme "compatible avec un non-pile exécutable" dont le noyau serons heureux de vous fournir en tant que tel.
Ici est un certain supplément de
@Thomas Pornin
's réponse..LC0
constante locale, e.g un littéral de chaîne..LFB0
local de la fonction de début,.LFE0
local de la fonction de fin,Le suffixe de ces étiquettes est un nombre, et commencer à partir de 0.
C'est gcc assembleur convention.
ces instructions ne comparez pas dans votre programme c, ils sont toujours exécutées au début de chaque fonction (mais cela dépend du compilateur/plate-forme)
ce bloc correspond à votre printf() de l'appel. la première instruction des lieux sur la pile de son argument (un pointeur vers "bonjour le monde"), puis appelle la fonction.
ces instructions sont à l'opposé de la première bloc, ils sont une sorte de manipulation de la pile pour animaux. toujours exécuté trop