计算机组成原理 | 逐行解析汇编代码中的栈调用

计算机组成原理 | 逐行解析汇编代码中的栈调用文章详细解释了 rbp rsp edx eax 等汇编语言中的寄存器在函数调用过程中的角色 包括它们如何存储参数 局部变量和函数返回值 以及如何通过栈帧管理内存

汇编技术名词

  • rbp(Register Base Pointer)是基址指针寄存器,它指向当前函数的栈帧的基址。栈帧是在函数调用期间用于保存局部变量和其他相关信息的一部分内存区域。通过在 rbp 中保存栈帧的基址,可以轻松地访问函数的参数和局部变量。
  • rsp(Register Stack Pointer)是栈指针寄存器,它指向当前栈顶的位置。栈是一种后进先出(LIFO)的数据结构,用于存储函数调用期间的临时数据。通过在 rsp 中保存栈顶的位置,可以在函数调用过程中分配和释放栈上的内存空间。
  • edx (Extended Data Register),数据寄存器,常用于存储和操作数据,作为通用寄存器之一。在函数调用中,edx 可用于存储参数值或临时变量。
  • eax (Extended Accumulator Register),累加器,用于执行算术和逻辑运算,以及处理函数返回值。eax 寄存器通常用于保存函数的返回值。
  • esi(Source Index Register)是源索引寄存器,通常用于存储源数据的地址或偏移量。它在字符串操作、循环遍历等场景中经常被使用。
  • edi(Destination Index Register)是目标索引寄存器,通常用于存储目标数据的地址或偏移量。它也常用于字符串操作、循环遍历等场景中。

一个简单的C语言示例:汇编代码逐行解析

function_example.c为例:

// function_example.c #include <stdio.h> int static add(int a, int b) { 
    return a+b; } int main() { 
    int x = 5; int y = 10; int u = add(x, y); } 

以下是function_example.c所对应的汇编代码:

int static add(int a, int b) { 
    0: 55 push rbp ; 保存调用者的基址指针 1: 48 89 e5 mov rbp,rsp ; 设置当前函数的基址指针 4: 89 7d fc mov DWORD PTR [rbp-0x4],edi ; 将第一个参数 a 保存在栈帧中 7: 89 75 f8 mov DWORD PTR [rbp-0x8],esi ; 将第二个参数 b 保存在栈帧中 return a+b; a: 8b 55 fc mov edx,DWORD PTR [rbp-0x4] ; 将 a 加载到寄存器 edx d: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] ; 将 b 加载到寄存器 eax 10: 01 d0 add eax,edx ; 将 a 和 b 相加并保存在寄存器 eax 中 } 12: 5d pop rbp ; 恢复调用者的基址指针 13: c3 ret ; 返回至调用者 0000000000000014 <main>: int main() { 
    14: 55 push rbp ; 保存调用者的基址指针 15: 48 89 e5 mov rbp,rsp ; 设置当前函数的基址指针 18: 48 83 ec 10 sub rsp,0x10 ; 在栈上分配 16 字节的空间 int x = 5; 1c: c7 45 fc 05 00 00 00 mov DWORD PTR [rbp-0x4],0x5 ; 将值 5 存储在变量 x 的位置上 int y = 10; 23: c7 45 f8 0a 00 00 00 mov DWORD PTR [rbp-0x8],0xa ; 将值 10 存储在变量 y 的位置上 int u = add(x, y); 2a: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8] ; 将变量 y 的值加载到寄存器 edx 2d: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] ; 将变量 x 的值加载到寄存器 eax 30: 89 d6 mov esi,edx ; 将 y 的值复制到 esi 寄存器,作为第二个参数 32: 89 c7 mov edi,eax ; 将 x 的值复制到 edi 寄存器,作为第一个参数 34: e8 c7 ff ff ff call 0 <add> ; 调用函数 add 39: 

逐行解释上述汇编代码,首先对add函数进行解释:

  1. push rbp:将调用者的基址指针 rbp 压入栈中,以保存调用者函数的栈帧信息。
  2. mov rbp, rsp:将栈指针 rsp 的值复制给当前函数的基址指针 rbp,用于建立当前函数的栈帧。
  3. mov DWORD PTR [rbp-0x4], edi:将第一个参数 a 的值(存储在寄存器 edi 中)保存在栈帧中的偏移量为 -0x4 的位置上。
  4. mov DWORD PTR [rbp-0x8], esi:将第二个参数 b 的值(存储在寄存器 esi 中)保存在栈帧中的偏移量为 -0x8 的位置上。
  5. mov edx, DWORD PTR [rbp-0x4]:将栈帧中偏移量为 -0x4 处的值(即参数 a)加载到寄存器 edx 中。
  6. mov eax, DWORD PTR [rbp-0x8]:将栈帧中偏移量为 -0x8 处的值(即参数 b)加载到寄存器 eax 中。
  7. add eax, edx:将 eaxedx 寄存器中的值相加,结果存储在 eax 中,即得到了 a + b 的结果。
  8. pop rbp:弹出栈顶素,即调用者函数的基址指针 rbp,以恢复调用者函数的上下文。
  9. ret:从当前函数返回,将控制流程返回给调用者。

接下来进入 main 函数的解释:

  1. push rbp:将调用者的基址指针 rbp 压入栈中,以保存调用者函数的栈帧信息。
  2. mov rbp, rsp:将栈指针 rsp 的值复制给当前函数的基址指针 rbp,用于建立当前函数的栈帧。
  3. sub rsp, 0x10:在栈上分配 16 字节的空间,用于存储局部变量和临时数据。
  4. mov DWORD PTR [rbp-0x4], 0x5:将值 5 存储在变量 x 的位置上,即栈帧中偏移量为 -0x4 的位置。
  5. mov DWORD PTR [rbp-0x8], 0xa:将值 10 存储在变量 y 的位置上,即栈帧中偏移量为 -0x8 的位置。
  6. mov edx, DWORD PTR [rbp-0x8]:将变量 y 的值加载到寄存器 edx 中。
  7. mov eax, DWORD PTR [rbp-0x4]:将变量 x 的值加载到寄存器 eax 中。
  8. mov esi, edx:将寄存器 edx 中的值(即变量 y 的值)复制到寄存器 esi1,作为第二个参数。
  9. mov edi, eax:将寄存器 eax 中的值(即变量 x 的值)复制到寄存器 edi 中,作为第一个参数。
  10. call 0 <add>:调用函数 add,将控制流程转移到函数 add 的代码处。

参考文献

  • 徐文浩. 函数调用:为什么会发生stack overflow?极客时间. 2019

  1. 通过将参数值加载到寄存器中,函数 add 可以直接从寄存器中读取参数值进行计算,而无需直接访问内存中的变量。这样可以提高执行效率,并减少对内存的访问次数。 ↩︎
今天的文章 计算机组成原理 | 逐行解析汇编代码中的栈调用分享到此就结束了,感谢您的阅读。
编程小号
上一篇 2025-01-01 12:57
下一篇 2025-01-01 12:51

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/98743.html