从汇编分析函数调用堆栈详细过程

前言

首先来说,我们今天的学习并不是为了学习汇编语言,而是为了更好地分析一些问题的底层原理。、

进入正题:

首先由下面简单的代码我们来考虑两个问题:
1.main函数调用sum,sum执行完以后,怎么知道回到哪个函数中?
2.sum函数执行完,回到main以后,怎么知道从哪一行指令继续运行的?

int sum(int a,int b)
{
	int temp = 0;
	temp = a + b;
	return temp;
}
int main()
{
	int a = 10;//mov dword ptr[edp-4], 0Ah
	int b = 20;//mov dword ptr[edp-8], 14h
	int ret = sum(a,b);
	cout <<"ret:"<<ret <<endl ;
	return 0;
}

首先,大家都知道函数运行的时候要在栈帧上开辟空间。一个函数的调用要先压参数且从右向左压,现在我们对这个代码从汇编的角度进行更深的剖析:

在main函数中我们定义的参数:
int a=10;实际相当于汇编中的mov dword ptr[edp-4], 0Ah 栈底偏移4
int b=20;实际相当于汇编中的mov dword ptr[edp-8], 14h 栈底偏移8
请添加图片描述
注意

  1. esp指的是堆栈指针且始终指向栈顶
  2. ebp指的是栈底指针

那么第三个参数ret我们该如何去理解它呢?
我们这里ret的初值是不知道的,它等于sum函数的返回值;那么一个函数的调用呢首先会做的事就是压栈,首先会把b压入栈,如下图请添加图片描述

最上面的一块就是sum函数形参变量b的内存从这里我们就知道函数调用过程中形参变量内存的开辟是在调用方函数这里就已经开辟好了的,因为是调用方的函数实参要压栈。随着压栈产生了push指令:

mov eax,dword ptr[ebp-8]
push eax
mov eax,dword ptr[ebp-4]
push eax

这个函数调用参数已经压完栈了,接下来就是call指令了:
请添加图片描述

首先call sum做的第一件事就是把这行指令的下一行指令的地址压栈

我们假设地址为0x08124458
请添加图片描述
第二件事情就是进入sum:
请添加图片描述
在sum里面我们不能觉得函数进来第一句话执行的就是int temp =0;我们千万不能忽略了 “” 和 “” ,它也是有指令生成的:

push ebp//调用方的栈底地址入栈
mov ebp, esp//ebp指向当前栈底
sub esp, 4Ch//sep往上跑,给sum函数开辟栈帧

在这里插入图片描述

以上指令就相当于在给我们的sum函数开辟栈帧空间,
这在我们gcc g++下面函数 “{ ”进来以后这三行指令并没有对栈进行一个初始化的操作,而在我们Windows下vs系列的编译器里面,我们给函数开辟栈帧,开辟完之后我们都会有:

rep stos
for

这两个指令就相当于我们代码中for循环的指令,会把我们的栈帧都全部初始化成0xCCCCCCCC。那我们一定见过以下这个例子:

int a;
cout<<a<<endl;
//-858993460  0xCCCCCCCC

这就是为什么一个局部变量没有初始化去打印它会打印出这个结果。

接下来到sum函数内的三条代码:

int temp = 0//mov dword ptr[ebp-4],0
temp = a+b;//mov eax,dword ptr[ebp+8]   mov dword ptr[ebp-4],eax
return temp;//mov eax,dword ptr[ebp-4]

然后到了“ }”:它做的第一件事就是

mov esp , ebp

就是相当于回退栈帧,把这个栈空间交还给系统,栈内的数据并没有清空。

下面这个例子可以让我们更好的理解:

int* func()
{
	int data=10;
	return &data;
}
int main()
{
	int *p=func();
	cout<<*p<<endl;
}

那么我们肯定可以评判上面func()这个代码肯定是不安全的,因为函数运行完栈帧回退之后这个栈空间已经交还给系统了,只不过我们仅仅还能再main函数中打印这个*p(因为栈帧回退并没有对栈上的数据进行清理)。所以我们还是尽量不要写上面的这种代码。

再往下看:

pop ebp ;出栈并把出栈的值赋给ebp

接下来是ret它做了两个操作
首先是一个出栈操作,把出栈的内容放入CPU的PC寄存器里面(PC寄存器存放的是下一条指令的地址)。所以我们CPU执行完这个ret指令就直接跳到这个0x08124458这个地址上去执行下一条指令了,就跳到下图这个地方来了:
请添加图片描述
所以呢 sum函数运行完它是怎么回到main函数,那么main函数又是怎么知道从
add esp,8 这条指令继续运行的呢?

就是因为它在进入sum函数的时候早已经把call指令下一行指令的地址入栈了,当这个sum函数调用完,“{ ”最后一个指令ret的时候出栈把下一行指令的地址直接放入PC寄存器里面,相当于我们看到的回到sum函数并且从刚才调用函数的下一条指令开始执行了
这时 add esp,8 执行之后
请添加图片描述

请添加图片描述
这是把刚才压栈的两个形参变量a和b的内存交还给系统了
然后esp又回到了main函数刚才的栈顶了。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值