2010-06-06 5 views
6

J'ai décidé que ce serait amusant pour apprendre l'assemblage x86 pendant les vacances d'été. J'ai donc commencé avec un programme très simple de hello world, en empruntant sur des exemples gratuits gcc -S pourrait me donner. J'ai fini avec ceci:i386 question d'assemblage: pourquoi dois-je me mêler du pointeur de la pile?

HELLO: 
    .ascii "Hello, world!\12\0" 
    .text 

.globl _main 
_main: 
    pushl %ebp  # 1. puts the base stack address on the stack 
    movl %esp, %ebp # 2. puts the base stack address in the stack address register 
    subl $20, %esp # 3. ??? 
    pushl $HELLO  # 4. push HELLO's address on the stack 
    call _puts  # 5. call puts 
    xorl %eax, %eax # 6. zero %eax, probably not necessary since we didn't do anything with it 
    leave    # 7. clean up 
    ret     # 8. return 
         # PROFIT! 

Il compile et fonctionne même! Et je pense que je comprends le plus de celui-ci. Cependant, la magie se produit à l'étape 3. Si je supprime cette ligne, mon programme va mourir entre l'appel à puts et xor à partir d'une erreur de pile mal alignée. Et devrais-je changer $20 à une autre valeur, il se bloque trop. Je suis donc arrivé à la conclusion que cette valeur est very important.

Le problème est, je ne sais pas ce qu'il fait et pourquoi il est nécessaire.

Quelqu'un peut-il m'expliquer? (Je suis sur Mac OS, cela importerait-il un jour?)

Répondre

3

Sur x86 OSX, la pile doit être alignée sur 16 octets pour les appels de fonction, voir document ABI here. Donc, l'explication est

 
push stack pointer (#1)   -4 
strange increment (#3)   -20 
push argument (#4)    -4 
call pushes return address (#5) -4 
total       -32 

Pour vérifier, changer la ligne n ° 3 de 20 $ à 4 $, qui fonctionne également. En outre, Ignacio Vazquez-Abrams souligne que le numéro 6 n'est pas facultatif. Les registres contiennent des résidus de calculs antérieurs, de sorte qu'il doit être explicitement mis à zéro.

J'ai récemment appris l'apprentissage (en apprentissage). Pour vous épargner le choc, les conventions d'appel 64 bits sont BEAUCOUP différentes (paramètres passés sur le registre). Trouvé this très utile pour l'assemblage 64bit.

3

La forme générale du commentaire devrait être "Allouer de l'espace pour les variables locales". Pourquoi changer il arbitrairement tomberait en panne je ne suis pas sûr. Je peux seulement le voir s'écraser si vous le réduisez. Et le commentaire approprié pour 6 est "Préparez-vous à retourner un 0 à partir de cette fonction".

+0

Les valeurs de retour sont donc passées dans '% eax'? Je pensais que ça irait toujours sur la pile puisqu'ils peuvent potentiellement être plus gros que 32 bits. Et aussi, pourquoi ai-je besoin d'allouer 24 octets si je n'utilise que 4 d'entre eux? (__EDIT__ ça fonctionne aussi avec 4. Donc je suppose que la pile doit être alignée sur une certaine limite.) – zneak

+0

On dirait que ça plante à cause d'un problème d'alignement, pas de débordement de pile. Les valeurs sont renvoyées dans edx: eax, eax ou une tranche de celle-ci, ou un registre FPU. –

+0

Je suis assez sûr que le pointeur de la pile doit être aligné sur des multiples d'un DWORD (4 octets) sur x86 parce qu'il est 32 bits. – erjiang

1

Notez que si vous compilez avec -fomit-frame-pointer, une partie de ce passe-partout %ebp disparaîtra. Le pointeur de base est utile pour le débogage mais n'est pas réellement nécessaire sur x86.

Je recommande également fortement l'utilisation de la syntaxe Intel, qui est prise en charge par toutes les fonctionnalités de GCC/binutils. J'avais l'habitude de penser que la différence entre AT et la syntaxe Intel était juste une question de goût, mais un jour je suis tombé sur this example où la mnémonique AT est totalement différente de celle d'Intel. Et puisque toute la documentation x86 officielle utilise la syntaxe Intel, elle semble être une meilleure solution.

Amusez-vous!

+0

Merci beaucoup. – zneak