2009-08-22 10 views
54

J'écris des programmes vides pour contrarier les codeurs stackoverflow, NON. Je suis juste en train d'explorer la chaîne d'outils GNU. Maintenant, ce qui suit peut être trop profond pour moi, mais pour continuer la saga de programme vide, j'ai commencé à examiner la sortie du compilateur C, le truc que consomme GNU.L'assemblage de GCC d'un programme vide sur x86, win32

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 as et ma connaissance minimale ASM x86:

  • .file "test.c" est la directive pour le nom de fichier logique.
  • .def: selon les documents "Commencer à définir les informations de débogage pour un nom de symbole". Qu'est-ce qu'un symbole (un nom/une variable de fonction?) Et quel type d'information de débogage?
  • .scl: les documents indiquent "La classe de stockage peut indiquer si un symbole est statique ou externe". Est-ce le même statique et externe Je sais de C? Et quel est ce '2'?
  • .type: stocke le paramètre "en tant qu'attribut de type d'une entrée de table de symboles", je n'ai aucune idée.
  • .endef: pas de problème.
  • .text: Maintenant, cela est problématique, il semble que ce soit quelque chose appelé section et j'ai lu que c'est l'endroit pour le code, mais les docs ne m'en disaient pas trop.
  • .globl"rend le symbole visible à ld.", le manuel est assez clair à ce sujet.
  • _main: Cela pourrait être l'adresse de départ pour ma fonction principale
  • pushl_ (?): Un appui long (32 bits), qui place RASE sur la pile
  • movl: déplacer 32bit. Pseudo-C: EBP = ESP;
  • andl: AND logique. Pseudo-C: ESP = -16 & ESP, je ne vois vraiment pas quel est le but de ceci.
  • call: Pousse l'adresse IP à la pile (afin que la procédure appelée puisse retrouver son chemin) et continue où __main est. (qu'est-ce que __main?)
  • movl: ce zéro doit être la constante que je retourne à la fin de mon code. Le MOV place ce zéro dans EAX.
  • leave: restaure la pile après une instruction ENTRÉE (?). Pourquoi?
  • ret: remonte à l'adresse d'instruction qui est enregistré sur la pile

Merci pour votre aide!

+8

Bonne question. :) –

+4

Cela ressemble à un excellent exercice pour un vrai geek. – JesperE

+3

J'ai trouvé la spécification COFF. Cela devrait donner quelques références à ce que "32" dans ".type" signifie etc: http://www.microsoft.com/whdc/system/platform/firmware/PECOFFdwn.mspx –

Répondre

54

.file " test.c »

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

.def ___main; .scl 2; .ty pe 32; .def

Les directives .def définissent un symbole de débogage. scl 2 signifie classe de stockage 2 (classe de stockage externe) .type 32 dit que ce sumbol est une fonction. Ces nombres seront définis par le format ex-pe-coff

___main est une fonction appelée qui prend en charge l'amorçage dont gcc a besoin (cela va faire des choses comme lancer des initialiseurs statiques et d'autres tâches d'entretien).

.text 

Begins une section de texte - le 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 comme _main, crée des symboles de débogage indiquant que _main est une fonction. Cela peut être utilisé par les débogueurs.

_main:

Lance un nouveau label (Il va finir par une adresse). la directive .globl ci-dessus rend cette adresse visible aux 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 la pile pointeur vers 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 l'aligne efficacement sur une limite de 16 octets. L'accès aux valeurs alignées sur la pile est beaucoup plus rapide que si elles n'étaient pas alignées. Toutes ces instructions précédentes sont à peu près un prologue de fonction standard.

call  ___main 

Appelle la fonction ___main qui va effectuer les tâches d'initialisation dont gcc a besoin. Appel poussera le pointeur d'instruction en cours sur la pile et sauter à l'adresse de ___main

movl  $0, %eax 

mouvement 0 au registre eax, (le 0 return 0;) le registre eax est utilisé pour maintenir La fonction renvoie des valeurs pour la convention d'appel stdcall.

laisser

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

movl  ebp,esp 
popl  ebp 

-à-dire qu'il "undo" les choses fait au début de la fonction - la restauration du cadre pointeur et pile à son état antérieur.

ret

Retourne à celui qui appelle cette fonction. Il va sortir le pointeur d'instruction de la pile (qu'une instruction d'appel correspondante aura placé là) et sauter là.

2

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 pendant son flux, une référence à où sont les arguments transmis à la fonction et où sont ses propres variables locales. La première chose qu'une fonction fait est de sauvegarder l'état du ebp donné en faisant pushl %ebp, il est vital pour la fonction qui fait l'appel, et de le remplacer par sa propre position de pile actuelle esp en faisant movl %esp, %ebp. La mise à zéro des 4 derniers bits de ebp à ce stade est spécifique à GCC, je ne sais pas pourquoi ce compilateur fait cela. Cela fonctionnerait sans le faire. Maintenant, nous allons dans les affaires, call ___main, qui est __main? Je ne sais pas non plus ... peut-être plus de procédures spécifiques GCC, et enfin la seule chose que fait votre main(), mettre la valeur de retour 0 avec movl $0, %eax et leave qui est le même que de faire movl %ebp, %esp; popl %ebp pour restaurer ebp état, puis ret à terminer. ret affiche eip et continue le flux de thread à partir de ce point, où qu'il soit (comme son principal(), cette ret conduit probablement à une procédure de noyau qui gère la fin du programme).

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

5

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

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

donc il masquez les 4 derniers bits de 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).

12

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'emphase et des ajouts.

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

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

Les trois premières instructions sont la mise en place d'une trame de pile qui est un terme technique pour le stockage de travail d'un sous-programme - les variables locales et temporaires pour la plupart. En poussant ebp, vous enregistrez la base du cadre de pile de l'appelant.Mettre esp dans ebp définit la base de notre cadre de pile. Le andl aligne le cadre de la pile sur une limite de 16 octets au cas où les variables locales de la pile nécessitent un alignement de 16 octets (pour les instructions x86 SIMD nécessitent cet alignement, mais l'alignement accélère les types ordinaires tels que int s et float s

à ce stade, vous auriez normalement attendre esp pour se déplacer dans la mémoire pour allouer de l'espace de pile pour les variables locales. Votre main a pas si gcc ne prend pas la peine.

l'appel à __main est particulière à la principale point d'entrée et n'apparaîtra pas dans les sous-programmes.)

Le reste va comme vous l'avez supposé. Register eax est l'endroit pour mettre des codes de retour entiers dans la spécification binaire. leave annule l'image de la pile et ret revient à l'appelant. Dans ce cas, l'appelant est le moteur d'exécution à faible niveau C qui fera la magie supplémentaire (comme appeler atexit() fonctions, définissez le code de sortie du processus et demander au système d'exploitation de mettre fin au processus.

4

Suite à la andl $-16,%esp, cela fonctionne parce que les bits de poids faible mise à zéro sera toujours régler %espvers le bas en valeur, et la pile grossit vers le bas sur x86.

Questions connexes