函数的调用过程(栈帧)

xiaoxiao2021-02-28  16

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函数中

转载请注明原文地址: https://www.6miu.com/read-1750060.html

最新回复(0)