欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

函数的调用(栈帧)

程序员文章站 2022-07-07 11:58:12
...

一、栈的结构:出栈(pop)、入栈(push)都是从一端进行的,因此栈遵循先入后出的规则

二、寄存器:1.通用寄存器:EAX、EBX、ECX、EDX

                2.EIP(PC):程序计数器(指向当前正在执行指令的下一条指令的地址)

           3.ESP(栈顶)、EBP(栈底):esp和ebp之间就是一个函数的栈帧,这个栈帧的大小就是通过对函数内定义的临时变量所需要的空间大小开辟出来的。因此,函数内定义的任何临时变量都位于栈中。

三、CPU的主要工作:读取指令->分析指令->执行指令

四、地址空间:我们放大栈区,对其研究

函数的调用(栈帧)

接下来我们看一段代码:

#include<stdio.h>
#include<windows.h>
int my_add(int a,int b)
{
	int z=0;
	z=a+b;
   return z;
}
int main()
{
	int a=0xAAAAAAAA;
	int b=0xBBBBBBBB;
	int ret=my_add(a,b);
	printf("you should run here!%d\n",ret);
	system("pause");
	return 0;
}


代码转为汇编:

函数的调用(栈帧)

汇编:定义并初始化a,b

此时在栈中是这样的,可以知道此时的栈帧是main函数的,此时我们知道,main函数不是第一个被调的函数,而startup才是第一个被调的函数

函数的调用(栈帧)

在调用call命令之前的汇编

函数的调用(栈帧)

这是内存和寄存器

函数的调用(栈帧)函数的调用(栈帧)

栈是这样的: 

下面的b,a为执行call命令之前所形成的临时变量,此时我们可以知道传参形成的临时变量一直在做入栈操作,且形参实例化是由右至左进行的

函数的调用(栈帧)


接下来执行call命令

函数的调用(栈帧)

call命令分为两步:

1.将当前汇编指令的下一条指令地址进行入栈保存

函数的调用(栈帧)


2..跳转至目标函数的入口地址(jmp),修改EIP。

函数的调用(栈帧)

执行call命令:

函数的调用(栈帧)

此时的寄存器和内存:

函数的调用(栈帧)

函数的调用(栈帧)

栈如下,此时就形成了my_add的栈帧,我们可以看出临时变量是在调用和被调之间

函数的调用(栈帧)

函数的调用(栈帧)

栈如下:

函数的调用(栈帧)

函数的调用(栈帧)

内存如下:

函数的调用(栈帧)

此时的栈:

函数的调用(栈帧)

函数的调用(栈帧)

函数的调用(栈帧)

可以看出函数的返回值是通过寄存器返回的

ret:1.将当前栈顶的返回值地址出栈

2.将弹出的数据修改EIP

函数的调用(栈帧)

理论如上已经说完,我们接下来看看实例:

1.不能使用b,将b的内容改变。

那么我们就通过a来该b,定义一个指针变量,对指针+1(实际+其所指类型)

#include<stdio.h>
#include<windows.h>
int my_add(int a,int b)
{
	int z=0;
	z=a+b;
	int *p=&a;
	p++;
	printf("%x\n",b);
	*p=10;
	printf("%x\n",b);
   return z;
}
int main()
{
	int a=0xAAAAAAAA;
	int b=0xBBBBBBBB;
	int ret=my_add(a,b);
	printf("you should run here!%d\n",ret);
	system("pause");
	return 0;
}

2.我们在完成my_add函数之后不想让他返回到main里面,我们想让他跳到我们任意指定的函数里,那么通过修改返回值的地址来实现

#include<stdio.h>
#include<windows.h>
void bug()
{
	printf("#############haha~i am bug!\n");
	system("pause");
	return;
}
int my_add(int a,int b)
{
	int z=a+b;
	int *p=&a;
	printf("myadd run\n");
	p--;
	*p=bug;
   return z;
}
int main()
{
	int a=0xAAAAAAAA;
	int b=0xBBBBBBBB;
	int ret;
	printf("main run\n");
	ret=my_add(a,b);
	printf("you should run here!%d\n",ret);
	system("pause");
	return 0;
}

但此时bug函数却没有返回到main,所以此时我们应该修改bug的返回值,

我们将main函数的地址进行备份

int my_add(int a,int b)
{
	int z=0;
	z=a+b;
     int *p=&a;
	printf("myadd run\n");
	p--;
	main_ret=(void*)*p;
	*p=(int)bug;
   return z;

在bug函数里返回main函数的地址

void *main_ret
void bug()
{
	int pos;
	int *p=&pos;
	p+=2;
	*p=main_ret;
	printf("#############haha~i am bug!\n");
	system("pause");
	return;
}

但此时程序却不能正常退出,找其原因,我们发现,在调用my_add函数我们是用call进行的,分为2步,但在调用bug的时候,只进行了一步,是直接跳过去的,这样我们的bug函数只有pop却没有push,esp的值向上偏移4个,导致栈帧失衡。所以此时我们应该将esp的值下移4个。由于c语言不好写,我们可以在其中插入汇编指令。

int main()
{
	int a=0xAAAAAAAA;
	int b=0xBBBBBBBB;
	int ret;
	printf("main run\n");
	ret=my_add(a,b);
	__asm{
		sub esp,4
	}
	printf("you should run here!%d\n",ret);
	system("pause");
	return 0;
}

相关标签: 栈帧