关于栈帧,我们需要了解一下C/C++程序内存的分配,如图所示:
栈区:由编译器自动分配释放,存放为运行函数而分配的局部变量、函数、参数、返回值数据、返回地址等。栈区地址依次逐减,与堆区相对而生。
栈帧:函数的调用过程中为函数开辟栈空间,用于本次函数的调用中临时变量的保存、现场保护。这块栈空间就是函数栈帧。
一个数据结构——栈,栈顶与栈底。
两个操作——push(入栈)与pop(出栈)。
寄存器——EAX、EBX、ECX、EDX (通用寄存器)
EBP 栈底寄存器(基址寄存器)
ESP 栈顶寄存器
EIP 指令寄存器(程序寄存器)
部分汇编——call(每人将当前正在执行指令的下一条指令的地址压入栈 中,并且跳转至目标函数的地址,开始过程调用。)
ret (弹出栈顶地址,将数据放入EIP)
VC++6.0对栈帧进行研究(不同平台下操作,原理相同偏移量不同)
#include
#include
int Add(int x,int y){
int z=x+y;
return z;
}
int main(){
int a=10;
int b=20;
int ret=Add(a,b);
printf("%d\n",ret);
system("pause");
return 0;
}
将以上代码进行调试,转成汇编语言,打开寄存器与内存。
mian函数及临时变量部分
此时寄存器与内存如下
10 int a=10;
00401078 mov dword ptr [ebp-4],0Ah //a_EAX
11: int b=20;
0040107F mov dword ptr [ebp-8],14h//b_ECX
12: int ret=Add(a,b);
00401086 mov eax,dword ptr [ebp-8]
00401089 push eax //ESP下移,b_EAX
0040108A mov ecx,dword ptr [ebp-4]
0040108D push ecx //ESP下移,a_ECX
0040108E call @ILT+0(Add) (00401005)
//ESP下移,存入00401093
Add函数栈帧部分
4: int Add(int x,int y){
00401020 push ebp //ESP下移,EBP压栈
00401021 mov ebp,esp//ESP地址_EBP
00401023 sub esp,44h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-44h]
0040102C mov ecx,11h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
5: int z=x+y;
00401038 mov eax,dword ptr [ebp+8]//a_EAX
0040103B add eax,dword ptr [ebp+0Ch]//a+b_EAX
0040103E mov dword ptr [ebp-4],eax
6: return z;
00401041 mov eax,dword ptr [ebp-4]// 返回EAX
返回
00401044 pop edi
00401045 pop esi
00401046 pop ebx
00401047 mov esp,ebp
00401049 pop ebp
0040104A ret //弹出栈顶地址数据,并放入EIP
00401093 add esp,8//ESP 上移8
00401096 mov dword ptr [ebp-0Ch],eax
13: printf("%d\n",ret);
00401099 mov edx,dword ptr [ebp-0Ch]
0040109C push edx
0040109D push offset string "%d\n" (00424024)
004010A2 call printf (00401200)
整个栈帧的调用过程完成。
小扩展:
1.证明形参实例化时由右向左的
int Add(int x,int y){
int*p=&x;
p++;
*p=20;
int z=x+y;
return z;
}// 21
int Add(int x,int y){
int*p=&x;
p++;
//*p=20;
int z=x+y;
return z;
}// 3
2.函数内部调用函数,返回方式的研究
#include
#include
void bug(){
printf("hello,i am a bug.\n");
system("pause");
}
int Add(int x,int y){
int*p=&x;
p--;
p=(int)bug;//导致系统奔溃
int z=x+y;
return z;
}
int main(){
int a=1;
int b=2;
int ret=Add(a,b);
printf("%d\n",ret);
system("pause");
return 0;
}
在Add函数中调用bug函数,返回值返回到bug函数中,但是编译器会崩掉,所以调用完bug函数后最终还是要返回到main函数中
这里写图片描述
bug要返回,首先得找自己的返回值
那就先来研究一下函数内部的第一个变量和第一个参数的关系:
void fun(int x)
{
int y;
printf("&x: %p\n");
printf("&y: %p\n");
}
得出x和y 的地址差12
对应于上图中局部变量到00401093的地址差8,所以可以将bug函数的代码改为:
void bug()
{
int x=0;
int *p=&x;
p+=2;
}
但是运行的时候编译器依然会崩掉,原因:
调用bug函数时,是指针跳转过去,没用call指令,也就是没有进行push操作
但是返回的时候执行了ret指令,也就是多pop了一次,即esp变大了一个地址,
所以还需将esp再减去4;
在c语言中插入汇编语言:–asm
__asm
{
sub esp,4;
}
这样就成功的从bug函数中返回到了main函数中.