最近网易云课堂开放了一节叫Linux内核分析的课程。一直对操作系统和计算机本质很感兴趣,于是进去看了下,才第一堂课,老师就要求学生写一篇关于课时1的博客作为作业。对于这种新颖的作业形式,笔者相当惊讶。好吧,作为任务,还是完成一下吧,刚好需要消化一下。本文将会按照要求,将一段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 %ebp

movl %esp, %ebp

movl 8(%ebp), %eax

addl $5, %eax

popl %ebp

ret

f:

pushl %ebp

movl %esp, %ebp

subl $4, %esp

movl 8(%ebp), %eax

movl %eax, (%esp)

call g

leave

ret

main:

pushl %ebp

movl %esp, %ebp

subl $4, %esp

movl $10, (%esp)

call f

addl $1, %eax

leave

ret

分析

具体的逐步分析,这里就省了,老师课上讲的很详细了,这里主要是要进行思考和归纳。

首先,我们看到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
movl  8(%ebp), %eax

为什么参数是从8(%ebp)中获得的呢?我们知道8(%ebp)表示的是以ebp为基准向栈底回溯8个字节得到,为什么是8个字节呢?

回想一下,在main函数中完成了参数进栈后做了两件事情:

1.由于call f指令的作用,call f下一条指令的地址被压栈了,这占用率4个字节

2.进入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函数内部没有任何的变量声明和函数调用栈一直都是空的,所以编译器优化了指令

总结

最后,通过这个例子,总结一下函数调用的过程:

进入函数:

当前栈基地址压栈(当前栈基地址实际上是前一个函数的栈基地址)

调用其他函数:

1.参数从右到左进栈

2.下一条指令地址进栈

退出函数:

1.栈顶esp归位,回到本函数的ebp

2.基地址回退到上一个函数的基地址

3.eip退回到上一个函数即将要执行的那条语句的地址上


来自:P_Chou Tech Space,作者:周平

链接:http://www.pchou.info/c-cpp/2015/03/03/c-and-asm.html

算法 | 一段C语言和汇编的对应分析,揭示函数调用的本质相关推荐

  1. 一段C语言和汇编的对应分析,揭示函数调用的本质

    本文作者周平,原创作品转载请注明出处 首先对会涉及到的一些CPU寄存器和汇编的基础知识罗列一下: 16位.32位.64位的CPU寄存器名称有所不同,比如指令地址寄存器ip,在16位中叫ip,32位中叫 ...

  2. keil4如何将c语言转换成汇编语言_Keil 中关于C语言编译生成汇编代码函数名规则...

    在keil 中 C语言的函数有带参数和不带参数之分. 一般的资料里说fun(void)类型的函数不带参数,所以,keil编译器生成的汇编的调用地址(函数名) 为fun.这没有错.事实上,不管C语言的函 ...

  3. 汇编语言属于C语言吧,汇编语言和c语言的区别是什么

    区别:汇编语言的效率高,对硬件的可操控性更强,体积小,不易维护,可移植性很差:c语言的效率比较低,硬件可操控性比较差,目标代码体积大,容易维护,可移植性很好. 汇编语言(Assembly Langua ...

  4. keil C对lib封装库反汇编成C语言,Keil软件“C语言”及“汇编”混编 —— 相关知识整理.doc...

    Keil软件"C语言"与"汇编"混编 相关知识整理 用Keil在C中嵌入汇编1 在Keil中嵌入汇编2 介绍直接嵌入汇编代码的方法4 采用汇编可能会有的好处5 ...

  5. C语言primcount素数计数,C语言与汇编的嵌入式编程:求100以内素数

    写汇编之前,需要搞清楚C语言代码的写法,这里以最简单的算法举例说明 C代码如下: #include void main(){ int i,j; int count=; for(i=;i<=;i+ ...

  6. c语言 程序段 数据段,C语言程序的段

    C语言程序的段 C语言在编译和连接后,将生成代码段(Text).只读数据段(ROData)和读写数据段(RWData).在运行时,除了以上三个区域外,还包括未初始化数据段(BSS)区域和堆(Heap) ...

  7. 牛逼c语言代码,这段c语言代码牛逼在哪?

    原标题:这段c语言代码牛逼在哪? 有人说C语言是世界上最牛逼的语言,因为操作系统就是用C语言编写的,学好了C才能更好的学习其他编程语言.为此,有人分享了下面一段代码,说是很牛逼的c语言代码,看得W3C ...

  8. KEIL编译器【C语言编译选项优化等级说明】【支持C99(变量声明在执行语句之后)】【反汇编设置】【C语言联合汇编】【use microlib选项】

    SYD8801是一款低功耗高性能蓝牙低功耗SOC,集成了高性能2.4GHz射频收发机.32位ARM Cortex-M0处理器.128kB Flash存储器.以及丰富的数字接口.SYD8801片上集成了 ...

  9. keil c语言pdf,Keil软件“C语言”与“汇编”混编 —— 相关知识整理.pdf

    Keil软件"C语言"与"汇编"混编 -- 相关知识整理.pdf Keil 软件软件C 语言语言与与汇编汇编混编混编 相关知识整理相关知识整理 用 Keil 在 ...

最新文章

  1. 文巾解题 67. 二进制求和
  2. python __builtins__ credits类 (15)
  3. boost::detail::yield相关的测试程序
  4. python编程快速上手实践项目答案_python编程快速上手之第4章实践项目参考答案...
  5. 前端如何玩转虚拟机_想运行虚拟机,用win10自带的Hyper-V即可,不用安装其它软件...
  6. .JQuery中的Ajax
  7. Python入门之类与面向对象(一)
  8. 设计FMEA步骤五:风险分析
  9. JAVA获取硬盘序列号
  10. Mac下安装Adobe pr
  11. 《少有人走的路-心智成熟的旅程》读书分享
  12. 前端SPA(single page web application单页面应用not水疗)
  13. 软件测试工作效率的衡量标准,软件测试人员绩效工作考核详细(33页)-原创力文档...
  14. PrettyTable的 reversesort 不起作用
  15. unity 转盘记录
  16. 金蝶EAS,KSQL,执行数据库方言
  17. Cookie的设置域名domain与跨域的问题
  18. matlab只读改为可修改,matlab – 获取绘图的只读属性名称列表
  19. 轩辕谷,黄帝的诞生地
  20. 智慧发电厂+智能发电厂web端平台管理系统+Axure高保真智慧电厂系统+能耗管理+告警管理+生产监控+安防设备管理+运维设备管理+监控面板+系统管理+智慧电厂+电厂系统+智慧电厂管理平台+rp原型

热门文章

  1. 【每日SQL打卡】​​​​​​​​​​​​​​​DAY 7丨字节面试真题【难度困难】
  2. 汇编中各寄存器的作用(16位CPU14个,32位CPU16个)和 x86汇编指令集大全(带注释)
  3. C++学习之路 | PTA乙级—— 1035 插入与归并 (25 分)(精简)
  4. matlab knn,MATLAB K近邻算法 — knnsearch() 函数 | 学步园
  5. hive 2.3 mysql_Note23:Hive-2.3.6安装配置
  6. 使用Atom快速打造好用的Markdown编辑器
  7. 两个numpy取相同值_闲谈Numpy的切片规则
  8. php-frm进程管理,PHP内核探索-进程管理
  9. android光传感实现摩斯密码,根据莫尔斯代码 - Android的闪烁闪光。 如何避免ANR次数由于睡觉? (火炬APP)...
  10. android平板截屏方法,Android 各种截屏方法