Aquí cómo va:
.file "test.c"
El nombre del archivo fuente original (usado por los depuradores).
.section .rodata
.LC0:
.string "Hello world!"
Una cadena terminada en cero se incluye en la sección ".rodata" ('ro' significa 'sólo lectura': la aplicación será capaz de leer los datos, pero cualquier intento de escribir en él lo harán 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
que almacenamos en el registro %ecx
el valor 4+%esp
(%esp
es el puntero de pila).
andl $-16, %esp
%esp
se modifica ligeramente para que sea un múltiplo de 16. Para algunos tipos de datos (el formato de coma flotante correspondiente a C de double
y long double
), el rendimiento es mejor cuando los accesos de memoria están en direcciones que son múltiplo de 16. Esto no es realmente necesario aquí, pero cuando se utiliza 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)
Ésta es un poco raro: en ese momento, la palabra en la dirección -4(%ecx)
es la palabra que estaba en la parte superior de la pila antes de la 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: que guardar %ebp
(ya que estamos a punto de modificarlo), a continuación, establecer %ebp
para que apunte al marco de pila. A partir de entonces, se usará %ebp
para acceder a los argumentos de la función, haciendo que %esp
vuelva a estar libre. (Sí, no hay ningún argumento, por lo que este es inútil para esa función.)
pushl %ecx
Ahorramos %ecx
(que lo necesitará en la salida de función, para restaurar %esp
en el valor que tenía antes de la andl
).
subl $20, %esp
Nos reservamos 32 bytes en la pila (recordemos que la pila crece "hacia abajo"). Ese espacio se usará para guardar los argumentos en printf()
(eso es exagerado, ya que hay un solo argumento, que usará 4 bytes [eso es un puntero]).
movl $.LC0, (%esp)
call printf
Nosotros "empujar" el argumento para printf()
(es decir, nos aseguramos de que %esp
puntos 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 al printf()
.
addl $20, %esp
Cuando printf()
rendimientos, se elimina el espacio asignado para los argumentos. Este addl
cancela lo que hizo el subl
anterior.
popl %ecx
Recuperamos %ecx
(presionada arriba); printf()
puede haberlo modificado (las convenciones de llamadas describen qué registro puede modificar una función sin restaurarlas al salir; %ecx
es uno de esos registros).
popl %ebp
Función epílogo: esto restaura %ebp
(correspondiente a la pushl %ebp
arriba).
leal -4(%ecx), %esp
Restauramos %esp
a su valor inicial. El efecto de este código de operación es almacenar en %esp
el valor %ecx-4
. %ecx
se configuró en el primer código de operación de función. Esto cancela cualquier alteración al %esp
, incluido el andl
.
ret
Función de salida.
.size main, .-main
Ajusta el tamaño de la función main()
: en cualquier punto durante el montaje, ".
" es un alias de "la dirección en la que estamos añadiendo cosas en este momento".Si se agregó otra instrucción aquí, iría a la dirección especificada por ".
". Por lo tanto, ".-main
", aquí está 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)"
GCC just loves to leave traces of its action. 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.
Es cierto, ¿alguna sugerencia? .. específico de Linux? o tal vez buenos blogs, sitios web? (Sí, puedo buscar esto en Google, pero a veces la gente está mejor informada de lo que Google le arroja ...) – Mohammed
No, yo no sería una de esas personas. Personalmente, sé * extremadamente * poco sobre el ensamblador. Quizás [esta pregunta] (http://stackoverflow.com/questions/199679/good-beginners-books-for-assembly-languages) sería útil? –
Votación para cerrar como demasiado amplia. –