思考题
Thinking 1.1
向objdump传入参数 -DS,-D代表反汇编所有内容,-S代表在结果中将十六进制汇编码和反汇编结果共同显示。
使用MIPS交叉编译工具链得到的对应main.c的反汇编对应main部分如下:
1 | 00000000 <main>: |
Thinking 1.2
使用我们编写的readelf 解析 target/mos 得到:
1 | 0:0x0 |
我们编写的 readelf 不能解析自身的原因是:hello在编译时加入了 -m32编译选项,而readelf没有,代表hello是按32位程序编译的,而readelf没有指定,按默认的64位编译。使用 objdump -h也能发现:hello 的类别是 ELF32,readelf的类别是 ELF64。
Thinking 1.3
由指导书附录A.1部分可知,操作系统上电启动需要调用 bootloader 先由 ROM 中刻好的 stage1 初始化硬件,再将存储设备中的 stage2 部分代码放入 RAM 并调用。显然这里的启动入口地址对应 bootloader 的 stage2 所在的地址,而不是操作系统的内核入口。待 stage2 载入内核,设置好启动参数后,才会跳转到在固定的内核地址的内核部分。
实验中,GXemul 本身支持加载 ELF 格式内核,所以启动流程被简化为直接加载内核到内存并跳转到内核的入口(具体内核各节的加载地址也可以由更改 Linker Script 文件来更改),因此只需要将内核放在规定好的内核入口处即可正常启动内核。
难点分析
指针基础
Exercise 1.1中我和其他同学发现一个共性问题,如下代码所示:
1 | sh_table = ehdr + ehdr->e_shoff; // wrong |
前者错误,后者正确。主要是因为C语言的指针运算特性:某type类型的指针+1实际上会让指针地址值增加sizeof(type),在本题情况下就是sizeof(Elf32_Ehdr),但这显然不是offset对应的单位,因此应该更换成类型为void *的binary指针,或者采用下列这种写法:
1 | sh_table = (void *)ehdr + ehdr->e_shoff; |
Linker Script
Exercise 1.2中,需要注意空格和示例内容一样,不能少空格,不然会syntax error。(冒号左右和左右大括号的左右都要加空格)
printk()
对于Exercise1.4,首先是涉及指针操作,不能搞混fmt和*fmt,即避免出现将fmt作为字符处理或进行类似*fmt++的操作。
其次是注意每次循环间width, long_flag, neg_flag, ladjust, padc五个变量值要记得重置回默认值(可以在循环开头直接赋值默认值,也可以通过保证所有分支结构都写else的方式解决,我使用的是后者)。
最后有一个需要略微阅读其他部分代码的地方:有符号整数对负数的处理。这里如果只是对负值进行判断并赋值neg_flag并传入的话是不够的,需要将num取负,因为print_num接收到的neg_flag仅用于指示是否输出负号,实际打印时永远按无符号数打印,所以需要取负。
实验体会
刚开始阅读Lab1部分的时候还有种不知所云的感受,但是采取“先记住,往后慢慢读”的方式逐渐的也就理解了神奇的操作系统(很小的一部分)。Lab1整体能学到不少东西,不过代码部分还是相对简单,个人认为相比CO略显轻松,期待后续内容。