红联Linux门户
Linux帮助

Linux内核分析(一)——简单C程序汇编代码分析

发布时间:2017-03-06 11:04:18来源:study.163.com作者:付何山
本实验代码库;
本文将通过一个简单的C语言程序(包含两层简单传值函数调用)分析其对应的汇编代码,从而初窥计算机在汇编指令层面上的执行过程。本文不涉及到编译器翻译C至汇编代码的过程,运行环境为64位linux系统;
 
一、von Neumann 结构
现代大多数计算机由运算器、存储器、控制器、输入设备、输出设备五大部件组成。
von Neumann结构应该是所有学习计算机的人都必定会接触到的结构,绝大多数现代计算机系统是建立在von Neumann体系结构上的。
von Neumann结构的中心思想在于存储程序。即将中央处理器与存储区分离,所有程序存储在存储器中。处理器根据寄存器值,依次取出指令并顺序执行。
 
二、课程实验分析
汇编一段简单的C语言代码,观察代码执行过程。未提及的概念若在分析过程中遇到将在分析过程中简略提及,详情请参见课程主页
运行实验楼环境,创建main.c。如下为main.c
Linux内核分析(一)——简单C程序汇编代码分析
可以看到main函数使用参数12,调用函数f,f调用函数g,g返回12+10给f,f返回22给main,main再加1993后返回。
使用 gcc -S -o main.s main.c -m32 将上述C语言文件编译为汇编代码main.s,如下所示。(注:该命令仅适用于64位linux系统,32位linux系统可能稍有不同)(注:截图不完整,请点击链接查看完整源代码)
Linux内核分析(一)——简单C程序汇编代码分析
其中以.开头的是一些标签,帮助程序将来能够更好的进行链接,在这里不对它进行讨论。将所有带.的标签删除后,得到withoutdotmain.s,如下所示:
Linux内核分析(一)——简单C程序汇编代码分析
运行过程分析:
1、程序从main入口进入,运行
pushl %ebp
movl %esp, %ebp
l的意思为其后进行的操作为32位的操作。pushl的作用是使栈基指针%ebp压栈,即让栈顶指针%esp所指位置的值(注:所指位置的值与%esp本身的值不同,前者为(%esp)后者为%esp)变为栈基指针%ebp的值,并将栈顶指针%esp减去一个栈元素的单位(在32位中为4个bytes)(注:栈是向下增长的)。
而后movl %esp, %ebp将当前%esp的值赋给%ebp。每一个函数进入时都将进行这两步,下不赘述。
2、程序将esp减4subl $4, %esp即下移一个单位。并使当前栈顶指针所指的值变为12movl $12, (%esp), 而后使用call f调用函数f 。这条指令同时也会将%eip压栈并将f的入口地址赋予%eip(%eip保存的是函数在不跳转或不调用情况下下一条应该执行的程序地址),故%esp会下移一个单位。此时12的位置在4(%esp)。
Linux内核分析(一)——简单C程序汇编代码分析
3、进入函数f,栈基指针入栈和栈顶指针下移更改。此时12的位置在%8(ebp)(又执行了一次入栈,%8(esp)也指向相同位置,因为两个指针现在指向同一个位置)将8(%ebp)值赋给通用寄存器%eax,即将12赋予%eaxmovl 8(%ebp), %eax,相当于函数传递形参。而后用%eax的值修改%esp当前所指的位置的值movl %eax, (%esp)。而后使用call g调用函数g,即将%eip压栈,将%esp减4,并将函数g的入口地址赋予%eip。
Linux内核分析(一)——简单C程序汇编代码分析
4、进入函数g,栈基指针入栈和栈顶指针下移更改。此时12的位置与3中f的情况类似,故movl 8(%ebp), %eax即将形参12赋予%eax,而后执行+10的操作addl $10, %eax,使之前压栈的栈基指针出栈popl %ebp(得到上一个函数栈基信息), 而后使用ret指令返回。ret指令与call相反,即将%eip出栈,修改%esp,得到调用函数下一条指令的执行地址。
Linux内核分析(一)——简单C程序汇编代码分析
5、返回函数f,执行leave指令,相当于执行
movl %ebp, %esp 
popl %ebp
其作用在于撤销进入函数时创建的堆栈。其后调用ret指令返回main函数。
6、main函数返回至addl $1993, %eax,对%eax执行加法操作,得到最终结果,而后使用leave,ret返回。注意%eax在函数调用过程中值不修改,因此可以用来保存最终的运行结果。
Linux内核分析(一)——简单C程序汇编代码分析
 
三、心得总结
这一章的内容还算比较简单,部分难点在于一些指令实际执行的内容不清楚,比如call,ret,leave(百度百科上对于leave指令的解释应该是错的)等,导致对程序运行的堆栈有些许疑惑。整体来说还算比较顺利。但博客编写花了比较多的时间,一方面是较少使用markdown,另一方面是在画图和语言组织上花费了一些功夫。
 
本文永久更新地址:http://www.linuxdiyf.com/linux/28949.html