Pracuję z C przez krótki czas i bardzo niedawno zacząłem zajmować się ASM. Kiedy kompiluję program:
int main(void)
{
int a = 0;
a += 1;
return 0;
}
Demontaż objdump ma kod, ale nops po 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
...
Z tego, czego się dowiedziałem, nops nic nie robi, a ponieważ po ret nie zostałby nawet stracony.
Moje pytanie brzmi: po co się przejmować? Czy ELF (linux-x86) nie może działać z sekcją .text (+ main) dowolnego rozmiaru?
Byłbym wdzięczny za każdą pomoc, po prostu próbując się nauczyć.
80483af
, być może jest to wypełnienie, aby wyrównać następną funkcję do 8 lub 16 bajtów.Odpowiedzi:
Przede wszystkim
gcc
nie zawsze to robi. Wypełnienie jest kontrolowane przez-falign-functions
, które jest automatycznie włączane przez-O2
i-O3
:Może być wiele powodów, dla których warto to zrobić, ale główny na x86 jest prawdopodobnie taki:
(Cytat z „Optimizing podprogramów w języku asemblera” autorstwa Agner Fog.)
edycja: Oto przykład, który demonstruje dopełnienie:
// align.c int f(void) { return 0; } int g(void) { return 0; }
Po skompilowaniu przy użyciu gcc 4.4.5 z domyślnymi ustawieniami otrzymuję:
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
Określenie
-falign-functions
daje: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
źródło
-falign-functions
.main
w pliku wykonywalnym (w moim przypadku jest to funkcja__libc_csu_fini
).Ma to na celu wyrównanie następnej funkcji do granicy 8, 16 lub 32 bajtów.
Z „Optimizing podprogramów w języku asemblera” autorstwa A. Fog:
źródło
foo
to 0x1234, to kod, który używa tego adresu w bliskim sąsiedztwie literału 0x1234, może w końcu wygenerować kod maszynowy, taki,mov ax,0x1234 / push ax / mov ax,0x1234 / push ax
jaki optymalizator mógłby następnie zastąpićmov ax,0x1234 / push ax / push ax
. Należy pamiętać, że po takiej optymalizacji funkcji nie można przenosić, więc eliminacja instrukcji poprawiłaby szybkość wykonywania, ale nie zwiększyłaby rozmiaru kodu.O ile pamiętam, instrukcje są przetwarzane potokowo w procesorze i różne bloki procesora (program ładujący, dekoder itp.) Przetwarzają kolejne instrukcje. Kiedy
RET
instrukcje są wykonywane, kilka następnych instrukcji jest już ładowanych do potoku procesora. To przypuszczenie, ale możesz zacząć szukać tutaj, a jeśli się dowiesz (może konkretna liczbaNOP
bezpiecznych, podziel się swoimi odkryciami).źródło
nop
s).ret
.ud2
lub,int3
które zawsze zawierają błędy, więc front-end wie, aby zamiast tego zatrzymać dekodowanie na przykład zasilaniadiv
rurociągu potencjalnie kosztownym lub fałszywym ładunkiem nieudanym TLB. Nie jest to potrzebne poret
lub bezpośrednimjmp
wywołaniu końcowym na końcu funkcji.