一段C语言和汇编的对应分析,揭示函数调用的本质
本文作者周平,原创作品转载请注明出处
首先对会涉及到的一些CPU寄存器和汇编的基础知识罗列一下:
- 16位、32位、64位的CPU寄存器名称有所不同,比如指令地址寄存器
ip
,在16位中叫ip
,32位中叫eip
,64位叫rip
- 32位的汇编指令通常以
l
结尾,比如movl
相当于mov
的含义 ebp
: 堆栈基地址 寄存器,这个寄存器保存的是当前执行绪的栈底地址
esp
: 堆栈栈顶 寄存器,这个寄存器保存的是当前执行绪的栈顶地址
eip
: 指令地址 寄存器,这个寄存器保存的是指令所在的地址,CPU会不断的根据eip
所指向的指令去内存取指令并执行,并自行累加取下一条指令逐条执行。eip
无法直接赋值,call
、ret
、jmp
等指令可以起到修改eip
的作用%
用于直接寻址寄存器,$
用于表示立即数。movl $8, %eax
表示把立即数8
存到eax
中()
用于内存间接寻址,比如movl $10, (%esp)
表示将立即数10
保存到esp
所指向的内存地址中8(%ebp)
表示先找到ebp
所指向的地址值+8
后得到的地址- 栈地址值是向下增长的,即栈顶从高地址向低地址移动
准备工作
准备一段C代码:
int g(int x)
{return x+5;
}int f(int x)
{return g(x);
}int main(void)
{return f(10)+1;
}
使用实验楼环境
编译成汇编代码
使用如下命令编译上面的c代码
gcc -S -o main.s main.c -m32
去掉不重要的部分后,得到:
汇编代码结果为:
g:pushl %ebpmovl %esp, %ebpmovl 8(%ebp), %eaxaddl $5, %eaxpopl %ebpret
f:pushl %ebpmovl %esp, %ebpsubl $4, %espmovl 8(%ebp), %eaxmovl %eax, (%esp)call gleaveret
main:pushl %ebpmovl %esp, %ebpsubl $4, %espmovl $10, (%esp)call faddl $1, %eaxleaveret
分析
具体的逐步分析,这里就省了,老师课上讲的很详细了,这里主要是要进行思考和归纳。
首先,我们看到3个C函数对应生成了3个部分的汇编代码,分别用函数名作为标号隔开了
int g(int x) -> g:
int f(int x) -> f:
int main(void) -> main:
我们知道程序是从main
函数开始执行的,那么当程序被加载并运行时,上面的汇编代码会被加载到内存的某一个区域。而且,CPU中的很多寄存器都会初始化,当然其中最重要的是eip
,因为eip
是指向下一条将要执行的命令所在的内存地址,所以此时的eip
应该指向main
标号下的pushl %ebp
:
main:
eip -> pushl %ebp
程序开始执行…
我们捆绑着看,首先先看这两条:
pushl %ebp
movl %esp, %ebp
再观察一下整个代码,有没有发现不仅仅是main
函数,函数f
和g
的开头也是这两个指令。分析一下,不难得出,这两条指令是指将当前栈基地址压栈后,重新将基地址定位到栈顶,这个含义其实是保存好当前的基地址,重新开始一个新的栈。由于函数可以调函数,这里的当前基地址,实际上是上一个函数的栈基地址。例如,在f
函数中的这两句指令,实际上保存的是main
函数的栈基地址。
接着来分析两句:
subl $4, %esp
movl $10, (%esp)
对照C代码不难发现,这是参数进栈,将立即数10
,保存到栈顶(esp所指向的内存地址是栈顶)。而在f
函数中也可以发现类似的语句:
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
所以,我们可以得出结论是,在调用函数前需要把参数逐个压栈,而压栈的顺序根据笔者的测试是从右向左的。
接着调用call
指令,跳转到f
函数,我们知道call
指令等同于下面的伪代码:
pushl %eip+1
movl %eip f
即把call
指令的后一条指令进栈后,将eip
赋值为目标函数的第一个指令地址。这样做显而易见:当所调用的函数结束后,需要返回当前函数继续执行,所以必须要保存下一条指令,否则回来的时候就找不到了。
来到f
函数,首先是保存main函数的栈基地址,然后需要调用g
函数,于是需要参数先进栈:
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
这里重点思考一下,f
函数是如何获得main函数传递过来的参数的,我们看到
movl 8(%ebp), %eax
为什么参数是从8(%ebp)
中获得的呢?我们知道8(%ebp)
表示的是以ebp为基准向栈底回溯8个字节得到,为什么是8个字节呢?
回想一下,在main
函数中完成了参数进栈后做了两件事情:
- 由于
call f
指令的作用,call f
下一条指令的地址被压栈了,这占用率4
个字节 - 进入
f
函数后,立即将main
函数的栈基地址进栈了,而且将ebp
靠向了栈顶esp
,这又占用了4
个字节
于是通过8(%ebp)
可以找到前一个函数的第一个整型参数的值。
一张图告诉你怎么回事:
看过了进入函数,调用函数的过程,再看一下函数是如何退出的。观察main
和f
不难发现,退出函数使用的是如下指令
leave
ret
leave
指令相当于如下指令:
movl %ebp, %esp
popl %ebp
- 第一条语句是将
esp
重置到ebp
,可以理解为清空当前函数所使用的栈 - 第二条语句是将栈顶值赋值给
ebp
,并弹出,栈顶值是什么呢?通过上面的分析不难发现,此时的栈顶值实际上是前一个函数的栈基地址,所以第二条语句的意思就是把ebp
恢复到前一个函数的栈基地址
接着ret
就是相当于,恢复指令指向:
popl %eip
为什么g函数没有leave呢?因为g函数内部没有任何的变量声明和函数调用栈一直都是空的,所以编译器优化了指令
总结
最后,通过这个例子,总结一下函数调用的过程:
进入函数:
- 当前栈基地址压栈(当前栈基地址实际上是前一个函数的栈基地址)
调用其他函数:
- 参数从右到左进栈
- 下一条指令地址进栈
退出函数:
- 栈顶
esp
归位,回到本函数的ebp
- 基地址回退到上一个函数的基地址
eip
退回到上一个函数即将要执行的那条语句的地址上
一段C语言和汇编的对应分析,揭示函数调用的本质相关推荐
- 算法 | 一段C语言和汇编的对应分析,揭示函数调用的本质
最近网易云课堂开放了一节叫Linux内核分析的课程.一直对操作系统和计算机本质很感兴趣,于是进去看了下,才第一堂课,老师就要求学生写一篇关于课时1的博客作为作业.对于这种新颖的作业形式,笔者相当惊讶. ...
- keil C对lib封装库反汇编成C语言,Keil软件“C语言”及“汇编”混编 —— 相关知识整理.doc...
Keil软件"C语言"与"汇编"混编 相关知识整理 用Keil在C中嵌入汇编1 在Keil中嵌入汇编2 介绍直接嵌入汇编代码的方法4 采用汇编可能会有的好处5 ...
- keil4如何将c语言转换成汇编语言_Keil 中关于C语言编译生成汇编代码函数名规则...
在keil 中 C语言的函数有带参数和不带参数之分. 一般的资料里说fun(void)类型的函数不带参数,所以,keil编译器生成的汇编的调用地址(函数名) 为fun.这没有错.事实上,不管C语言的函 ...
- c语言 程序段 数据段,C语言程序的段
C语言程序的段 C语言在编译和连接后,将生成代码段(Text).只读数据段(ROData)和读写数据段(RWData).在运行时,除了以上三个区域外,还包括未初始化数据段(BSS)区域和堆(Heap) ...
- 牛逼c语言代码,这段c语言代码牛逼在哪?
原标题:这段c语言代码牛逼在哪? 有人说C语言是世界上最牛逼的语言,因为操作系统就是用C语言编写的,学好了C才能更好的学习其他编程语言.为此,有人分享了下面一段代码,说是很牛逼的c语言代码,看得W3C ...
- keil c语言pdf,Keil软件“C语言”与“汇编”混编 —— 相关知识整理.pdf
Keil软件"C语言"与"汇编"混编 -- 相关知识整理.pdf Keil 软件软件C 语言语言与与汇编汇编混编混编 相关知识整理相关知识整理 用 Keil 在 ...
- 【C语言与汇编】简单学学C到汇编代码
C语言与汇编 部分过程可参考C++ primer plus 本书余下的篇幅讨论源代码文件中的内容:本节讨论创建源代码文 件的技巧.有些C++实现(如Microsoft Visual C++.Embar ...
- f2812的语言与标准c不同,F2812中C语言调用汇编函数(续)
参考资料: (1) SPRU514 ---- TMS320F28x Optimizing C/C++ Compiler User's Guide.pdf; (2) spru430d ---- TMS3 ...
- f2812的c语言与标准c语言,F2812中C语言调用汇编函数
F2812中C语言调用汇编函数 参考资料: (1) SPRU514 ---- TMS320F28x Optimizing C/C++ Compiler User's Guide.pdf; (2) sp ...
最新文章
- [BZOJ1106/POI2007]Tet立方体大作战
- 第十六届全国大学生智能车竞赛文化衫LOGO主图案设计
- UA OPTI512R 傅立叶光学导论17 离散傅立叶变换简介
- 蓝桥学院2019算法题1.3
- html纵向文本,html – 垂直对齐CSS圈中多行的文本
- 100C之13:他该如何存款?
- 基于Java+SpringBoot+vue+node.js实现自行车租赁平台管理系统
- KlayGE 4.4中渲染的改进(五):OpenGL 4.4和OpenGLES 3
- 微课|中学生可以这样学Python(3.2节):双分支选择结构
- JavaScript前端俄罗斯方块小游戏
- php 怎么开启错误报告,php错误报告级别怎么设置?
- native react 图片裁剪_React Native图片选择裁剪组件
- Python—SJ—实验4—DNA翻译
- Sigfox获法国最大一笔VC投资,打造物联网自己的互联网
- 各类数值型数据间的混合运算
- 分块专题整理(分块+莫队)
- WINCE 注册表修改
- 啥是inference推理/推断?
- java断路器触发条件_断路器,AOP实现断路器模式 ------------Hystrix
- Delete `␍`eslint(prettier/prettier) 错误的解决方案