Deencoding de código ensamblador equivalente de código C

Queriendo ver la salida del comstackdor (en ensamblador) para algún código C, escribí un progtwig simple en C y generé su archivo de ensamblaje usando gcc.

El código es este:

#include  int main() { int i = 0; if ( i == 0 ) { printf("testing\n"); } return 0; } 

El ensamblaje generado para ello está aquí (solo la función principal):

 _main: pushl %ebpz movl %esp, %ebp subl $24, %esp andl $-16, %esp movl $0, %eax addl $15, %eax addl $15, %eax shrl $4, %eax sall $4, %eax movl %eax, -8(%ebp) movl -8(%ebp), %eax call __alloca call ___main movl $0, -4(%ebp) cmpl $0, -4(%ebp) jne L2 movl $LC0, (%esp) call _printf L2: movl $0, %eax leave ret 

Estoy en una pérdida absoluta para correlacionar el código C y el código de assembly. Todo lo que tiene que hacer el código es almacenar 0 en un registro y compararlo con un 0 constante y realizar la acción adecuada. Pero, ¿qué está pasando en la asamblea?

Como main es especial, a menudo puede obtener mejores resultados haciendo este tipo de cosas en otra función (preferiblemente en su propio archivo sin main ). Por ejemplo:

 void foo(int x) { if (x == 0) { printf("testing\n"); } } 

Probablemente sería mucho más claro como assembly. Hacer esto también le permitiría comstackr con optimizaciones y seguir observando el comportamiento condicional. Si tuviera que comstackr su progtwig original con un nivel de optimización por encima de 0, probablemente eliminaría la comparación, ya que el comstackdor podría seguir adelante y calcular el resultado. Con este código, parte de la comparación está oculta para el comstackdor (en el parámetro x ), por lo que el comstackdor no puede hacer esta optimización.

Lo que realmente es el material extra

 _main: pushl %ebpz movl %esp, %ebp subl $24, %esp andl $-16, %esp 

Esto es configurar un marco de stack para la función actual. En x86, un marco de stack es el área entre el valor del puntero de stack (SP, ESP o RSP para 16, 32 o 64 bits) y el valor del puntero base (BP, EBP o RBP). Esto es supuestamente donde las variables locales viven, pero no realmente, y los marcos de stack explícitos son opcionales en la mayoría de los casos. Sin alloca uso de alloca de alloca y / o de longitud variable requeriría su uso.

Esta construcción particular del marco de stack es diferente a la de las funciones no main porque también se asegura de que la stack esté alineada a 16 bytes. La resta de ESP incrementa el tamaño de la stack en más de lo suficiente para mantener las variables locales y el andl resta efectivamente de 0 a 15, lo que hace que se alineen con 16 bytes. Esta alineación parece excesiva, excepto que forzaría a la stack a comenzar también con el caché alineado y con la palabra alineada.

 movl $0, %eax addl $15, %eax addl $15, %eax shrl $4, %eax sall $4, %eax movl %eax, -8(%ebp) movl -8(%ebp), %eax call __alloca call ___main 

No sé qué hace todo esto. alloca aumenta el tamaño del marco de stack al alterar el valor del puntero de stack.

 movl $0, -4(%ebp) cmpl $0, -4(%ebp) jne L2 movl $LC0, (%esp) call _printf L2: movl $0, %eax 

Creo que sabes lo que hace esto. De lo contrario, justo antes de que se mueva, la call está moviendo la dirección de su cadena a la ubicación superior de la stack para que pueda ser recuperada por printf. Debe pasarse a la stack para que printf pueda usar su dirección para inferir las direcciones de los otros argumentos de printf (si hay alguno, que no hay en este caso).

 leave 

Esta instrucción elimina el marco de stack del que se habló anteriormente. Esencialmente es movl %ebp, %esp seguido de popl %ebp . También hay una instrucción de enter que se puede usar para construir cuadros de stack, pero gcc no la usó. Cuando los marcos de stack no se usan explícitamente, EBP se puede usar como un registro general puropose y en lugar de leave el comstackdor solo agregaría el tamaño de marco de stack al puntero de stack, lo que disminuiría el tamaño de stack por el tamaño de marco.

 ret 

No necesito explicar esto.

Cuando se comstack con optimizaciones.

Estoy seguro de que volverá a comstackr todo esto con diferentes niveles de optimización, por lo que señalaré algo que puede suceder que probablemente le resulte extraño. He observado que gcc reemplaza printf y fprintf con fprintf y fputs , respectivamente, cuando la cadena de formato no contenía ningún % y no se pasaron parámetros adicionales. Esto se debe a que (por muchas razones) es mucho más barato llamar a puts y fputs y al final aún obtiene lo que desea imprimir.

No te preocupes por el preámbulo / postámbulo, la parte que te interesa es:

 movl $0, -4(%ebp) cmpl $0, -4(%ebp) jne L2 movl $LC0, (%esp) call _printf L2: 

Debe ser bastante evidente en cuanto a cómo esto se correlaciona con el código C original.

La primera parte es un código de inicialización, que no tiene ningún sentido en el caso de su ejemplo simple. Este código se eliminaría con un indicador de optimización.

La última parte puede ser asignada al código C:

 movl $0, -4(%ebp) // put 0 into variable i (located at -4(%ebp)) cmpl $0, -4(%ebp) // compare variable i with value 0 jne L2 // if they are not equal, skip to after the printf call movl $LC0, (%esp) // put the address of "testing\n" at the top of the stack call _printf // do call printf L2: movl $0, %eax // return 0 (calling convention: %eax has the return code) 

Bueno, gran parte de ella es la sobrecarga asociada con la función. main () es solo una función como cualquier otra, así que tiene que almacenar la dirección de retorno en la stack al inicio, configurar el valor de retorno al final, etc.

Recomendaría usar GCC para generar un código fuente y un ensamblador mixtos que le mostrarán el ensamblador generado para cada fuente de fuente.

Si desea ver el código C junto con el ensamblaje al que se convirtió, use una línea de comando como esta:

 gcc -c -g -Wa,-a,-ad [other GCC options] foo.c > foo.lst 

Consulte http://www.delorie.com/djgpp/v2faq/faq8_20.html

En linux, solo usa gcc. En Windows descargue Cygwin http://www.cygwin.com/


Editar: vea también esta pregunta ¿ Usando GCC para producir un ensamblaje legible?

y http://oprofile.sourceforge.net/doc/opannotate.html

Necesita algunos conocimientos sobre el lenguaje ensamblador para comprender el ensamblado generado por el comstackdor de C.

Este tutorial puede ser útil

Vea aquí más información. Puede generar el código de ensamblaje con C comentarios para una mejor comprensión.

 gcc -g -Wa,-adhls your_c_file.c > you_asm_file.s 

Esto debería ayudarte un poco.