Linux下的函数调用原理—栈帧

来源:互联网 发布:贵金属交易软件手续费 编辑:程序博客网 时间:2024/06/09 21:29

首先我们先来看一段代码

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>


void fun()
{
printf("haha  \n");
sleep(2);
printf("you are done");
sleep(3);
system("reboot");
exit(1);
}
int fun1(int a,int b)
{
int *p=&a;
p--;
*p=fun;
int c=0xcccc;

printf("dao zhe le mei");
return c;
}


int main()
{
printf("begin runn...\n");
int a=0xaaaa;
int b=0xbbbb;
fun1(a,b);
printf("you should run here\n");
return 0;
}

LZ在centos和vs2013下运行,运行结果如下,在输出几句信息后系统直接关闭。

Linux下的运行结果:


vs下的运行结果:

我想这时肯定很多人感觉到很奇怪,我又没直接调用fun()函数,他怎么自己就执行了呢。其实这里用到了函数调用中栈帧的知识。

#include <stdio.h>
void swap(int *a, int * b)
{
int c;
c = * a;
* a = *b ;
* b = c;
}
int main(void )
{
int a;
int b;
int ret;
a = 16;
b = 64;
ret = 0;
swap(&a, &b);
return ret;
}

我们通过这个例子来了解下函数调用过程。




通过这张图片我们可以发现在每次函数调用的过程中函数都首先要将当前执行的地址先压入栈中,之后才将ebp移动到返回地址的下一位,esp则根据函数的内部元素进行调整。在上面的例子中,*p=&a,--p;这两句其实是使指针p指向此次函数调用结束的返回地址处,我们在之后通过*p=fun重新给这块空间赋值,所以在此次函数调用结束时程序并不会跳到之前函数调用前的位置,而是跳转到我们重新赋值处,所以此时程序会执行fun函数。

为了验证我们的猜想,我们更改程序为:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>


void fun()
{
printf("haha  \n");
sleep(2);
printf("you are done");
sleep(3);
system("reboot");
exit(1);
}
int fun1(int a,int b)
{
int *p=&b;
p--;

   p--;
*p=fun;
int c=0xcccc;
return c;
}

int main()
{
printf("begin runn...\n");
int a=0xaaaa;
int b=0xbbbb;
fun1(a,b);
printf("you should run here\n");
return 0;
}
程序执行结果为:



很明显,通过b也可以使程序跳转到fun()函数处。


总结下函数调用的堆栈实现过程:

见下图,假设函数A调用该函数B,我们称函数A为“调用者”,B函数为"被调用者“则函数调用过程可以这么描述:
(1)先将调用者(A)的堆栈的基址(ebp)入栈,以保存之前任务的信息。

(2)然后调用者(A)的栈顶指针(esp)的值赋给ebp,作为新的基址(即被调用者B的栈底)。

(3)然后在这个基址(被调用者B的栈底)上开辟(一般用sub指令)相应的空间用作被调用者B的栈空间。

(4)函数B返回后,从当前栈帧的ebp即恢复为调用者A的栈顶(esp),使栈顶恢复函数B被调用前的位置;然后调用者A再从恢复后的栈顶可弹出之前的ebp值(可以)这么做是因为这个值在函数调用的前一步被压入堆栈)。这样,ebp和esp就都恢复了调用函数B前的位置,也就是恢复函数B 调用前的状态。




写的比较简单,在之后的深入学习后再视情况进行补充。

0 0
原创粉丝点击