programming pelicula app assembly

pelicula - assembly synonym



¿Cuál es el significado de cada línea de la salida de ensamblaje de un mundo de C hello? (3)

Aquí cómo va:

.file "test.c"

El nombre original del archivo de origen (utilizado por los depuradores).

.section .rodata .LC0: .string "Hello world!"

Se incluye una cadena con terminación cero en la sección ".rodata" ("ro" significa "solo lectura": la aplicación podrá leer los datos, pero cualquier intento de escribir en ella desencadenará una excepción).

.text

Ahora escribimos cosas en la sección ".text", que es donde va el código.

.globl main .type main, @function main:

Definimos una función llamada "main" y globalmente visible (otros archivos de objetos podrán invocarla).

leal 4(%esp), %ecx

Almacenamos en el registro %ecx el valor 4+%esp ( %esp es el puntero de la pila).

andl $-16, %esp

%esp se modifica ligeramente para que se convierta en un múltiplo de 16. Para algunos tipos de datos (el formato de coma flotante correspondiente al double y long double C), el rendimiento es mejor cuando los accesos a la memoria están en direcciones que son múltiplos de 16. Esto no es realmente necesario aquí, pero cuando se usa sin el indicador de optimización ( -O2 ...), el compilador tiende a producir bastante código genérico inútil (es decir, código que podría ser útil en algunos casos pero no aquí).

pushl -4(%ecx)

Este es un poco raro: en ese punto, la palabra en la dirección -4(%ecx) es la palabra que estaba en la parte superior de la pila antes del andl . El código recupera esa palabra (que debería ser la dirección de retorno, por cierto) y la empuja nuevamente. Este tipo de emula lo que se obtendría con una llamada de una función que tenía una pila alineada de 16 bytes. Supongo que este push es un remanente de una secuencia de copia de argumentos. Como la función ha ajustado el puntero de la pila, debe copiar los argumentos de la función, a los que se puede acceder a través del valor anterior del puntero de la pila. Aquí, no hay argumento, excepto la dirección de retorno de la función. Tenga en cuenta que esta palabra no se usará (una vez más, este es código sin optimización).

pushl %ebp movl %esp, %ebp

Este es el prólogo de función estándar: guardamos %ebp (ya que estamos a punto de modificarlo), luego establecemos %ebp para apuntar al marco de la pila. A partir de entonces, se %ebp para acceder a los argumentos de la función, haciendo que %esp vuelva a estar libre. (Sí, no hay argumento, así que esto es inútil para esa función).

pushl %ecx

Ahorramos %ecx (lo necesitaremos en la salida de función, para restaurar %esp en el valor que tenía antes de andl ).

subl $20, %esp

Reservamos 32 bytes en la pila (recuerde que la pila crece "hacia abajo"). Ese espacio se usará para guardar los argumentos en printf() (eso es exagerado, ya que hay un único argumento, que usará 4 bytes [eso es un puntero]).

movl $.LC0, (%esp) call printf

"Empujamos" el argumento a printf() (es decir, nos aseguramos de que %esp apunta a una palabra que contiene el argumento, aquí $.LC0 , que es la dirección de la cadena constante en la sección rodata). Luego llamamos a printf() .

addl $20, %esp

Cuando printf() retorna, eliminamos el espacio asignado para los argumentos. Este addl cancela lo que hizo el subl anterior.

popl %ecx

Recuperamos %ecx ( %ecx arriba); printf() puede haberlo modificado (las convenciones de llamada describen qué registro puede modificar una función sin restaurarlos al salir; %ecx es uno de esos registros).

popl %ebp

Epílogo de función: restaura el %ebp (correspondiente al pushl %ebp anterior).

leal -4(%ecx), %esp

Restauramos %esp a su valor inicial. El efecto de este opcode es almacenar en %esp el valor %ecx-4 . %ecx se configuró en el primer opcode de función. Esto cancela cualquier alteración a %esp , incluido el andl .

ret

Salida de función

.size main, .-main

Esto establece el tamaño de la función main() : en cualquier punto durante el ensamblaje, " . " Es un alias para "la dirección en la que estamos agregando cosas ahora". Si se agregó otra instrucción aquí, iría a la dirección especificada por " . ". Por lo tanto, " .-main ", aquí, es el tamaño exacto del código de la función main() . La directiva .size indica al ensamblador que escriba esa información en el archivo de objeto.

.ident "GCC: (GNU) 4.3.0 20080428 (Red Hat 4.3.0-8)"

A GCC le encanta dejar rastros de su acción. Esta cadena termina como un tipo de comentario en el archivo de objeto. El enlazador lo eliminará.

.section .note.GNU-stack,"",@progbits

Una sección especial donde GCC escribe que el código puede acomodar una pila no ejecutable. Este es el caso normal. Las pilas ejecutables son necesarias para algunos usos especiales (no estándar C). En los procesadores modernos, el kernel puede hacer una pila no ejecutable (una pila que desencadena una excepción si alguien intenta ejecutar como código algunos datos que están en la pila); Algunas personas lo consideran una "característica de seguridad" porque poner código en la pila es una forma común de explotar los desbordamientos del búfer. Con esta sección, el ejecutable se marcará como "compatible con una pila no ejecutable" que el kernel proporcionará gustoso como tal.

Ejecuté gcc -S sobre esto:

int main() { printf ("Hello world!"); }

y obtuve este código de ensamblaje:

.file "test.c" .section .rodata .LC0: .string "Hello world!" .text .globl main .type main, @function main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $20, %esp movl $.LC0, (%esp) call printf addl $20, %esp popl %ecx popl %ebp leal -4(%ecx), %esp ret .size main, .-main .ident "GCC: (GNU) 4.3.0 20080428 (Red Hat 4.3.0-8)" .section .note.GNU-stack,"",@progbits

Tengo curiosidad por entender esta salida. ¿Alguien puede compartir algunos consejos para entender este resultado, o si alguien puede marcar comentarios en contra de cada una de estas líneas / grupo de líneas explicando qué hace, sería genial?


Aquí hay un suplemento a la @Thomas Pornin de @Thomas Pornin .

  • .LC0 constante local, por ejemplo, cadena literal.
  • .LFB0 función local comenzando,
  • .LFE0 función local final,

El sufijo de estas etiquetas es un número, y comienza desde 0.

Esta es la convención de ensamblador gcc.


leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $20, %esp

estas instrucciones no se pueden comparar en su programa c, siempre se ejecutan al comienzo de cada función (pero depende del compilador / plataforma)

movl $.LC0, (%esp) call printf

este bloque corresponde a tu llamada printf (). la primera instrucción coloca en la pila su argumento (un puntero a "hello world") y luego llama a la función.

addl $20, %esp popl %ecx popl %ebp leal -4(%ecx), %esp ret

Estas instrucciones son opuestas al primer bloque, son un tipo de material de manipulación de pila. siempre ejecutado también