前言

回头好好打基础,这次准备写写清华的ucore实验,废话不多说,先是lab1,代码还有相关的笔记资料放在github,都在study_notes文件下

正文

Part1

  1. 操作系统镜像文件ucore.img是如何一步一步生成的?(需要比较详细地解释Makefile中每一条相关命令和命令参数的含义,以及说明命令导致的结果)
  2. 一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?

  3. 首先我们大概分析一下Makefile中的主要代码,能够发现在207行看到.DEFAULT_GOAL这个默认目标文件是205定义的TARGETS,但是在Makefile中并没有TARGETS的定义,然后我上网翻了翻,原来是在tools/function.mk中由do_create_target宏中更改,再有create_target来进行调用,create_target函数根据载入的参数不同来返回相应的模板。所以在Makefile中调用一次create_target就会给TARGETS添加一个目标。最终生成了bin目录下的四个文件:bootblock,kernel,sign,ucore.img。

    178到186行:

    看到ucore.img是由kernel和bootblock生成,dd命令将起组装

    来看kernel是怎么生成的,143到149行

    依赖tools/kernel.ld文件对起进行编译,然后Makefile太复杂了,我们直接看他的输出,前面的一些gcc的是他编译具体文件的指令,然后看这个ld指令

    -T 让连接器使用指定的脚本

    ld链接了.o的文件生成了bin/kernel文件,具体的.o文件就是上面显示的这些。

    然后看bootblock是怎么生成的,159到166行:

    然后看输出bootblock.o生成需要依赖bootasm.o, bootmain.o文件,然后还有一个sign来进行签名

    ld相关的参数

    -m 模拟为i386上的连接器
    -nostdlib 不使用标准库
    -N 设置代码段和数据段均可读写
    -e 指定入口
    -Ttext 制定代码段开始位置

    打印出来的命令

    gcc相关的参数,直接翻网上的

    -ggdb 生成可供gdb使用的调试信息。这样才能用qemu+gdb来调试bootloader or ucore。
    -m32 生成适用于32位环境的代码。我们用的模拟硬件是32bit的80386,所以ucore也要是32位的软件。
    -gstabs 生成stabs格式的调试信息。这样要ucore的monitor可以显示出便于开发者阅读的函数调用栈信息
    -nostdinc 不使用标准库。标准库是给应用程序用的,我们是编译ucore内核,OS内核是提供服务的,所以所有的服务要自给自足。
    -fno-stack-protector 不生成用于检测缓冲区溢出的代码。这是for 应用程序的,我们是编译内核,ucore内核好像还用不到此功能。
    -Os 为减小代码大小而进行优化。根据硬件spec,主引导扇区只有512字节,我们写的简单bootloader的最终大小不能大于510字节。
    -I

    添加搜索头文件的路径

    -fno-builtin 除非用_builtin_前缀,否则不进行_builtin_函数的优化

    然后是生成sign的命令:

    打印出来的命令

    在结果里面没有打印出来sign是如何处理bootblock的,那我们只能从Makefile中来看

    答案给的命令打印出来是objcopy -S -O binary obj/bootblock.o obj/bootblock.out意思是拷贝二进制代码bootblock.o到.out文件中,然后是这个makefile命令

    bin/sign obj/bootblock.out bin/bootblock使用sign处理

    这里的sign的功能是通过编译执行一个预先写好的 tools/sign.c 文件,读取整个 obj/bootblock.out ,判断文件大小是不是小于等于 510 ,如果不是说明构建失败,退出。如果成功则填充 magic number 0xAA55 ,输出到 bin/bootblock 中。

    最后的将kernel和bootblock导入ucore.img中

    生成一个有10000个块的文件,每个块默认512字节,用0填充

    把bootblock中的内容写到第一个块

    从第二个块开始写kernel中的内容

    dd命令的相关参数(dd命令用于读取、转换并输出数据。)

if : input file 。

of : output file 。

count :读取并写入的 block size 数。

bs : block size ,默认 512 。

seek :跳过写入文件的 block size 数。

conv : conv 符号处理的东西有点多,就这里的 notrunc 是不设置 writesyscall 的参数 O_TRUNCdd 默认会设置),这就使得 dd 输出的文件已存在时,如果现在输出的比原来的要小,则文件大小保持被 dd 之前一样不变。

  1. 磁盘主引导扇区只有512字节,磁盘最后两个字节为0x55AA, 由不超过466字节的启动代码和不超过64字节的硬盘分区表加上两个字节的结束符组成

Part2

  1. 从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。
  2. 在初始化位置0x7c00设置实地址断点,测试断点正常。
  3. 从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较。
  4. 自己找一个bootloader或内核中的代码位置,设置断点并进行测试。

  5. 更改tools/gdbinit的内容设置为

    然后在lab1的目录下make debug

    si单步调试

    x /2i $pc //显示当前eip处的汇编指令

  6. 然后打断点在0x7c00处,c是continue的意思

    然后查看指令x /5i $pc

    能够看到打出来的汇编和我们的bootblock中的汇编代码是一样的

Part3

BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。请分析bootloader是如何完成从实模式进入保护模式的。

  • 为何开启A20,以及如何开启A20
  • 如何初始化GDT表
  • 如何使能和进入保护模式
  1. 为了兼容。8086只有20根总线,但是“段:偏移”寻址方式(段*4+偏移,段和偏移寄存器都是16位,所以最大为FFFF0+FFFF=10FFEF)支持的地址略高于2^20,当高于0x100000时,需要取模进行回卷,但是80286有了24根总线寻址范围为16MB,所以为了兼容,当A20总线关闭保持回卷,否则禁用回卷可以直接访问高位

  2. 这里直接贴叶姐姐的吧...GDT表太麻烦了,在bootblock.s中是建立了格式并初始化

  3. 有一个cr0寄存器置为1时,就进入保护模式

最后整个bootblock.s的代码分析在lab3/bootblocks_analysis.md文件中

Part4

通过阅读bootmain.c,了解bootloader如何加载ELF文件。通过分析源代码和通过qemu来运行并调试bootloader&OS,

  • bootloader如何读取硬盘扇区的?
  • bootloader是如何加载ELF格式的OS?
  1. 主要看bootmain主函数,一步一步进行分析,首先是读取第一个扇区,然后判断ELF头,然后是加载之后的扇区,最后是利用强制转换根据ELF头中的信息找到内核的入口来调用函数,具体的分析在bootblockc_analysis.md文件中
  2. 在libs/elf.h文件中定义了ELF头的信息,通过检查一个magic number判断,最后根据ELF文件头中的的信息跳转到e_entry,即内核的指定入口

Part5

主要就是程序堆栈那些东西,以前上什么破网安课的时候都说烂了,从高到低增长,函数参数从右到左压栈,压返回地址eip,压当前的ebp,最后是函数中的局部变量压入栈

然后根据注释我们可以写代码了

make qemu后打印结果

最后一行的unknow翻一下obj/bootblock.asm,查一下7d71

然后看一下应该就是指最初进入kernel的地址

Part6

  1. 中断描述符表(也可简称为保护模式下的中断向量表)中一个表项占多少字节?其中哪几位代表中断处理代码的入口?
  2. 请编程完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。在idt_init函数中,依次对所有中断入口进行初始化。使用mmu.h中的SETGATE宏,填充idt数组内容。每个中断的入口由tools/vectors.c生成,使用trap.c中声明的vectors数组即可。
  3. 请编程完善trap.c中的中断处理函数trap,在对时钟中断进行处理的部分填写trap函数中处理时钟中断的部分,使操作系统每遇到100次时钟中断后,调用print_ticks子程序,向屏幕上打印一行文字”100 ticks”。

  4. 8个字节,offset为32位,0-15为低16位,48-63为高16位,两段组成偏移量,16-31为段选择子得到段基址,加上前面的段偏移量就可以得到中断处理代码的入口。

  5. 根据题目注释写

    先是初始化中断向量表,然后循环填充,SETGATE宏,其中五个参数的意思是

  • 中断描述符表
  • 判断是中断还是trap,这里都是中断直接0
  • 段选择器,看下memlayout.h中定义的,一般是选择内核代码段,直接用他定义的宏
  • 表示偏移,就是__vectors中对应的值
  • 权限,这里除了用户态转内核态权限是User其他中断都应该是内核态权限

    然后设置用户态到内核态的(这里我没看到注释描述。。。直接看了下answer添加了一个),最后调用lidt指定将中断向量表加载到指定的寄存器中。

  1. 很简单不多叙述了

    最后的输出make qemu

总结

lab1还是主要熟悉下基本的操作,继续学习了,边社畜边做lab好慢啊。