lab1-实验报告
Thingking 1.3
Q: Thinking 1.1 在阅读 附录中的编译链接详解 以及本章内容后,尝试分别使用实验环境中 的原生 x86 工具链(gcc、ld、readelf、objdump 等)和 MIPS 交叉编译工具链(带有 mips-linux-gnu- 前缀,如 mips-linux-gnu-gcc、mips-linux-gnu-ld),重复其中的编译和解析过程,观察相应的结果,并解释其中向objdump传入的参数的含义。
A: 我们对于示例代码test_hello.c进行测试
1 |
|
使用原生工具链
1
2
3gcc -o test_hello test_hello.c
readelf -h test_hello
objdump -d test_hello利用readelf查看文件头信息得到:

反汇编得到

MIPS 交叉编译工具链
1
2
3mips-linux-gnu-gcc -o test_mips test_hello.c
mips-linux-gnu-readelf -h test_mips
mips-linux-gnu-objdump -d test_mips

objdump参数用法
-d:对目标文件中的可执行段进行反汇编,将机器码转换为汇编代码,方便开发者查看程序的指令序列-D:对目标文件的所有段进行反汇编,包括不可执行段,如数据段等。-h:显示目标文件的各个段的头部信息,包括段的名称、大小、偏移等。-S:将源代码和反汇编代码混合显示,方便开发者对照查看源代码和对应的汇编代码。-t:显示目标文件的符号表信息,包括符号的名称、地址、类型等。
Thinking 1.2
Q 尝试使用我们编写的readelf程序,解析之前在target目录下生成的内核ELF文件。and 编写的readelf程序不可以解析readelf文件本身,为什么系统工具可以解析readelf ?
A 自行编写的readelf文件解析mos得到
1 | 0:0x0 |
利用readelf -h hello 和readelf -h readelf指令,我们得到ELF头文件中显示的类型,可知hello是32位,而readelf是64位的,而我们编写的readelf.c文件是针对32位文件编写,无法解析64位的readelf文件(如不能用typedef uint32_t Elf32_Off; 的数据类型取做64位的运算)
Thinking 1.3
Q: 在理论课上我们了解到,MIPS 体系结构上电时,启动入口地址为 0xBFC00000 (其实启动入口地址是根据具体型号而定的,由硬件逻辑确定,也有可能不是这个地址,但 一定是一个确定的地址),但实验操作系统的内核入口并没有放在上电启动地址,而是按照 内存布局图放置。思考为什么这样放置内核还能保证内核入口被正确跳转到? (提示:思考实验中启动过程的两阶段分别由谁执行。)
A实验中使用的QEMU模拟器已经提供了bootloader的引导(启动)功能,并且支持直接加载ELF格式的内核。操作系统启动过程被简化为加载内核到内存(kernel.lds控制),之后跳转到内核入口(kernel.lds通过ENTRY(_start)设置程序入口为_start函数,链接后程序从_start函数开始执行)。
难点分析
此次Lab1实验的重难点主要有以下两个方面:
- ELF文件的解析
- **
printk**函数的实现
ELF文件的解析
ELF 是一种用于可执行文件(executable)、目标文件(relocatable)和库(shared object)的文件格式,而我们编译出的MOS内核文件本质上也是一个ELF文件
ELF的主要结构如下:

从图中可以得到,ELF文件主要由以下几部分构成:
- ELF头:记录程序的基本信息——体系结构与操作系统,同时包含了节头表与段头表相关信息
- 段头表:包含程序各个段的信息,该信息需要在运行和装载时使用
- 节头表:包含程序各个节的信息,该信息需要在程序编译和链接时间使用
- 段头表表项与节头表表项
我们需要学会解析ELF文件并获取相关信息,同时学习通过链接器等指导其中某些信息的生成与储存。以解析section为例,
文件头地址
binary+ 节头表偏移量e_shoff得到节头表地址,同时从文件头得到节头表表项大小e_shentsize和 节头表表项数e_shnum。binary + e_shoff + i * e_shentsize就是第i个节头表表项的地址。
printk实现逻辑
与实现输出功能相关的主要由三个文件
- kern/machine.c : 其中主要实现了函数
void printcharc(char ch), 该函数的功能是向控制台输出字符ch(本质上是向一块特殊的内存中写入ch) - kern/printk.c : 其中主要是实现了
void printk(const char*fmt,...),该函数实际上是把回调函数void outputk(void *data, const char*buf, size_t len)与输出参数传递到了vprintfmt中 - lib/print.c: 其中实现了
vprintfmt(fmt_callback_t out, void *data, const char *fmt, va_list ap)这个实现格式化输出的主体逻辑
回调函数:是一个只能通过函数指针调用的函数。将某函数的指针作为参数传递给另一个函数,在另一个函数中使用该函数指针,我们认为该函数是一个回调函数。在调用
qsort函数时间传入的cmp实际上就是一个回调函数
实验体会
Lab1主要学习目标是掌握操作系统启动的基本流程、掌握ELF文件的结构和功能以及编写printk函数。本次实验所需要实际编写的代码并不多,可能就是数十行。学习时间主要花费在了阅读源代码上,由于各个文件的依赖文件众多,阅读起来确实稍显麻烦。此外,涉及操作系统内核的众多源文件中含有大量指针运用,使用不当容易出错。
此次实验难度不大,且通过printk函数的实战让我第一次认真去了解一个C语言库函数的基础实现,我觉得这非常有趣。
实验参考
与回调函数相关内容我主要参考了一下链接https://cookedbear.top/p/44391.html






