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