IT狗工作室C语言C语言&嵌入式

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

2019-10-18  本文已影响0人  铁甲万能狗

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


sss5.png

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

调用者函数的栈帧

具体例子

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
干净的汇编代码

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

反编译示例

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

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

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


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

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

main函数的栈帧

返回地址入栈

add函数的栈帧

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


add函数的上下文
  1. mov $0xff71b,%ebp :被弹出的0xff71b的地址值会覆盖ebp目前的内容,此时ebp会指向如图中的0xff71b
    mov $0xff71b,%ebp执行时的状态

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

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

后记

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

上一篇下一篇

猜你喜欢

热点阅读