为什么要使用堆栈?
    一个过程调用可以象跳转(jump)命令那样改变程序的控制流程, 但是与跳转不同的是, 当工作完成时,函数把控制权返回给调用之后的语句或指令。 这种高级抽象实现起来要靠堆栈的帮助。
    堆栈也用于给函数中使用的局部变量动态分配空间, 同样给函数传递参数和函数返回值也要用到堆栈。

堆栈区详解
    堆栈是一块保存数据的连续内存。 一个名为堆栈指针(SP)的寄存器指向堆栈的顶部。堆栈的底部在一个固定的地址。 堆栈的大小在运行时由内核动态地调整。

堆栈由逻辑堆栈帧组成。当调用函数时逻辑堆栈帧被压入栈中, 当函数返回时逻辑堆栈帧被从栈中弹出。 堆栈帧包括函数的参数, 函数地局部变量, 以及恢复前一个堆栈帧所需要的数据, 其中包括在函数调用时指令指针(IP)的值。

堆栈既可以向下增长(向内存低地址)也可以向上增长, 这依赖于具体的实现。在我们的例子中, 堆栈是向下增长的。堆栈指针(SP)也是依赖于具体实现的。它可以指向堆栈的最后地址,或者指向堆栈之后的下一个空闲可用地址。 在我们的讨论当中, SP指向堆栈的最后地址。

除了堆栈指针(SP指向堆栈顶部的的低地址)之外, 为了使用方便还有指向帧内固定地址的指针叫做帧指针(FP)。有些文章把它叫做局部基指针(LB-local base pointer)。从理论上来说, 局部变量可以用SP加偏移量来引用。 然而, 当有字被压栈和出栈后, 这些偏移量就变了。 尽管在某些情况下编译器能够跟踪栈中的字操作, 由此可以修正偏移量, 但是在某些情况下不能。而且在所有情况下, 要引入可观的管理开销。 而且在有些机器上, 比如Intel处理器, 由SP加偏移量访问一个变量需要多条指令才能实现。

因此, 许多编译器使用第二个寄存器, FP, 对于局部变量和函数参数都可以引用, 因为它们到FP的距离不会受到PUSH和POP操作的影响。 在Intel CPU中, BP(EBP)用于这个目的。 在Motorola CPU中, 除了A7(堆栈指针SP)之外的任何地址寄存器都可以做FP。考虑到我们堆栈的增长方向, 从FP的位置开始计算, 函数参数的偏移量是正值, 而局部变量的偏移量是负值。

当一个例程被调用时所必须做的第一件事是保存前一个FP(这样当例程退出时就可以恢复)。 然后它把SP复制到FP, 创建新的FP, 把SP向前移动为局部变量保留空间。 这称为例程的序幕(prolog)工作。当例程退出时, 堆栈必须被清除干净, 这称为例程的收尾(epilog)工作。 Intel的ENTER和LEAVE指令, Motorola的LINK和UNLINK指令, 都可以用于有效地序幕和收尾工作。
这里利用了一个简单的例子来做堆栈溢出示例。首先描述了该例子编
译后的内存分配情况,然后修改这个例子,使它成为一个典型的溢出程
序。分析溢出时的堆栈情况。

------------------------------------------------------------------

一个简单的堆栈例子
example1.c:
------------------------------------------------------------------
void function(int a, int b, int c) {
  char buffer1[5];
  char buffer2[10];
}

void main() {
  function(1,2,3);
}
------------------------------------------------------------------
    使用gcc的-S选项编译, 以产生汇编代码输出:
      $ gcc -S -o example1.s example1.c

通过查看汇编语言输出, 我们看到对function()的调用被翻译成:
        pushl $3
        pushl $2
        pushl $1
        call function
 
    以从后往前的顺序将function的三个参数压入栈中, 然后调用function()。 指令call会把指令指针(IP)也压入栈中。 我们把这被保存的IP称为返回地址(RET)。 在函数中所做的第一件事情是例程的序幕工作:
        pushl ëp
        movl %esp,ëp
        subl $20,%esp

将帧指针EBP压入栈中。 然后把当前的SP复制到EBP, 使其成为新的帧指针。 我们把这个被保存的FP叫做SFP。 接下来将SP的值减小, 为局部变量保留空间。

内存只能以字为单位寻址。 一个字是4个字节, 32位。 因此5字节的缓冲区会占用8个字节(2个字)的内存空间, 而10个字节的缓冲区会占用12个字节(3个字)的内存空间。 这就是为什么SP要减掉20的原因。 这样我们就可以想象function()被调用时堆栈的模样(每个空格代表一个字节):
内存低地址                                            内存高地址
          buffer2      buffer1  sfp  ret  a    b    c
<------  [            ][        ][    ][    ][    ][    ][    ]
堆栈顶部                                                堆栈底部

制造缓冲区溢出
    现在试着修改我们第一个例子, 让它可以覆盖返回地址, 而且使它可以执行任意代码。堆栈中在buffer1[]之前的是SFP, SFP之前是返回地址。 ret从buffer1[]的结尾算起是4个字节。应该记住的是buffer1[]实际上是2个字即8个字节长。 因此返回地址从buffer1[]的开头算起是12个字节。 我们会使用这种方法修改返回地址, 跳过函数调用后面的赋值语句'x=1;', 为了做到这一点我们把返回地址加上8个字节。 代码看起来是这样的:
example3。c:
--------------------------------------------------------------------
void function(int a, int b, int c) {
  char buffer1[5];
  char buffer2[10];
  int *ret;

ret = buffer1 + 12;
  (*ret) += 8;
}

void main() {
  int x;

x = 0;
  function(1,2,3);
  x = 1;
  printf("%d\n",x);
}
-------------------------------------------------------------------
    我们把buffer1[]的地址加上12, 所得的新地址是返回地址储存的地方。 我们想跳过赋值语句而直接执行printf调用。

如何知道应该给返回地址加8个字节呢? 我们先前使用过一个试验值(比如1), 编译该程序, 祭出工具gdb:

-----------------------------------------------------------------
[aleph1]$ gdb example3
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions。
There is absolutely no warranty for GDB; type "show warranty" for details。
GDB 4。15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
(no debugging symbols found)...
(gdb) disassemble main
Dump of assembler code for function main:
0x8000490 :      pushl  ëp
0x8000491 :    movl  %esp,ëp
0x8000493 :    subl  $0x4,%esp
0x8000496 :    movl  $0x0,0xfffffffc(ëp)
0x800049d :    pushl  $0x3
0x800049f :    pushl  $0x2
0x80004a1 :    pushl  $0x1
0x80004a3 :    call  0x8000470
0x80004a8 :    addl  $0xc,%esp
0x80004ab :    movl  $0x1,0xfffffffc(ëp)
0x80004b2 :    movl  0xfffffffc(ëp),êx
0x80004b5 :    pushl  êx
0x80004b6 :    pushl  $0x80004f8
0x80004bb :    call  0x8000378
0x80004c0 :    addl  $0x8,%esp
0x80004c3 :    movl  ëp,%esp
0x80004c5 :    popl  ëp
0x80004c6 :    ret
0x80004c7 :    nop
------------------------------------------------------------------

我们看到当调用function()时, RET会是0x8004a8, 我们希望跳过在0x80004ab的赋值指令。 下一个想要执行的指令在0x8004b2。 简单的计算告诉我们两个指令的距离为8字节。

为什么要使用堆栈? sp和fp的解释相关推荐

  1. StackWalker 堆栈打印

    主要用途: 1.打印异常堆栈 2.生成dump的时候可以同时生成堆栈日志,不一定调试dump C++输出函数调用堆栈 转:http://www.codeproject.com/Articles/111 ...

  2. 如何得知深睡眠等各类进程的函数堆栈 -- DW-SW等

    1.               Abstract . 2.               Introduction 我们开发的时候很痛苦的一件事情就是在进程进入深睡眠以后,没有调试手段,往往这种问题还 ...

  3. 转载_如何得知深睡眠等各类进程的函数堆栈 -- DW-SW等

    1.               Abstract . 2.               Introduction 我们开发的时候很痛苦的一件事情就是在进程进入深睡眠以后,没有调试手段,往往这种问题还 ...

  4. 51单片机中断的调用寄存器组(PSW)的作用,以及汇编堆栈的作用,堆栈指针的SP的使用方法,RAM的运行和ROM在单片机具体运行和C语言的优势和中断,定时器基础知识(上)

    一,堆栈在汇编的作用,以及PUSH ACC 和 PUSH PSW 通过最简单的8051单片机RAM的分配可知,单片机从烧程序到达到一定的工作过程.首先明白为什么要引如"烧"写的过程 ...

  5. 【转】sp,lr,pc即汇编语言中几个常见寄存器的使用

    arm汇编基础(转) 先看个例子: void test2(int a,int b,int c) { int k=a,j=b,m=c; } GCC反汇编: 00000064 <test2>: ...

  6. 函数调用栈空间以及fp寄存器

    Arm上函数调用的规则在ARM System Developer's Guide文档中的ATPCS部分有详细的定义,这里主要通过函数调用过程中函数栈的情况来说明fp和sp等寄存器的作用.有关ATPCS ...

  7. 单片机DPTR(DPH,DPL)和SP特殊寄存器C语言中应用?这三个特殊寄存器对C程序员来说是透明的,不用C程序员操作,编译的时候会自动运用这三个寄存器ACC寄存器和B寄存器也类似

    单片机DPTR(DPH,DPL)和SP特殊寄存器C语言中应用?这三个特殊寄存器对C程序员来说是透明的,不用C程序员操作,编译的时候会自动运用这三个寄存器,ACC寄存器和B寄存器也类似 编译器把C译成指 ...

  8. 提高C++性能的编程技术笔记:内联+测试代码

    内联类似于宏,在调用方法内部展开被调用方法,以此来代替方法的调用.一般来说表达内联意图的方式有两种:一种是在定义方法时添加内联保留字的前缀:另一种是在类的头部声明中定义方法. 虽然内联方法的调用方式和 ...

  9. linux中断系统那些事之----中断处理过程【转】

    转自:http://blog.csdn.net/xiaojsj111/article/details/14129661 以外部中断irq为例来说明,当外部硬件产生中断时,linux的处理过程.首先先说 ...

最新文章

  1. POJ1185:火炮(减少国家)
  2. kmp——next数组的应用---cout the string
  3. 【华为云专家原创】 服务注册与发现如何满足服务治理?
  4. Exchange 2013 、Lync 2013、SharePoint 2013
  5. python--综合小案例--文件读取以及梳理
  6. 设计模式系列--Singleton
  7. Python之面向对象进阶篇
  8. 故障解决:端口已被占用 1080
  9. java细节篇(==和equals的区别)
  10. 学习 stm32(TTL)串口通信控制16路舵机控制板(维特智能)
  11. 批量提取多个Excel文件内指定单元格的数据(文件名和数据)
  12. MSF-02-木马捆绑
  13. 序列模型与注意力机制总结
  14. 软件质量之道:PCLint之中的一个
  15. 图片验证码识别程序全面分析
  16. 黑码定制衬衫质量怎么样? 男装攻略-AI智能量体
  17. mangabz漫画网鬼灭之刃漫画爬虫
  18. 移动 电信 联通 APN cmwap cmnet ctwap ctnet 3gwap uniwap 3gnet uninet
  19. git push提交时提示与gitlab邮箱不一致
  20. 【C生万物】 从0开始学习C语言

热门文章

  1. 编译ExoPlayer扩展模块av1遇到的问题及解决
  2. python之WEB登录密码暴力破解
  3. Halcon实例分析——autobahn.hdev快速检测道路标志
  4. 我们的游戏世界(背包【仓库】,交易,任务,简单经济系统,装备)实现(基于仙剑demo聊聊游戏世界)第一篇谈谈交易
  5. php和java的区别菜鸟教程_浅谈Java和PHP的主要区别
  6. java日期格式 hh HH kk
  7. 数据库的查询优化方法
  8. 怎么自由裁剪图片大小?分享一款在线图片编辑工具
  9. 中创存储|想要一个好用的分布式存储云盘,到底该怎么选
  10. 五个热门的免费Linux视频软件下载