lab1_report

lab1_report

Charles Lv7

lab1_report

lab_thinkings

Thinking 1.1

question

请阅读附录中的编译链接详解,尝试分别使用实验环境中的原生 x86 工具链(gccldreadelfobjdump 等)和 MIPS 交叉编译工具链(带有 mips-linux-gnu-前缀),重复其中的编译和解析过程,观察相应的结果,并解释其中向 objdump 传入的参数的含义。

answer
尝试使用x86工具链和MIPS交叉编译工具链
1. 创建新文件main.c
1
2
3
4
5
6
int main(){
int a=1;
int b=2;
int c=a+b;
return 0;
}
2.使用x86工具链

首先使用gcc -c main.c指令形成main.o文件,之后使用objdump -DS main.o进行反汇编。就能得到如下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
Disassembly of section .text:

0000000000000000 <main>:
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%rbp)
f: c7 45 f8 02 00 00 00 movl $0x2,-0x8(%rbp)
16: 8b 55 f4 mov -0xc(%rbp),%edx
19: 8b 45 f8 mov -0x8(%rbp),%eax
1c: 01 d0 add %edx,%eax
1e: 89 45 fc mov %eax,-0x4(%rbp)
21: b8 00 00 00 00 mov $0x0,%eax
26: 5d pop %rbp
27: c3 ret

Disassembly of section .comment:

0000000000000000 <.comment>:
0: 00 47 43 add %al,0x43(%rdi)
3: 43 3a 20 rex.XB cmp (%r8),%spl
6: 28 55 62 sub %dl,0x62(%rbp)
9: 75 6e jne 79 <main+0x79>
b: 74 75 je 82 <main+0x82>
d: 20 31 and %dh,(%rcx)
f: 31 2e xor %ebp,(%rsi)
11: 33 2e xor (%rsi),%ebp
13: 30 2d 31 75 62 75 xor %ch,0x75627531(%rip) # 7562754a <main+0x7562754a>
19: 6e outsb %ds:(%rsi),(%dx)
1a: 74 75 je 91 <main+0x91>
1c: 31 7e 32 xor %edi,0x32(%rsi)
1f: 32 2e xor (%rsi),%ch
21: 30 34 29 xor %dh,(%rcx,%rbp,1)
24: 20 31 and %dh,(%rcx)
26: 31 2e xor %ebp,(%rsi)
28: 33 2e xor (%rsi),%ebp
2a: 30 00 xor %al,(%rax)

Disassembly of section .note.gnu.property:

0000000000000000 <.note.gnu.property>:
0: 04 00 add $0x0,%al
2: 00 00 add %al,(%rax)
4: 10 00 adc %al,(%rax)
6: 00 00 add %al,(%rax)
8: 05 00 00 00 47 add $0x47000000,%eax
d: 4e 55 rex.WRX push %rbp
f: 00 02 add %al,(%rdx)
11: 00 00 add %al,(%rax)
13: c0 04 00 00 rolb $0x0,(%rax,%rax,1)
17: 00 03 add %al,(%rbx)
19: 00 00 add %al,(%rax)
1b: 00 00 add %al,(%rax)
1d: 00 00 add %al,(%rax)
...

Disassembly of section .eh_frame:

0000000000000000 <.eh_frame>:
0: 14 00 adc $0x0,%al
2: 00 00 add %al,(%rax)
4: 00 00 add %al,(%rax)
6: 00 00 add %al,(%rax)
8: 01 7a 52 add %edi,0x52(%rdx)
b: 00 01 add %al,(%rcx)
d: 78 10 js 1f <.eh_frame+0x1f>
f: 01 1b add %ebx,(%rbx)
11: 0c 07 or $0x7,%al
13: 08 90 01 00 00 1c or %dl,0x1c000001(%rax)
19: 00 00 add %al,(%rax)
1b: 00 1c 00 add %bl,(%rax,%rax,1)
1e: 00 00 add %al,(%rax)
20: 00 00 add %al,(%rax)
22: 00 00 add %al,(%rax)
24: 28 00 sub %al,(%rax)
26: 00 00 add %al,(%rax)
28: 00 45 0e add %al,0xe(%rbp)
2b: 10 86 02 43 0d 06 adc %al,0x60d4302(%rsi)
31: 5f pop %rdi
32: 0c 07 or $0x7,%al
34: 08 00 or %al,(%rax)
3.使用MIPS交叉编译工具链

首先使用mips-linux-gnu-gcc -c main.c指令形成main.o文件,之后使用mips-linux-gnu-objdump -DS main.o进行反汇编。就能得到如下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
main.o:     文件格式 elf32-tradbigmips


Disassembly of section .text:

00000000 <main>:
0: 27bdffe8 addiu sp,sp,-24
4: afbe0014 sw s8,20(sp)
8: 03a0f025 move s8,sp
c: 24020001 li v0,1
10: afc20004 sw v0,4(s8)
14: 24020002 li v0,2
18: afc20008 sw v0,8(s8)
1c: 8fc30004 lw v1,4(s8)
20: 8fc20008 lw v0,8(s8)
24: 00621021 addu v0,v1,v0
28: afc2000c sw v0,12(s8)
2c: 00001025 move v0,zero
30: 03c0e825 move sp,s8
34: 8fbe0014 lw s8,20(sp)
38: 27bd0018 addiu sp,sp,24
3c: 03e00008 jr ra
40: 00000000 nop
...

Disassembly of section .reginfo:

00000000 <.reginfo>:
0: e000000c sc zero,12(zero)
...

Disassembly of section .MIPS.abiflags:

00000000 <.MIPS.abiflags>:
0: 00002002 srl a0,zero,0x0
4: 01010005 lsa zero,t0,at,0x1
...

Disassembly of section .pdr:

00000000 <.pdr>:
0: 00000000 nop
4: 40000000 mfc0 zero,c0_index
8: fffffffc 0xfffffffc
...
14: 00000018 mult zero,zero
18: 0000001e 0x1e
1c: 0000001f 0x1f

Disassembly of section .comment:

00000000 <.comment>:
0: 00474343 0x474343
4: 3a202855 xori zero,s1,0x2855
8: 62756e74 0x62756e74
c: 75203130 jalx 480c4c0 <main+0x480c4c0>
10: 2e332e30 sltiu s3,s1,11824
14: 2d317562 sltiu s1,t1,30050
18: 756e7475 jalx 5b9d1d4 <main+0x5b9d1d4>
1c: 31292031 andi t1,t1,0x2031
20: 302e332e andi t6,at,0x332e
24: 地址 0x0000000000000024 越界。


Disassembly of section .gnu.attributes:

00000000 <.gnu.attributes>:
0: 41000000 mftc0 zero,c0_index
4: 0f676e75 jal d9db9d4 <main+0xd9db9d4>
8: 00010000 sll zero,at,0x0
c: 00070405 0x70405
objdump的参数简单介绍

objdump命令的常用参数有以下几个:

参数 含义
-d 将代码段反汇编 反汇编那些应该还有指令机器码的section
-D 与 -d 类似,但反汇编所有section
-S 将代码段反汇编的同时,将反汇编代码和源代码交替显示,源码编译时需要加-g参数,即需要调试信息
-C 将C++符号名逆向解析
-l 反汇编代码中插入源代码的文件名和行号
-j section: 仅反编译所指定的section,可以有多个-j参数来选择多个section

因此,指导书上的objdump -DS [executable file]这一命令中-DS表示将所有的section反汇编,并将反汇编代码和原码交替显示

Thinking 1.2

question

思考下述问题:

  • 尝试使用我们编写的 readelf 程序,解析之前在 target 目录下生成的内核 ELF 文件。

  • 也许你会发现我们编写的 readelf 程序是不能解析 readelf 文件本身的,而我们刚才介绍的系统工具 readelf 则可以解析,这是为什么呢?(提示:尝试使用 readelf -h,并阅读 tools/readelf 目录下的 Makefile,观察 readelfhello 的不同)

answer
解析target/mos

target目录下运行readelf -h mos的结果如下:

image-20230307171735319

用我们自己编写的readelf解析如下:

image-20230307181914115

解析tools/readelf/hellotools/readelf/readelf并比较区别

tools/readelf目录下运行readelf -h hello的结果如下:

image-20230307172451136

tools/readelf目录下运行readelf -h readelf的结果如下:

image-20230307172606602

通过观察发现,两个程序的类别不同,且readelf文件的类型为DYN而非EXEC,所以合理推断这是readelf不能解析自己的原因,文件类型不匹配。

Thinking 1.3

question

在理论课上我们了解到,MIPS 体系结构上电时,启动入口地址为 0xBFC00000(其实启动入口地址是根据具体型号而定的,由硬件逻辑确定,也有可能不是这个地址,但一定是一个确定的地址),但实验操作系统的内核入口并没有放在上电启动地址,而是按照内存布局图放置。思考为什么这样放置内核还能保证内核入口被正确跳转到?

(提示:思考实验中启动过程的两阶段分别由谁执行。)

answer

因为我们在kernel.lds中设置好了各个节被加载的位置,即最终的segment地址,同时使用了ENTRY(_start)指定了程序入口(即内核入口),最终保证了内核入口能够被正确跳转。

lab_difficulties

lab1本身题目不难,难度在于如何理解源码乃至其背后的原理。主要难点包括如何使用指针来获取ELF文件中各个sectionsegment中的数据和printk实现逻辑的梳理。

lab1叫做操作系统的启动,但由于GXemul本身提供了bootloader的功能,所以lab1的任务可以简化为加载内核到内存和跳转到内核的入口两个任务。

常规的启动流程涉及 bootloader 对软硬件的初始化,十分复杂。但 MOS 运行的环境是 GXemul 模拟器。 GXemul 支持直接加载 ELF 格式的内核,因此我们只需要把内核编译成正确的 ELF 可执行文件就可以启动了

从操作系统角度理解 MIPS 体系结构

首先是对于lab1非常重要的一张图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
* Part 2. Our conventions.
*/

/*
o 4G -----------> +----------------------------+------------0x100000000
o | ... | kseg2
o KSEG2 -----> +----------------------------+------------0xc000 0000
o | Devices | kseg1
o KSEG1 -----> +----------------------------+------------0xa000 0000
o | Invalid Memory | /|\
o +----------------------------+----|-------Physical Memory Max
o | ... | kseg0
o KSTACKTOP-----> +----------------------------+----|-------0x8040 0000-------end
o | Kernel Stack | | KSTKSIZE
/|\
o +----------------------------+----|------
|
o | Kernel Text | |
PDMAP
o KERNBASE -----> +----------------------------+----|-------0x8001 0000 |
o | Exception Entry | \|/
\|/
o ULIM -----> +----------------------------+------------0x8000 0000-------
o | User VPT | PDMAP
/|\
o UVPT -----> +----------------------------+------------0x7fc0 0000 |
o | pages | PDMAP
|
o UPAGES -----> +----------------------------+------------0x7f80 0000 |
o | envs | PDMAP
|
o UTOP,UENVS -----> +----------------------------+------------0x7f40 0000 |
o UXSTACKTOP -/ | user exception stack | BY2PG
|
o +----------------------------+------------0x7f3f f000 |
o | | BY2PG
|
o USTACKTOP ----> +----------------------------+------------0x7f3f e000 |
o | normal user stack | BY2PG
|
o +----------------------------+------------0x7f3f d000 |
a | |
|
a ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
a . .
|
a . .
kuseg
a . .
|
a |~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
|
a | |
|
o UTEXT -----> +----------------------------+------------0x0040 0000 |
o | reserved for COW | BY2PG
|
o UCOW -----> +----------------------------+------------0x003f f000 |
o | reversed for temporary | BY2PG
|
o UTEMP -----> +----------------------------+------------0x003f e000 |
o | invalid memory |
\|/
a 0 ------------> +----------------------------+ ----------------------------
o
*/

这张图记录了我们lab1实验中的内存布局图,也是我们lab1的最大难点的解决利器,我们要学会利用这张图链接到正确的位置上。

掌握 ELF 文件的结构和功能

ELF 是一种用于可执行文件、目标文件和库的文件格式。ELF 格式是是 UNIX 系统实验室作为 ABI(Application Binary Interface)而开发和发布的,现在早已经是 Linux 下的标准格式了。我们在之前曾经看见过的.o 文件就是 ELF 所包含的三种文件类型中的一种,称为可重定位 (relocatable) 文件,其它两种文件类型分别是可执行 (executable) 文件和共享对象 (shared object) 文件,这两种文件都需要链接器对可重定位文件进行处理才能生成。下图时ELF文件的基本结构——

img

这里,我们再补充一下关于 ELF 文件中节(section)的概念。在链接过程中,目标文件被看成节的集合,并使用节头表来描述各个节的组织。换句话说,节记录了在链接过程中所需要的必要信息。

节名称 功能
.text 保存可执行文件的操作指令。
.data 保存已初始化的全局变量和静态变量。
.bss 保存未初始化的全局变量和静态变量。

完成 printk 函数的编写

可能是被OO影响,感觉printk的编写特别像递归下降的文法读入,所以实际编写时注重细节对每个参数分类处理即可。具体流程可以参考下面这张流程图。

img

我们的printk()函数时通过调用print.c文件中定义的 vprintfmt()函数来实现打印的,这个vprintfmt()函数实现了printf的核心逻辑,虽然函数很长,但是逻辑并不复杂。实际上就是,遍历一遍格式字符串fmt,如果没有遇到\0或者%就直接打印到终端,如果遇到%就按照%[flags][width][length]<specifier>的形式进行输出格式的解析,并以这种输出格式打印从可变参数列表获得的参数;如果遇到\n就结束整个打印过程。

lab_summary

MOS内核文件组织

目录 功能
init 目录 内核初始化相关代码
include 目录 存放系统头文件
lib 目录 存放一些常用的库函数,包括 vprintfmt
kern 目录 存放内核的主体代码
tests 目录 存放测试程序
tools 目录 存放一些实用工具,包括 readelf该目录下的 C 程序使用原生工具链构建(而非交叉编译),在宿主环境(而非 GXemul)下运行
target 目录 存放编译的产物
Makefile 用于组装 MOS 内核的 Makefile 文件

make用法

指令 功能
make run 使用 GXemul 仿真内核
make dbg 以调试模式启动 GXemul
make objdump 将内核镜像反汇编并输出到 target/mos.objdump 中
make clean 清空编译器构建的文件,以待重新编译

总结

Lab1主要让我们学习操作系统启动的基本流程、掌握ELF文件的结构和功能,以及最终完成一个printf函数的书写。本次Lab花费时间大约为8个小时,大多数时间都花在阅读源代码和思考源代码的逻辑上。由于完成每个功能依赖的代码文件不止一个,而且在vim中阅读代码终归不如在vscode等图形化编辑器上方便,因此给代码阅读和理解带来了不小的麻烦。此外,课程组提供的源代码中大量使用了“指针”,也给代码的理解带来了一定的困难。

  • Title: lab1_report
  • Author: Charles
  • Created at : 2023-03-07 12:38:53
  • Updated at : 2023-11-05 21:36:18
  • Link: https://charles2530.github.io/2023/03/07/lab1-report/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments