C语言中分为几个区,常用的区按地址从小到大分布是:代码区-静态全局区-堆区-全局共享区-栈区,静态区从小到大是已初始化静态区,未初始化静态区。而我们经常使用的区域是栈区,而栈帧就是在栈区,而且有一点是和其他区有一个明显的分别,就是栈区内部是从大到小使用的。
基本的栈帧就是下图这样的:
贴上使用的代码:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<windows.h>
int fun(int x,int y)
{
int c = 0xcccccccc;
int *p = &x;
p++;
*p = 0xdddddddd;
printf("after :b = %x\n", y);
return c;
}
int main()
{
int a = 0xaaaaaaaa;
int b = 0xbbbbbbbb;
int ret = fun(a, b);
printf("2333333333\n");
system("pause");
return 0;
}
然后我们来详细讲一下栈帧的形成和构造。
到这,main函数的栈帧区间就已经创建好,并且把fun函数的形参实体化在main中,且保存了call指令的下一指令的地址,便于fun函数的返回。
现在的栈帧结构大概就是这样:
依据此图,我们很容易可以理解通过fun函数的第一个形参(并不是第一个实体化的,而是fun函数括号里面从左到右的第一个参数)可以更改第二个参数的值,而不通过第二个参数来完成。
最后,就是fun的返回和main的返回以及esp、ebp归回到main的地方
借用这种方式,我们还可以给fun函数里面用x的地址实现调用函数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<windows.h>
void *main_ret = NULL;
void bug()
{
int flag = 0;
int *p = &flag;
p += 4;
printf("you see a little bug,hahahhhhh\n");
Sleep(2000);
printf("what can you do?\n");
Sleep(2000);
printf("nothing\n");
Sleep(2000);
printf("just wait\n");
Sleep(5000);
*p = main_ret;
printf("now,see again\n");
Sleep(2000);
}
int fun(int x,int y)
{
int c = 0xcccccccc;
int *p = &x;
p--;
main_ret = *p;
printf("what do you see?\n");
*p = (int)bug;
return c;
}
int main()
{
int a = 0xaaaaaaaa;
int b = 0xbbbbbbbb;
int ret = fun(a, b);
_asm
{
sub esp, 4
}
printf("2333333333\n");
system("pause");
return 0;
}
代码就是这个样子,根据第一幅栈帧图的结构,使用fun函数中第一个参数的地址,从而达到调用函数的目的。
并且,可以看到,main中用 汇编语言将esp+4,是因为:main用call调用fun函数,fun用地址调用bug函数,bug函数返回fun,fun返回main,main自己也 有返回值。所以此时ret指令返回了三次,而call指令只 调用了两次,所以esp多出栈一次,所以要给esp+4,平衡栈帧结构。
本人为初学者,对汇编语言和编译器的运行方式并不是很精通,如果各位读者发现以上内容有错误问题,请在评论区回复,我会在第一时间修正。