ucore OS lab: bootloader启动ucore (1)

理解通过make生成执行文件的过程

1.操作系统镜像文件ucore.img是如何一步一步生成的?(需要比较详细地解释Makefile中每一条相关命令和命令参数的含义,以及说明命令导致的结果)

通过 make V= 命令可看build过程中,make执行了哪些命令。

出现最多的编译命令,如以下形式 (除源文件和目标文件外,其他参数差不多):

gcc -Ikern/init/ -march=i686 -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/init/init.c -o obj/kern/init/init.o

通过源文件和目标文件进行逐步分析:

  • 编译 kern/init/init.c ,生成 obj/kern/init/init.o
  • 编译 kern/libs 下的 stdio.c readline.c, 在obj/kern/libs 目录下生成对应的.o文件
  • 编译 kern/debug 下的panic.c kdebug.c kmonitor.c, 在obj/kern/debug 目录下生成对应.o文件
  • 编译 kern/driver 下的 clock.c console.c picirq.c intr.c, 同上在对应目录生成.o文件
  • 编译 kern/trap 下的trap.c vectors.S trapentry.S, 在对应目录生成.o文件
  • 编译 kern/mm 下的pmm.c 文件,生成 obj/kern/mm/pmm.o
  • 编译 libs 下的 string.c printfmy.c 文件, 在 obj/libs下生成对应.o文件
  • ld命令指定tools目录下的kernel.ld作为link的script,将上述.o文件链接生成 bin/kernel
  • 编译 tools/sign.c, 生成sign.o并进一步得到 bin/sign
  • 编译 boot目录下的 bootasm.S 和 bootmain.c编译生成相应.o文件,两个文件链接生成 obj/bootblock.o, 最后通过sign工具生成 bin/bootblock
  • 通过 dd 命令将bin目录下的bootloader和kernel写到 ucore.img

对应到 makefile 文件中:

# 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)

totarget函数的作用是给参数加上路径前缀: \
totarget = $(addprefix $(BINDIR)$(SLASH),$(1))

if: 输入文件 \
of:输出文件 \
count:拷贝的块数\
seek = 1:从输出文件跳过1个块数再开始拷贝 \
conv = notrunc : 输入文件比输出文件小时,不截断输出文件 \
dd命令参考链接:https://www.runoob.com/linux/linux-comm-dd.html

从上可知, ucore.img 的生成需要 bootblock 和 kernel 两个二进制文件。

接下来是 bootblock 对应的makefile部分 :

# 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)

其中,
bootfiles = $(call listf_cc,boot)

listf_cc 函数的作用是从 boot/* 中过滤出所有 .c 和 .S 文件,即 bootmain.c 和 bootasm.S, 在makefile中截取了如下相关描述:

CTYPE   := c S
listf_cc = $(call listf,$(1),$(CTYPE))  
# list all files in some directories: (#directories, #types) 
listf = $(filter $(if $(2),$(addprefix %.,$(2)),%),\
          $(wildcard $(addsuffix $(SLASH)*,$(1)))) 
# wildcard会展开后面的通配符,即返回符合 boot/*的所有内容,且以空格分隔

$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc)) ' \
通过这条命令,从 bootfiles 循环取出源文件名,会批量生成gcc编译命令,具体的过程较为复杂。最后得到 bootasm.o 和 bootmain.o。

实际命令:


gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs \
    -nostdinc  -fno-stack-protector -Ilibs/ -Os -nostdinc \
    -c boot/bootasm.S -o obj/boot/bootasm.o

其中关键的参数为:(直接从lab1_result拿过来的,因为的确好多不认识...,有些还难查)
    -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<dir>  添加搜索头文件的路径

gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc    -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootmain.c -o obj/boot/bootmain.o

新出现参数:      
    -fno-builtin  除非用__builtin_前缀,否则不进行builtin函数的优化

下面一段就是将生成的 bootasm.o , bootmain.o 和 sign 进行连接,得到二进制程序 bootblock 的 makefile 部分:

$(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)

实际命令:

#首先生成bootlock.o
ld -m    elf_i386 -nostdlib -N -e start -Ttext 0x7C00 obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o
 其中关键的参数为:
    -m <emulation>  模拟为i386上的连接器
    -nostdlib  不使用标准库
    -N  设置代码段和数据段均可读写
    -e <entry>  指定入口
    -Ttext  制定代码段开始位置

#拷贝二进制代码bootblock.o到bootblock.out
 objcopy -S -O binary obj/bootblock.o obj/bootblock.out
    其中关键的参数为:
        -S  移除所有符号和重定位信息
        -O <bfdname>  指定输出格式

#使用sign工具处理bootblock.out,生成bootblock
  bin/sign obj/bootblock.out bin/bootblock

从上可见 bootblock的生成还需要 sign。实验指导书中解释这是一个C语言小程序,是辅助工具,用于生成一个符合规范的硬盘主引导扇区。
这是生成sign的 makefile 部分:

# create 'sign' tools
$(call add_files_host,tools/sign.c,sign,sign)
$(call create_target_host,sign,sign)

实际命令:

gcc -Itools/ -g -Wall -O2 -c tools/sign.c \
    -o obj/sign/tools/sign.o
gcc -g -Wall -O2 obj/sign/tools/sign.o -o bin/sign

下面是生成 kernel 的 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)

KINCLUDE: 头文件搜索目录 \
KSRCDIR: 源文件目录 \
KOBJS: 包含了所有连接需要的.o文件 \
KCFLAGS: gcc编译命令参数

$(call add_files_cc, $(call listf_cc,$(KSRCDIR)), kernel, $(KCFLAGS))

此条命令批量地生成编译命令,这里生成 .o 文件的编译命令类似,以init.o为例(即开头所述的那条)

gcc -Ikern/init/ -march=i686 -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/init/init.c -o obj/kern/init/init.o

link 的实际命令:

ld -m    elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel  obj/kern/init/init.o obj/kern/libs/stdio.o obj/kern/libs/readline.o obj/kern/debug/panic.o obj/kern/debug/kdebug.o obj/kern/debug/kmonitor.o obj/kern/driver/clock.o obj/kern/driver/console.o obj/kern/driver/picirq.o obj/kern/driver/intr.o obj/kern/trap/trap.o obj/kern/trap/vectors.o obj/kern/trap/trapentry.o obj/kern/mm/pmm.o  obj/libs/string.o obj/libs/printfmt.o

# -T 后指定link的script

当bootblock 和 kernel 都准备好后,即可用 dd 装到 ucore.img 中了。

makefile教程参考 : https://seisman.github.io/how-to-write-makefile/index.html

2.一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?

前面提到sign是一个c语言辅助工具,用于生成一个符合规范的硬盘主引导扇区。从sign.c中可发现主引导扇区是512字节大小,第511个字节是0x55,第512个字节是0xAA.

发表评论