(windows下,代码如下)#include&l"/>

一段程序,认识栈帧

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一段程序,认识栈帧相关的知识,希望对你有一定的参考价值。

一、认识栈帧

先来看一段神奇的代码:

技术分享

 

(windows下,代码如下)

#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
void fun()
{
 printf("You Are Done\n");
 Sleep(2000);
 printf("Suppose The Computer Will Shut Down~~~~\n");  
 //上面这行如果换成system("reboot")之类的呢?
 Sleep(2000);
 exit(1);
}
int fun1(int a, int b)
{
 int *p = &a;
 p--;
 *p = fun;
 int c = 0xcccc;
 return c;
}
int main()
{
 printf("begin run...\n");
 int a = 0;
 int b = 1;
 fun1(a, b);
 printf("you should run here\n");
 return 0;
}

执行结果为:

    linux下:

技术分享

 

    win下:

技术分享

 

结果似乎都和我们预期的不一样啊

按理说,程序从main开始执行

中间调用fun1函数

调用完毕后应该继续执行下面的printf

然后输出:

    you should run here

 

而实际上,程序最终却进入了fun函数

之所以这样,是因为栈帧的缘故。

如果你对上面发生的事情感到好奇,可以接着往下看

 

二、原理解释

关于栈帧,百度百科是这样解释的:

C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。

 

也就是说,上面的代码,在内存方面可以这样理解:

技术分享

 

简单解释一下,

我们知道C语言中函数中定义的变量是在栈上开辟的,这张图片就表示栈内存,

其地址从上往下表示从大到小

 

main函数中,先后将a b 入栈,

然后调用fun1(a, b)

图片中的这个fun1() 其实不准确,它应该是 返回地址

这个 返回地址 就是表示:执行fun1(a, b)完毕后,应该返回到这个地方接着执行main函数中剩下的代码。

此外,在fun1() 和 b 之间,还应该存放一个东西:栈指针ebp(图上没有表示出来)

 

然后参数 a b 是局部变量,也分别入栈,

借助调试工具,可以看到,a b 的地址分别为图中所示

然后定义一个指针p指向a

接下来p--,这时p指向的地址为0x0018fc20,也就是 刚刚说的 返回地址

 

这时候,应该能发现,返回地址已经变了, 变成了fun的地址

也就是说,当执行完fun1()后,程序并不会返回到 main函数中调用它的地方,而是接着调用fun函数

技术分享

 

这就导致程序不可思议地进入了我们没有预想到的地方,调用了我们本不想调用的函数,

而且由于这个返回地址的丢失,在调用完毕fun后,程序也会因为找不到返回地址而挂掉。

(我在代码中执行了  exit(1);  这句话强行终止了程序)

 

以上就是代码的原理解释了。

接下来,利用刚刚所get到的栈帧方面的知识,可以做一个事情:

 

 

三、修改b的值

要求:不要直接修改a、b变量,而通过栈帧,实现修改a、b变量的值

 

代码:

void fun1(int a, int b)
{
 int *p = &a;
 p -= 2;
 int ReturnAddr = *p;   //返回值
 //修改main中的a
 p = ReturnAddr - 4 * 2;
 *p = 11111;
 //修改main中的b
 p = ReturnAddr - 4 * 5;
 *p = 12345;
}
int main()
{
 printf("begin run...\n");
 int a = 0;
 int b = 1;
 fun1(a, b);
 printf("you should run here\n");
 printf("%d\n", a);
 printf("%d\n", b);
 return 0;
}

之前画的内存图比较简单,要想实现这个要求,必须进一步了解栈帧是怎么存放的了,下面是比较详细的栈内存图:

技术分享

 

 

linux代码:

技术分享

运行结果

技术分享

(gcc 和 vs编译的程序的栈帧存放规则不同)

 

以上是关于一段程序,认识栈帧的主要内容,如果未能解决你的问题,请参考以下文章

[Android Pro] 深入理解函数的调用过程——栈帧

函数调用过程原理及栈帧分析

JVM之一:操作数栈和局部变量表

JVM之一:操作数栈和局部变量表

JVM之一:操作数栈和局部变量表

Java虚拟机栈和内存模型