55 votes

La sortie d'assemblage de GCC d'un programme vide sur x86, win32

J'écris vide programmes d'importuner l'enfer hors de stackoverflow codeurs, PAS. Je suis tout simplement découvrir la chaîne d'outils gnu.

Maintenant ce qui suit est peut-être trop profond pour moi, mais pour continuie le programme vide de la saga que j'ai commencé à examiner la sortie du compilateur C, les trucs GNU, comme en consomme.

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. J'ai utilisé l' as manuel et mon minimale ASM x86 connaissances:

  • .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 ou 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 le même statique et externe je sais par C? Et qu'est ce que '2'?
  • .type: stocke le paramètre "comme l'attribut type de symbole d'entrée de la table", je n'ai aucune idée.
  • .endef: pas de problème.
  • .text: C'est problématique, il semble être quelque chose qui s'appelle l'article et j'ai lu que c'est la place pour le code, mais les docs ne me dites pas trop.
  • .globl "fait le symbole visible de ld.", le manuel est assez clair sur ce point.
  • _main: Cela peut être l'adresse de départ (?) pour ma fonction principale
  • pushl_: Long (32 bits) pousser, ce qui le place EBP sur la pile
  • movl: 32 bits déplacer. Pseudo-C: EBP = ESP;
  • andl: Logique ET. Pseudo-C: ESP = -16 & ESP, je ne vois vraiment pas quel est le point de cet.
  • call: Pousse l'IP de la pile (donc la procédure d'appel peut trouver son chemin de retour) et se poursuit là où __main . (qu'est-ce que __principal?)
  • movl: c'zéro doit être la constante I retour à la fin de mon code. Le MOV lieux de ce à zéro dans EAX.
  • leave: restaure la pile après l'ENTRÉE de l'enseignement (?). Pourquoi?
  • ret: remonte à l'adresse d'instruction qui est sauvegardé sur la pile

Merci pour votre aide!

56voto

nos Points 102226

.fichier "test.c"

Les commandes de démarrage . sont les directives à l'assembleur. Cette juste dit que c'est "fichier.c", que les informations peuvent être exportées vers les informations de débogage de l'exe.

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

.def directives définit les symboles de débogage. scl 2 moyens de stockage de classe 2(externes de stockage de classe) .type 32 dit ce sumbol est une fonction. Ces numéros seront définies par le pe-coff exe format

___principal est un appel de fonction qui prend soin de l'amorçage gcc besoins(il va faire des choses comme l'exécution c++ initialiseurs statiques et d'autres de ménage nécessaire).

.text

Commence une section de texte - code de la vie ici.

.globl _main

définit la _main symbole comme un phénomène global, qui permettra de rendre visible à l'éditeur de liens et à d'autres modules, c'est lié.

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

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

_main:

Commence un nouveau label(Ça va finir, une adresse). l' .globl la directive ci-dessus fait de cette adresse visible pour les autres entités.

pushl       %ebp

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

movl        %esp, %ebp

Déplace le pointeur de pile pour ebp. ebp est souvent appelé le pointeur de l'image, c'points au sommet de la pile de valeurs au sein de l'actuel "frame"(fonction d'habitude), (en se référant à des variables sur la pile via ebp peut aider les débogueurs)

andl $-16, %esp

Padn la pile avec fffffff0 qui effectivly aligne sur un 16 frontière d'octet. L'accès à l'alignés valeurs sur la pile sont beaucoup plus rapidement que si elles étaient des non alignés. Toutes ces instructions précédentes sont assez bien une fonction standard prologue.

call        ___main

Appelle la ___fonction principale qui va faire l'initialisation des trucs que gcc besoins. Appel va pousser le pointeur d'instruction en cours sur la pile et le saut à l'adresse de ___principaux

movl        $0, %eax

déplacer 0 dans le registre eax,(0 return 0;) le registre eax est utilisé pour contenir des valeurs de retour de fonction pour la convention d'appel stdcall.

laisser

Le congé d'instruction est assez bien d'abréviation pour

movl     ebp,esp
popl     ebp

c'est à dire qu'il "annulations" de la substance fait au début de la fonction, - la restauration de l'image du pointeur de pile et de son état d'origine.

ret

Retourne à celui qui a appelé cette fonction. Il va pop le pointeur d'instruction de la pile (qui correspondant de l'instruction call va y avoir placé) et de sauter.

12voto

George Phillips Points 2433

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

Vous avez compris la plupart des -- je vais juste faire des remarques supplémentaires pour l'accent et les ajouts.

__main est un sous-programme dans la GNU bibliothèque standard qui prend soin de plusieurs start-up de l'initialisation. Il n'est pas strictement nécessaire pour les programmes en C, mais est nécessaire seulement dans le cas où le code C est un lien avec le C++.

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

Les trois premières instructions sont la mise en place d'un cadre de pile qui est un terme technique pour la mémoire de travail d'un sous-programme, locales et variables temporaires pour la plupart. Poussant ebp enregistre la base de l'appelant cadre de pile. Mettre esp en ebp définit la base de notre cadre de pile. L' andl aligne le cadre de la pile à 16 frontière d'octet juste au cas où toutes les variables locales dans la pile nécessitent 16 octets d'alignement (pour le x86 instructions SIMD exiger que l'alignement, mais l'alignement n'vitesse ordinaires types - ints et floats.

À ce stade, vous auriez normalement s'attendre à esp pour obtenir déplacé vers le bas en mémoire à allouer de l'espace de pile pour les variables locales. Votre main a pas de gcc n'est pas la peine.

L'appel à l' __main est spécial pour le principal point d'entrée et ne figurent généralement dans les sous-routines.

Le reste se passe comme vous l'a supposé. Inscrivez eax est la place pour mettre d'entiers codes de retour dans le binaire spec. leave annule le cadre de la pile et de l' ret remonte à l'appelant. Dans ce cas, l'appelant est le faible niveau d'exécution C, qui fera magiques supplémentaires (comme appelant atexit() des fonctions, régler le code de sortie du processus et de demander au système d'exploitation pour terminer le processus.

5voto

Robert Points 674

À propos de cela etl -16 $,% esp

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

Ainsi, il masquera les 4 derniers bits de l'ESP (btw: 2 ** 4 = 16) et conservera tous les autres bits (que le système cible ait 32 ou 64 bits).

4voto

caf Points 114951

De plus à l' andl $-16,%esp , cela fonctionne parce que les bits de poids faible mise à zéro sera toujours ajuster %esp de la valeur, et la pile se développe vers le bas sur x86.

2voto

Havenard Points 9023

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

ebp est utilisé par la fonction pour stocker l'état initial de l' esp lors 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 qu'une fonction n'est à enregistrer l'état de l' ebp faire pushl %ebp, il est vital pour la fonction de faire l'appel, et que la remplace par sa propre pile actuelle position esp faire movl %esp, %ebp. Mise à zéro du 4 derniers bits de ebp à ce stade est GCC spécifique, je ne sais pas pourquoi ce compilateur est-ce que. Il pourrait fonctionner sans le faire. Maintenant, enfin, nous allons dans les affaires, call ___main, qui est __principaux? Je ne sais pas... peut-être plus de GCC procédures spécifiques, et enfin la seule chose que votre main() n'est, de retour est la valeur que 0 movl $0, %eax et leave qui est le même que le fait de faire movl %ebp, %esp; popl %ebp de restaurer ebp , ret à la fin. ret pop eip et continuer à fil flux à partir de ce point, là où il est (comme le main(), ce ret conduit sans doute à certains de noyau procédure qui gère à la fin du programme).

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X