Question

J'écris des programmes vides pour embêter l'enfer des codeurs stackoverflow, PAS. Je suis en train d'explorer la toolchain gnu.

Voici ce qui est peut-être trop profond pour moi, mais pour continuie la saga du programme vide que j'ai commencé à examiner la sortie du compilateur C, la GNU stuff comme consume.

gcc version 4.4.0 (TDM-1 mingw32)

test.c:

int main()
{
    return 0;
}

gcc -S test.c

    .file   "test.c"
    .def    ___main;    .scl    2;  .type   32; .endef
    .text
.globl _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    call    ___main
    movl    $0, %eax
    leave
    ret 

Pouvez-vous expliquer ce qui se passe ici? Voici mon effort pour le comprendre. Je l'ai utilisé le manuel de as et ma connaissance minimale ASM x86:

  • .file "test.c" est la directive pour le nom de fichier logique.
  • .def: selon les docs "commencer à définir les informations de débogage pour un nom de symbole" . Qu'est-ce qu'un symbole (un nom de fonction / variable?) Et quel type d'informations de débogage?
  • .scl: docs disent "Classe de stockage peut signaler si un symbole est statique ou externe" . Est-ce la même statique et externe Je sais de C? Et qu'est-ce que '2'?
  • .type:. Stocke les paramètres "comme l'attribut type d'une entrée de table des symboles" , je n'ai pas la moindre idée
  • .endef:. Pas de problème
  • .text: Maintenant, ce qui est problématique, il semble être quelque chose appelé la section et j'ai lu que son lieu pour le code, mais les documents ne m'a pas dit trop
  • .
  • .globl "rend le symbole visible ld." , le manuel est tout à fait clair.
  • _main: Cela pourrait être l'adresse de départ (?) Pour ma fonction principale
  • pushl_: Une longue (32 bits) poussée, qui place RASE sur la pile
  • movl: déplacer 32 bits. Pseudo-C: EBP = ESP;
  • andl: ET logique. Pseudo-C:. ESP = -16 & ESP, je ne vois pas vraiment ce qui est le point de cette
  • call: Pousse l'IP à la pile (si la procédure appelée peut trouver son chemin de retour) et continue où __main est. (Ce qui est __main?)
  • movl: ce zéro doit être mon retour constant à la fin de mon code. Les endroits MOV ce zéro dans EAX.
  • leave: restaure la pile après une instruction ENTRER (?). Pourquoi?
  • ret: retourne à l'adresse d'instruction qui est enregistré sur la pile

Merci pour votre aide!

Était-ce utile?

La solution

  

.file "test.c"

Commandes commençant par. sont des directives à l'assembleur. Cela dit tout cela est « file.c », ces informations peuvent être exportées vers les informations de débogage de l'exe.

  

def ___main; .scl 2;   .type 32; .endef

def directives définit un symbole de débogage. scl deux moyens de classe de stockage 2 (classe de stockage externe) .type 32 dit ce sumbol est une fonction. Ces chiffres seront définis par le format exe pe-COFF

___ principale est une fonction appelée qui prend soin de bootstrapping que les besoins gcc (ça va faire des choses comme c ++ run initialiseurs statiques et d'autres le ménage nécessaire).

.text

Begins une section de texte -. Code vit ici

  

.globl _main

définit le symbole _main comme mondial, ce qui rendra visible à l'éditeur de liens et à d'autres modules qui est lié à.

.def        _main;  .scl    2;      .type   32;     .endef

Même chose que _main, crée des symboles de débogage indiquant que _main est une fonction. Ceci peut être utilisé par les débogueurs.

  

_main:

Lance un nouveau label (Il va finir par une adresse). la directive .globl fait ci-dessus cette adresse visible à d'autres entités.

pushl       %ebp

Enregistre l'ancien pointeur de cadre (registre ebp) sur la pile (il peut donc être mis en place lorsque cette fonction se termine)

movl        %esp, %ebp

déplace le pointeur de pile dans le registre ebp. ebp est souvent appelé le pointeur de trame, il pointe en tête des valeurs de la pile dans le « cadre » en cours (fonction habituellement), (en référence à des variables sur la pile via ebp peut aider débogueurs)

  

etl $ -16% esp

Ands la pile avec fffffff0 qui aligne effectivly sur une limite de 16 octets. L'accès aux valeurs alignées sur la pile sont beaucoup plus rapides que si elles étaient non alignés. Toutes ces instructions précédentes sont à peu près un prologue de fonction standard.

call        ___main

appelle la fonction ___main qui fera l'initialisation des choses que les besoins gcc. Appel poussera le pointeur d'instruction en cours sur la pile et sauter à l'adresse de ___ principale

movl        $0, %eax

0 mouvement vers le registre de eax, (0 dans le return 0;). Le registre eax est utilisé pour maintenir les valeurs de retour de fonction de la convention d'appel stdcall

  

congé

L'instruction de congé est à peu près un raccourci pour

movl     ebp,esp
popl     ebp

i.e.. il « undo » les choses fait au début de la fonction -. pointeur de la restauration de la trame et pile à son état

  

RET

Retourne à celui qui appelle cette fonction. Il va pop le pointeur d'instruction de la pile (où il aura placé une instruction d'appel correspondant) et sauter là-bas.

Autres conseils

Il y a un exercice très similaire décrit ici: http://en.wikibooks.org/wiki/ X86_Assembly / GAS_Syntax

Vous avez compris la plus grande partie - je vais juste faire des notes supplémentaires pour l'accent et les ajouts

.

__main est un sous-programme dans la bibliothèque standard GNU qui prend en charge diverses initialisation de démarrage. Il est pas strictement nécessaire pour les programmes C mais est nécessaire au cas où le code C est la liaison avec C ++.

_main est votre sous-programme principal. Comme les deux _main et __main sont des emplacements de code qu'ils ont la même classe de stockage et le type. Je n'ai pas encore creusé encore les définitions pour .scl et .type. Vous pouvez obtenir une illumination en définissant quelques variables globales.

Les trois premières instructions mettent en place un cadre de pile qui est un terme technique pour le stockage de travail d'un sous-programme - variables locales et temporaires pour la plupart. Pousser ebp sauve la base du cadre de pile de l'appelant. La mise en esp ebp définit la base de notre cadre de pile. Le andl aligne le cadre de pile à une limite de 16 octets au cas où toutes les variables locales sur la pile exigent un alignement de 16 octets (pour les instructions SIMD x86 exigent que l'alignement, mais l'alignement n'accélérer types ordinaires tels que ints et floats.

À ce stade, vous auriez normalement attendre esp pour se déplacé vers le bas en mémoire pour allouer de l'espace de pile pour les variables locales. Votre main a pas si gcc ne dérange pas.

L'appel à __main est spécial au principal point d'entrée et n'apparaissent généralement dans les sous-routines.

Le reste va comme vous deviné. Inscrivez-eax est l'endroit idéal pour mettre les codes de retour entier dans la spécification binaire. leave annule le cadre de pile et ret remonte à l'appelant. Dans ce cas, l'appelant est le moteur d'exécution à faible niveau C qui fera la magie supplémentaire (comme appeler les fonctions de atexit(), définissez le code de sortie du processus et demander au système d'exploitation de mettre fin au processus.

En ce qui concerne que etl -16 $,% esp

  • 32 bits: -16 en décimal est égal à 0xfffffff0 en représentation hexadécimale
  • 64 bits: -16 en décimal est égal à 0xfffffffffffffff0 en représentation hexadécimale

Alors il masquez les 4 derniers bits de l'ESP. (BTW: 2 ** 4 égal à 16) et conservera tous les autres bits (peu importe si le système cible est de 32 ou 64 bits)

Suite à la andl $-16,%esp, cela fonctionne parce que le réglage des bits de poids faible à zéro ajustera toujours %esp bas de la valeur, et la pile grossit vers le bas sur x86.

Je n'ai pas toutes les réponses, mais je peux expliquer ce que je sais.

ebp est utilisé par la fonction pour stocker l'état initial de esp au cours de son débit, une référence à l'endroit où sont les arguments passés à la fonction et où sont ses propres variables locales. La première chose fonction fait est de sauvegarder l'état du ebp donné pushl %ebp faire, il est essentiel à la fonction qui font l'appel, et qu'il remplace par sa propre position de la pile actuelle esp faire movl %esp, %ebp. Les 4 derniers Remise à zéro des morceaux de ebp à ce stade est GCC spécifique, je ne sais pas pourquoi ce compilateur fait ça. Il travaillerait sans le faire. Maintenant, enfin, nous allons dans les affaires, call ___main, qui est __main? Je ne sais pas non plus ... peut-être des procédures spécifiques plus du CCG, et enfin la seule chose que votre main () ne, réglez la valeur de retour comme 0 avec movl $0, %eax et leave qui est la même chose que faire movl %ebp, %esp; popl %ebp pour restaurer l'état ebp, puis ret terminer. pops ret eip et continuer flux de fil de ce point, là où il est (comme son principal (), ce ret conduit probablement à une procédure de noyau qui gère la fin du programme).

La plupart est tout au sujet de la gestion de la pile. J'ai écrit un tutoriel détaillé sur la façon dont la pile est utilisé il y a quelque temps, il serait utile d'expliquer pourquoi toutes ces choses sont faites. Mais son en portugais ...

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top