第4篇:戏说程序栈-栈帧

本篇详细讲解有关IA32约定中的程序栈帧,我栈顶到栈底的方向逐一回顾一下。


sss5.png

当前栈帧从栈顶栈底如下构成

  • 创建的参数表为要调用的函数建立的参数
  • 局部变量:即在函数内部声明的变量(如果有)
  • 保存的寄存器的上下文(如果有),当被调用函数返回之前,为调用者函数恢复原来的寄存器中的数据,如果当前架构有足够的空闲物理寄存器可供使用,一些寄存器信息可能不需要压入栈。
  • 旧的帧指针,即前一栈帧的ebp指针*,它指向前一帧的栈底。

调用者函数的栈帧

  • 参数列表
  • 本地变量(如果调用者函数内部有声明局部变量)
  • 返回地址:我们知道调用者函数内部的执行到call指令时会将call指令所在行的下一条指令的内存地址压入栈,当被调用者函数内部执行到ret指令后,从栈中弹出返回地址,以便调用者函数回到该地址对应的指令继续执行余下的指令。

具体例子

int add(int x,int y){
      return x+y;
}
int main(void){
    int s=11;
    int b=23;
    b=b+s;
    return 0;
}

上面的汇编代码你会发现有很多以.cfi_为前缀的指令,这些称为cfi指令集,主要用于C/C++调试器执行并获取程序栈帧的状态信息,跟生成的汇编代码没任何关系,编译后的可执行程序也不会执行它们,所以不要理会它们。如果你非得转牛角尖(浪费时间),可以参考如下链接。如果你想生成干净的汇编代码的话,可以在编译的时候,加上 -fno-asynchronous-unwind-tables这个选项,在这个示例中

gcc -S hello.c -o hello.s -fno-asynchronous-unwind-tables
干净的汇编代码

我们看到上图,下买你我们会逐步用图例来讲解。

  • 设定代码”就是每个函数的汇编版本初始化栈帧中通用初始化的操作,下面会用详细的图例进行讲解
  • 主体代码”是业务代码对应的汇编版本,即一系列局部变量初始化和call指令调用其他函数等。
  • 标号3的位置是“完成代码”就是被调用函数返回值的处理和清理当前函数栈帧占用的内存。

反编译示例

其实我们可以进一步都我们的代码进一步反编译可以更加彻底了解内部的操作,通过如下指令

 gcc -m32 -g hello.s -o hello.out;
 objdump -d ./hello.out > hello.dmp;

我们得到很多信息,首先我们搜索关键字main函数,我们会发现main函数的内存地址是0x80483ea,如图所示。


再进一步,通过80483ea这个关键字,我们进一步搜索,发现几个关键的信息:

  • main函数对应的内存地址落在<_start>这个代码段,而<_start>这个标签是整个汇编程序的全局入口,这类似于C程序的main函数。
  • 我们还发现在<_start>内部main的函数地址0x80483ea也被压入栈。如下图所示。
  • <_start>标签的内存地址是0x80482e0,这个地址可以作为我们进一步顺藤摸瓜的的条件。
    2019-10-16 13-03-03屏幕截图.png

再次,当你尝试通过_start的地址去查找,基本上找不出什么东西了。但有一样东西可以肯定的是_start调用了__blibc_start_main等相关底层库函数用于支持我们main函数的执行。因为我们这里讨论的是跟用户定义的函数相关的话题,C底层的函数不是我们考虑的问题。

main函数的栈帧

  • push %esp指令是用于前面一帧的ebp指针地址压入栈,与此同时%esp会从原来前一帧的位置向低地址下移始终指向最新的栈顶(暗含的指令subl 1,%esp)
  • push %esp实际上就是当前栈帧对前一帧状态的一个备份操作。以便能够在当前栈还原前一帧的ebp指针。
  • mov %esp,%ebp将%esp的值覆盖%ebp的值,即当前esp指针指向的位置就是当面main函数的栈底。
  • sub $0x18,%esp,对esp指针中的地址值作减法操作esp下移动24个字节,这实质上已经改变esp指针中的地址值,这样做的目的是为我们接下来main内部的局部变量和要调用的被调用函数“add”需要用到的参数列表分配所需的栈空间
  • movl $0xb,-4(%ebp)movl $0x17,-4(%ebp)分别将变量值11和23写入main栈内已分配的空间,这两条指令通过对ebp相对寻址的方式找到局部变量的栈内存位置,相对寻址并没有改变%ebp中的地址值。
    分配局部变量
  • movl -4(%ebp),%eax指令就是将第二个局部变量缓存到eax寄存器中。
    创建参数列表
  • movl %eax,0x4(%esp)指令将刚才eax缓存的变量值分配到栈的参数域。
  • 接着下面的两条指令跟上面的两条都是做相同的事情,寄存器只是一个变量值的一个中转站。


  • 到这里我们在main栈帧中参数区的参数表和局部变量中的顺序是相反的。
  • 之前main栈帧在设置代码的阶段在分配了24个字节的空间,但实际上目前我们用到只有了16个字节,这种情况是非常常见的。但许多的文章根本就没向读取明确说明这一点。另外一种比较复杂的情况,如果入栈的是一个用户自定义的数据类型的struct,可能会和其他基本数据类似进行内存对齐,出去空的字节块的情况是非常频繁的。
  • call 80483dd就是执行add函数的地址,该地址位于80483dd的位置。如果你有看前文,call指令等价如下几个以下几条指令操作
    • call指令的下一条指令的地址会作为返回地址入栈(即:push 8048410)。
    • esp指针指向栈顶(即subl $1,%esp)。
    • 最后隐式执行jmp 80483dd这条指令 ,此时将从main函数的上下文跳转到被调用函数add的上下文。
返回地址入栈

add函数的栈帧

当main执行call指令后,跳转到add函数的上下文,如下图


add函数的上下文
  • 这里关于add函数的设定代码部分,同理跟之前main函数的设定代码部分的分析没多大出入。

  • mov 0xc(%ebp),%eaxmov 0x8(%ebp),%eax分别从上一帧main的栈帧获取参数23和参数11,分别加载到寄存器eax和寄存器edx,以便下一步计算使用,如下图所示。
  • add %edx,%eax指令和刚才寄存器edx和寄存器eax的参数值副本执行加法运算,计算后的结果将保存到eax寄存器

  • pop %ebp指令其实弹出上一栈main栈帧的ebp地址告知CPU让寄存器ebp将(注意我用的字眼,表示还没有执行完成的状态)重新指向main栈帧的栈底(即:图中我假设的地址0xff71b),实质上pop等价如下几条指令的操作。
    1. mov -(%esp),%esp :esp将指向0xff700即上一帧main的返回地址所在帧的内存地址。
      mov -(%esp),%esp
  1. mov $0xff71b,%ebp :被弹出的0xff71b的地址值会覆盖ebp目前的内容,此时ebp会指向如图中的0xff71b
    mov $0xff71b,%ebp执行时的状态
  • ret指令主要隐式地执行两条指令
    • pop 0x804841从栈中弹出这个返回地址
    • mov 0x804841,%eip这条指令会将返回地址覆盖eip寄存器中的值,在没有语句之时,但尚未转移到返回地址的瞬间状态如下图。
      ret指令

按照惯例,被调用者函数的返回值会放在eax寄存器中,eax的选择是相当随意的,可能是%ecx或%edx等,具体根据不同的C/C++编译器的实现而定。

被调用者函数在执行ret指令时,会将(计算过)的适合4个字节的任意类型的返回值保存到(通常是%eax)寄存器,也可能是其他寄存器, x86环境中的eax寄存器只有4个字节。如果要返回大于4个字节的数据类型,最好的方式是返回一个自定义类型的对象的指针,而不是对象本身。

  • 返回时,调用者函数在%eax寄存器(也可能是其他寄存器)中找到返回值。如下图,我们的main函数栈帧,会将eax寄存器的返回值保存到便来那个域当中。
  • 当然,main函数也会存在返回值的情况,下面同样的情况,main函数在执行ret指令之前也将返回值0,写入的寄存器eax。
    main的返回值也被加载到eax寄存器,等待返回给更上一帧的C/C++库函数
  • 最后要说的指令就是leave指令,它隐式等价执行如下语句,他们用于清理main栈帧中的所有局部变量和参数等占用的栈空间。
    • mov %ebp,%esp
    • pop %ebp
      leave指令的执行

后记

本篇的解决了前一篇提出的许多问题点,并以详实的例子覆盖了栈的大部分话题。目前没有具体提及到寄存器保存约定的细节,因为用到例子的计算就两个操作数字的加法运算本来就不需要栈将寄存器的状态数据进行入栈操作。而实际上,在高层语言设计复杂的算法的时候当转译成汇编的代码不外乎乘除加减,位运算等一系列基础的运算操作以及大量的加载和存储的指令集(move),那么必然涉及到被调用函数在call 被调用函数之前,需要寄存器作为操作数参与一系列的运算和存储计算的中间结果,但CPU中的物理寄存器数量是有限的资源,并且同一个寄存器也可能被其他线程中的函数向其写入数据,那么之前计算的计算结果必然遭到破坏。因此就有了栈寄存器状态执行入栈保存其数据状态,等到需要的时候,再从栈内弹出以供调用函数使用,那么这个话题后面有空会慢慢补上。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,192评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,186评论 1 303
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,844评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,471评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,876评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,891评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,068评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,791评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,539评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,772评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,250评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,577评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,244评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,146评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,949评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,995评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,812评论 2 276

推荐阅读更多精彩内容

  • 原文地址:C语言函数调用栈(一)C语言函数调用栈(二) 0 引言 程序的执行过程可看作连续的函数调用。当一个函数执...
    小猪啊呜阅读 4,509评论 1 19
  • 首先寄存器使用惯例:eip :指令地址寄存器,保存程序计数器的值,当前执行的指令的下一条指令的地址值,16位中为i...
    扎Zn了老Fe阅读 1,875评论 0 0
  • 栈: 在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函...
    zjfclimin阅读 3,760评论 0 5
  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,671评论 0 27
  • 今天白百何出轨的事情刷屏了!出轨?没出轨?没出轨?出轨!大多数人都在看热闹!问问自己,你是白百何吗?你了解她平时的...
    风吹絮阅读 170评论 0 0