x86-64-Funktionsaufrufe und ABIs in der Praxis
Argumente, gesicherte Register, Stackausrichtung und Unwinding unter System V und Windows x64
x86-64-Funktionsaufrufe und ABIs in der Praxis
Diese Blogartikel sind als Lehrmaterial gedacht. Dieser Artikel vergleicht System V AMD64 und Microsoft x64. Kernel, Syscalls, vectorcall und Toolchain-Erweiterungen benötigen eigene Zieldokumentation.
Der CPU-Befehl call speichert lediglich eine Rücksprungadresse und überträgt die Kontrolle. Das ABI legt fest, wo Argumente und Ergebnisse liegen, wer Register sichert, wie der Stack ausgerichtet und ein Frame abgewickelt wird.
ISA, Calling Convention und ABI
- Die ISA definiert Befehle und Registerverhalten.
- Die Calling Convention definiert Argumente, Ergebnisse, gesicherten Zustand und Stackverantwortung.
- Das ABI umfasst zusätzlich Typlayout, Objektformat, Symbole, Relocations und Unwind-Regeln.
extern "C" steuert C++-Sprachbindung und Name Mangling, macht aus einer Windows-Funktion aber keine System-V-Funktion.
Vergleich
| Punkt | System V AMD64 | Microsoft x64 |
|---|---|---|
| Integerargumente | rdi,rsi,rdx,rcx,r8,r9 | Position 1–4: rcx,rdx,r8,r9 |
| FP-Argumente | vor allem xmm0–xmm7 | Position 1–4: xmm0–xmm3 |
| Integerergebnis | rax | rax |
| FP-Ergebnis | xmm0 | xmm0 |
| Caller-Bereich | kein Shadow Space | 32 Byte Shadow Space |
| Red Zone | 128 Byte für geeignete User-Space-Leaf-Funktionen | keine |
| wichtige nonvolatile GPR | rbx,rbp,r12–r15,rsp | zusätzlich rdi,rsi |
| nonvolatile XMM | keine | untere 128 Bit von xmm6–xmm15 |
Aggregate, Vektoren, variadische Funktionen und versteckte Rückgabezeiger brauchen weitere Klassifikation.
Caller-saved und Callee-saved
Volatile Register dürfen sich über einen Aufruf ändern; der Caller sichert einen benötigten Livewert. Verwendet der Callee ein nonvolatile Register, muss er es restaurieren.
push rbx
mov rbx, rcx
; use rbx
pop rbx
ret
Sichern bedeutet nicht zwingend push. Ein unbenutztes Register braucht keine Sicherung, und ein Stackslot mit mov ist ebenfalls möglich.
System-V- und Windows-Aufruf
System V:
mov edi, 10
mov esi, 20
mov edx, 30
call combine
; result in rax
Windows x64:
sub rsp, 40
mov ecx, 10
mov edx, 20
mov r8d, 30
call combine
add rsp, 40
32 Byte sind der vom Caller bereitgestellte Shadow Space. Weitere 8 Byte ergeben unter Berücksichtigung der von call abgelegten Rücksprungadresse eine typische Ausrichtung. Der Shadow Space wird auch bei weniger als vier Argumenten reserviert.
C-long ist im System-V-LP64-Modell üblicherweise 64 Bit, im Windows-LLP64-Modell aber 32 Bit. x64 erzwingt keine einheitliche Quellsprachbreite.
Stackausrichtung braucht einen Zeitpunkt
Unterscheiden Sie:
- unmittelbar vor
callbeim Caller; - Callee-Eintritt nach dem Ablegen der Rücksprungadresse;
- nach dem Prolog;
- unmittelbar vor einem nested
call.
Die Modulo-Regel eines Zeitpunkts auf einen anderen anzuwenden erzeugt die typische 8-Byte-Verwirrung. Windows x64 verlangt außerhalb von Prolog und Epilog 16-Byte-Ausrichtung von rsp.
Red Zone und Unwind
Geeignete System-V-User-Space-Leaf-Funktionen dürfen 128 Byte unter rsp verwenden. Diese Annahme gilt nicht über einen nested Call, unter Windows, im Kernel oder bei deaktivierter Red Zone.
Windows-Non-Leaf-Funktionen müssen realen Prolog und Unwind-Metadaten in Einklang halten.
Work PROC FRAME
push rbx
.pushreg rbx
sub rsp, 32
.allocstack 32
.endprolog
; body
add rsp, 32
pop rbx
ret
Work ENDP
Die Direktiven führen die Befehle nicht aus, sondern beschreiben sie. Eine Abweichung kann normale Rückkehrtests bestehen und trotzdem Exceptions, Profiler und Stack Walking zerstören.
Stack- und FP-Argumente
Windows übergibt Positionen ab fünf auf dem Stack. Der Offset hängt davon ab, ob er sich auf den Callee-Eintritt oder den Zustand nach dem Prolog bezieht. [rsp+40] darf ohne Zeitpunkt nicht pauschal als fünftes Argument betrachtet werden.
void f(int a, double b, int c, float d);
Unter Windows ergibt sich ungefähr a -> rcx, b -> xmm1, c -> r8, d -> xmm3. Gezählt werden Argumentpositionen, nicht nur FP-Argumente.
Variadisch, Aggregate und versteckte Parameter
Für printf-artige Aufrufe gelten Zusatzregeln:
- System V übermittelt in
alInformationen zu Vektorregisterargumenten. - Microsoft x64 dupliziert variadische FP-Werte in das entsprechende Integerregister.
- C Default Argument Promotions machen aus
floateindouble.
System V klassifiziert Aggregate in Eightbytes und kann sie auf Integer- und SSE-Register aufteilen. Windows übergibt bestimmte Größen als Wert und andere über einen Zeiger auf ein Caller-Temporary. Handgeschriebene ASM-Grenzen sollten feste skalare Breiten, einfache extern "C"-Funktionen und Assertions für sizeof, alignof, offsetof bevorzugen. Caller- und Callee-Disassembly müssen beide geprüft werden.
Große Rückgabestrukturen können einen versteckten Rückgabezeiger hinzufügen und alle sichtbaren Argumente scheinbar um eine Position verschieben.
Tail Call
wrapper:
; Argumente für target anordnen
jmp target
Wenn die ABI-Bedingungen passen, ersetzt der Compiler ein abschließendes call plus ret durch jmp; target kehrt direkt zum ursprünglichen Caller zurück.
Debug-Reihenfolge
- Verwenden Caller und Callee dieselbe Architektur und dasselbe ABI?
- Stimmen Symbol und Sprachbindung?
- Liegen Bits und Typen unmittelbar vor
callan den richtigen Stellen? - Auf welchen
rsp-Zeitpunkt bezieht sich ein Stackoffset? - Stimmen Ausrichtung und Windows Shadow Space?
- Werden alle geänderten nonvolatile Register auf jedem Pfad restauriert?
- Entsprechen Windows-Frame-Direktiven dem echten Prolog?
- Wird das Ergebnis mit korrekter Breite aus
raxoder ausxmm0gelesen?
Zusammenfassung
call und ret sind nur das Hardwaregerüst. System V verwendet sechs wichtige Integerargumentregister und eine Red Zone. Microsoft x64 verwendet vier positionsbasierte Registerplätze, 32 Byte Shadow Space, nonvolatile XMM-Register und strenge Unwind-Regeln. Beweisen Sie den Vertrag mit Caller-/Callee-Disassembly, Grenztests und Stack Walking.