首页 技术 正文
技术 2022年11月9日
0 收藏 603 点赞 4,410 浏览 1950 个字

转载自地址:http://blog.csdn.net/zsy2020314/article/details/9429707

 

 

 

今天突然想分析一下函数在相互调用过程中栈帧的变化,还是想尽量以比较清晰的思路把这一过程描述出来,关于c函数调用原理的理解是很重要的。

1.关于栈

首先必须明确一点也是非常重要的一点,栈是向下生长的,所谓向下生长是指从内存高地址->低地址的路径延伸,那么就很明显了,栈有栈底和栈顶,那么栈顶的地址要比栈底低。对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

c函数调用过程原理及函数栈帧分析

2.举个简单的实例,从汇编的视角看函数调用

2.1建立一个简单的程序,程序文件名为  main.c

开发测试环境:

Ubuntu 12.04

gcc版本:4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)  (是Ubuntu自带的)

  1. <span style=”font-size:18px;”>/*main.c代码:*/
  2. void swap(int *a,int *b)
  3. {
  4. int c;
  5. c = *a;
  6. *a = *b;
  7. *b = c;
  8. }
  9. int main(void)
  10. {
  11. int a ;
  12. int b ;
  13. int ret;
  14. a =16;
  15. b = 64;
  16. ret = 0;
  17. swap(&a,&b);
  18. ret = a – b;
  19. return ret;
  20. }</span>

2.2编译

#gcc    -g   -o   main   main.c

#objdump   -d  main   >   main.dump

#gcc   -Wall   -S  -o   main.s   main.c

c函数调用过程原理及函数栈帧分析

这样大家可以看main.s也可以看main.dump,这里我们选择使用main.dump。

截取关键的部分,即_start,   swap  ,  main,为什么会有_start呢,因为ELF格式的入口其实是_start而不是main()。下面的图展示了main()函数调用swap()前后的栈空间的结构。右边的数字代表相对帧指针的偏移字节数。后面我们使用GDB调试就会发现栈的变化跟下图是一致的。

(!!!请注意,由于栈对齐的缘故,编译器分配栈空间时可能会有没用到的内存地址,而这些没使用到的内存地址就没在下图表示出来,所以下图只能当作示意图来了解函数栈帧结构!!具体的栈内存内容以下文的GDB调试的信息为准!!!)

c函数调用过程原理及函数栈帧分析

下面是main.dump中_start的代码注释,比较重要的是对esp的栈对齐操作,esp是16字节对齐的,注意左边行号的右边的0x8048300一类的数字是指令地址。

c函数调用过程原理及函数栈帧分析

下面是main.dump中swap()函数和main()函数的汇编代码,代码旁有详细的注释。

c函数调用过程原理及函数栈帧分析

下面我们使用GDB调试main.c的代码,使用刚才编译好的main镜像。

# gdb    start      (启动gdb)

# (gdb) file     main      (加载镜像文件)

# (gdb) break  main     (把main()设置为断点,注意gdb并没有把断点设置在main的第一条指令,而是设置在了调整栈指针为局部变量保留空间之后)

# (gdb) run                     (运行程序)

# (gdb) stepi                  (单步执行,不熟悉gdb的童鞋要注意了,stepi命令执行之后显示出来的源代码行或者指令地址,都是即将执行的指令,而不是刚刚执行完的指令!)

c函数调用过程原理及函数栈帧分析

c函数调用过程原理及函数栈帧分析

c函数调用过程原理及函数栈帧分析

c函数调用过程原理及函数栈帧分析

c函数调用过程原理及函数栈帧分析

c函数调用过程原理及函数栈帧分析

c函数调用过程原理及函数栈帧分析

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:8,983
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,500
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,344
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,127
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,762
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,838