您的位置:首页 > 运维架构 > Linux

函数调用中的栈帧问题

2016-07-04 15:22 274 查看
首先我们先来看一段代码

#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的栈空间。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Linux 栈帧