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
.