2015-03-05 23:21:14 aganlengzi 阅读数 1083
  • Linux C语言编程基础视频精讲

    本课程讲解Linux编程下C语言的基础编程知识,包括环境构建,Linux shell命令 ,Linux VI编辑器,Linux GCC编译器使用,C语言保留字,变量,表达式、语句、函数、程序的结构、数据结构与算法、链表与栈。

    17668 人正在学习 去看看 沈寒

主要理解了Linux进程的栈和函数的栈帧的区别和联系


上图中刻画的是一个Linux进程的结构图,其中本文想要关注的栈如上图所示是栈底在0xc0000000地址递减的一块进程内存区域
Linux系统中为每个进程分配的实际大小未做深究
Linux进程的栈的整体认知就是这么多,即
1)栈底在0xc0000000(并不需要栈底指针,函数的栈帧才需要栈底指针)
2)栈的增长方向,地址递减方向
3)栈顶指针esp
4)大小未知(不是本文关注的重点)
5)里面存放的内容是什么呢?

进程的栈中存放的是函数调用的栈帧

即另一个概念是上文中提到的函数的栈帧的概念及其与Linux进程栈的关系
函数的栈帧就在进程中的栈的结构和分配的地址空间及实际内存中
函数的栈帧整体的理解是:
1)ebp是函数栈帧的栈底
2)esp是栈顶(一个函数的活动记录就在ebp(高地址)和esp(低地址)之间)
3)一个进程的所有的函数的栈帧在调用此函数的时候建立,返回调用者后,被调函数的栈帧不再存在
4)所有的函数的栈帧的建立和消失是随着进程的执行而变化的一个动态过程
5)栈帧中存放的内容是什么呢?

栈帧中存放的内容一般包括以下几个方面的内容:
1)函数的返回地址和参数
2)临时变量:包括函数的非静态局部变量和编译器自动生成的其他变量
3)保存的上下文:包括在函数调用前后需要保证不变的寄存器值
进程中的函数在进程提供的栈中要解决的是调用者和被调者的关系
1)如何从调用者进入被调者?call
2)如何从被调者返回调用者?ret
3)调用者如何支配被调者(被调函数参数传递)?参数
4)被调者的价值如何体现(返回值的传递)?寄存器eax、edx

其中还有两个问题值得去看
1)被调函数返回值的传递方式
2)函数的调用惯例

2016-05-11 16:51:37 xy010902100449 阅读数 6131
  • Linux C语言编程基础视频精讲

    本课程讲解Linux编程下C语言的基础编程知识,包括环境构建,Linux shell命令 ,Linux VI编辑器,Linux GCC编译器使用,C语言保留字,变量,表达式、语句、函数、程序的结构、数据结构与算法、链表与栈。

    17668 人正在学习 去看看 沈寒

1、关于栈

对于程序,编译器会对其分配一段内存,在逻辑上可以分为代码段,数据段,堆,栈

  • 代码段:保存程序文本,指令指针EIP就是指向代码段,可读可执行不可写
  • 数据段:保存初始化的全局变量和静态变量,可读可写不可执行
  • BSS:未初始化的全局变量和静态变量
  • 堆(Heap):动态分配内存,向地址增大的方向增长,可读可写可执行
  • 栈(Stack):存放局部变量,函数参数,当前状态,函数调用信息等,向地址减小的方向增长,非常非常重要,可读可写可执行。如下图所示:

这里写图片描述

首先必须明确一点也是非常重要的一点,栈是向下生长的,所谓向下生长是指从内存高地址->低地址的路径延伸,那么就很明显了,栈有栈底和栈顶,那么栈顶的地址要比栈底低。对x86体系的CPU而言,其中
—> 寄存器ebp(base pointer )可称为“帧指针”或“基址指针”,其实语意是相同的。
—> 寄存器esp(stack pointer)可称为“ 栈指针”。
要知道的是:
—> ebp 在未受改变之前始终指向栈帧的开始,也就是栈底,所以ebp的用途是在堆栈中寻址用的。
—> esp是会随着数据的入栈和出栈移动的,也就是说,esp始终指向栈顶。
见下图,假设函数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调用前的状态。
这个过程在AT&T汇编中通过两条指令完成,即:
leave
ret
这两条指令更直白点就相当于:
mov %ebp , %esp
pop %ebp
这里写图片描述

2、简单实例

开发测试环境:
Linux ubuntu 3.11.0-12-generic
gcc版本:gcc version 4.8.1 (Ubuntu/Linaro 4.8.1-10ubuntu8)
下面我们用一段代码说明上述过程:

int bar(int c, int d)
{
    int e = c + d;
    return e;
}

int foo(int a, int b)
{
    return bar(a, b);
}

int main(void)
{
    foo(2, 3);
    return 0;
}

gcc -g Code.c ,加上-g,那么用objdump -S a.out 反汇编时可以把C代码和汇编代码穿插起来显示,这样C代码和汇编代码的对应关系看得更清楚。反汇编的结果很长,以下只列出我们关心的部分。

080483ed <bar>:
int bar(int c, int d)
{
 80483ed:   55                      push   %ebp
 80483ee:   89 e5                   mov    %esp,%ebp
 80483f0:   83 ec 10                sub    $0x10,%esp
    int e = c + d;
 80483f3:   8b 45 0c                mov    0xc(%ebp),%eax
 80483f6:   8b 55 08                mov    0x8(%ebp),%edx
 80483f9:   01 d0                   add    %edx,%eax
 80483fb:   89 45 fc                mov    %eax,-0x4(%ebp)
    return e;
 80483fe:   8b 45 fc                mov    -0x4(%ebp),%eax
}
 8048401:   c9                      leave  
 8048402:   c3                      ret    

08048403 <foo>:

int foo(int a, int b)
{
 8048403:   55                      push   %ebp
 8048404:   89 e5                   mov    %esp,%ebp
 8048406:   83 ec 08                sub    $0x8,%esp
    return bar(a, b);
 8048409:   8b 45 0c                mov    0xc(%ebp),%eax
 804840c:   89 44 24 04             mov    %eax,0x4(%esp)
 8048410:   8b 45 08                mov    0x8(%ebp),%eax
 8048413:   89 04 24                mov    %eax,(%esp)
 8048416:   e8 d2 ff ff ff          call   80483ed <bar>
}
 804841b:   c9                      leave  
 804841c:   c3                      ret    

0804841d <main>:

int main(void)
{
 804841d:   55                      push   %ebp
 804841e:   89 e5                   mov    %esp,%ebp
 8048420:   83 ec 08                sub    $0x8,%esp
    foo(2, 3);
 8048423:   c7 44 24 04 03 00 00    movl   $0x3,0x4(%esp)
 804842a:   00 
 804842b:   c7 04 24 02 00 00 00    movl   $0x2,(%esp)
 8048432:   e8 cc ff ff ff          call   8048403 <foo>
    return 0;
 8048437:   b8 00 00 00 00          mov    $0x0,%eax
}
 804843c:   c9                      leave  
 804843d:   c3                      ret    

整个程序的执行过程是main调用foo,foo调用bar,我们用gdb跟踪程序的执行,直到bar函数中的int e = c + d;语句执行完毕准备返回时,这时在gdb中打印函数栈帧,因为此时栈已经生长到最大。

ZP1015@ubuntu:~/Desktop/c/Machine_Code$ gdb a.out 
GNU gdb (GDB) 7.6.1-ubuntu
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/ZP1015/Desktop/c/Machine_Code/a.out...done.
(gdb) start
Temporary breakpoint 1 at 0x8048423: file Code.c, line 14.
Starting program: /home/ZP1015/Desktop/c/Machine_Code/a.out 

Temporary breakpoint 1, main () at Code.c:14
14      foo(2, 3);
(gdb) s
foo (a=2, b=3) at Code.c:9
9       return bar(a, b);
(gdb) s
bar (c=2, d=3) at Code.c:3
3       int e = c + d;
(gdb) disas
Dump of assembler code for function bar:
   0x080483ed <+0>: push   %ebp
   0x080483ee <+1>: mov    %esp,%ebp
   0x080483f0 <+3>: sub    $0x10,%esp
=> 0x080483f3 <+6>: mov    0xc(%ebp),%eax
   0x080483f6 <+9>: mov    0x8(%ebp),%edx
   0x080483f9 <+12>:    add    %edx,%eax
   0x080483fb <+14>:    mov    %eax,-0x4(%ebp)
   0x080483fe <+17>:    mov    -0x4(%ebp),%eax
   0x08048401 <+20>:    leave  
   0x08048402 <+21>:    ret    
End of assembler dump.
(gdb) si
0x080483f6  3       int e = c + d;
(gdb) 
0x080483f9  3       int e = c + d;
(gdb) 
0x080483fb  3       int e = c + d;
(gdb) 
4       return e;
(gdb) 
5   }
(gdb) bt
#0  bar (c=2, d=3) at Code.c:5
#1  0x0804841b in foo (a=2, b=3) at Code.c:9
#2  0x08048437 in main () at Code.c:14
(gdb) info registers
eax            0x5  5
ecx            0xbffff724   -1073744092
edx            0x2  2
ebx            0xb7fc4000   -1208205312
esp            0xbffff658   0xbffff658
ebp            0xbffff668   0xbffff668
esi            0x0  0
edi            0x0  0
eip            0x8048401    0x8048401 <bar+20>
eflags         0x206    [ PF IF ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0  0
gs             0x33 51
(gdb) x/20x $esp
0xbffff658: 0x0804a000  0x08048492  0x00000001  0x00000005
0xbffff668: 0xbffff678  0x0804841b  0x00000002  0x00000003
0xbffff678: 0xbffff688  0x08048437  0x00000002  0x00000003
0xbffff688: 0x00000000  0xb7e2d905  0x00000001  0xbffff724
0xbffff698: 0xbffff72c  0xb7fff000  0x0000002a  0x00000000
(gdb) 

下面从主函数开始,一步一步分析函数调用过程:

0804841d <main>:

int main(void)
{
 804841d:   55                      push   %ebp
 804841e:   89 e5                   mov    %esp,%ebp
 8048420:   83 ec 08                sub    $0x8,%esp
    foo(2, 3);
 8048423:   c7 44 24 04 03 00 00    movl   $0x3,0x4(%esp)
 804842a:   00 
 804842b:   c7 04 24 02 00 00 00    movl   $0x2,(%esp)
 8048432:   e8 cc ff ff ff          call   8048403 <foo>

要调用函数foo先要把参数准备好,第二个参数保存在esp+4指向的内存位置,第一个参数保存在esp指向的内存位置,可见参数是从右向左依次压栈的。然后执行call指令,这个指令有两个作用:

  1. foo函数调用完之后要返回到call的下一条指令继续执行,所以把call的下一条指令的地址0x8048437压栈,同时把esp的值减4
  2. 修改程序计数器eip,跳转到foo函数的开头执行。
int foo(int a, int b)
{
 8048403:   55                      push   %ebp
 8048404:   89 e5                   mov    %esp,%ebp
 8048406:   83 ec 08                sub    $0x8,%esp
    return bar(a, b);
 8048409:   8b 45 0c                mov    0xc(%ebp),%eax
 804840c:   89 44 24 04             mov    %eax,0x4(%esp)
 8048410:   8b 45 08                mov    0x8(%ebp),%eax
 8048413:   89 04 24                mov    %eax,(%esp)
 8048416:   e8 d2 ff ff ff          call   80483ed <bar>

push %ebp指令把ebp寄存器的值压栈,同时把esp的值减4。这两条指令合起来是把原来ebp的值保存在栈上,然后又给ebp赋了新值。在每个函数的栈帧中,ebp指向栈底,而esp指向栈顶,在函数执行过程中esp随着压栈和出栈操作随时变化,而ebp是不动的,函数的参数和局部变量都是通过ebp的值加上一个偏移量来访问,例如foo函数的参数a和b分别通过ebp+8和ebp+12来访问。所以下面的指令把参数a和b再次压栈,为调用bar函数做准备,然后把返回地址压栈,调用bar函数:

080483ed <bar>:
int bar(int c, int d)
{
 80483ed:   55                      push   %ebp  
 80483ee:   89 e5                   mov    %esp,%ebp
 80483f0:   83 ec 10                sub    $0x10,%esp
    int e = c + d;
 80483f3:   8b 45 0c                mov    0xc(%ebp),%eax
 80483f6:   8b 55 08                mov    0x8(%ebp),%edx
 80483f9:   01 d0                   add    %edx,%eax
 80483fb:   89 45 fc                mov    %eax,-0x4(%ebp)
    return e;
 80483fe:   8b 45 fc                mov    -0x4(%ebp),%eax
}
 8048401:   c9                      leave  
 8048402:   c3                      ret    

08048403 <foo>:

这次又把foo函数的ebp压栈保存,然后给ebp赋了新值,指向bar函数栈帧的栈底,通过ebp+8和ebp+12分别可以访问参数c和d。bar函数还有一个局部变量e,可以通过ebp-4来访问。所以后面几条指令的意思是把参数c和d取出来存在寄存器中做加法,计算结果保存在eax寄存器中,再把eax寄存器存回局部变量e的内存单元。bar函数有一个int型的返回值,这个返回值是通过eax寄存器传递的,所以首先把e的值读到eax寄存器中,然后执行leave指令,最后是ret指令。

在gdb中可以用bt命令和frame命令查看每层栈帧上的参数和局部变量,现在可以解释它的工作原理了:如果我当前在bar函数中,我可以通过ebp找到bar函数的参数和局部变量,也可以找到foo函数的ebp保存在栈上的值,有了foo函数的ebp,又可以找到它的参数和局部变量,也可以找到main函数的ebp保存在栈上的值,因此各层函数栈帧通过保存在栈上的ebp的值串起来了。

地址0x804841b处是foo函数的返回指令:

}
 804841b:   c9                      leave  
 804841c:   c3                      ret    

重复同样的过程,又返回到了main函数。

    return 0;
 8048437:   b8 00 00 00 00          mov    $0x0,%eax
}
 804843c:   c9                      leave  
 804843d:   c3                      ret    

整个函数执行完毕。

函数调用和返回过程中的需要注意这些规则:

  1. 参数压栈传递,并且是从右向左依次压栈。
  2. ebp总是指向当前栈帧的栈底。
  3. 返回值通过eax寄存器传递。

局部变量申请栈空间时的入栈顺序
**在没有溢出保护机制下的编译时,我们可以发现,所有的局部变量入栈的顺序(准确来说是系统为局部变量申请内存中栈空间的顺序)是正向的,即哪个变量先申明哪个变量就先得到空间,
也就是说,编译器给变量空间的申请是直接按照变量申请顺序执行的。
在有溢出保护机制下的编译时,情况有了顺序上的变化,对于每一种类型的变量来说,栈空间申请的顺序都与源代码中相反,即哪个变量在源代码中先出现则后申请空间;而对不同的变量来说,申请的顺序也不同,有例子可以看出,int型总是在char的buf型之后申请,不管源代码中的顺序如何(这应该来源于编译器在进行溢出保护时设下的规定)。**

推荐博文:
c函数调用过程原理及函数栈帧分析

2016-08-27 00:20:57 holyshit666 阅读数 508
  • Linux C语言编程基础视频精讲

    本课程讲解Linux编程下C语言的基础编程知识,包括环境构建,Linux shell命令 ,Linux VI编辑器,Linux GCC编译器使用,C语言保留字,变量,表达式、语句、函数、程序的结构、数据结构与算法、链表与栈。

    17668 人正在学习 去看看 沈寒

栈帧和指针式c语言的精髓。栈帧是一种特殊的数据结构,栈帧用来保存当前函数的父一级函数的栈底指针,当前函数的局部变量以及被调用函数返回后下一条汇编指令的地址,栈帧位于栈内存中,从逻辑上讲,栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等等。首先应该明白,栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。

以以下程序为例,分析c程序栈帧的创建:

#include <stdio.h>
#include <stdlib.h>
void bug()
{
printf("haha,i am a bug!!!\n");
Sleep(3);
system("reboot");
exit(1);
}
int fun(int a,int b)
{
int* p=&a;
p--;
*p=bug;
printf("fun is called\n");
return a+b;
}
int main()
{
int a=0xaaaa;
int b=0xbbbb;
int c=fun(a,b);
printf("you should run here,%d\n",c);
return 0;
}

主函数的汇编代码:


fun函数的汇编代码:


bug函数的汇编代码:


对整个程序进行调试并进行分析:



从汇编代码可以看出:0x80484ba是fun函数的开始处,与main函数中的call对应。进入fun函数段之后,是fun函数压栈的动作,基本顺序和main函数的压栈过程一致。

在linux系统出现死机或异常重启情况时,我们通常会获取死机时的backtrace信息,即函数调用顺序和函数入参,这样就可以精准的定位到导致死机的代码段进一步分析。


2018-01-28 12:39:18 abc_670 阅读数 425
  • Linux C语言编程基础视频精讲

    本课程讲解Linux编程下C语言的基础编程知识,包括环境构建,Linux shell命令 ,Linux VI编辑器,Linux GCC编译器使用,C语言保留字,变量,表达式、语句、函数、程序的结构、数据结构与算法、链表与栈。

    17668 人正在学习 去看看 沈寒

栈帧(stack frame),机器用栈来传递过程参数,存储返回信息,保存寄存器用于以后恢复,以及本地存储为单个过程(函数调用)分配的那部分栈称为栈帧。栈帧其实 是两个指针寄存器,寄存器ebp为帧指针(指向该栈帧的最底部),而寄存器esp为栈指针(指向该栈帧的最顶部),当程序运行时,栈指针可以移动(大多数的信息的访问都是通过帧指针的,换句话说,就是如果该栈存在,ebp帧指针是不移动的,访问栈里面的元素可以以ebp为基准访问ebp指针下面或者上面的元素)。

概括起来就是,栈帧的主要作用是用来控制和保存一个过程的所有信息的。栈帧结构如下所示:


(图片选自互联网)

栈是从高地址向低地址存储。所以越是低的地址,越是靠后入栈。


栈帧对应的汇编代码如下:


下面对一个简单的加法函数调用的程序进行反编译来分析函数栈帧,程序代码如下:

#include <stdio.h>
int Add(int x, int y)
{
 int a = x;
 int b = y;
 return a+b;
}
int main()
{
 int a = 10;
 int b = 20;
 int ret = Add(a, b) ;
 printf("%d\n", ret) ;
 return 0;
}


反编译的代码如下:(本来想放汇编源代码,结果总是排版出问题,无语了)

main()函数如下:





add()函数如下:





栈帧指针绘图分析:

图片转载自http://www.cnblogs.com/33debug/p/6773059.html



开始执行main()函数的状态:



执行了mov ebp,esp之后栈帧指针的变化


可见在19FF40处保留着19FF80,它是初始EBP的值


main()中的赋值语句如下:



执行完赋值语句后ESP的值如下;


ESP的值增加了,而EBP始终作为当前函数操作的基准


add()函数调用参数如下:



因为栈的特性,所以是逆序压入栈,参数入栈后再call add()函数,再看一下堆栈的情况:


确实是逆序的,不过不知道为什么要弄这么多空间


执行完add()的mov ebp,esp之后,栈帧变化如下:


1号箭头指向的是刚压入栈的参数,2号箭头指向的是原EBP的值,因为EBP现在指向被调用函数的栈帧底部,所以基准发生变化,可以参考上一张图片比较


add函数内加法运算,先把传入参数的值赋给eax和ecx,在把eax和ecx的值传给函数内的临时变量local.1和local.2,再相加



add()函数运行完后执行恢复栈帧的语句,执行完后栈帧变化如下:


首先EBP的值变回了原来的值,而且EBP指向最开始的EBP的值,所以在调用函数结束后栈帧会恢复到原来的状态


执行完add()函数后,esp加8,所以add()函数内的局部变量不可用了,因为栈帧无法到达参数所在的地址

接着就是把结果压入栈,然后调用printf()函数,这个过程跟调用add()函数大同小异,所以不展开介绍了


调用完printf()函数后,esp加8,所以函数内的局部变量不可用了,接着esp又加0x4c,栈帧移动,此时整个main()函数内的变量都失效了,所以一开始移动这么多是考虑整个main()可能需要声明使用很多变量


最后main()结束,栈帧恢复到最开始的状态


不过也可以看到,EBP还指向一个19FF94,所以说还是可以继续分析下去的,但是今天就到这里了。


不过这里有个细节,刚才提到了ESP加8,相当于删除被调用函数内的局部变量,但其实函数执行完后,是不用管栈中的参数的,由于只是临时使用存储在栈中的值,下一次在存入其他的值自然会覆盖掉原有值,而且栈内存是固定的,所以既不能也没有必要释放内存。

除此之外,还有个细节,ESP加8是在main()中执行的,其实也是可以在被调用函数中执行,这就是另一个知识点了,函数调用约定里面会对这些加以规定。

函数调用约定(calling convention):对函数调用时如何传递参数的一种约定。

通过前面对函数栈帧的分析,我们知道调用函数前要先把参数压入栈传递给函数。栈内存是固定的,ESP用来指示栈的当前位置,函数调用约定就是解决函数调用后如何处理ESP。主要的函数调用约定有:

  1. cdecl
  2. stdcall
  3. fastcall
一.cdecl

1、采用桟传递参数,参数从右向左依次入栈;

2、由调用者负责恢复栈顶指针;

3、在函数名前加上一个下划线前缀,格式为_function;

要注意的是,调用参数个数可变的函数只能采用这种方式(如printf)。

上面演示的程序就属于cdcel


二.stdcall

1)采用桟传递全部参数,参数从右向左压入栈;
2)
被调用函数负责恢复栈顶指针 ;
3)   函数名自动加前导的下划线,后面是函数名,之后紧跟一个@符号,其后紧跟着参数的尺寸,例如_function@4;




因此,stdcall与cdecl的区别就是谁负责恢复栈顶指针和编译后函数的名称问题。




三.fastcall

采用fasecall的函数声明方式为:
int __fastcall function(int a,int b)
fastcall调用约定意味着:
1、函数的第一个和第二个(从左向右)32字节参数(或者尺寸更小的)通过ecx和edx传递,其他参数通过桟传递。从第三个参数(如果有的话)开始从右向左的顺序压栈;
2、被调用函数恢复栈顶指针;
3、在函数名之前加上"@",在函数名后面也加上“@”和参数字节数,例如@function@8;


这次对函数堆栈及函数调用的学习加深了我对函数的底层认识,也增强了用OD调试的能力


学完之后再看这幅图,真的是感触颇多!!!


2016-06-28 03:16:00 dengnv0445 阅读数 5
  • Linux C语言编程基础视频精讲

    本课程讲解Linux编程下C语言的基础编程知识,包括环境构建,Linux shell命令 ,Linux VI编辑器,Linux GCC编译器使用,C语言保留字,变量,表达式、语句、函数、程序的结构、数据结构与算法、链表与栈。

    17668 人正在学习 去看看 沈寒

  栈帧(stack frame),机器用栈来传递过程参数,存储返回信息,保存寄存器用于以后恢复,以及本地存储。为单个过程(函数调用)分配的那部分栈称为栈帧。栈帧其实是两个指针寄存器,

寄存器%ebp为帧指针,而寄存器%esp为栈指针,当程序运行时,栈指针可以移动(大多数的信息的访问都是通过帧指针的)。总之简单一句话,栈帧的主要作用是用来控制和保存一个过程的

所有信息的。栈帧结构如下所示:

  下面,我们通过一个简单的程序来了解栈帧:

  简单的函数分析,如下图:

  该函数的栈帧情况

当*p=bug,修改栈帧中保存返回值的位置,使得函数不是正常返回,而是进入bug函数,当遇到exit函数,程序终止,不在跳回到main函数中终止。

转载于:https://www.cnblogs.com/hanxiaoyu/p/5622069.html

没有更多推荐了,返回首页