He estado trabajando con C por un tiempo y recientemente comencé a entrar en ASM. Cuando compilo un programa:
int main(void)
{
int a = 0;
a += 1;
return 0;
}
El desmontaje de objdump tiene el código, pero nops después de ret:
...
08048394 <main>:
8048394: 55 push %ebp
8048395: 89 e5 mov %esp,%ebp
8048397: 83 ec 10 sub $0x10,%esp
804839a: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%ebp)
80483a1: 83 45 fc 01 addl $0x1,-0x4(%ebp)
80483a5: b8 00 00 00 00 mov $0x0,%eax
80483aa: c9 leave
80483ab: c3 ret
80483ac: 90 nop
80483ad: 90 nop
80483ae: 90 nop
80483af: 90 nop
...
Por lo que aprendí, los nops no hacen nada, y dado que después de ret ni siquiera serían ejecutados.
Mi pregunta es: ¿por qué molestarse? ¿No podría ELF (linux-x86) funcionar con una sección .text (+ main) de cualquier tamaño?
Agradecería cualquier ayuda, solo tratando de aprender.
80483af
, entonces tal vez sea un relleno para alinear la siguiente función a 8 o 16 bytes.Respuestas:
En primer lugar,
gcc
no siempre hace esto. El relleno está controlado por-falign-functions
, que se activa automáticamente por-O2
y-O3
:Puede haber varias razones para hacer esto, pero la principal en x86 es probablemente esta:
(Citado de "Optimización de subrutinas en lenguaje ensamblador" por Agner Fog.)
editar: Aquí hay un ejemplo que demuestra el relleno:
// align.c int f(void) { return 0; } int g(void) { return 0; }
Cuando se compila usando gcc 4.4.5 con la configuración predeterminada, obtengo:
align.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <f>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: b8 00 00 00 00 mov $0x0,%eax 9: c9 leaveq a: c3 retq 000000000000000b <g>: b: 55 push %rbp c: 48 89 e5 mov %rsp,%rbp f: b8 00 00 00 00 mov $0x0,%eax 14: c9 leaveq 15: c3 retq
Especificar
-falign-functions
da:align.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <f>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: b8 00 00 00 00 mov $0x0,%eax 9: c9 leaveq a: c3 retq b: eb 03 jmp 10 <g> d: 90 nop e: 90 nop f: 90 nop 0000000000000010 <g>: 10: 55 push %rbp 11: 48 89 e5 mov %rsp,%rbp 14: b8 00 00 00 00 mov $0x0,%eax 19: c9 leaveq 1a: c3 retq
fuente
-falign-functions
.main
en el ejecutable (en mi caso, esa función es__libc_csu_fini
).Esto se hace para alinear la siguiente función con un límite de 8, 16 o 32 bytes.
De "Optimización de subrutinas en lenguaje ensamblador" por A.Fog:
fuente
foo
es 0x1234, entonces el código que usa esa dirección muy cerca de un 0x1234 literal podría terminar generando un código de máquina como elmov ax,0x1234 / push ax / mov ax,0x1234 / push ax
que el optimizador podría reemplazarmov ax,0x1234 / push ax / push ax
. Tenga en cuenta que las funciones no deben reubicarse después de dicha optimización, por lo que la eliminación de instrucciones mejoraría la velocidad de ejecución, pero no el tamaño del código.Por lo que recuerdo, las instrucciones se canalizan en la CPU y diferentes bloques de la CPU (cargador, decodificador y demás) procesan las instrucciones posteriores. Cuando
RET
se están ejecutando instrucciones, algunas de las siguientes instrucciones ya están cargadas en la canalización de la CPU. Es una suposición, pero puede comenzar a investigar aquí y si lo descubre (tal vez el número específico de correosNOP
electrónicos que son seguros, comparta sus hallazgos, por favor.fuente
nop
s).ret
.ud2
oint3
que siempre falla, por lo que el front-end sabe que debe detener la decodificación en su lugar de alimentar unadiv
carga de TLB potencialmente cara o falsa en la tubería, por ejemplo. Esto no es necesario después de un tailcallret
directo ojmp
al final de una función.