当前位置: 首页 > article >正文

初始EBP和ESP的设置

下面用一个示意图和详细解释说明为什么在函数栈帧中函数参数通常放在基址指针(EBP)的正偏移位置,以及为什么是用+4、+8等偏移量来访问参数。如果有多个参数,它们又是如何排列的。


栈帧的基本结构

在经典的 32 位 x86 调用约定(比如 cdecl)中,一个函数进入时通常会执行如下指令建立栈帧:

push    ebp         ; 保存上一个函数的基址指针
mov     ebp, esp    ; 将当前 ESP 的值复制到 EBP,建立新的栈帧基准
sub     esp, N      ; 为局部变量分配 N 字节的空间(N 根据局部变量的多少设定)

这时栈内的内存布局一般如下(地址从高到低):

 高地址
  +-----------------------+
  |      参数 k           | <- [EBP + (4 + 4*k)] (k从0开始计数)
  +-----------------------+
  |      ...              |
  +-----------------------+
  |      参数2            | <- [EBP+12]
  +-----------------------+
  |      参数1            | <- [EBP+8]
  +-----------------------+
  |   返回地址(Call 指令压入) | <- [EBP+4]
  +-----------------------+
  |   保存的旧EBP        | <- [EBP](由 push ebp 保存)
  +-----------------------+
  |   局部变量区域        | <- [EBP-4], [EBP-8], ...(由 sub esp, N 分配)
  +-----------------------+
  低地址

为什么参数放在“正偏移”位置?

  1. EBP 的固定作用
    当程序执行完以下指令后,

    push ebp
    mov ebp, esp
    

    此时 EBP 被固定下来,作为当前函数栈帧的基准地址。

    • 旧 EBP 被保存于 [EBP]
    • 返回地址 则在调用 call 指令时压入栈中,位于 [EBP+4]
    • 而后调用者在调用函数时已经将所有参数压入栈中,这些参数就位于返回地址之上,也就是在 EBP+8 及更高的位置。
  2. 局部变量与参数的区隔

    • 局部变量:为了便于访问,编译器将其分配在栈帧中距离 EBP 较近、低地址的负偏移区(比如 [EBP-4][EBP-8] 等)。
    • 函数参数:由于在调用时参数是先被压入栈中,再由 call 压入返回地址,所以它们位于 EBP 以上(正偏移区域)。
      例如,函数的第一个参数就位于 [EBP+8],第二个参数在 [EBP+12],依此类推。
  3. 为什么用 +4 和 +8 等偏移?

    • [EBP+0]:实际上就是 EBP 自身,存放的是保存的旧 EBP 值。
    • [EBP+4]:存放着调用 call 指令时推入的返回地址。
    • [EBP+8]:即第一个参数,因为在函数调用时,参数在返回地址之上依次压入,所以离 EBP 的距离是 8 字节。
    • 如果有多个参数,依次排列为 [EBP+8](第一个)、[EBP+12](第二个)、[EBP+16](第三个)等等。
      也就是说,参数的偏移量依赖于保存的返回地址和每个参数的大小(通常 32 位系统中每个参数 4 字节)。

举例说明

考虑下面的 C 函数:

int add(int a, int b) {
    return a + b;
}

调用时的栈帧布局

当调用 add 函数时,调用者会按从右到左的顺序将参数压入栈,然后执行 call add。假设参数压入栈后的情况如下:

 高地址
  +--------------+
  |    b         |   <- 实际在栈中的位置(稍后在函数中为 [EBP+12])
  +--------------+
  |    a         |   <- 实际在栈中的位置(稍后在函数中为 [EBP+8])
  +--------------+
  | 返回地址     |   <- 由 call 指令压入,存于 [EBP+4]
  +--------------+
  | 保存的旧EBP  |   <- push ebp 后保存,存于 [EBP]
  +--------------+
  | 局部变量区域 |   <- 如果有局部变量,位于 [EBP-4] 等
  +--------------+
  低地址

add 函数内部,通过以下指令可以访问参数:

  • 第一参数 amov eax, [ebp+8]
  • 第二参数 bmov edx, [ebp+12]

函数体内就可以使用这些参数进行计算返回 a+b


总结

  • “正偏移”
    指的是相对于固定的基址指针(EBP)向高地址方向计算的偏移。由于栈帧内局部变量采用负偏移(例如 [EBP-4]),而返回地址和参数则在 EBP 之上,所以参数访问时使用正偏移。

  • 为什么 [EBP+4] 和 [EBP+8]?

    • [EBP+4] 存放着返回地址。
    • [EBP+8] 是第一个函数参数;如果有多个参数,则依次使用 [EBP+12][EBP+16] 等来访问后续参数。
  • 多个参数
    参数依次按从右到左被压入栈中(在 cdecl 中),因此在调用函数后,第一参数总是位于 [EBP+8],第二参数位于 [EBP+12],第三参数位于 [EBP+16],等等。

这种布局设计使得在函数内部可以通过固定(且不随栈操作变化)的 EBP 来方便、准确地访问所有参数和局部变量,从而简化了编译器生成代码及调试时对栈帧的分析。


http://www.kler.cn/a/595822.html

相关文章:

  • Android Compose 图像修饰深度解析(八)
  • 使用Python轻松拆分PDF,每页独立成文件
  • (一)丶Windows安装RabbitMQ可能会遇到的问题
  • JavaScript性能优化实战:深入探讨性能瓶颈与优化技巧
  • STM32 SPI总线驱动CH376T实现U盘/TF卡读写全解析—CH376数据手册分析(中上) | 零基础入门STM32第七十三步
  • Event driven agentic document workflows 笔记 - 3
  • 【Javascrip】Javascript练习01 REST API using Express.js.
  • NFS 安装与测试
  • MySQL数据库入门到大蛇尚硅谷宋红康老师笔记 高级篇 part11
  • C++修炼:内存管理
  • 最质量实践Docker
  • Github 2025-03-21Java开源项目日报Top9
  • 【Linux 维测专栏 2 -- Deadlock detection介绍】
  • 解决 C 盘空间不足,免费软件高效清理
  • 初级:控制流程面试题精讲
  • 《实战指南:基于Linux环境部署与应用Milvus向量数据库》
  • (四)---四元数的基础知识-(定义)-(乘法)-(逆)-(退化到二维复平面)
  • C++学习之QT中HTTP正则表达式
  • 从OSI七层网络模型角度了解CAN通信协议
  • Android HAL服务注册与获取服务