实验内容
- 阅读 uCore 实验项目开始文档 (uCore Lab 0),准备实验平台,熟悉实验工具。
- uCore Lab 1:系统软件启动过程
(1) 编译运行 uCore Lab 1 的工程代码;
(2) 完成 uCore Lab 1练习 1-4 的实验报告;
(3) 尝试实现 uCore Lab 1 练习 5-6 的编程作业;
(4) 思考如何实现 uCore Lab1 扩展练习 1-2。
编译运行 uCore Lab 1 的工程代码
命令行输入 make
有报错,可能是没装qemu,安装后重试。
报错,提示make: Nothing to be done for 'TARGETS'.
,输入make clean
后再次输入make
即可。
完成uCore Lab 1 练习 1-4 实验报告
练习 1:理解通过 make 生成执行文件的过程
列出本实验各练习中对应的OS原理的知识点,并说明本实验中的实现部分如何对应和体现了原理中的基本概念和关键知识点。
在此练习中,大家需要通过静态分析代码来了解:
操作系统镜像文件ucore.img是如何一步一步生成的?(需要比较详细地解释Makefile中每一条相关命令和命令参数的含义,以及说明命令导致的结果)
查看makefile文件中注释为create ucore.img这一部分的内容:
# create ucore.img
UCOREIMG := $(call totarget,ucore.img)
$(UCOREIMG): $(kernel) $(bootblock)
$(V)dd if=/dev/zero of=$@ count=10000
$(V)dd if=$(bootblock) of=$@ conv=notrunc
$(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc
$(call create_target,ucore.img)
UCOREIMG := $(call totarget,ucore.img)
表示调用call函数生成UCOREIMG
$(UCOREIMG): $(kernel) $(bootblock)
看出为了生成ucore.img,首先需要生成bootblock、kernel
$(V)dd if=/dev/zero of=$@ count=10000
表示创建一个每个块默认为512字节,一共10000个块,用0填充的文件,分配给UCOREIMG。
$(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc
表示从UCOREIMG的第二个块开始写kernel里的内容,seek=1代表跳过1个块之后再开始
$(call create_target,ucore.img)
直接返回。
查看makefile中注释为kernel部分:
KINCLUDE += kern/debug/ \
kern/driver/ \
kern/trap/ \
kern/mm/
KSRCDIR += kern/init \
kern/libs \
kern/debug \
kern/driver \
kern/trap \
kern/mm
KCFLAGS += $(addprefix -I,$(KINCLUDE))
$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS))
KOBJS = $(call read_packet,kernel libs)
# create kernel target
kernel = $(call totarget,kernel)
$(kernel): tools/kernel.ld
$(kernel): $(KOBJS)
@echo + ld $@
$(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS)
@$(OBJDUMP) -S $@ > $(call asmfile,kernel)
@$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel)
$(call create_target,kernel)
KOBJS = $(call read_packet,kernel libs)
表示使用call函数链接read_packet和kernel libs给KOBJS
kernel = $(call totarget,kernel)
代表表示调用call函数生成kernel,实际为文件bin/kernel
$(kernel): tools/kernel.ld
表示生成kernel文件需要依赖tools以及kernel.ld链接配置文件
@echo + ld $@中的echo
表示显示内容,ld代表链接,$@
代表目标文件,语句代表将下面的文件和目标文件链接起来,同时打印kernel目标文件名
$(kernel): $(KOBJS)
表示生成kernel时还需要依赖KOBJS
$(call create_target,kernel)
生成kernel直接返回
输入make "V="
查看kernel生成的部分
看到生成kernel需要的二进制文件 kernel.ld init.o stdio.o readline.o panic.o kdebug.o kmonitor.o clock.o console.o picirq.o intr.o trap.o vectors.o trapentry.o pmm.o string.o printfmt.o
即由 kernel 文件夹下的所有文件,用 gcc 编译后用 ld 指令链接在一起,生成 kernel文件
找到makefile
文件中注释为create bootblock
的内容
# create bootblock
bootfiles = $(call listf_cc,boot)
$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc))
bootblock = $(call totarget,bootblock)
$(bootblock): $(call toobj,$(bootfiles)) | $(call totarget,sign)
@echo + ld $@
$(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 $^ -o $(call toobj,bootblock)
@$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock)
@$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock)
@$(call totarget,sign) $(call outfile,bootblock) $(bootblock)
$(call create_target,bootblock)
$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc))
编译bootfiles生成.o文件
实际生成bootasm.o,bootmain.o的代码由宏批量生成。
@echo + ld $@
代表将下面的文件和目标文件链接起来,同时打印kernel目标文件名
@$(call totarget,sign) $(call outfile,bootblock) $(bootblock)
表示用bin/sign工具将之前的obj/bootblock.out用来生成bin/bootblock目标文件
$(call create_target,bootblock)
直接返回
输入make "V="
,查看生成bootblock
的代码
bootblock 由 bootasm.S 和 bootmain.c 文件生成,为系统的引导文件,然后用 sign.c 编译出来的程序处理 bootblock.out 生成 bootblock,使之符合引导文件的格式。
最后,用 dd 指令生成一个大文件,即有 10000 个块,每个块 512 字节,模拟磁盘扇区,然后将 bootblock 写入第一个块,从第二个块写入 kernel 文件。
一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
查看sign.c文件
则符合规范的硬盘主引导扇区的特征为
- 一共512个字节
- 倒数第二个字节是0x55
- 倒数第一个字节是0xAA
练习 2:使用qemu执行并调试lab1中的软件。(要求在报告中简要写出练习过程)
为了熟悉使用qemu和gdb进行的调试工作,我们进行如下的小练习:
- 从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。
- 在初始化位置0x7c00设置实地址断点,测试断点正常。
- 从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较。
- 自己找一个bootloader或内核中的代码位置,设置断点并进行测试。
从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行
根据附录的内容,查看BIOS的代码并单步调试
修改 lab1/tools/gdbinit
set architecture i8086 //将执行模式设为i8086
target remote :1234 //使用本地端口1234进行qmenu和gdb之间的通信
然后在lab1终端输入make debug
,在gdb调试界面输入si单步跟踪BIOS的执行,可用通过语句x /2i $pc
查看当前汇编指令
可见开始gdb在BIOS的第一条指令0xfff0处停止。
输入si
后,能看到gdb跳转到下一地址,即可单步跟踪,输入x /2i $pc
会显示当前eip处的汇编指令,可以正常调试。
在初始化位置0x7c00设置实地址断点,测试断点正常。
在gdbinit 内设置位置0x7c00的断点,如下
file bin/kernel
target remote :1234
set architecture i8086
b *0x7c0a
continue
x/2i $pc
接着 make debug
,如下
可见初始显示了0x7c00开始的两条指令,并能继续用si调试,符合在在 gdbinit 中定义的操作
从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较。
为了方便查看我们反汇编出来的代码,修改 Makefile 中关于 debug 的部分,将运
行的汇编指令存入 q.log 中。即调用qemu时加上 -d in_asm -D q.log 参数。
将这部分代码
debug: $(UCOREIMG)
$(V)$(QEMU) -S -s -parallel stdio -hda $< -serial null &
$(V)sleep 2
$(V)$(TERMINAL) -e "gdb -q -tui -x tools/gdbinit"
替换成如下代码
debug: $(UCOREIMG)
$(V)$(TERMINAL) -e "$(QEMU) -S -s -d in_asm -D $(BINDIR)/qemu.log -parallel stdio -hda $< -serial null"
$(V)sleep 2
$(V)$(TERMINAL) -e "gdb -q -tui -x tools/gdbinit"
这样能在编译时将运行的汇编指令存入了 q.log 。在 bin 文件夹打开 q.log 。
而 q.log 在19262行开始的内容如下
bootasm.S
与bootblock.asm
的代码如下
可见反汇编得到的代码与bootasm.S和bootblock.asm基本相同。
自己找一个bootloader或内核中的代码位置,设置断点并进行测试。
在0x00007c0a处设置断点并调试,将 gdbinit 修改如下再make debug
file bin/kernel
target remote :1234
set architecture i8086
b *0x7c0a
continue
x/2i $pc
可见成功在0x00007c0a处设置了断点,显示了前两条指令,并能进行进一步调试。
练习 3:分析bootloader进入保护模式的过程。(要求在报告中写出分析)
BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。请分析bootloader是如何完成从实模式进入保护模式的。
提示:需要阅读小节“保护模式和分段机制”和lab1/boot/bootasm.S源码,了解如何从实模式切换到保护模式,需要了解:
- 为何开启A20,以及如何开启A20
- 如何初始化GDT表
- 如何使能和进入保护模式
为何开启A20,以及如何开启A20
根据附录