【BUAA-OS】lab1_内核,启动,printf
发表于:2023-03-16 | 分类: BUAA操作系统
字数统计: 1.9k | 阅读时长: 7分钟 | 阅读量:

BUAA-OS lab1实验报告,关于elf,mos系统内核结构,系统启动流程,和printf函数的实现。

lab1_log

一个关于ssh权限的小插曲

  • 我按照OS的教程配置本地连接实验的跳板机,但是在配置好ssh的config之后到ssh连接这一步出现了问题,报错bad ownership
  • 在同学助教的帮助下发现.ssh文件夹的权限开大了反而有问题,应该开小一点,打开属性,点击安全,把权限删到只剩下用户就行。

思考题

Thinking1.1

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

  • 我们知道C语言从源代码到可执行文件需要四个步骤,预处理 -> 编译 -> 汇编 -> 链接,分别通过命令gcc -E/S/c/I来完成。我的复现过程如下:
    1.1
  • 通过man objdump我们知道,其中objdump传递的-DS(disassemble source)的意义是将后面传入的所有文件的section反汇编并输出结果(而且大写的S的意义是将反汇编的代码与原有机器码一同显示),但是我们将其重定向至另一个文件中,结果如下(只放出了main函数):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 链接前:
0000000000000000 <main>:
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # f <main+0xf>
f: 48 89 c7 mov %rax,%rdi
12: e8 00 00 00 00 call 17 <main+0x17>
17: b8 00 00 00 00 mov $0x0,%eax
1c: 5d pop %rbp
1d: c3 ret

# 链接后:
0000000000001149 <main>:
1149: f3 0f 1e fa endbr64
114d: 55 push %rbp
114e: 48 89 e5 mov %rsp,%rbp
1151: 48 8d 05 ac 0e 00 00 lea 0xeac(%rip),%rax # 2004 <_IO_stdin_used+0x4>
1158: 48 89 c7 mov %rax,%rdi
115b: e8 f0 fe ff ff call 1050 <puts@plt>
1160: b8 00 00 00 00 mov $0x0,%eax
1165: 5d pop %rbp
1166: c3 ret
  • 虽然我们生成的程序是x86的,但是我们能够明显的看出来,我们最后比较未链接的反汇编文件与完整编译后的反汇编文件中,原本出现printf函数的位置,地址从0变成了一段有意义的地址
  • 当然,我们也可以通过readelf指令来看两个可执行文件之间的节头表的差异这里就先不再展示了。

Thinking1.2

Thinking 1.2 思考下述问题:
• 尝试使用我们编写的 readelf 程序,解析之前在 target 目录下生成的内核 ELF 文件。
• 也许你会发现我们编写的 readelf 程序是不能解析 readelf 文件本身的,而我们刚才介绍的系统工具 readelf 则可以解析,这是为什么呢?(提示:尝试使用 readelf-h,并阅读 tools/readelf 目录下的 Makefile,观察 readelf 与 hello 的不同)

  • 我们自己实现的readelf函数的作用是输出ELF文件中所有节头的地址,我用它来解析target目录下的内核,产生的结果如下:
    1.2.1
  • readelf目录下的Makefile中,它们俩分别长这样: (hello文件被编译成了32位
    1.2.2
  • 而我的尝试确实也表明readelf无法解析本身但是readelf命令可以:
    1.2.3
  • 我们也用readelf命令来解析第一问中我们可以用自己的readelf解析的内核文件,发现结果如下:
    1.2.4
  • 比较两个的结果,除地址上的不同外,只有系统架构不一致,所以我认为原因是:是我们自己实现的readelf不能解析x86-64架构的文件。

Thinking1.3

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

  • 系统启动分为两个阶段,分别在ROM或FLASH上和RAM上执行,然后在ROM上的系统引导程序Bootloader会将Stage2的代码复制到RAM上,并跳转至入口函数(也就是内核入口),所以只要Bootloader跳转正确,内核放置在哪无所谓。

难点分析

Exercise 1.1

  • 主要是要补全readelf的c程序,它的功能是输出ELF文件中所有节头所在的地址,所以做这一题的核心是理解节头表的结构原理,以及看懂elf.h中结构体的意思
  • 我们从ehdr这个结构体的代码中可以知道,节头表的偏移、节头项的数目、每个节头项的大小。
  • 所以我们只需要获得节头表的首地址,然后在它的基础上累加,对于得到的每一项,需要注意的是我们仅仅只得到了表项,也就是一个Elf32_Shdr的示例,我们需要访问结构体的sh_addr属性获得最终的地址。即:
1
*(sh_table + i*sh_entry_size).sh_addr

Exercise 1.2

  • 第二道题的难点主要在于理解内核的节的结构,并通过Linker Script建立起链接
  • 剩下的比较的简单,按照题中给的示例及include/mmu.h 中的内存布局图做就行

Exercise 1.3

  • .S文件代表汇编文件,所以需要用汇编写出。
  • 只需要填写两行代码:
  • 首先建立起栈,将sp移动到初始栈顶的位置,看内存布局图我们知道,在0x8040_0000,所以可以用lui指令赋值。
  • 然后就是跳转,由于跳转之后不用再回退,所以用j指令即可。

Exercise 1.4

  • 然后就到了最具难度的一题:实现一个printk函数,最主要的是看懂指导书中关于几个关联的库的关系,还是比较清晰的。
  • 但是关于变长参数这一块,我本来还有点看不太懂(可能是我的问题),这里需要重点辨析的是传入的fmt指针,指向的是栈的最后一个参数(栈顶地址),由于压栈是从右向左压,所以最后一个参数就是形式化字符串,而ap的意义就是后面的参数表,只是需要初始化,以及通过va_arg(va_list ap, 类型)去访问。
  • 另外最重要的是指导书并没有详细给出我们需要在print.c中做的工作,所以一定要先看懂源代码再开始补全!!!(这可能也是指导书信息不对称的考虑吧,因为看懂源码的能力还是挺重要的,移植或补全别的东西可不会有指导书教你)

实验体会

  • 本次实验核心内容就是通过elf了解内核的结构以及它的形成掌握思考系统启动的流程完成一个printk的函数
  • 这是MOS操作系统运行起来的基础,大体上做到了与理论课程的内容相匹配,收获颇多。
  • 另外感慨的就是读懂源码的能力了,这个上面已经感慨过了,总之就是十分的重要(read the F**king source code!)
上一篇:
【BUAA-OO】ElevatorDispatcher1.0
下一篇:
【BUAA-数分2】北航信息类数分二资料