通过汇编语言理解栈的过程调用

一种受限制的线性表,栈底为高地址,栈顶为低地址。

重要寄存器

ebp:即MIPS中的fp,用来存储当前函数的基地址
esp:即MIPS中的sp,用来存储函数调用栈的栈顶地址,在出栈和压栈时发生变化

C代码

#include <stdio.h>
int Add(int x, int y) {
    int z = 0;
    z = x + y;
    return z;
}
int main()  {
    int a = 10;
    int b = 20;
    int ret = Add(a, b);
    printf("ret = %d\n", ret);
    return 0;
}

环境

CPU配置:Intel(R) Core(TM) i7-6700HQ @2.60GHz 2.59GHz

内存配置:8.00GB

操作系统名称及版本号:Windows 10 家庭中文版 版本1803

内核版本号:OS内部版本 17134.81

DevC++版本号:5.11

main函数的汇编代码

   0x0000000000401559 <+0>:	push   rbp
   0x000000000040155a <+1>:	mov    rbp,rsp
   0x000000000040155d <+4>:	sub    rsp,0x30
   0x0000000000401561 <+8>:	call   0x402120 <__main>
   0x0000000000401566 <+13>:	mov    DWORD PTR [rbp-0x4],0xa
   0x000000000040156d <+20>:	mov    DWORD PTR [rbp-0x8],0x14
   0x0000000000401574 <+27>:	mov    edx,DWORD PTR [rbp-0x8]
   0x0000000000401577 <+30>:	mov    eax,DWORD PTR [rbp-0x4]
   0x000000000040157a <+33>:	mov    ecx,eax
   0x000000000040157c <+35>:	call   0x401530 <Add(int, int)>
   0x0000000000401581 <+40>:	mov    DWORD PTR [rbp-0xc],eax
   0x0000000000401584 <+43>:	mov    eax,DWORD PTR [rbp-0xc]
   0x0000000000401587 <+46>:	mov    edx,eax
   0x0000000000401589 <+48>:	lea    rcx,[rip+0x2a70]        # 0x404000
   0x0000000000401590 <+55>:	call   0x402b38 <printf>
   0x0000000000401595 <+60>:	mov    eax,0x0
   0x000000000040159a <+65>:	add    rsp,0x30
   0x000000000040159e <+69>:	pop    rbp
   0x000000000040159f <+70>:	ret    

Add函数的汇编代码

   0x0000000000401530 <+0>:	push   rbp
   0x0000000000401531 <+1>:	mov    rbp,rsp
   0x0000000000401534 <+4>:	sub    rsp,0x10
   0x0000000000401538 <+8>:	mov    DWORD PTR [rbp+0x10],ecx
   0x000000000040153b <+11>:	mov    DWORD PTR [rbp+0x18],edx
   0x000000000040153e <+14>:	mov    DWORD PTR [rbp-0x4],0x0
   0x0000000000401545 <+21>:	mov    edx,DWORD PTR [rbp+0x10]
   0x0000000000401548 <+24>:	mov    eax,DWORD PTR [rbp+0x18]
   0x000000000040154b <+27>:	add    eax,edx
   0x000000000040154d <+29>:	mov    DWORD PTR [rbp-0x4],eax
   0x0000000000401550 <+32>:	mov    eax,DWORD PTR [rbp-0x4]
   0x0000000000401553 <+35>:	add    rsp,0x10
   0x0000000000401557 <+39>:	pop    rbp
   0x0000000000401558 <+40>:	ret      

通过汇编代码理解过程调用

首先,我们来看C代码,主函数中先定义一个a,b然后调用函数Add,实现相加返回。

先看初始化部分

   0x0000000000401559 <+0>:	push   rbp
   0x000000000040155a <+1>:	mov    rbp,rsp
   0x000000000040155d <+4>:	sub    rsp,0x30
   0x0000000000401561 <+8>:	call   0x402120 <__main>

main函数刚开始运行前,进行初始化,先将rbp压栈,再将ebp的值赋给rsp,rsp通过sub指令减去0x30,即开辟空间,接着跳转至main函数,并设置返回地址。

   0x0000000000401566 <+13>:	mov    DWORD PTR [rbp-0x4],0xa
   0x000000000040156d <+20>:	mov    DWORD PTR [rbp-0x8],0x14
   0x0000000000401574 <+27>:	mov    edx,DWORD PTR [rbp-0x8]
   0x0000000000401577 <+30>:	mov    eax,DWORD PTR [rbp-0x4]
   0x000000000040157a <+33>:	mov    ecx,eax
   0x000000000040157c <+35>:	call   0x401530 <Add(int, int)>

接着是a,b的压栈过程,前2句指令表示在之前开辟的空间内压栈2个值,分别为0xa,0x14,表示十进制的10和20;

这2句指令对应的C代码为int a = 10; int b = 20;

接着是将栈中的值赋给edx和eax寄存器,再把eax寄存器的值赋给ecx,这里我的理解是为传参过程进行准备

一顿操作之后,ecx寄存器的值为10,edx寄存器的值为20。紧接着是call指令,跳转至Add函数,同时留下返回地址。

   0x0000000000401530 <+0>:	push   rbp
   0x0000000000401531 <+1>:	mov    rbp,rsp
   0x0000000000401534 <+4>:	sub    rsp,0x10

跳转到Add函数前,和main函数一样,进行初始化操作,对rbp进行压栈,此时的rbp为main函数的栈顶,Add函数的栈基,类似的操作,进行赋值后减去0x10,开辟空间。

   0x0000000000401538 <+8>:	mov    DWORD PTR [rbp+0x10],ecx
   0x000000000040153b <+11>:	mov    DWORD PTR [rbp+0x18],edx
   0x000000000040153e <+14>:	mov    DWORD PTR [rbp-0x4],0x0

这部分我认为是所有代码中最难理解得到一段,即压栈位置是在main函数的栈帧之中,查阅资料得到,形参的实例化是在上一函数的栈帧中完成的(这里是main函数),前面分析的ecx寄存器的值是10,edx寄存器的值是20,所以这2条指令的含义就是对a,b进行压栈,实例化(存储的地址有别于之前的a,b的地址,保证形参不影响实参),第三条指令就是局部变量的压栈,对应C代码中的int z = 0;

可以看到,实例化形参a,b的地址是b在高地址,a在高地址,栈是先进后出的,故我们就不难理解为什么C/C++中参数的传递顺序的从右到左了。

   0x0000000000401545 <+21>:	mov    edx,DWORD PTR [rbp+0x10]
   0x0000000000401548 <+24>:	mov    eax,DWORD PTR [rbp+0x18]
   0x000000000040154b <+27>:	add    eax,edx
   0x000000000040154d <+29>:	mov    DWORD PTR [rbp-0x4],eax
   0x0000000000401550 <+32>:	mov    eax,DWORD PTR [rbp-0x4]
   0x0000000000401553 <+35>:	add    rsp,0x10
   0x0000000000401557 <+39>:	pop    rbp
   0x0000000000401558 <+40>:	ret      

接着的代码就好理解了,将实例化后的值赋给2个寄存器,再进行add指令进行相加,结果存储在eax寄存器中,再将eax寄存器的赋给z;最后rsp加0x10,清除空间,返回,结束过程调用!

(tips:之所以有很多寄存器与栈之间的相互赋值,是因为mov指令不能直接进行2个已经有值得寄存器直接的赋值操作,也不能读取栈的数据赋到另一个地址中)

 

 

 

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值