今天我们来研究一下FreeBSD在x86平台上的启动引导代码,源代码的位置boot/i386/pmbr/pmbr.s。在分析代码之前,先简单介绍一些和启动引导有关的背景知识。
早先的硬盘都是使用MBR(主引导记录)方式进行分区管理和引导的。这种方式有几个特点:
- MBR是硬盘的第一个扇区(LBA0),开机时由BIOS负责装载到内存0x7c00地址处,然后从第1条指令开始执行;
- MBR中包含两部分重要内容:一是引导代码,二是分区表;
- MBR方式支持4个主分区,其中一个主分区可设置为扩展分区,扩展分区中的“子分区”,称之为逻辑分区;
- MBR的分区表用来表示LBA(逻辑块地址)的存储空间长度为32位,因而最多表示2^32个块,也就是说,MBR方式支持的硬盘容量最大为2TB;
近几年来,随着2T硬盘的使用,MBR就不够用了,替代MBR的,叫GPT。GPT实际上是Intel自20世纪末就开始开发一种新到分区表格式。而当时的硬盘不过几十G容量。Intel开发的新分区表格式使用64位作为LBA(参见pmbr.s中lba定义: lba: .quad 1),因此可以最大支持2^64个块。GPT方式支持的硬盘容量最大为?。GPT使用硬盘开始的34个块作为分区的管理信息使用,其中:
LBA 0:Protective MBR,兼容BIOS。
LBA 1: 分区表头。Partition table header。
LBA 2-33:共32个块,每个块中包含4个分区表项,每个分区标项128字节。
start的地址,即整个pmbr的链接基准地址为$ORG=0x600,所以main链接地址为0x600+delta, 其中delta=$main-start,即main和start之间的差值。链接地址不等于运行地址,真正的运行地址是BIOS装载的地址,开机后pmbr被装载到$LOAD=0x07c00位置。也就是说,当start标号后的第一条语句”cld”执行时,pc的值是0x7c00。sp即栈指针则指向$EXEC+SECSIZE*4 = 0x600+0x200*4=0xe00。
xorw %ax,%ax
movw %ax,%es
movw %ax,%ds
movw %ax,%as
movw $STACK,%sp
到此为止,指令都是从0x7c00顺序执行的。
下面,pmbr要将把自身从main标号开始到结束的代码区域整体搬迁到内存更低到位置去继续执行,具体复制到0x600+delta位置上。由于pmbr原本就是链接到0x600的,所以$start地址在汇编器看来就是0x600,尽管实际上被装载到0x7c00。代码移动之前,汇编器地址和实际运行地址的距离是(LOAD-EXEC);而当代码移动之后,汇编器地址和实际运行地址就重合了。所以目的起始地址为$main;源起始地址是多少呢?我们知道,装载和链接的差值,是(LOAD-EXEC),所以源起始地址为$main+(LOAD-EXEC),即$main-EXEC+LOAD。要移动的长度则是$SECSIZE-(main-start)。
movw $main,%di
movw $SECSIZE-(main-start),%cx
rep
movsb
+------------+
| |
| |
| |
| |
| |
+------------+ EXEC
^ | |
| | |
low memory | | |
| +---> +------------+ dest = main
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | +------------+ LOAD
| | | start: |
| | | |
high memory | | | ... |
+ | +- +------------+ source = main + LOAD - EXEC
| | | main: |
| | | |
SECSIZE - (main - start) +--+ | |
| | |
| | |
+- +------------+ SECSIZE
当代码移动完成后,就跳转到新到的地址:即$EXEC+(main-start)执行。那是不是应该使用:
jmp EXEC+(main-start) 或 jmp main
错了!汇编器在处理这条指令时,并不知道自己的真实运行地址。汇编器认为pmbr是从ORG或EXEC处开始运行的。start等于EXEC,所以上述地址实际上等于main。由于jmp指令时下一条指令地址为main。所以汇编器在计算jmp 偏移量时得出的结论是:偏移量为0。
为了让汇编器计算出正确的偏移量,我们看一下到底移动了多远。
汇编器在汇编jmp语句时,是要计算出要跳转地址相对于下一条指令地址的相对位移。由于下一条指令地址为main,要向前跳转的位移delta为(LOAD-EXEC),所以要jmp main - (LOAD - EXEC)。即:
jmp main-LOAD+EXEC
本文深入分析了FreeBSD在x86平台上的启动引导代码,包括MBR与GPT分区表的区别、代码迁移过程以及关键指令解释。
655

被折叠的 条评论
为什么被折叠?



