一段程序,认识栈帧

一、认识栈帧

先来看一段神奇的代码:

(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编译的程序的栈帧存放规则不同)

时间: 2024-10-01 03:01:46

一段程序,认识栈帧的相关文章

Linux中程序的栈帧分析以及修改函数地址

下面有一段代码: #include <stdio.h> #include <unistd.h> #include <stdlib.h> void fun() {    printf("i am the evil func\n");    exit(1); } int fun1(int a,int b) {     int *p=&a;     p--;     *p=fun;     int c=0xcccc;     return c; }

程序运行 栈帧分析 以及 修改栈帧中数据以及函数地址

1 在栈帧中 修改函数调用地址 使得程序运行 跳转到 自己指定的函数 而原程序的作者完全不知道这段程序的执行中已经执行了别人的代码[可能是恶意的] 2 修改栈帧中的变量的值 不通过变量名 如 修改变量b的值 不通过变量b的名称 [这需要对变量在栈帧中的分布有一定的了解 ] 压栈是 a先压栈 b后压栈 压栈时 栈顶向低地址方向前进 变量a在变量b的上面 变量a的地址 0xbfa92d88 变量b的地址 0xbfa92d84

小程序的栈帧分析

函数调用另一个词语表示叫作过程.一个过程调用包括将数据和控制从代码的一部分传递到另一部分.另外,它还必须在进入时为过程的局部变量分配空间,并在推出时释放这些空间.而数据传递,局部变量的分配和释放通过操纵程序栈来实现. 栈帧也叫过程活动记录,是编译器用来实现过程函数调用的一种数据结构. 下面的一段代码,在main函数中并没有调用使虚拟机重启的函数,但是虚拟机为什么会重启? 这个原因就与栈帧有关,以下的图是对这个程序的解析 利用栈帧的知识,还可以不用b,但是可以改变b变量的值. 以下是结果:

C语言函数调用及栈帧结构

source:http://blog.csdn.net/qq_29403077/article/details/53205010 一.地址空间与物理内存 (1)地址空间与物理内存是两个完全不同的概念,真正的代码及数据都存在物理内存中. 物理储存器是指实际存在的具体储存器芯片,CPU在操纵物理储存器的时候都把他们当做内存来对待,把他们看成由若干个储存单元组成的逻辑储存器,这个逻辑储存器就是我们所说的地址空间. 地址空间大小与逻辑储存器大小不一定相等. (2)进程的地址空间分布 进程的地址空间包括:

程序分析(栈帧问题)

如下程序,在主函数中并未调用,但为何会重启? 在主函数中,调用了fun1的函数,而在fun1函数中,p--后,将栈帧上p地址上的内容修改成了fun 的地址,因此,程序运行到fun函数中. 2. 修改栈帧上变量的内容 (不通过变量名) 测试结果: 在fun函数中,b先压栈,a后压栈,变量b在变量a的上面.

栈帧 --- 程序运行中数据的更改问题

一.栈 栈作为一种特殊的数据结构而存在("后入先出"存储),是一种只能在一端进行插入和删除操作的特殊线性表. 大多数CPU上的程序实现使用栈来支持函数调用操作.栈用来传递函数参数.存储返回信息.临时保存寄存器原有值以用于回复以及存储局部数据. 栈有很多自己的特性,它具有记忆功能,对栈的插入与删除操作中,不需要改变栈底指针:而且栈是从高地址向低地址延伸的.每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息.因此栈作用就是用来保持栈帧的活动记录(即函数调用).

函数调用过程栈帧变化详解

http://www.cnblogs.com/fxplove/articles/2574451.html 数调用另一个词语表示叫作 过程.一个过程调用包括将数据和控制从代码的一部分传递到另一部分.另外,它还必须在进入时为过程的局部变量分配空间,并在推出时释放这些空间.而数据传递,局部变量的分配和释放通过操纵程序栈来实现.在了解本文章之前,您需要先对程序的进程空间有所了解,即对进程如何使用内存?如果你知道这些,下面的内容将是很easy的事情了.为了您的回顾还是将简单的分布图贴出来,便于您的回顾.

函数的调用过程(栈帧)

1.什么是栈帧? 栈帧也叫过程活动记录,是编译器用来实现函数调用过程的一种数据结构.C语言中,每个栈帧对应着一个未运行完的函数.从逻辑上讲,栈帧就是一个函数执行的环境:函数调用框架.函数参数.函数的局部变量.函数执行完后返回到哪里等等.栈是从高地址向低地址延伸的.每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息.寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址). 2.Add()函数的调用过程 我们以Add()函数为例深入的研究一

浅谈栈帧(一)

好久没有更新了,最近打算把近期所学的内容更新一下 今天说一说内存栈帧方面的吧=.= 关于栈帧:首先我们呢来了解一下它的基本概念. 1.堆栈:对于堆栈,其实就是我们程序进行执行,那么我们必须给它一块地盘,有了地基,才能够建筑出我们所需要的东西.没有地我们是无法去干任何事情的. 在计算机中,这个地盘其实就对于我们的内存空间.我们的程序其实就相当于施工队伍.我们所给出命令.然后对其进行指挥,完成我们布置的任务. (1)堆栈中有什么呢? 1.函数调用框架. 2.传递参数. 3.保存返回地址. 4.提供局