MIT6.828-Lab1-Part2-Bootloader

 

The Boot Loader

PC的软盘、硬盘都是以512字节为单位,被分成一个个扇区(sector)。每次读写操作都是扇区的整数倍进行。

存有bootloader程序的盘是可启动的(bootable),其第一个扇区被称作启动扇区(boot sector)。当BIOS找到启动盘之后:

  1. 将512(29=0x200)字节的启动扇区加载到物理地址 0x7c00-0x7dff
  2. CS:IP 设置为 0x0000:0x7c00,通过 jmp 指令跳转到 0x7c00,将PC的控制权交给Bootloader。

CD-ROM是以2048字节为单位,启动过程更复杂。但6.828使用传统的启动机制,这意味为bootloader程序需要小于512字节。bootloader包含两个文件:一个是汇编代码源文件 boot/boot.S,另一个是C源文件 boot/main.c

booloader作用为:

  1. 将处理的操作模式从实模式切换为32-bit的保护模式,只有这样才能访问超过1MB(220)的地址空间。
  2. 通过使用x86的特定IO指令,直接访问IDE磁盘设备寄存器,从磁盘中读取内核。

Exercise3:

  1. 熟悉GDB命令
  2. 在0x7c00处设置断点,使用GDB的x/Ni ADDR 对源码进行反汇编,和源码 boot/boot.S 反汇编后的 /obj/boot/boot.asm 文件做比较。
  3. 追踪到 boot/main.c 中的 bootmain(),更进一步地到 readsect()。找出和 readsect() 程序的每一条语句所对应的汇编指令。回到 bootmain(),找出把内核文件从磁盘读取到内存的那个 for loop 所对应的汇编语句。找出当循环结束后会执行哪条语句,在那里设置断点,继续运行到断点,然后运行完所有的剩下的语句。

加载内核

Exercise4:

  1. 阅读 The C programming Language 的 Sec5.1-Sec5.5
  2. 理解 pointers.c 文件的输出
    1. 重点理解输出的 1-6 行
    2. 理解输出的 2-4 行为什么是这样
    3. 输出的第5行为什么看起来像是程序崩溃了

ELF(Executable and linkable Format)文件是一种可执行文件和可链接文件的标准格式,是Unix系统中广泛使用的一种二进制文件格式。ELF文件包含了程序的代码、数据、符号表、重定位表等信息。它的结构分为:ELF Header、Program Header Table、Section Header Table。

  1. ELF Header:文件类型、机器类型(x86、ARM、MIPS)、入口地址(ELFHDR->e_entry)、程序头表偏移量(ELFHDR->e_phoff)
  2. Program Header Table:记载了此文件的各个段的信息。代码段、数据段、符号表段等
  3. Section Header Table:各个节区的信息。代码节区、数据节区、符号表节区等

在6.868中,对三个段感兴趣:

  • .text段:存放程序的可执行指令
  • .rodata段:存放所有只读数据的数据段,如字符串常量
  • .data段:存放所有初始化过的数据段,如有初始值的global变量

当linker(链接器)在计算内存布局时,在紧跟在 .data 段后面的 .bss 段中存储未初始化的global变量。由于C语言要求所有未初始化的global变量值为0,所以linker只需记录 .bss 段的地址和大小。由loader(装载器)将程序装入内存后赋予初值0

通过如下指令来观察kernel的所有段的名字、大小、链接地址

$ objdump -h obj/kern/kernel

Screenshot from 2023-04-23 22-13-30

  • VMA(virtual memory address):程序在运行时使用的虚拟内存地址,由linker生成,可以在链接过程中调整
  • LMA(load memory address):程序在内存中的实际物理地址

通常情况下VMA和LMA实现通的,例如 objdump -h obj/boot/boot.out 结果显示 boot.out 的 .text 段 VMA = LMA = 0x00007c00

ELF 文件的 Program Header Table 记录了如何load这些段(section),即 ELF 的哪些部分需要load到内存中,并且load到内存中的哪个地址。可以通过下面的命令进行查看:

$ objdump -x obj/kern/kernel

Screenshot from 2023-04-23 22-27-52

在Program Header中标为 LOAD 区域将被load到内存。联系 boot.c 的源码 readseg(ph->pa, ph->pmemsz, ph->p_offset) 进行理解

  • off:offset,偏移量。段的起始位置在文件中的偏移量
  • vaddr:virtual address,段在虚拟内存中的起始地址
  • paddr:physical address,段在物理内存中的起始地址
  • align:对齐方式(猜的)
  • filesz:file size,段在文件中的大小
  • memsz:memory size,段在内存中的大小
  • flags:段的标志:可读、可写、可执行

BIOS通常会把boot sector(启动扇区)加载到内存地址–0x7c00 ,这也被设定为启动扇区的 LMA/load_address,也是它的VMA/link_address,可以通过 objdump -x obj/boot/boot.out 查看boot的.text段验证。

boot/Makefrag 中写入 -Ttext 0x7c00 来设定启动扇区的 link address。后续这个地址会被linker使用,保证链接器产生正确的代码。

Exercise5:

  1. 修改 boot/Makefrag 中的 0x7c00,看看效果,最后别忘了还原

回头看上文 objdump -h obj/kern/kernel 的 .text 段,发现kernel告诉boot loader程序它希望加载在低地址(LMA = 0x00100000 is small),但是希望运行在高地址(VMA = 0xf0100000 is big)。后续会在下一小节讨论这个问题。

除了上述的段信息,ELF文件还有一个字段 e_entry 很重要,它指示了程序的入口(entry point)链接地址(link address),即程序运行时的 .text 段的内存地址。通过下面的命令查看,发现为 0x0010000c

$ objdump -f obj/kern/kernel

Screenshot from 2023-04-24 15-56-24

所以现在可以理解 boot/main.c,即boot loader程序的运行过程:从硬盘读取kernel的每个段(section)到内存中 后 跳转到kernel的入口处(entry point)。

Exercise6:

  1. 熟悉 gdb 命令。 x/Nx addr 可以查看从addr开始的N个word的内容,一个word是多少字节和汇编平台有关。
  2. 在BIOS进入boot loader的时刻和boot loader进入kernel的那一刻查看 0x00100000在内的连续8个word的内容,思考它们为什么不同?第二次这个内存处存放的是什么东西?

参考资料

  1. cnblog-fatsheep9146