GNU/Linux >> Tutoriels Linux >  >> Linux

Quelle est la signification de chaque ligne de la sortie d'assemblage d'un C hello world ?

Voici comment ça se passe :

        .file   "test.c"

Le nom du fichier source d'origine (utilisé par les débogueurs).

        .section        .rodata
.LC0:
        .string "Hello world!"

Une chaîne terminée par zéro est incluse dans la section ".rodata" ("ro" signifie "lecture seule" :l'application pourra lire les données, mais toute tentative d'écriture dedans déclenchera une exception).

        .text

Maintenant, nous écrivons les choses dans la section ".text", où va le code.

.globl main
        .type   main, @function
main:

On définit une fonction appelée "main" et visible globalement (d'autres fichiers objets pourront l'invoquer).

        leal    4(%esp), %ecx

Nous stockons dans le registre %ecx la valeur 4+%esp (%esp est le pointeur de pile).

        andl    $-16, %esp

%esp est légèrement modifié pour devenir un multiple de 16. Pour certains types de données (le format à virgule flottante correspondant au double de C et long double ), les performances sont meilleures lorsque les accès mémoire sont à des adresses multiples de 16. Ce n'est pas vraiment nécessaire ici, mais lorsqu'il est utilisé sans le drapeau d'optimisation (-O2 ...), le compilateur a tendance à produire pas mal de code générique inutile (c'est-à-dire du code qui pourrait être utile dans certains cas mais pas ici).

        pushl   -4(%ecx)

Celui-ci est un peu bizarre :à ce moment-là, le mot à l'adresse -4(%ecx) est le mot qui était en haut de la pile avant le andl . Le code récupère ce mot (qui devrait être l'adresse de retour, soit dit en passant) et le pousse à nouveau. Ce type d'émule ce qui serait obtenu avec un appel d'une fonction qui avait une pile alignée de 16 octets. Je suppose que ce push est un reste d'une séquence de copie d'arguments. Puisque la fonction a ajusté le pointeur de pile, elle doit copier les arguments de la fonction, qui étaient accessibles via l'ancienne valeur du pointeur de pile. Ici, il n'y a pas d'argument, sauf l'adresse de retour de la fonction. Notez que ce mot ne sera pas utilisé (encore une fois, c'est du code sans optimisation).

        pushl   %ebp
        movl    %esp, %ebp

C'est le prologue de la fonction standard :nous économisons %ebp (puisque nous sommes sur le point de le modifier), puis définissez %ebp pour pointer vers le cadre de la pile. Par la suite, %ebp sera utilisé pour accéder aux arguments de la fonction, faisant %esp libre à nouveau. (Oui, il n'y a pas d'argument, donc c'est inutile pour cette fonction.)

        pushl   %ecx

Nous économisons %ecx (nous en aurons besoin à la sortie de la fonction, pour restaurer %esp à la valeur qu'il avait avant le andl ).

        subl    $20, %esp

Nous réservons 32 octets sur la pile (rappelez-vous que la pile grandit "vers le bas"). Cet espace sera utilisé pour stocker les arguments de printf() (c'est exagéré, puisqu'il y a un seul argument, qui utilisera 4 octets [c'est un pointeur]).

        movl    $.LC0, (%esp)
        call    printf

Nous "poussons" l'argument à printf() (c'est-à-dire que nous nous assurons que %esp pointe sur un mot qui contient l'argument, ici $.LC0 , qui est l'adresse de la chaîne constante dans la section rodata). Ensuite, nous appelons printf() .

        addl    $20, %esp

Quand printf() renvoie, nous supprimons l'espace alloué aux arguments. Ce addl annule ce que le subl ci-dessus a fait.

        popl    %ecx

Nous récupérons %ecx (poussé dessus); printf() peut l'avoir modifié (les conventions d'appel décrivent quel registre une fonction peut modifier sans les restaurer à la sortie ; %ecx est l'un de ces registres).

        popl    %ebp

Épilogue de la fonction :cela restaure %ebp (correspondant au pushl %ebp ci-dessus).

        leal    -4(%ecx), %esp

Nous restaurons %esp à sa valeur initiale. L'effet de cet opcode est de stocker en %esp la valeur %ecx-4 . %ecx a été défini dans le premier opcode de fonction. Cela annule toute modification de %esp , y compris le andl .

        ret

Sortie de fonction.

        .size   main, .-main

Ceci définit la taille du main() fonction :à tout moment de l'assemblage, ". " est un alias pour "l'adresse à laquelle nous ajoutons des choses en ce moment". Si une autre instruction était ajoutée ici, elle irait à l'adresse spécifiée par ". ". Ainsi, ".-main ", ici, c'est la taille exacte du code de la fonction main() . Le .size indique à l'assembleur d'écrire ces informations dans le fichier objet.

        .ident  "GCC: (GNU) 4.3.0 20080428 (Red Hat 4.3.0-8)"

GCC adore laisser des traces de son action. Cette chaîne se termine comme une sorte de commentaire dans le fichier objet. L'éditeur de liens le supprimera.

        .section        .note.GNU-stack,"",@progbits

Une section spéciale où GCC écrit que le code peut contenir une pile non exécutable. C'est le cas normal. Des piles exécutables sont nécessaires pour certaines utilisations spéciales (pas le C standard). Sur les processeurs modernes, le noyau peut créer une pile non exécutable (une pile qui déclenche une exception si quelqu'un essaie d'exécuter en tant que code certaines données qui se trouvent sur la pile) ; ceci est considéré par certaines personnes comme une "fonction de sécurité" car mettre du code sur la pile est un moyen courant d'exploiter les débordements de tampon. Avec cette section, l'exécutable sera marqué comme "compatible avec une pile non exécutable" que le noyau fournira volontiers comme tel.


    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ecx
    subl    $20, %esp

ces instructions ne se comparent pas dans votre programme c, elles sont toujours exécutées au début de chaque fonction (mais cela dépend du compilateur/de la plate-forme)

    movl    $.LC0, (%esp)
    call    printf

ce bloc correspond à votre appel printf(). la première instruction place sur la pile son argument (un pointeur vers "hello world") puis appelle la fonction.

    addl    $20, %esp
    popl    %ecx
    popl    %ebp
    leal    -4(%ecx), %esp
    ret

ces instructions sont opposées au premier bloc, ce sont une sorte de trucs de manipulation de pile. toujours exécuté aussi


Voici un supplément à @Thomas Pornin la réponse.

  • .LC0 constante locale, par exemple un littéral de chaîne.
  • .LFB0 début de la fonction locale,
  • .LFE0 fin de fonction locale,

Le suffixe de ces étiquettes est un nombre et commence à 0.

C'est la convention de l'assembleur gcc.


Linux
  1. Comment insérer du texte au début de chaque ligne dans Vim

  2. Comment savoir sur quelle version d'Os X je suis depuis la ligne de commande ?

  3. Compter les caractères de chaque ligne avec Wc ?

  4. Que signifie la sortie de Ps ?

  5. Quelle est la signification de POSIX ?

Quel est le refid dans la sortie ntpq -p ?

Quelle est la signification de *nix ?

Que signifie "liste noire" sur GStreamer ?

Quel est le deuxième état dans la sortie ip link show

Une commande pour afficher chaque ligne vers l'avant puis vers l'arrière

Signification de la ligne buffers/cache dans la sortie de free