ESP寻址
ESP 寻址定义
ESP 寻址是以栈指针ESP作为基准地址,搭配固定偏移量 ESP+数值 读写栈内参数、临时数据的寻址方式,是堆栈传参函数读取入参的核心手段
学习前提:掌握寄存器传参、堆栈压参调用逻辑
两种传参对照铺垫
- 寄存器传参(少参数)
参数直接放进通用寄存器,无需栈、不用 ESP 寻址读参
cpp
;调用
mov ecx,10
mov edx,20
call AddReg
;函数
AddReg:
mov eax,ecx
add eax,edx
ret
- 堆栈传参(多参数标配)
调用方 push 压参数,函数内部依靠 ESP 偏移取值,也就是 ESP 寻址
cpp
push 20
push 10
call AddStack
add esp,8 ;外平衡
ESP 寻址底层原理
执行 call 瞬间,栈布局固定:
ESP:call 自动压入的返回地址(占 4 字节)
ESP+4:第一个压入的参数
ESP+8:第二个压入的参数 每多一个参数,偏移额外 + 4
函数内部取值示例:
cpp
AddStack:
mov eax,[esp+4] ;ESP寻址取参数1
add eax,[esp+8] ;ESP寻址取参数2
ret
配套堆栈平衡规则
平衡标准:call 执行前 和 call 结束后 ESP 数值完全一致
外平衡 (cdecl):函数只用普通 ret,call 后add esp,总参数字节
内平衡 (stdcall):函数结尾ret n,自动弹返回地址 + 抬高 ESP 清理参数 失衡后果:栈地址错乱、返回地址读取错误、程序崩溃
ESP 寻址优缺点
优点
1.写法简单,仅用 ESP 加固定偏移就能读取所有栈参数
2.无需额外基址寄存器,节省 EBX/EBP 等通用寄存器
缺点
1.ESP 是动态变化的:函数内 push 寄存器、sub esp 开局部变量都会改变 ESP 值,偏移量跟着变大,计算繁琐
2.复杂多层逻辑、大量局部变量时,偏移极易算错,读错参数
3.若中途频繁改动 ESP,必须手动重新换算偏移,稳定性差
EBP寻址
EBP 寻址核心优势
1.EBP 是栈基址(栈底指针),专门用作稳定寻址基准
2.ESP 会因为 push、sub esp、局部变量分配持续变动,EBP 一经初始化全程固定不变
3.偏移值EBP±数值从头到尾统一,不会因为栈操作改来改去,大幅降低寻址计算失误
EBP 标准初始化流程(函数固定开头序言 prolog)
1.push ebp:把上层函数原来的 EBP 压栈保存,防止覆盖丢失
2.mov ebp, esp:让 EBP 定格在进入函数那一刻的 ESP 位置,锁定基准
3.sub esp, N:抬高 ESP,向下开辟 N 字节栈空间,存放局部变量
执行完这三步,EBP 数值永久固定,不管后面怎么 push、读写栈,基准不动
栈布局(EBP 锁定后)
以 2 个栈传参举例:
| 内存位置 | 存放内容 |
|---|---|
| EBP+0 | 保存的上层旧 EBP |
| EBP+4 | CALL 压入的返回地址 |
| EBP+8 | 第一个传入参数 |
| EBP+12 | 第二个传入参数 |
| EBP-4、EBP-8 ...... | 函数内部局部变量 |
加法函数代码演示(EBP 寻址)
cpp
;调用端 cdecl外平衡
push 20
push 10
call AddEbp
add esp,8
;函数主体
AddEbp:
push ebp ;1.保存旧ebp
mov ebp,esp ;2.ebp锁定当前esp基准
sub esp,0x8 ;3.开辟8字节局部变量空间
mov eax,[ebp+8] ;取参数1=10
mov [ebp-4],eax ;存入局部变量1
mov eax,[ebp+12] ;取参数2=20
mov [ebp-8],eax ;存入局部变量2
mov eax,[ebp-4]
add eax,[ebp-8] ;求和,结果放eax
;函数收尾epilog,恢复栈平衡
mov esp,ebp ;把esp还原回ebp基准,清空局部变量空间
pop ebp ;弹出上层旧ebp,恢复
ret ;返回,外部平衡栈
EBP 寻址配套栈平衡收尾(epilog 尾声)
固定恢复步骤:
1.mov esp, ebp:直接把 ESP 拉回 EBP 的基准位置,一次性释放所有 sub esp 开辟的局部栈空间,不用逐次加减修正
2.pop ebp:还原调用方原本的 EBP 值
3.ret / ret n 完成返回
平衡作用
1.一键抹平函数内部所有局部变量、临时 push 带来的 ESP 变动
2.保证退出函数时栈状态和进入时匹配,杜绝寻址错乱、栈失衡崩溃
3.对比纯 ESP 寻址:复杂多层代码、多局部变量场景下,EBP 偏移全程稳定,几乎不会算错偏移量
对比 ESP 与 EBP 寻址
ESP:基准动态变化,每一次栈操作都要重算偏移,简单小函数能用,复杂函数极易出错
EBP:基准永久固定,参数、局部变量偏移全程不变,是编译器 C/C++ 函数默认标准栈寻址方案