1.栈:
特点:
- 先进后出;
- 有栈顶(esp)也有栈底(ebp);
入栈:push
出栈:pop;
main函数在mainCRTStartup被调用,所以Startup是c中第一个被调的函数
调函数形成栈帧,消耗时间空间
返回释放栈帧cpu中常用的寄存器:
通用寄存器:EAX,EBX,ECX,EDX
程序计数器:EIP(pc)(存放当前指令的下一条指令)
EBP:基址寄存器(栈底寄存器)
ESP:栈顶寄存器
函数调用在汇编语言里的指令call:
- 保存当前指令的下一条指令的地址,目的是方便恢复(入栈保存)
- 跳转到目标函数的入口地址处(jmp修改EIP)
2.栈帧的建立与撤销
#include<stdio.h>
int myAdd(int x,int y)
{
int z=x+y;
return z;
}
int main()
{
int a=0xaaaaaaaa;
int b=0xbbbbbbbb;
int c=myAdd(a,b);
printf("You should run here:%d\n",c);
return 0;
}
汇编如下:
12: int a=0xaaaaaaaa;
00401078 mov dword ptr [ebp-4],0AAAAAAAAh
13: int b=0xbbbbbbbb;
0040107F mov dword ptr [ebp-8],0BBBBBBBBh
14: int c=myAdd(a,b);
00401086 mov eax,dword ptr [ebp-8]
00401089 push eax
0040108A mov ecx,dword ptr [ebp-4]
0040108D push ecx
对应的栈帧图为:
此时对应的eax里为b,ecx里为a,
所以是先形成临时变量b,再形成临时变量a,即形参实例化时是从右向左,且临时变量保存在当前栈的esp栈顶所指向的内存里
临时变量是在被调和调用函数的栈帧结构之间
接下来执行call指令:
1.将当前指令的下一条地址(00401093)进行入栈保存
2. 跳转至目标函数的入口地址处(jmp),即修改eip的地址为myADD函数的入口地址00401020
@ILT+0(_myAdd):
00401005 jmp myAdd (00401020)
@ILT+5(_main):
0040100A jmp main (00401060)
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,44h
7: int z=x+y;
00401038 mov eax,dword ptr [ebp+8]
0040103B add eax,dword ptr [ebp+0Ch]
0040103E mov dword ptr [ebp-4],eax
8: return z;
形成新的栈结构,供myADD使用,同时将eip的地址改为myADD code的地址
并且将a+b的结果压入栈中
mov ebp,esp意思是ebp和esp的内容保持一致、
myAdd函数的返回值是通过eax寄存器返回的
函数调用结束,返回到main函数中,栈帧撤销
ret的作用:
1. 将当前保存的函数的地址进行出栈
2. 弹出数值,恢复EIP
EIP地址重新修改为00401093,即返回到main函数中
00401093 add esp,8
00401096 mov dword ptr [ebp-0Ch],eax
15: printf("You should run here:%d\n",c);
00401099 mov edx,dword ptr [ebp-0Ch]
0040109C push edx
0040109D push offset string "You should run here:%d\n"
调用结束后此时的栈结构为
修改myAdd函数的返回值,不让函数返回到main函数中,而是返回到自己写的另一个函数bug函数中
代码如下:
void bug()
{
printf("hello,I am a bug!:)\n");
system("pause");
}
int myAdd(int x,int y)
{
printf("myAdd begin run...\n");
int z=0;
int *p=&x;
p--;
*p=(int)bug;
z=x+y;
return z;
}
在myAdd函数中调用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函数中