2009-08-22 7 views
54

Escribo programas vacíos para molestar a los codificadores de stackoverflow, NO. Solo estoy explorando la cadena de herramientas gnu.Salida de conjunto de GCC de un programa vacío en x86, win32

Ahora lo siguiente puede ser demasiado profundo para mí, pero para continuar la saga de programa vacía, he comenzado a examinar la salida del compilador de C, lo que GNU consume.

gcc version 4.4.0 (TDM-1 mingw32) 

test.c:

int main() 
{ 
    return 0; 
} 

gcc -S test.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 

Puede explicar lo que pasa aquí? Aquí está mi esfuerzo por entenderlo. He utilizado el manual as y mi conocimiento mínimo x86 ASM:

  • .file "test.c" es la Directiva para el nombre de archivo lógico.
  • .def: de acuerdo con los documentos "Comenzar a definir la información de depuración para un nombre de símbolo". ¿Qué es un símbolo (un nombre/variable de función?) Y qué tipo de información de depuración?
  • .scl: docs say "La clase de almacenamiento puede marcar si un símbolo es estático o externo". ¿Es este el mismo estático y externo Lo sé de C? ¿Y qué es ese '2'?
  • .type: almacena el parámetro "como el atributo de tipo de una entrada de tabla de símbolos", no tengo ni idea.
  • .endef: no hay problema.
  • .text: Ahora 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 dicen 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_ (?): Una larga (32 bits) de empuje, lo que sitúa EBP en la pila
  • movl: 32 bits movimiento. Pseudo-C: EBP = ESP;
  • andl: lógico AND. Pseudo-C: ESP = -16 & ESP, realmente no veo cuál es el sentido de esto.
  • call: Empuja la dirección IP a la pila (para que el procedimiento llamado pueda encontrar su camino de regreso) y continúa donde está __main. (¿Qué es __main?)
  • 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: se remonta a la dirección de la instrucción que se guarda en la pila

Gracias por su ayuda!

+8

Buena pregunta. :) –

+4

Suena como un excelente ejercicio para un verdadero geek. – JesperE

+3

Encontré la especificación COFF. Esto debería dar algunas referencias a lo que "32" en ".type" significa, etc.: http://www.microsoft.com/whdc/system/platform/firmware/PECOFFdwn.mspx –

Respuesta

54

.FILE " test.c"

comandos que comienzan con. son las directivas al ensamblador. esto se dice que es 'file.c', esa información puede ser exportada a la información de depuración del exe.

.def ___main; .scl 2; .ty pe 32; .endef

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

___main es una función llamada que se encarga de la inicialización que necesita el gcc (hará cosas como ejecutar iniciadores estáticos C++ y otras tareas domésticas necesarias).

.text 

comienza una sección de texto - Código vive aquí.

.globl _main

define el símbolo _main tan global, lo que hará más visible para el enlazador y para otros módulos que está vinculada en.

.def  _main; .scl 2;  .type 32;  .endef 

Mismo cosa como _main, crea símbolos de depuración que indican que _main es una función. Esto puede ser usado por depuradores.

_main:

inicia una nueva etiqueta (Se va a terminar una dirección). la directiva .globl anterior hace que esta dirección sea visible para otras entidades.

pushl  %ebp 

países del viejo puntero de marco (registro ebp) en la pila (por lo que puede volver a poner en su lugar cuando termina esta función)

movl  %esp, %ebp 

Mueve el apilar puntero al registro ebp. ebp a menudo se llama el puntero de marco, que apunta en la parte superior de los valores de la pila dentro del "marco" actual (función por lo general), (en referencia a las variables en la pila a través de ebp puede ayudar depuradores)

andl $ - 16,% esp

Ands the stack con fffffff0 que lo alinea de forma efectiva en un límite de 16 bytes. El acceso a los valores alineados en la pila es mucho más rápido que si estuvieran desalineados. Todas estas instrucciones anteriores son prácticamente un prólogo de funciones estándar.

call  ___main 

Llama a la función ___main que hará las cosas de inicialización que gcc necesita. Llamada empujará el puntero de instrucción actual en la pila y salta a la dirección del ___main

movl  $0, %eax 

movimiento 0 al registro EAX, (el 0 en return 0;) el registro EAX se utiliza para mantener valores de retorno de función para la convención de llamadas stdcall.

dejar

La instrucción de la licencia es más o menos la abreviatura de

movl  ebp,esp 
popl  ebp 

es decir, "deshacer" la materia hecha en el inicio de la función - la restauración de la trama puntero y apilar a su estado anterior.

ret

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

2

No tengo todas las respuestas, pero puedo explicar lo que sé.

ebp es utilizado por la función para almacenar el estado inicial de esp durante su flujo, una referencia a dónde están los argumentos pasados ​​a la función y dónde están sus propias variables locales. Lo primero que hace una función es guardar el estado del ebp dado haciendo pushl %ebp, es vital para la función que realiza la llamada, y la reemplaza por su propia posición de pila actual esp haciendo movl %esp, %ebp. Poner a cero los últimos 4 bits de ebp en este punto es específico de GCC, no sé por qué este compilador hace eso. Funcionaría sin hacerlo. Ahora finalmente entramos en el negocio, call ___main, ¿quién es __main? No sé tampoco ... tal vez más procedimientos específicos de GCC, y finalmente lo único que hace su main(), establecer el valor de retorno como 0 con movl $0, %eax y leave que es lo mismo que hacer movl %ebp, %esp; popl %ebp para restaurar el estado ebp, luego ret para terminar. ret aparece eip y continúa el flujo de subprocesos desde ese punto, donde sea que esté (como es main(), este ret probablemente lleva a algún procedimiento de kernel que maneja el final del programa).

La mayor parte se trata de administrar la pila. Escribí un tutorial detallado sobre cómo se usa la pila hace algún tiempo, sería útil explicar por qué se hacen todas esas cosas. Pero está en portugués ...

5

con respecto a ese andl $ -16,% esp

  • 32 bits: -16 en decimal es igual a 0xfffffff0 en representación hexadecimal
  • 64 bits: -16 en decimal es igual a 0xfffffffffffffff0 en representación hexadecimal

Así que será enmascarar los últimos 4 bits de ESP (por cierto: 2 ** 4 es igual a 16) y conservará todos los demás bits (no importa si el sistema de destino es 32 o 64 bits).

12

Hay un ejercicio muy similar se describe aquí: http://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax

que haya descubierto la mayor parte de ella - Voy a tomar notas adicionales para el énfasis y adiciones.

__main es una subrutina en la biblioteca estándar de GNU que se encarga de la inicialización de varias iniciaciones. No es estrictamente necesario para los programas C, pero se requiere solo en caso de que el código C se vincule con C++.

_main es su subrutina principal. Como ambos _main y __main son ubicaciones de código, tienen la misma clase y tipo de almacenamiento. Todavía no he desenterrado las definiciones para .scl y .type. Puede obtener algo de iluminación definiendo algunas variables globales.

Las tres primeras instrucciones están configurando un marco de pila que es un término técnico para el almacenamiento de una subrutina en funcionamiento: variables locales y temporales en su mayor parte. Al presionar ebp se guarda la base del marco de pila de la persona que llama.Poner esp en ebp establece la base de nuestro marco de pila. El andl alinea el marco de la pila con un límite de 16 bytes en caso de que cualquier variable local en la pila requiera alineación de 16 bytes (para las instrucciones SIMD x86 requiere esa alineación, pero la alineación acelera tipos ordinarios como int sy float s.

en este punto se esperaría normalmente esp para obtener movido hacia abajo en la memoria para asignar espacio de pila para las variables locales. Su main tiene ninguna tan gcc no molesta.

la llamada a __main es especial a la principal punto de entrada y típicamente no aparecerá en las subrutinas.

El resto va como suponía. El registro eax es el lugar para poner códigos enteros de retorno en la especificación binaria. leave deshace el marco de la pila y ret vuelve a la persona que llama. En este caso, la persona que llama es el tiempo de ejecución de bajo nivel C, que va a hacer mágico adicional (como llamar atexit() funciones, establecer el código de salida para el proceso y pedir al sistema operativo para terminar el proceso.

4

En relación con la andl $-16,%esp, esto funciona porque el establecimiento de los bits de baja a cero siempre se ajustará %esp abajo en el valor, y la pila crece hacia abajo en x86.

Cuestiones relacionadas