tutorialspoint tutorial programming how example course code assembler c gcc assembly x86 x86-64

c - tutorial - nasm programming



main() a veces mantiene el puntero de marco con-fomit-frame-pointer en x86 (1)

Accidentalmente descubrí algo extraño sobre el puntero de fomit-frame-puntero con GCC en x86 cuando estaba haciendo la tarea.
Mira el siguiente código (que parece bastante absurdo, pero de alguna manera relacionado con la forma en que descubrí el problema)

#include <stdio.h> void foo(void); int main() { foo(); return 0; } void foo() { printf("0x%x/n", *(unsigned char *)main); }

cuando se compila con -m64 -O1 indicador (-fomit-frame-pointer habilitado), el desensamblaje es como el siguiente

0000000000400500 <foo>: 400500: 48 83 ec 08 sub $0x8,%rsp 400504: 0f b6 35 14 00 00 00 movzbl 0x14(%rip),%esi # 40051f <main> 40050b: bf c4 05 40 00 mov $0x4005c4,%edi 400510: b8 00 00 00 00 mov $0x0,%eax 400515: e8 c6 fe ff ff callq 4003e0 <printf@plt> 40051a: 48 83 c4 08 add $0x8,%rsp 40051e: c3 retq 000000000040051f <main>: 40051f: 48 83 ec 08 sub $0x8,%rsp 400523: e8 d8 ff ff ff callq 400500 <foo> 400528: b8 00 00 00 00 mov $0x0,%eax 40052d: 48 83 c4 08 add $0x8,%rsp 400531: c3 retq

Todo parece estar bien porque% rbp no aparece en absoluto. Sin embargo, cuando el código se compila con -m32 -O1 indicador (a partir de gcc 4.6 -fomit-frame-pointer se convierte en predeterminado y el mío es GCC 4.8.2) o incluso utiliza -fomit-frame-pointer explícitamente, el desmontaje es como el siguiente .

08048400 <foo>: 8048400: 83 ec 1c sub $0x1c,%esp 8048403: 0f b6 05 1e 84 04 08 movzbl 0x804841e,%eax 804840a: 89 44 24 04 mov %eax,0x4(%esp) 804840e: c7 04 24 c0 84 04 08 movl $0x80484c0,(%esp) 8048415: e8 b6 fe ff ff call 80482d0 <printf@plt> 804841a: 83 c4 1c add $0x1c,%esp 804841d: c3 ret 0804841e <main>: 804841e: 55 push %ebp 804841f: 89 e5 mov %esp,%ebp 8048421: 83 e4 f0 and $0xfffffff0,%esp 8048424: e8 d7 ff ff ff call 8048400 <foo> 8048429: b8 00 00 00 00 mov $0x0,%eax 804842e: c9 leave 804842f: c3 ret

La función foo ve bastante igual en 32 bits y 64 bits. Sin embargo, a diferencia del de 64 bits, las dos primeras instrucciones de main son (observe que está compilado con -fomit-frame-pointer):

push %ebp mov %esp, %ebp

que se asemeja al código x86 normal.
Después de varios experimentos, encontré que si main llama a otra función, el código será como el de arriba, y si no hay una llamada de función en main , el código se parecerá a los de 64 bits.

Sé que esta pregunta puede parecer extraña, pero tengo curiosidad acerca de por qué existe esta diferencia entre el código x86 y x86_64, y solo existe con la función main() .


Esto no está relacionado con -fomit-frame-pointer hasta donde yo sé, sino que es un resultado de la alineación de la pila.

necesidades main para alinear la pila (con and $0xfffffff0, %esp ) para que las funciones que llama obtengan la alineación que esperan. Esto destruye el antiguo valor de esp , que en consecuencia tiene que ser salvado y restaurado para que el ret haga lo correcto. (Cuando se ejecuta el ret , esp debe estar apuntando a la misma ubicación que estaba al ingresar a main , es decir, en la dirección de retorno que se guardó en la pila).

Así que esp tiene que ser salvado y restaurado: ¿y por qué no a un registro de ebp como ebp ? De hecho, ebp es una buena opción porque hay una instrucción específica, leave , para realizar el movl %ebp, %esp/popl %ebp deseado movl %ebp, %esp/popl %ebp al final de la página main .

En x64, se puede esperar que la pila se alinee al ingresar a main , por lo que no es necesario alinear y el consiguiente almacenamiento y restauración de esp .