← SEARCHABLE ARCHIVE
Assembly · 2026-08-15

x86-64-Speicheradressierung und Stackframes

Effektive Adressen, Ausrichtung, Stackwachstum und Funktionsrahmen nachvollziehen

INTERACTIVE READING MODE →

x86-64-Speicheradressierung und Stackframes

Diese Blogartikel sind als Lehrmaterial gedacht. Die Beispiele verwenden Intel-Syntax. Das genaue Stacklayout hängt von ABI, Compiler, Optimierung und Debuginformationen ab.

Im Assembler kann player.health als [rcx+12] und values[i] als [rdi+rsi*4] erscheinen. Auch eine lokale Variable ist kein dauerhaftes Stackfach, sondern Speicher relativ zum aktuellen rsp oder einem optionalen Frame Pointer.

Effektive Adressen

[base + index * scale + displacement]

scale ist 1, 2, 4 oder 8; alle anderen Bestandteile können entfallen.

mov eax, [rcx]
mov eax, [rcx + 12]
mov eax, [rcx + rdx*4]
mov eax, [rcx + rdx*4 + 12]

Der Klammerausdruck wird als Adresse berechnet und in der angegebenen Breite gelesen oder geschrieben. lea gibt dagegen nur die berechnete Adresse zurück.

lea rax, [rcx + 8]    ; Adresse
mov rax, [rcx + 8]    ; dort gespeicherter Wert

Registerbits tragen kein Zeiger-Typetikett. Beim Debuggen sollte deshalb markiert werden, ob ein Wert eine Adresse oder Nutzdaten darstellt.

Arrays und Strukturen

Für ein Array aus 4-Byte-int:

mov eax, dword ptr [rcx + rdx*4]

Die Adresse lautet base + index * sizeof(int). Eine 12-Byte-Struktur braucht zusätzliche Arithmetik, da der Hardware-Scale nur 1, 2, 4 und 8 erlaubt.

lea rax, [rdx + rdx*2]
mov ecx, [rcx + rax*4 + 4]

Ein Feldoffset ist ein Binärvertrag, kein Feldname. Reihenfolge, Packing, Vererbung oder ABI können seine Bedeutung ändern.

static_assert(offsetof(Player, health) == 12);
static_assert(sizeof(Player) == 16);

RIP-relativ und Little Endian

lea rdi, [rip + message]
mov eax, dword ptr [rip + counter]

Die Daten werden relativ zur Befehlsposition gefunden, statt eine vollständige absolute Adresse einzubetten. Dynamisches Linken kann zusätzliche Indirektion über ELF-GOT/PLT oder PE-Importtabellen erzeugen.

0x1122334455667788 liegt im x86-64-Speicher so:

Adresse +0  +1  +2  +3  +4  +5  +6  +7
Byte    88  77  66  55  44  33  22  11

Endianness beschreibt die Bytefolge einer Mehrbyte-Speicherdarstellung, nicht die Reihenfolge der Arithmetik.

Ausrichtung hat mehrere Verträge

Viele x86-64-Loads und -Stores tolerieren unaligned Adressen. Das macht sie nicht immer korrekt oder kostenlos.

  • Cacheline- und Seitengrenzen können die Kosten erhöhen.
  • Einige SIMD-Formen erfordern Ausrichtung.
  • Das ABI verlangt Stackausrichtung an Aufrufgrenzen.
  • Ein unterausgerichteter typisierter Zeiger kann in C/C++ undefiniertes Verhalten verursachen.
  • Atomarität hängt von Befehl, Breite, Ausrichtung und Grenze ab.

Der Stack wächst zu kleineren Adressen

push rbx       ; rsp -= 8; [rsp] = rbx
pop rbx        ; rbx = [rsp]; rsp += 8

call speichert die Rücksprungadresse und überträgt die Kontrolle; ret holt sie zurück.

vor call        RSP = S
am Callee-Eintritt RSP = S - 8
nach ret        RSP = S

Auch wenn moderne ABIs viele Argumente in Registern übergeben, liegt die Rücksprungadresse auf dem Stack.

Stackframe

push rbp
mov rbp, rsp
sub rsp, 32
; body
add rsp, 32
pop rbp
ret

Der Code sichert rbp, erzeugt einen stabilen Bezugspunkt, reserviert 32 Byte und macht alles rückgängig. Ein Frame Pointer ist optional; optimierter Code kann rbp allgemein verwenden oder ganz ohne Frame auskommen.

Ein Frame kann lokale Arrays, Register-Spills, gesicherte nonvolatile Register, Stackargumente, Windows-Shadow-Space, Padding, Canary und dynamischen Speicher enthalten. [rsp+40] ist daher nicht automatisch „die fünfte lokale Variable“.

Leaf-Funktionen und ABI-Unterschiede

add_three:
    lea eax, [rdi + rsi]
    add eax, edx
    ret

Eine Leaf-Funktion kann den Stack vollständig vermeiden. Unter Windows x64 reserviert der Caller 32 Byte Shadow Space. System V kennt diesen Bereich nicht, erlaubt geeigneten User-Space-Leaf-Funktionen aber eine 128-Byte-Red-Zone unter rsp. Windows besitzt diese Red Zone nicht.

Pufferschleife als Vertrag lesen

xor eax, eax
xor r8d, r8d
loop_begin:
    cmp r8, rdx
    jae loop_end
    movsxd r9, dword ptr [rcx + r8*4]
    add rax, r9
    inc r8
    jmp loop_begin
loop_end:
    ret

[rcx+r8*4] ist die mögliche Adresse von values[r8], movsxd erweitert ein signed 32-Bit-Element. Bei count > 0 muss der Caller mindestens count*4 lesbare Bytes bereitstellen. ASM kann die Allokationsgröße nicht aus einem Zeiger ermitteln.

Debug-Arbeitsblatt

ZeitpunktrspBedeutung von [rsp]Sicherungen
EintrittBasisRücksprungadressenoch keine
nach PrologBasis - NLocal/Save-Bereichauflisten
vor nested callABI-Ausrichtung prüfenOutgoing-BereichLivewerte schützen
nach EpilogEintrittswertRücksprungadressevollständig restauriert

Stackbeschädigung zeigt sich oft als ret zu einer fremden Adresse, veränderte nonvolatile Caller-Register, nur bei Exceptions kaputtes Stack Walking oder Faults in ausgerichteten SIMD-Befehlen.

Zusammenfassung

x86-64 bildet effektive Adressen als [base + index*scale + displacement]. Scale verrät oft die Elementgröße, Displacement den Feldoffset. call legt die Rücksprungadresse auf den Stack; Funktionen reservieren lokale, Spill-, Sicherungs- und Aufrufbereiche nur bei Bedarf. Statt eines universellen Framebilds muss jede Änderung von rsp verfolgt werden.

Offizielle Quellen