Ich habe eine kurze Zeit mit C gearbeitet und vor kurzem angefangen, mich mit ASM zu beschäftigen. Wenn ich ein Programm kompiliere:
int main(void)
{
int a = 0;
a += 1;
return 0;
}
Die objdump-Demontage hat den Code, aber nops nach dem 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
...
Nach dem, was ich gelernt habe, tun Nops nichts, und da nach Ret würde nicht einmal ausgeführt werden.
Meine Frage ist: Warum sich die Mühe machen? Konnte ELF (Linux-x86) nicht mit einem Textabschnitt (+ main) beliebiger Größe arbeiten?
Ich würde mich über jede Hilfe freuen, nur um zu lernen.
80483af
, ist es vielleicht ein Auffüllen, um die nächste Funktion auf 8 oder 16 Bytes auszurichten.Antworten:
Erstens
gcc
macht das nicht immer so. Die Polsterung wird gesteuert durch-falign-functions
, die automatisch aktiviert wird durch-O2
und-O3
:Es kann mehrere Gründe dafür geben, aber der Hauptgrund für x86 ist wahrscheinlich folgender:
(Zitiert aus "Optimieren von Unterprogrammen in Assemblersprache" von Agner Fog.)
edit: Hier ist ein Beispiel, das das Auffüllen demonstriert:
// align.c int f(void) { return 0; } int g(void) { return 0; }
Beim Kompilieren mit gcc 4.4.5 mit Standardeinstellungen erhalte ich:
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
Angabe
-falign-functions
ergibt: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
quelle
-falign-functions
.main
in der ausführbaren Datei folgt (in meinem Fall ist diese Funktion__libc_csu_fini
).Dies geschieht, um die nächste Funktion an der 8-, 16- oder 32-Byte-Grenze auszurichten.
Aus "Optimieren von Unterprogrammen in Assemblersprache" von A.Fog:
quelle
foo
0x1234 lautet, generiert der Code, der diese Adresse in unmittelbarer Nähe eines Literals 0x1234 verwendet, möglicherweise einen Maschinencode,mov ax,0x1234 / push ax / mov ax,0x1234 / push ax
den der Optimierer dann ersetzen könntemov ax,0x1234 / push ax / push ax
. Beachten Sie, dass Funktionen nach einer solchen Optimierung nicht verschoben werden dürfen, sodass das Eliminieren von Anweisungen die Ausführungsgeschwindigkeit verbessern würde, jedoch nicht die Codegröße.Soweit ich mich erinnere, werden Anweisungen in der CPU weitergeleitet, und verschiedene CPU-Blöcke (Lader, Decoder usw.) verarbeiten nachfolgende Anweisungen. Wenn
RET
Anweisungen ausgeführt werden, werden bereits einige nächste Anweisungen in die CPU-Pipeline geladen. Es ist eine Vermutung, aber Sie können hier anfangen zu graben und wenn Sie es herausfinden (vielleicht die spezifische Anzahl vonNOP
s, die sicher sind, teilen Sie bitte Ihre Ergebnisse mit.quelle
nop
s führen würde).ret
.ud2
oderint3
die immer fehlerhaft ist , sodass das Front-End weiß, dass die Decodierung stattdessen gestoppt werden mussdiv
B. eine potenziell teure oder unechte TLB-Fehllast in die Pipeline einzuspeisen. Dies wird nach einemret
oder einem direktenjmp
Tailcall am Ende einer Funktion nicht benötigt .