GNU/Linux >> Tutoriales Linux >  >> Linux

¿Cuál es el significado de cada línea de la salida del ensamblado de un mundo C hello?

Aquí cómo va:

        .file   "test.c"

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

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

Se incluye una cadena terminada en cero en la sección ".rodata" ("ro" significa "solo lectura":la aplicación podrá leer los datos, pero cualquier intento de escribirlos 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 "principal" y visible globalmente (otros archivos objeto 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 punto flotante correspondiente al double de C y long double ), 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 una gran cantidad de código inútil genérico (es decir, código que podría ser útil en algunos casos pero no aquí).

        pushl   -4(%ecx)

Este es un poco extraño: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, por cierto, debería ser la dirección de retorno) y la empuja de nuevo. Este tipo de emula lo que se obtendría con una llamada de una función que tuviera una pila alineada de 16 bytes. Supongo que este push es un remanente de una secuencia de copia de argumentos. Dado que la función ha ajustado el puntero de la pila, debe copiar los argumentos de la función, a los que se podía acceder a través del valor anterior del puntero de la pila. Aquí, no hay ningún argumento, excepto la dirección de retorno de la función. Tenga en cuenta que esta palabra no se utilizará (una vez más, este es un código sin optimización).

        pushl   %ebp
        movl    %esp, %ebp

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

        pushl   %ecx

Guardamos %ecx (lo necesitaremos al salir de la función, para restaurar %esp al valor que tenía antes del andl ).

        subl    $20, %esp

Reservamos 32 bytes en la pila (recuerda que la pila crece "hacia abajo"). Ese espacio se usará para almacenar los argumentos de printf() (eso es exagerado, ya que hay un solo 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 printf() .

        addl    $20, %esp

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

        popl    %ecx

Recuperamos %ecx (empujado 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 la función:esto restaura %ebp (correspondiente al 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 estableció en el código de operación de la primera función. Esto cancela cualquier alteración a %esp , incluido el andl .

        ret

Salir de la función.

        .size   main, .-main

Esto establece el tamaño del main() función:en cualquier momento durante el ensamblaje, ". " es un alias para "la dirección en la que estamos agregando cosas en este momento". Si se agregara 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() . El .size La directiva le indica al ensamblador que escriba esa información en el archivo del objeto.

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

A GCC le encanta dejar huellas de su acción. Esta cadena termina como una especie 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. Se necesitan pilas ejecutables para algunos usos especiales (no C estándar). En los procesadores modernos, el núcleo puede crear una pila no ejecutable (una pila que activa una excepción si alguien intenta ejecutar como código algunos datos que están en la pila); Algunas personas ven esto como una "característica de seguridad" porque poner código en la pila es una forma común de explotar los desbordamientos de búfer. Con esta sección, el ejecutable se marcará como "compatible con una pila no ejecutable" que el kernel felizmente proporcionará como tal.


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

estas instrucciones no se comparan 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 su llamada printf(). la primera instrucción coloca en la pila su argumento (un puntero a "hola mundo") 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 una especie de material de manipulación de pila. siempre ejecutado también


Aquí hay un complemento para @Thomas Pornin la respuesta de.

  • .LC0 constante local, por ejemplo, cadena literal.
  • .LFB0 comienzo de la función local,
  • .LFE0 terminación de función local,

El sufijo de esta etiqueta es un número y comienza desde 0.

Esta es la convención del ensamblador gcc.


Linux
  1. ¿Cuáles son las responsabilidades de cada componente de pseudoterminal (pty) (software, lado maestro, lado esclavo)?

  2. ¿Cómo saber en qué versión de Os X estoy desde la línea de comandos?

  3. ¿Contar los caracteres de cada línea con Wc?

  4. ¿Qué significa en la salida de Ps?

  5. ¿Cuál es el significado de POSIX?

Iterando sobre cada línea de salida ls -l

¿Cuál es el significado de *nix?

¿Cuál es el significado de fork() y grep en Linux?

¿Cuál es el segundo estado en ip link show output?

Un comando para mostrar cada línea hacia adelante y luego hacia atrás

Significado de la línea buffers/cache en la salida de free