函数的调用过程(栈帧结构)—C语言版

主要从三个方面来详解栈帧

1. 在c语言中关于函数调用过程,用栈帧来分析

2. 可以通过栈帧来修改函数返回时的地址

3. 让不能正常返回的函数可以返回


----------

测试环境 VS2008 win10

----------

1.函数的调用过程 栈帧分析(栈帧在C语言中是指每一个函数调用时栈区会自动地为其开辟一块空间即为栈帧。栈帧中保存了该函数的返回地址和局部变量)

先来看一段代码:

<textarea readonly="readonly" name="code" class="C++">

#include <stdio.h>

int Add(int x,int y)
{
	int z = 0;
	z = x+y;
	return z;
}

int main()
{
	int a = 0xaaaaaaaa;
	int b = 0xbbbbbbbb;
	int ret = Add(a,b);
	printf("ret = %d\n", ret);
	return 0;
}
</textarea>

`然后一直按F10调试到 int c =Add(a,b)这一步,同时可以在窗口里打开反汇编 函数中的变量都是存在于栈区中(当然了用stactic定义的变量不存于栈区),并且这些变量会一直存在于程序运行期间

----------

ebp为栈底指针,esp为栈顶指针,EIP用于存放当前指令的下一条指令的地址

----------

栈区是向下生长的(从高地址到低地址),先将ESP向下生长然后对新的变量进行压栈。下面来看a和b的反汇编代码
<textarea readonly="readonly" name="code" class="汇编">
	int a = 0xaaaaaaaa;
00E9141E  mov         dword ptr [a],0AAAAAAAAh 
	int b = 0xbbbbbbbb;
00E91425  mov         dword ptr [b],0BBBBBBBBh 
	int ret = Add(a,b);
00E9142C  mov         eax,dword ptr [b] 
00E9142F  push        eax  
00E91430  mov         ecx,dword ptr [a] 
00E91433  push        ecx  
00E91434  call        @ILT+215(_Add) (0E910DCh) 
00E91439  add         esp,8 
00E9143C  mov         dword ptr [ret],eax 
</textarea>

可以看出即将调用Add函数时是将b先压栈,a后压栈,随后调用call指令(call指令有两个作用 1> 将call指令的下一条指令的地址存到栈中  2> 用目标函数的地址覆盖EIP的值)

<img src="https://img-blog.csdn.net/2017094030746?watmark/2/text/aHR0cDovL2cuY3Nkbi5uZXQvV5bm1hbjIzMw

==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="40%">


然后接着用F11进入Add函数内部

<textarea readonly="readonly" name="code" class="汇编">
int Add(int x,int y)
{
00E913B0  push        ebp  
00E913B1  mov         ebp,esp 
00E913B3  sub         esp,0CCh 
00E913B9  push        ebx  
00E913BA  push        esi  
00E913BB  push        edi  
00E913BC  lea         edi,[ebp-0CCh] 
00E913C2  mov         ecx,33h 
00E913C7  mov         eax,0CCCCCCCCh 
00E913CC  rep stos    dword ptr es:[edi] 
	int z = 0;
00E913CE  mov         dword ptr [z],0 
	z = x+y;
00E913D5  mov         eax,dword ptr [x] 
00E913D8  add         eax,dword ptr [y] 
00E913DB  mov         dword ptr [z],eax
</textarea>

这段汇编是将EBP先压栈,此时的EBP指向的是main函数的栈底,然后使ESP的内容移动到EBP中,此时相当于EBP指向了另一个栈帧的栈底,而使得ESP减去一个数,,这样一来开辟了一块新的栈帧,而这个新的栈帧即为Add函数的栈帧返回值z,将其先保存到EAX寄存器里,然后移动EBP,ESP,使得回到main函数的栈帧中。而调用ret时和call相反

2. 可以通过栈帧来修改函数返回时的地址

<textarea readonly="readonly" name="code" class="C">
#include <stdio.h>
#include <windows.h>

void bug()
{
	printf("I am a bug");
	Sleep(3000);
}

int Add(int x, int y)
{
	int *p = &x;
	p--;
	*p = bug;
	printf("Add is a 函数\n");
}

int main()
{
	int a = 0;
	int b = 0;
	int ret = Add(a,b);

	printf("ret: %d\n", ret);
	return 0;
}
</textarea>

将call指令的下一条指令的地址用bug函数的地址覆盖掉,达到了跳转到bug函数执行

执行结果:

Add is a 函数

I am a bug请按任意键继续

3. 让不能正常返回的函数可以返回


在上面的 bug函数中,当bug函数调用完毕后不知道返回时的地址,所以我们可以在进入bug函数之前保存一下call指令的下一条指令的地址

#include <stdio.h>
#include <windows.h>

void *ret = NULL;

void bug()
{
	int c = 0;
	int *p = &c;
	p+=2;
	*p = (int)ret;
	printf("I am a bug");
	Sleep(3000);
}

int Add(int x, int y)
{
	int *p = &x;
	p--;
	ret = *p;
	*p = bug;
	printf("Add is a 函数\n");
	return 0;
}

int main()
{
	int a = 0xaaaaaaaa;
	int b = 0xbbbbbbbb;
	int ret = Add(a,b);

    printf("main is running\n");
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值