Estoy experimentando con el desensamblaje de archivos binarios clang de programas C simples (compilados con -O0 ), y estoy confundido acerca de cierta instrucción que se genera.
Aquí hay dos funciones main vacías con argumentos estándar, una de las cuales devuelve valor y la otra no:
// return_void.c void main(int argc, char** argv) { } // return_0.c int main(int argc, char** argv) { return 0; }Ahora, cuando desarmo sus ensamblajes, se ven razonablemente diferentes, pero hay una línea que no entiendo:
return_void.bin: (__TEXT,__text) section _main: 0000000000000000 pushq %rbp 0000000000000001 movq %rsp, %rbp 0000000000000004 movl %edi, -0x4(%rbp) 0000000000000007 movq %rsi, -0x10(%rbp) 000000000000000b popq %rbp 000000000000000c retq return_0.bin: (__TEXT,__text) section _main: 0000000100000f80 pushq %rbp 0000000100000f81 movq %rsp, %rbp 0000000100000f84 xorl %eax, %eax # We return with EAX, so we clean it to return 0 0000000100000f86 movl $0x0, -0x4(%rbp) # What does this mean? 0000000100000f8d movl %edi, -0x8(%rbp) 0000000100000f90 movq %rsi, -0x10(%rbp) 0000000100000f94 popq %rbp 0000000100000f95 retqSolo se genera cuando uso la función no es nula, así que pensé que podría ser otra forma de devolver 0, pero cuando cambié la constante devuelta, esta línea no cambió en absoluto:
// return_1.c int main(int argc, char** argv) { return 1; } empty_return_1.bin: (__TEXT,__text) section _main: 0000000100000f80 pushq %rbp 0000000100000f81 movq %rsp, %rbp 0000000100000f84 movl $0x1, %eax # Return value modified 0000000100000f89 movl $0x0, -0x4(%rbp) # This value is not modified 0000000100000f90 movl %edi, -0x8(%rbp) 0000000100000f93 movq %rsi, -0x10(%rbp) 0000000100000f97 popq %rbp 0000000100000f98 retq¿Por qué se genera esta línea y cuál es su propósito?
El propósito de esa área se revela mediante el siguiente código
int main(int argc, char** argv) { if (rand() == 42) return 1; printf("Helo World!\n"); return 0; }Al principio lo hace
movl $0, -4(%rbp)entonces el regreso anticipado queda de la siguiente manera
callq rand cmpl $42, %eax jne .LBB0_2 movl $1, -4(%rbp) jmp .LBB0_3y luego al final lo hace
.LBB0_3: movl -4(%rbp), %eax addq $32, %rsp popq %rbp retq Por lo tanto, esta área está reservada para almacenar el valor de retorno de la función. No parece ser muy necesario y no se usa en código optimizado, pero en modo -O0 así es como funciona.
clang está haciendo espacio en la pila para los argumentos (registros edi y rsi ) y también pone el valor 0 en la pila, por alguna razón. Supongo que clang compila su código en una representación SSA como esta:
int main(int argc, char** argv) { int a; a = 0; return a; } Esto explicaría por qué se asigna una ranura de pila. Si clang también hace una propagación constante, esto explicaría por qué eax se pone a cero en lugar de cargarse desde -4(%rbp) . En general, no piense demasiado en construcciones dudosas en un ensamblaje no optimizado . Después de todo, le prohibiste al compilador eliminar código inútil.
movl $0x0,-0x4(%rbp) Esta instrucción almacena 0 en %rbp - 4 . Parece que clang asigna una variable local oculta para un valor de retorno implícito de main.
De la lista de correo clang:
Sí. Asignamos una variable local implícita para contener el valor de retorno; declaraciones de retorno luego simplemente inicialice la ranura de retorno y salte al epílogo, donde se carga y devuelve la ranura. No usamos un phi porque el flujo de control para llegar al epílogo no es necesariamente tan simple como una rama simple, debido a las limpiezas en los ámbitos locales (como los destructores de C++).
Los valores de retorno implícitos como los de main se manejan con un almacén implícito en el prólogo.
Fuente: http://lists.cs.uiuc.edu/pipermail/cfe-dev/2012-February/019767.html