relations premezclado juarez investor historia grupo concreto chihuahua cemex cementos bmv c gcc x86 gas tdm-mingw

premezclado - Salida de conjunto de GCC de un programa vacío en x86, win32



grupo cementos de chihuahua historia (5)

.file "test.c"

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

.def ___main; .scl 2; . tipo 32; .endef

Las directivas .def definen 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 de pe-coff exe

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

.text

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

.globl _main

define el símbolo _main como global, lo que lo hará visible para el vinculador y para otros módulos vinculados.

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

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

_principal:

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

pushl %ebp

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

movl %esp, %ebp

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

andl $ -16,% esp

Ands la pila con fffffff0 que la alinea effectivamente 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 necesita gcc. La llamada empujará el puntero de instrucción actual en la pila y saltará a la dirección de ___main

movl $0, %eax

mover 0 al registro eax, (el 0 en retorno 0;) el registro eax se usa para mantener valores de retorno de función para la convención de llamadas stdcall.

salir

La instrucción de abandono es más o menos una abreviatura de

movl ebp,esp popl ebp

es decir, "deshace" las cosas hechas al comienzo de la función, restaurando el puntero del marco y la pila a su estado anterior.

retirado

Regresa a quien haya llamado 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í.

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

Ahora, lo siguiente puede ser demasiado profundo para mí, pero para continuar con la saga vacía del programa comencé a examinar el resultado del compilador de C, el material 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

¿Puedes explicar lo que sucede aquí? Aquí está mi esfuerzo por entenderlo. He utilizado el 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 "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 dice "La clase de almacenamiento puede marcar si un símbolo es estático o externo" . ¿Es esto lo mismo estático y externo que conozco 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 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 en esto.
  • _main: Esta podría ser la dirección inicial (?) para mi función principal
  • pushl_ : un empuje largo (32 bits), que coloca a EBP en la pila
  • movl : movimiento de 32 bits. 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 IP a la pila (para que el procedimiento llamado pueda encontrar su camino de regreso) y continúa donde __main es. (¿Qué es __main?)
  • movl : este cero debe ser la constante que regreso 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 se guarda en la pila

¡Gracias por tu ayuda!


Además de andl $-16,%esp , esto funciona porque establecer los bits bajos a cero siempre ajustará el valor de %esp , y la pila crecerá hacia abajo en x86.


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

Ya descubriste la mayor parte. Haré notas adicionales para enfatizarlas y agregarlas.

__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 tu subrutina principal. Dado que tanto _main como __main son ubicaciones de código, tienen la misma clase y tipo de almacenamiento. Todavía no he desenterrado las definiciones para .scl y .scl todavía. 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 trabajo de una subrutina: variables locales y temporales en su mayor parte. Presionar ebp 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 comunes como int s y float s.

En este punto, normalmente esperaría que esp se moviera hacia abajo en la memoria para asignar espacio de pila para las variables locales. Tu main no tiene ninguno, así que gcc no se molesta.

La llamada a __main es especial para el punto de entrada principal y normalmente no aparecerá en las subrutinas.

El resto va como supones. Registrar 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 regresa a la persona que llama. En este caso, la persona que llama es el tiempo de ejecución C de bajo nivel que hará magia adicional (como llamar a las funciones atexit() , establecer el código de salida para el proceso y solicitar al sistema operativo que finalice el proceso.


Con respecto a eso 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

Por lo tanto, ocultará los últimos 4 bits de ESP (por cierto: 2 ** 4 es igual a 16) y retendrá todos los demás bits (no importa si el sistema de destino es 32 o 64 bits).


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

ebp utiliza ebp 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 hace la llamada, y luego lo reemplaza por su propia posición de pila actual haciendo movl %esp, %ebp . ebp 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 tu main () hace, establece el valor de retorno como 0 con movl $0, %eax y leave lo mismo que haciendo movl %ebp, %esp; popl %ebp movl %ebp, %esp; popl %ebp para restablecer el estado de ebp , luego ret para finalizar. ret pops eip y continuar el flujo de subprocesos desde ese punto, donde sea que esté (como es main (), este ret probablemente lleva a algún procedimiento kernel que maneje 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 ...