lab1 实验报告
思考题
Thinking 1.1
参数含义:
objdump -D test 反汇编test中的所有section
objdump -S test 将代码段反汇编的同时,将反汇编代码和源代码交替显示,编译时需要给出-g,即需要调试信息。
我的link script:
SECTIONS
{
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}
我的think1.c
int main() {
int a = 1;
int b = 2;
int c = a + 2;
return 0;
}
使用以下命令进行操作:
cppath=/OSLAB/compiler/usr/bin/mips_4KC- #不想打那么多次路径,用变量记录路径
${cppath}gcc -E think1.c > E-think1 #预处理
${cppath}gcc -c think1.c #编译think1.c
${cppath}ld think1.o -T my.lds -o think1 #链接并重命名可执行文件
${cppath}objdump -DS think1.o >o.txt #反汇编.o文件
${cppath}objdump -DS think1 >out.txt #反汇编.out可执行文件
以下为预处理和反汇编得到的结果:
E-think1: (源文件预处理后的结果)
# 1 "think1.c"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "think1.c"
int main() {
int a = 1;
int b = 2;
int c = a + 2;
return 0;
}
o.txt:
think1.o: file format elf32-tradbigmips
Disassembly of section .text:
00000000 <main>:
0: 27bdffe0 addiu sp,sp,-32
4: afbe0018 sw s8,24(sp)
8: 03a0f021 move s8,sp
c: 24020001 li v0,1
10: afc20010 sw v0,16(s8)
14: 24020002 li v0,2
18: afc2000c sw v0,12(s8)
1c: 8fc20010 lw v0,16(s8)
20: 24420002 addiu v0,v0,2
24: afc20008 sw v0,8(s8)
28: 00001021 move v0,zero
2c: 03c0e821 move sp,s8
30: 8fbe0018 lw s8,24(sp)
34: 27bd0020 addiu sp,sp,32
38: 03e00008 jr ra
3c: 00000000 nop
Disassembly of section .reginfo:
00000000 <.reginfo>:
0: e0000004 sc zero,4(zero)
...
Disassembly of section .pdr:
00000000 <.pdr>:
0: 00000000 nop
4: 40000000 mfc0 zero,c0_index
8: fffffff8 sdc3 $31,-8(ra)
...
14: 00000020 add zero,zero,zero
18: 0000001e 0x1e
1c: 0000001f 0x1f
Disassembly of section .comment:
00000000 <.comment>:
0: 00474343 0x474343
4: 3a202847 xori zero,s1,0x2847
8: 4e552920 c3 0x552920
c: 342e302e ori t6,at,0x302e
10: 30202844 andi zero,at,0x2844
14: 454e5820 0x454e5820
18: 454c444b 0x454c444b
1c: 20342e31 addi s4,at,11825
20: 20342e30 addi s4,at,11824
24: 2e302900 sltiu s0,s1,10496
out.txt:
think1: file format elf32-tradbigmips
Disassembly of section .text:
00010000 <main>:
10000: 27bdffe0 addiu sp,sp,-32
10004: afbe0018 sw s8,24(sp)
10008: 03a0f021 move s8,sp
1000c: 24020001 li v0,1
10010: afc20010 sw v0,16(s8)
10014: 24020002 li v0,2
10018: afc2000c sw v0,12(s8)
1001c: 8fc20010 lw v0,16(s8)
10020: 24420002 addiu v0,v0,2
10024: afc20008 sw v0,8(s8)
10028: 00001021 move v0,zero
1002c: 03c0e821 move sp,s8
10030: 8fbe0018 lw s8,24(sp)
10034: 27bd0020 addiu sp,sp,32
10038: 03e00008 jr ra
1003c: 00000000 nop
Disassembly of section .reginfo:
00010040 <.reginfo>:
10040: e0000004 sc zero,4(zero)
...
Disassembly of section .pdr:
00000000 <.pdr>:
0: 00010000 sll zero,at,0x0
4: 40000000 mfc0 zero,c0_index
8: fffffff8 sdc3 $31,-8(ra)
...
14: 00000020 add zero,zero,zero
18: 0000001e 0x1e
1c: 0000001f 0x1f
Disassembly of section .comment:
00000000 <.comment>:
0: 00474343 0x474343
4: 3a202847 xori zero,s1,0x2847
8: 4e552920 c3 0x552920
c: 342e302e ori t6,at,0x302e
10: 30202844 andi zero,at,0x2844
14: 454e5820 0x454e5820
18: 454c444b 0x454c444b
1c: 20342e31 addi s4,at,11825
20: 20342e30 addi s4,at,11824
24: 2e302900 sltiu s0,s1,10496
以下为我用DiffMerge比较的o.txt和out.txt
可见main()
的地址在0x10000,与link script的设置一致。
Thinking 1.2
此为我用系统自带的readelf -h
来解析内核文件vmlinux 和测试文件testELF的结果。
![image-20220326214345647](https://s2.loli.net/2022/04/29/vsTxDGMaONAVwjB.png)
此图中上部为vmlinux内核文件,下部为测试testELF文件。
可见vmlinux为大端存储文件,mips机器。 testELF为小端文件,Intel机器。
由此可知我们的readelf程序只可以解析小端存储的文件。此外,还只能对32位文件进行分析。
Thinking 1.3
操作系统的启动分为两个部分,加电后取指寄存器复位到固定值(就是启动入口地址,这个地址是固定的,是硬件逻辑决定的),然后进入Stage1。
在Stage1,执行硬件初始化和Stage2的准备工作并跳转到Stage2。
在Stage2,才会进行内核和根文件系统的载入工作,得益于Stage1的处理,实现了硬件初始化和软件初始化的初步分离,此时完全可以根据内存布局图来加载内核。
回看整个过程,启动入口地址是bootloader的位置,内核的载入在Stage2,此时已经可以使用RAM了,可以按照内存布局来加载内核。
实验操作系统使用GXemul仿真器,支持直接加载ELF格式的内核。其已经提供Stage1的功能,可以按照内存布局来加载内核。
Thinking 1.4
为了避免发生页面冲突现象。需要:
- 不同程序段占用空间尽量独立,没有重合
- 避免一个页面被多个程序段占用
因此,若当前程序占用的末页面为vi,那么后续程序应从vi+1页面开始。
Thinking 1.5
通过查看顶层的Makefile文件,可以知道源码地址,通过反汇编内核,可以知道各函数在内存中的位置。以下为反汇编的结果:
![image-20220327151938491](https://s2.loli.net/2022/04/29/9uQzX4TnfqM6H3Z.png)
内核的入口在:学号/boot/start.S
,存储地址为0x80010000。
main函数在学号/init/main.c
,存储地址为0x80010040。
内核会从_start方法处开始,在该方法的结尾处有jal main
,借此可以跳转到main函数中。
通过反汇编,可以看出每个函数会有一个确定的地址。在跨文件调用函数时,首先会将需要保存的数据入栈保护,再用jal跳转到相应函数的地址。
Thinking 1.6
联系在学号/include/asm/cp0regdef
中宏定义文件,可以将文件中的各个寄存器名与CP0中的寄存器对应
mtc0 zero,CP0_STATUS #将CP0中SR($12)寄存器清零
mfc0 t0, CP0_CONFIG #取出CP0中$16的值
and t0,~0x7 #后三位清零
ori t0,0x2 #将1号位置1
mtc0 t0, CP0_CONFIG #将处理后的值放回到CP0中$16,具体操作为将Config寄存器0号位和2号位置0,将1号位置1
实验难点展示
体会与感想
对课下部分的一些想法
个人认为本次实验的课下部分难度并不是非常大,虽然需要大量阅读代码,但是因为有着指导书的详细教导,大任务被分割成了一个个小任务,总体而言还算知道要如何要做什么,难度不大。
但是我认为思考题的难度太大,且缺少一些必要的教程和提醒。必须承认,通过思考题,我对于整个实验内容有了更加清晰和全面的了解。但是整个过程实在是太痛苦了,且浪费了大量宝贵的时间!!!因为思考题几乎没有提示,一旦卡住,大量的时间直接就浪费了。
例如思考题1.1,各位助教大大能不能在指导书中提醒一下我们直接使用课程组的gcc是无法实现链接的?需要使用ld来完成链接。且能不能提一嘴让各位同学在完成了exercise 1.3之后再来尝试做本题。 在做本题时,我知道我需要使用课程组提供的工具进行反汇编,但是对于使用这些工具时会遇上的一些细节问题,我一无所知,就因为一个链接的问题,我被卡了至少两个小时没有进展,最后是找助教才解决的问题。
总而言之,指导书为我们提供了一个很好的入门操作系统的机会,通俗的讲解和脉络清晰的指导书让我们学起来的体验非常好。 就是希望助教大大们可以在具体操作细节方面多写一些提示,让我们可以在一些小地方减少时间的消耗,将更宝贵的时间拿来阅读代码、理解整个系统。
感想
lab1是非常耗时也让我收获颇丰的一次学习经历。 个人至少在课下花费了超过12个小时。走了不少弯路。
我学习lab1起码有三遍,第一遍是跟着指导书走一遍,完成了课下的exercise,理解了boot loader、elf、link script、print.c,成功完成了作业,但是对于我在lab1干了啥事没有一个宏观了解。
第二遍也是跟着指导书走的,主要是为了完成思考题。这次注意到了boot loader中的三次初始化、会使用指导书中各种之前“看会”了的工具,对本次实验的流程有个一个大概的了解,我们整个lab1其实就只做了一个操作系统的启动而已。
第三次学习是因为我决定扫盲,所以上网学了makefile,通过阅读makefile,我知道了整个lab的框架,又因为第二次学习时学会了“反汇编”这个好东西,所以就自己在虚拟机上随便玩,反汇编我们的虚拟机。必须要说OS项目真的是结构清晰、命名讲究。boot
用于OS的启动后的初始化,lib
中存的是本次实验中的print文件,是我们自己的lib,gxemul
中存的是我们的OS,readelf
中的是我们自己的readelf小工具……。
除了感觉OS好nb之外,还感觉这玩意好花时间啊。下周OO电梯月就来了,课业压力好大啊,希望我的头发能活过这一学期。
指导书反馈
- 指导书错误
指导书p73的Thinking1.4,显然没有把内容复制全,以下为mooc网上的截图和指导书的截图。
- mooc指导书中,此图右上角的va+i位置标错了,其应该在bin_size的结束位置
残留难点
gcc链接过程中,动态库和静态库的链接形式。
链接器链接文件时,内核文件可以直接指出要的地址空间,实际也会加载过去,那么一般文件呢?可执行文件已经完成了编译与链接,即各函数的逻辑地址已经确定,一大群可执行文件需要的的逻辑地址势必有重叠,操作系统是如何解决这个问题的。