Salida de ensamblaje de GCC de un programa vacío en x86, win32

Salida de ensamblaje de GCC de un programa vacío en x86, win32


Escribo programas vacíos para molestar a los codificadores de stackoverflow, NO. Solo estoy explorando la cadena de herramientas gnu.


Ahora, lo siguiente podría ser demasiado profundo para mí, pero para continuar con la saga de programas vacíos, comencé a examinar la salida del compilador C, las cosas que GNU consume.


gcc version 4.4.0 (TDM-1 mingw32)

prueba.c:


int main()
{
return 0;
}

gcc -S prueba.c


    .file   "test.c"
.def ___main; .scl 2; .type 32; .endef
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
call ___main
movl $0, %eax
leave
ret

¿Puedes explicar lo que sucede aquí? Aquí está mi esfuerzo por entenderlo. He usado el as manual y mi conocimiento mínimo de ASM x86:



  • .file "test.c" es la directiva para el nombre de archivo lógico.

  • .def :de acuerdo con los documentos "Empezar a definir información de depuración para un nombre de símbolo" . ¿Qué es un símbolo (¿un nombre de función/variable?) y qué tipo de información de depuración?

  • .scl :los documentos dicen "La clase de almacenamiento puede marcar si un símbolo es estático o externo" . ¿Es esto lo mismo estático? y externo Lo sé de C? ¿Y qué es ese '2'?

  • .type :almacena el parámetro "como atributo de tipo de una entrada de la tabla de símbolos" , no tengo ni idea.

  • .endef :no hay problema.

  • .text :Ahora bien, esto es problemático, parece ser algo llamado sección y he leído que es el lugar para el código, pero los documentos no me dijeron demasiado.

  • .globl "hace que el símbolo sea visible para ld". , el manual es bastante claro al respecto.

  • _main: Esta podría ser la dirección inicial (?) de mi función principal

  • pushl_ :Un empuje largo (32 bits), que coloca EBP en la pila

  • movl :movimiento de 32 bits. Pseudo-C:EBP = ESP;

  • andl :AND lógico. Pseudo-C:ESP = -16 & ESP , Realmente no veo cuál es el punto de esto.

  • call :Empuja la IP a la pila (para que el procedimiento llamado pueda encontrar su camino de regreso) y continúa donde __main es. (¿Qué es __principal?)

  • movl :este cero debe ser la constante que devuelvo al final de mi código. El MOV coloca este cero en EAX.

  • leave :restaura la pila después de una instrucción ENTER (?). ¿Por qué?

  • ret :vuelve a la dirección de instrucción que está guardada en la pila


¡Gracias por tu ayuda!


Respuestas:



Comandos que comienzan con . son directivas para el ensamblador. Esto solo dice que esto es "archivo.c", esa información se puede exportar a la información de depuración del exe.



Las directivas .def definen un símbolo de depuración. scl 2 significa clase de almacenamiento 2 (clase de almacenamiento externo). El tipo 32 dice que este símbolo es una función. Estos números serán definidos por el formato exe pe-coff


___main es una función llamada que se encarga del arranque que necesita gcc (hará cosas como ejecutar inicializadores estáticos c++ y otras tareas de limpieza necesarias).



Comienza una sección de texto:el código vive aquí.



define el símbolo _main como global, lo que lo hará visible para el enlazador y para otros módulos que estén enlazados.



Lo mismo que _main, crea símbolos de depuración que indican que _main es una función. Esto puede ser utilizado por los depuradores.



Comienza una nueva etiqueta (Terminará como una dirección). la directiva .globl anterior hace que esta dirección sea visible para otras entidades.



Guarda el puntero de marco antiguo (registro ebp) en la pila (para que pueda volver a colocarse cuando finalice esta función)



Mueve el puntero de la pila al registro ebp. ebp a menudo se denomina puntero de marco, apunta a la parte superior de los valores de la pila dentro del "marco" actual (función generalmente), (referirse a las variables en la pila a través de ebp puede ayudar a los depuradores)



Anda la pila con fffffff0 que la alinea efectivamente en un límite de 16 bytes. El acceso a los valores alineados en la pila es mucho más rápido que si no estuvieran alineados. Todas estas instrucciones anteriores son prácticamente un prólogo de función estándar.


call        ___main

Llama a la función ___main que inicializará las cosas que necesita gcc. Call empujará el puntero de instrucción actual en la pila y saltará a la dirección de ___main



mueva 0 al registro eax, (el 0 devuelve 0;) el registro eax se usa para contener valores de retorno de función para la convención de llamada stdcall.



La instrucción de licencia es más o menos una forma abreviada de



es decir, "deshace" lo que se hizo al comienzo de la función, restaurando el puntero del marco y la pila a su estado anterior.



Vuelve a quien haya llamado a esta función. Sacará el puntero de instrucción de la pila (que la instrucción de llamada correspondiente habrá colocado allí) y saltará allí.