← 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 或帧指针寻找的存储空间。

有效地址格式

[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 是二进制契约,不是字段名。字段顺序、packing、继承或 ABI 改变都可能让硬编码数字失效。

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

RIP-relative 与小端序

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 字节,再反向恢复。但帧指针并非必需;优化程序可把 rbp 当普通寄存器,甚至完全不分配栈帧。

帧可能包含局部数组、寄存器 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。

官方资料