← SEARCHABLE ARCHIVE
Assembly · 2026-08-15

x86-64 記憶體定址與堆疊框架

追蹤有效位址、對齊、堆疊成長方向與函式框架

INTERACTIVE READING MODE →

x86-64 記憶體定址與堆疊框架

本部落格文章以教學為目的。 範例採用 Intel 語法;精確的堆疊配置會隨 ABI、編譯器、最佳化與除錯資訊而變化。

在組合語言裡,player.health 可能變成 [rcx+12],values[i] 可能變成 [rdi+rsi*4]。區域變數也不是永久固定的「堆疊格子」,而是相對當下 rsp 或 frame pointer 尋找的儲存空間。

有效位址格式

[base + index * scale + displacement]

scale 只能是 1、2、4、8,其他項目可省略。

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

大括號內先被計算成位址,再依運算元寬度存取。lea 是例外:它只回傳計算出的位址。

lea rax, [rcx + 8]    ; 位址
mov rax, [rcx + 8]    ; 該位址的值

暫存器位元沒有「指標型別」標籤,除錯時要明確記錄每個值是位址還是資料。

陣列與結構

四位元組 int 陣列:

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

位址是 base + index * sizeof(int)。12 位元組結構無法只靠 scale 表達,因此會有額外計算。

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

結構欄位 offset 是 binary contract,不是欄位名稱。欄位順序、packing、繼承或 ABI 改變都可能讓硬編碼數字失效。

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

RIP-relative 與 little endian

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

程式以目前指令位置為基準尋找資料,不把完整絕對位址寫進指令。動態連結時可能再經 ELF GOT/PLT 或 PE import table 間接存取。

0x1122334455667788 在 x86-64 記憶體中的順序是:

位址  +0  +1  +2  +3  +4  +5  +6  +7
位元組 88  77  66  55  44  33  22  11

端序描述多位元組值的記憶體排列,不會反轉算術或位元運算。

對齊不是單一問題

x86-64 的許多 load/store 容忍未對齊,但不代表所有情況都正確且免費。

  • 跨 cache line 或 page 可能增加成本。
  • 某些 SIMD 形式要求對齊。
  • ABI 要求函式呼叫邊界的堆疊對齊。
  • C/C++ 中解參考未正確對齊的 typed pointer 可能是未定義行為。
  • 原子性取決於指令、寬度、對齊與邊界。

堆疊往低位址成長

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

call 儲存回傳位址並轉移控制;ret 取回位址。

call 前        RSP = S
callee 入口    RSP = S - 8
ret 後         RSP = S

即使現代 ABI 以暫存器傳多數參數,回傳位址仍由 call 放到堆疊。

堆疊框架

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

這段保存 caller 的 rbp、建立穩定基準、配置 32 位元組,再反向復原。但 frame pointer 並非必要;最佳化程式可把 rbp 當一般暫存器,甚至完全不配置堆疊框架。

框架可能包含區域陣列、register spill、保存的 nonvolatile 暫存器、stack 參數、Windows shadow space、對齊 padding、canary 與動態配置。因此不能看到 [rsp+40] 就武斷地說是「第五個區域變數」。

leaf 函式與 ABI 差異

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

不呼叫其他函式的 leaf 可能完全不用堆疊。Windows x64 caller 會預留 32 位元組 shadow space。System V 沒有 shadow space,但符合條件的 user-space leaf 可使用 rsp 下方 128 位元組 red zone;Windows 沒有這個 red zone。

把緩衝區迴圈讀成契約

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] 是 values[r8] 的候選位址,movsxd 把 signed 32 位元元素擴展到 64 位元。若 count > 0,caller 必須提供至少 count*4 位元組可讀記憶體。ASM 無法只從指標得知配置大小。

除錯工作表

時點rsp[rsp] 意義保存狀態
入口基準回傳位址尚無
prologue 後基準 - Nlocal/save 區列出使用項目
nested call 前驗證 ABI 對齊outgoing 區保護 live 值
epilogue 後入口值回傳位址全部復原

堆疊損壞常表現為 ret 跳到無關位址、caller 的 nonvolatile 值改變、只在例外時中斷 stack walk,或在對齊 SIMD 指令上 fault。

總結

x86-64 用 [base + index*scale + displacement] 建立有效位址。scale 常顯示元素大小,displacement 常顯示欄位 offset。call 把回傳位址放入堆疊,函式只在需要時配置 local、spill、保存與呼叫區。不要背一張萬用框架圖,而要逐指令追蹤 rsp。

官方資料