一、《linux内核分析》实验一实验报告
在进行实验楼操作之前,先听授了网易云课堂中孟老师关于“计算机是如何工作的?”的介绍。其中主要涉及到了冯诺依曼体系结构,或称为存储程序计算机、从硬件角度和程序员的角度对计算机中内存与CPU之间关系的理解、API——程序与计算机的接口界面、ABI——程序与CPU的接口界面、16位和32位以及64位的X86体系结构的寄存器,其中印象最深的是堆栈指针、段寄存器和EIP等。而对于堆栈指针,在本科阶段学习数据结构课程的时候已经有过相关的编程与实践,但是却没有真正在汇编中进行过相关的操作。紧接着做了一下实验楼中的实验——反汇编一个简单的C程序,下边是我的操作过程,以及我对这部分汇编代码的理解。
C程序代码:
实验楼给出的代码中,简单修改了一下参数,最后的返回结果为11。那么计算机是如何执行的呢?C作为一门高级程序设计语言,计算机是读不懂这段代码的。根据孟老师的讲解,计算机首先借助于gcc命令将这段程序编译成汇编程序,然后通过汇编翻译程序将其翻译成计算机二进制机器指令,最后CPU中的IP寄存器读取到内存中这段程序对应的一大串二进制指令进行计算执行,最后将结果进行输出。实验中,将这段程序保存为main.c文件,通过gcc main.c进行编译,得到一个a.out的文件,执行./a.out,最后通过gcc -S -o main.s main.c -m32命令将main.c的源程序反汇编成对应的汇编代码,按孟老师所讲,main.s里面所有以.开头的代码主要是一些用于链接的辅助信息,并不会被执行,在这里为了方便学习,可以先把它们删除掉,从而得到一个纯净的汇编代码,如下图所示:
不难发现,不管是main函数还是g和f函数,它们编译成汇编之后,前两行的汇编指令都是相同的。这个和函数栈帧有关。机器是用栈来传递过程参数,存储返回信息的过程,过程调用开始时,都会为当前这个过程建立一个栈帧。%ebp是帧指针,%esp是栈指针。这两句的意思是保存旧的帧,创建新的栈帧。由于函数可以调函数,这里的当前基地址,实际上是上一个函数的栈基地址。在这里,f函数中的这两条指令,实际上是保存的main函数的栈基地址、g里边保存的是f函数的栈基地址。
接着分析:
subl $4, %esp
movl $2, (%esp)
参数进栈,将立即数2保存到esp所指向的内存地址——栈顶。然后执行call指令,调用f函数。f函数前两行指令保存main函数的栈基地址,然后接着参数进栈,将立即数8保存到栈顶,并与立即数3相加,并将结果放入栈顶。接着调用g函数。那么问题来了,函数是如何退出的呢?观察mian和f函数,发现退出函数使用的如下指令:
leave
ret
leave指令相当于如下指令:
movl %ebp, %esp //清空当前函数所使用的栈
popl %ebp //把ebp恢复到前一个函数的栈基地址
接着ret恢复指令指向:popl %eip。
为什么g函数没有leave呢?因为g函数内部没有任何的变量声明和函数调用,栈一直都是空的,所以编译器优化了指令。下边以一张图来说明上边指令的执行过程。
实验补充:程序执行过程堆栈图分析
二、《linux内核设计与实现》第1、2、18章学习总结
从第1章的linux内核简介到第2章的从linux内核出发,再到第18章所讲述的如何对linux内核进行调试以及调试中需要注意的一些事项和问题,最后结合网易云课堂孟老师对如何构造一个简单的linux系统MenuOS的视频讲解与操作演示,我开始尝试用自己的Linux系统环境来搭建这样一个MenuOS,首先是从https://www.kernel.org中下载到linux-3.18.6.tar.xz的源代码,然后进行解压缩、编译;这个过程还算顺利。不过编译的时间确实比较长,花了大概15分钟。但是,在制作根文件系统时,遇到了一些问题,当执行:
gcc -o init linktable.c menu.c test.c -m32 -static –lpthread
命令时,遇到了如下问题:
通过参考nixCraft的描述,发现我自己的系统环境中缺少32位嵌入式GNU的C库,安装后得以解决了。
yum install glibc-devel.i686
可是还存在一个问题,如下:
[root@localhost menu]# gcc -o init linktable.c menu.c test.c -m32 -static –lpthread
gcc: –lpthread: No such file or directory
对于这个问题,网上尝试查找相关解决办法,但未能解决。很遗憾没能成功制作出根文件系统,时间原因,博客就先写到这里,后续遗留问题将跟进解决。
参考文档: