2010-06-06 17 views
6

Decidí que sería diversión para aprender a ensamblar x86 durante las vacaciones de verano. Así que comencé con un programa hello world muy simple, pidiendo prestado en ejemplos gratuitos gcc -S podría darme. Terminé con esto:i386 pregunta de ensamblaje: ¿por qué necesito mezclarme con el puntero de la pila?

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! 

compila e incluso funciona! Y creo que entiendo más de él.

Sin embargo, la magia sucede en el paso 3. ¿Eliminaría esta línea, mi programa moriría entre la llamada a puts y la xor de un error de pila desalineada. Y cambiaría $20 a otro valor, se bloqueará también. Entonces llegué a la conclusión de que este valor es very importante.

El problema es que no sé qué es y por qué es necesario.

¿Alguien me puede explicar? (Estoy en Mac OS, ¿alguna vez importaría?)

Respuesta

3

En x86 OSX, la pila debe estar alineada 16 byte para las llamadas a funciones, ver ABI doc here. Entonces, la explicación es

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

Para verificar, cambie la línea # 3 de $ 20 a $ 4, que también funciona.

Además, Ignacio Vazquez-Abrams señala que el # 6 no es opcional. Los registros contienen restos de cálculos previos, por lo que debe ponerse a cero explícitamente.

Recientemente aprendí (todavía estoy aprendiendo) el ensamblaje. Para ahorrarle el impacto, las convenciones de llamadas de 64 bits son MUCHO diferentes (los parámetros se pasan en el registro). Se encontró this muy útil para el ensamblaje de 64 bits.

3

La forma general del comentario debería ser "Asigna espacio para variables locales". ¿Por qué cambiando lo bloquearía arbitrariamente? No estoy seguro. Solo puedo verlo estrellándose si lo reduces. Y el comentario apropiado para 6 es "Prepárese para devolver un 0 de esta función".

+0

¿Los valores de retorno se pasan en '% eax'? Pensé que siempre iría a la pila, ya que potencialmente pueden ser más grandes que 32 bits. Y también, ¿por qué tengo que asignar 24 bytes si solo voy a usar 4 de ellos? (__EDIT__ también funciona con 4. Así que supongo que la pila tiene que estar alineada en un cierto límite). – zneak

+0

Parece que está fallando debido a un problema de alineación, no a un desbordamiento de la pila. Los valores se devuelven en edx: eax, eax o una porción de los mismos, o un registro FPU. –

+0

Estoy bastante seguro de que el puntero de pila debe alinearse en múltiplos de un DWORD (4 bytes) en x86 porque es de 32 bits. – erjiang

1

Tenga en cuenta que si compila con -fomit-frame-pointer algo de ese %ebp puntero desaparecerá. El puntero base es útil para la depuración, pero en realidad no es necesario en x86.

También recomiendo usar la sintaxis Intel, que es compatible con todas las cosas de GCC/binutils. Solía ​​pensar que la diferencia entre AT & T y la sintaxis de Intel era solo cuestión de gustos, pero un día me encontré con this example, donde el ATnegoic AT & es totalmente diferente del Intel. Y dado que toda la documentación oficial de x86 usa la sintaxis de Intel, parece una mejor forma de hacerlo.

¡Diviértete!

+0

Muchas gracias. – zneak

Cuestiones relacionadas