Agradeceríamos tu apoyo para compartir nuestras crónicas en referencia a las ciencias informáticas.
Solución:
Gracias, Kin3TiX, por hacer una pregunta de asm-novato que no era solo un volcado de código de un código desagradable sin comentarios, y un problema realmente simple. 🙂
Como una forma de mojarse los pies con ASM, sugeriría trabajar con funciones DIFERENTES a main
. por ejemplo, solo una función que toma dos argumentos enteros y los agrega. Entonces el compilador no puede optimizarlo. Todavía puede llamarlo con constantes como argumentos, y si está en un archivo diferente de main
, no se incluirá en línea, por lo que incluso puede recorrerlo con un solo paso.
Comprender lo que está sucediendo en el nivel de ASM tiene algunos beneficios cuando se compila main
, pero aparte de los sistemas integrados, solo escribirás bucles internos optimizados en asm. En mi opinión, no tiene mucho sentido usar asm si no vas a optimizarlo al máximo. De lo contrario, probablemente no superará la salida del compilador de la fuente, que es mucho más fácil de leer.
Otros consejos para comprender la salida del compilador: compile congcc -S -fno-stack-check -fverbose-asm
. Los comentarios después de cada instrucción suelen ser buenos recordatorios de para qué fue esa carga. Muy pronto degenera en un lío de temporales con nombres como D.2983
, pero algo comomovq 8(%rdi), %rcx # a_1(D)->elements, a_1(D)->elements
le ahorrará un viaje de ida y vuelta a la referencia ABI para ver en qué función entra arg %rdi
, y qué miembro de estructura está en el desplazamiento 8.
Consulte también ¿Cómo eliminar el “ruido” de la salida del ensamblaje GCC / clang?
¿Qué hacen las líneas que van desde .cfi_startproc a call__main?
_main:
LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
.cfi
cosas son información de desenrollado de pila para depuradores (y manejo de excepciones de C ++) para desenrollar la pila. No estará allí si mira asm desde objdump -d
salida en lugar de gcc -S
, o puedes usar -fno-asynchronous-unwind-tables
.
El asunto de empujar %ebp
y luego establecerlo en el valor del puntero de la pila en la entrada de la función configura lo que se llama un “marco de pila”. Esta es la razón por %ebp
se llama puntero base. Estos insns no estarán allí si compila con -fomit-frame-pointer
, que le da al código un registro adicional para trabajar. Eso está activado de forma predeterminada en -O2
. (Esto es enorme para 32 bits x86, ya que le lleva de 6 a 7 reglas utilizables. (%esp
todavía está ligado al ser el puntero de la pila; En teoría, es posible guardarlo temporalmente en un registro xmm o mmx y luego usarlo como otro registro GP, pero los compiladores nunca harán eso y hace que las cosas asíncronas como las señales POSIX o Windows SEH sean inutilizables, además de dificultar la depuración).
los leave
instrucción antes del ret
también es parte de este material de marco de pila.
Los indicadores de cuadro son en su mayoría un bagaje histórico, pero hacen que las compensaciones en el cuadro de la pila sean consistentes. Con los símbolos de depuración, puede rastrear la pila de llamadas sin problemas incluso con -fomit-frame-pointer
y es el predeterminado para amd64. (La ABI amd64 tiene requisitos de alineación para la pila, también es MUCHO mejor en otros aspectos. Por ejemplo, pasa argumentos en las reglas en lugar de en la pila).
andl $-16, %esp
subl $16, %esp
los and
alinea la pila con un límite de 16 bytes, independientemente de lo que era antes. los sub
reserva 16 bytes en la pila para esta función. (Observe cómo falta en la versión optimizada, porque optimiza cualquier necesidad de almacenamiento en memoria de cualquier variable).
call ___main
__main
(nombre de asm = ___main
) es parte de cygwin: llama a las funciones constructor / init para bibliotecas compartidas (incluida libc). En GNU / Linux, esto lo maneja _start
(antes de que se alcance main) e incluso enlaces dinámicos que permiten que libc se inicialice antes que el ejecutable. _start
incluso se alcanza. He leído que los ganchos del enlazador dinámico (o _start
a partir de una static ejecutable) en lugar de código en main
haría ser posible bajo Cygwin, pero simplemente eligen no hacerlo de esa manera.
(Este mensaje de lista de correo antiguo indica _main
es para constructores, pero ese main no debería tener que llamarlo en plataformas que admiten obtener el código de inicio para llamarlo).
movb $5, 15(%esp)
movb $10, 14(%esp)
movsbl 15(%esp), %edx
movsbl 14(%esp), %eax
addl %edx, %eax
leave
ret
¿Por qué la salida inicial de GCC es mucho más detallada?
Sin las optimizaciones habilitadas, gcc mapea las declaraciones de C lo más literalmente posible en asm. Hacer cualquier otra cosa llevaría más tiempo de compilación. Por lo tanto, movb
es de los inicializadores para sus dos variables. El valor de retorno se calcula haciendo dos cargas (con extensión de signo, porque necesitamos convertir a int ANTES de agregar, para que coincida con la semántica del código C tal como está escrito, en lo que respecta al desbordamiento).
No puedo imaginar para qué sirven las dos operaciones de resta.
Sólo hay uno sub
instrucción. Reserva espacio en la pila para las variables de la función, antes de la llamada a __main
. ¿De qué otro submarino estás hablando?
¿Qué hacen .section, .ident, .def .p2align, etc., etc.?
Consulte el manual del ensamblador GNU. También disponible localmente como páginas de información: ejecutar info gas
.
.ident
y .def
: Parece que gcc pone su sello en el archivo de objeto, para que pueda saber qué compilador / ensamblador lo produjo. No es relevante, ignórelos.
.section
: determina en qué sección del objeto ELF archivan los bytes de todas las instrucciones o directivas de datos siguientes (p. ej. .byte 0x00
) entrar, hasta el próximo .section
directiva de ensamblador. Cualquiera code
(solo lectura, compartible), data
(datos de lectura / escritura inicializados, privados), o bss
(segmento de almacenamiento de bloque. inicializado en cero, no ocupa ningún espacio en el archivo de objeto).
.p2align
: Poder de 2 Alinear. Rellene con las instrucciones nop hasta la alineación deseada. .align 16
es lo mismo que .p2align 4
. Las instrucciones de salto son más rápidas cuando el objetivo está alineado, debido a que la instrucción se recupera en fragmentos de 16B, no cruza un límite de página o simplemente no cruza un límite de línea de caché. (La alineación 32B es relevante cuando el código ya está en la caché uop de un Intel Sandybridge y posterior). Consulte los documentos de Agner Fog, por ejemplo.
El núcleo de por qué agregué este bit es para ilustrar por qué estoy confundido de que la versión de 4 líneas de este código ensamblador pueda lograr efectivamente el mismo efecto que las demás. Me parece que GCC ha agregado muchas “cosas” cuyo propósito no puedo discernir.
Ponga el código de interés en una función por sí misma. Muchas cosas son especiales sobre main
.
Tienes razón en que un mov
-inmediato y a ret
son todo lo que se necesita para implementar la función, pero gcc aparentemente no tiene atajos para reconocer programas completos triviales y omitir main
marco de pila de ‘s o la llamada a _main
. >.
Sin embargo, buena pregunta. Como dije, simplemente ignore toda esa basura y preocúpese solo por la pequeña parte que desea optimizar.
.cfi
(información de trama de llamada) las directivas se utilizan en gas
(Gnu ASsembler) principalmente para depurar. Permiten que el depurador desenrolle la pila. Para deshabilitarlos, puede usar el siguiente parámetro cuando invoca el controlador de compilación -fno-asynchronous-unwind-tables
.
Si desea jugar con el compilador en general, puede usar el siguiente comando de invocación del controlador de compilación -o
o simplemente usa el compilador interactivo de godbolt
Sección de Reseñas y Valoraciones
Nos puedes estimular nuestra función añadiendo un comentario o dejando una valoración te lo agradecemos.