深入理解 GNU GRUB - 02 boot.S 2.2 MBR结构 2.3 boot.S代码结构

来源:互联网 发布:在淘宝网上开店 编辑:程序博客网 时间:2024/05/15 04:26

转载注明出处(cppgp: http://blog.csdn.net/cppgp)

 

2.2 MBR结构
历史悠久的MBR结构自从IBM兼容PC出现以来一直就没变过(但是为支持2TiB以上硬盘而出现的GUID/EFI结构将更改MBR结构)。MBR结构分为三部分,分别是引导指令、分区表DPT (Disk Partition Table)、幻数Magic (Magic=0x55AA)。其中引导指令占用446字节(0~0x1BD),DPT占用64字节(0x1BE~0x1FD),Magic占用2字节。
Magic值总是等于0x55AA,用来标记MBR的有效性。大多数BIOS检测Magic值判断是否为可引导设备,但是也有些BIOS使用另外的字段检测。在所有的小端设备上(例如80x86机器),在写入时需要设置为0xAA55。
DPT是硬盘分区表(Disk Partition Table)的缩写。MBR支持4个基本分区项,每个分区项占用16字节。可以将其中一个基本分区项标记为扩展分区(逻辑分区),扩展分区的第一个扇区称为EBR (Extended Boot Record) ,和MBR有类似的结构,但是只能利用两个基本分区项,其中一个用来划分分区,另一个指向新的EBR,这样就可以实现更多的分区。每个分区项的布局完全一致,如下:
偏移量        字节数        描述
0x00        1        分区状态,
0x80为可引导分区
0x00为不可引导分区
其他值无效
0x01        3        该分区第一个扇区CHS地址,格式见稍后描述
0x04        1        分区文件系统格式,比如NTFS/FAT32/Linux等
0x05        3        该分区最后一个扇区CHS地址,格式见稍后描述
0x08        4        该分区第一个扇区的绝对LBA地址
                          表示从磁盘开始到该分区的扇区数
0x0C        4        该分区的扇区数量

3字节的CHS地址结构如下:
偏移量            描述
0x00            磁头
0x01            低6位表示扇区,高2位表示柱面的9~10位
0x04            柱面的低8位

对于CHS结构还有疑问者,查阅2.1.4的CHS模式读可加强理解。因为CHS寻址有7.88GiB限制,现在的硬盘一般都是用LBA寻址,因此分区表中有用的字段是状态字段(0x00)、分区格式(0x03)、扇区偏移量(0x08~0x0B)和扇区数量(0x0C~0x0x0F)。我在虚拟机上测试过,清零硬盘分区的起始CHS地址和结束CHS地址不会导致错误。
446字节的引导指令负责加载另外的磁盘数据,进而引导整个系统。而这也正是boot.S所完成的功能。1.3节的内容大概介绍了加载磁盘数据的过程,而后文的boot.S代码结构分析和详细注释展示具体的实现过程。

2.3 boot.S代码结构
boot.S生成512字节的机器码,其中0x0~0x1BD (0~445) 共446字节是磁盘(硬盘/软盘)均可能用到的指令;0x1BE~0x1FD (446~510) 共64字节指令是软盘读取需要的指令,完成软盘驱动器复位、读取;0x1F~0x1FF (511~512) 是标识字段为0x 55AA(小端上需要表述为0xAA55)。GRUB安装程序判断存储媒介是硬盘或软盘。如果是硬盘,写入0x0~0x1BD和0x1FE~0x1FF两个字段。如果是软盘则写入整个MBR(512字节)。下面先简单介绍代码对指令位置的安排,其中0x7C**是指MBR加载到内存后对应指令所在的内存地址。
boot.S首先是一个跳转指令和一个空指令,占用3字节空间(0x7C00~0x7C03)。
之后保留一字节空间(0x7C04),后续代码保存BIOS调用探测到的读模式(LBA/CHS)到这里。执行到GRUB的第二步时还会使用这里保存的读模式标记。
之后是BIOS参数块BPB (BIOS parameter block)所在空间,共保留0x56(0x7C04~0x7C59)字节,当前只用到开始的16字节。BPB首字节(0x7C04)用来保存BIOS调用探测到的读模式(LBA/CHS),执行到GRUB的第二步时还会使用这里保存的读模式标记。接下来的16字节(0x7C05~0x7C14)用作BIOS LBA读调用的DAP(参见2.1.2),这16个字节是和CHS参数块是复用的,如果是CHS,则用作保存BIOS调用获取的驱动器CHS参数,后来的CHS读扇区用它判定扇区是否越界。
之后是2字节的跳转指令(0x7C5A~0x7C5B),加载完毕后跳向此处执行,因为jmp不支持立即数,因此保存在这里,值为0x8000。
之后是8字节的GRUB内核起始扇区LBA地址(0x7C5C~0x7C63),注意顺序,首先是低4字节,然后是高4字节。默认值为低4字节0x01,高4字节0x00,即磁盘的第二个绝对扇区。
之后是一字节的驱动器(0x7C64),默认设置为0xFF,安装时候改写成正确的引导驱动器,如果代码中探测到依然是0xFF,则会赋默认驱动器0x80(第一块硬盘编号)。
之后关闭中断,检测磁盘驱动器,设置寄存器,设置堆栈,打开中断,使用BIOS例程判断读取模式,读取磁盘数据,保存读取模式(保存到mode即0x7C04位置),最后设置正确的寄存器,然后跳转到第二步执行。
0x1BE~0x1FD是只针对软盘读取的指令。这些指令不会安装到硬盘MBR(硬盘上这里是DPT区域,写入DPT会破坏分区)。
0x1FE~0x1FF保存标识数0xAA55(0x1FE值为0x55,0x1FF值为0xAA,注意0xAA55是针对小端序处理器的)。
下面是编译(未安装)的grub-1.98/boot.S的二进制码:


下面是安装以后的MBR内内容:


对照可以看出,DPT部分是有差异的,这是因为boot.S编译后的指令码中这部分是软盘复位和读指令,而硬盘MBR中保存DPT。正如同前文所述,我尝试把DPT中CHS地址相关的部分给置0后,测试GRUB仍然正常工作(偏移量是)。
另外一个差异是0x7DB8~0x7DBE空间的6个字节,boot.S注释说是为了兼容Windows NT,因为Windows NT在这里插入了一个幻数,我尝试把这6个字节(偏移量0x1B8~0x1BD)置0,经测试GRUB是可以正常工作的。
8字节的GRUB内核安装扇区也一样,是因为我的虚拟机的GRUB内核确实安装在第二个扇区,如果是和Windows共存的双系统安装,这8字节可能会有所差异(需要指向实际的安装扇区)。
MBR中Windows NT幻数和DPT中CHS地址部分清的指令如下:


上述指令清零Windows NT兼容幻数,以及硬盘第一基本分区、第二基本分区的起始和结束CHS地址。如果你使用多系统(Windows/Linux/Etc.)请谨慎运行。

原创粉丝点击