转载注明出处(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的二进制码:
- 0X7C00 EB 63 90 00 00 00 00 00 00 00 00 00 00 00 00 00
- 0X7C10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
- 0X7C20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
- 0X7C30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
- 0X7C40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
- 0X7C50 00 00 00 00 00 00 00 00 00 00 00 80 01 00 00 00
- 0X7C60 00 00 00 00 FF FA EB 07 F6 C2 80 75 02 B2 80 EA
- 0X7C70 74 7C 00 00 31 C0 8E D8 8E D0 BC 00 20 FB A0 64
- 0X7C80 7C 3C FF 74 02 88 C2 52 BE 88 7D E8 24 01 BE 05
- 0X7C90 7C F6 C2 80 74 48 B4 41 BB AA 55 CD 13 5A 52 72
- 0X7CA0 3D 81 FB 55 AA 75 37 83 E1 01 74 32 31 C0 89 44
- 0X7CB0 04 40 88 44 FF 89 44 02 C7 04 10 00 66 8B 1E 5C
- 0X7CC0 7C 66 89 5C 08 66 8B 1E 60 7C 66 89 5C 0C C7 44
- 0X7CD0 06 00 70 B4 42 CD 13 72 05 BB 00 70 EB 73 B4 08
- 0X7CE0 CD 13 73 0A F6 C2 80 0F 84 D8 00 E9 82 00 66 0F
- 0X7CF0 B6 C6 88 64 FF 40 66 89 44 04 0F B6 D1 C1 E2 02
- 0X7D00 88 E8 88 F4 40 89 44 08 0F B6 C2 C0 E8 02 66 89
- 0X7D10 04 66 A1 60 7C 66 09 C0 75 4E 66 A1 5C 7C 66 31
- 0X7D20 D2 66 F7 34 88 D1 31 D2 66 F7 74 04 3B 44 08 7D
- 0X7D30 37 FE C1 88 C5 30 C0 C1 E8 02 08 C1 88 D0 5A 88
- 0X7D40 C6 BB 00 70 8E C3 31 DB B8 01 02 CD 13 72 29 8C
- 0X7D50 C3 60 1E B9 00 01 8E DB 31 F6 BF 00 80 8E C6 FC
- 0X7D60 F3 A5 1F 61 FF 26 5A 7C BE 8E 7D E8 44 00 EB 0E
- 0X7D70 BE 93 7D E8 3C 00 EB 06 BE 9D 7D E8 34 00 BE A2
- 0X7D80 7D E8 2E 00 CD 18 EB FE 47 52 55 42 20 00 47 65
- 0X7D90 6F 6D 00 48 61 72 64 20 44 69 73 6B 00 52 65 61
- 0X7DA0 64 00 20 45 72 72 6F 72 0D 0A 00 BB 01 00 B4 0E
- 0X7DB0 CD 10 AC 3C 00 75 F4 C3 00 00 00 00 00 00 24 12
- 0X7DC0 0F 09 00 BE BD 7D 31 C0 CD 13 46 8A 0C 80 F9 00
- 0X7DD0 75 0F BE DA 7D E8 DA FF EB A4 46 6C 6F 70 70 79
- 0X7DE0 00 BB 00 70 B8 01 02 B5 00 B6 00 CD 13 72 D7 B6
- 0X7DF0 01 B5 4F E9 F8 FE 00 00 00 00 00 00 00 00 55 AA
下面是安装以后的MBR内内容:
- 0X7C00 EB 63 90 00 00 00 00 00 00 00 00 00 00 00 00 00
- 0X7C10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
- 0X7C20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
- 0X7C30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
- 0X7C40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
- 0X7C50 00 00 00 00 00 00 00 00 00 00 00 80 01 00 00 00
- 0X7C60 00 00 00 00 FF FA EB 07 F6 C2 80 75 02 B2 80 EA
- 0X7C70 74 7C 00 00 31 C0 8E D8 8E D0 BC 00 20 FB A0 64
- 0X7C80 7C 3C FF 74 02 88 C2 52 BE 88 7D E8 24 01 BE 05
- 0X7C90 7C F6 C2 80 74 48 B4 41 BB AA 55 CD 13 5A 52 72
- 0X7CA0 3D 81 FB 55 AA 75 37 83 E1 01 74 32 31 C0 89 44
- 0X7CB0 04 40 88 44 FF 89 44 02 C7 04 10 00 66 8B 1E 5C
- 0X7CC0 7C 66 89 5C 08 66 8B 1E 60 7C 66 89 5C 0C C7 44
- 0X7CD0 06 00 70 B4 42 CD 13 72 05 BB 00 70 EB 73 B4 08
- 0X7CE0 CD 13 73 0A F6 C2 80 0F 84 D8 00 E9 82 00 66 0F
- 0X7CF0 B6 C6 88 64 FF 40 66 89 44 04 0F B6 D1 C1 E2 02
- 0X7D00 88 E8 88 F4 40 89 44 08 0F B6 C2 C0 E8 02 66 89
- 0X7D10 04 66 A1 60 7C 66 09 C0 75 4E 66 A1 5C 7C 66 31
- 0X7D20 D2 66 F7 34 88 D1 31 D2 66 F7 74 04 3B 44 08 7D
- 0X7D30 37 FE C1 88 C5 30 C0 C1 E8 02 08 C1 88 D0 5A 88
- 0X7D40 C6 BB 00 70 8E C3 31 DB B8 01 02 CD 13 72 29 8C
- 0X7D50 C3 60 1E B9 00 01 8E DB 31 F6 BF 00 80 8E C6 FC
- 0X7D60 F3 A5 1F 61 FF 26 5A 7C BE 8E 7D E8 44 00 EB 0E
- 0X7D70 BE 93 7D E8 3C 00 EB 06 BE 9D 7D E8 34 00 BE A2
- 0X7D80 7D E8 2E 00 CD 18 EB FE 47 52 55 42 20 00 47 65
- 0X7D90 6F 6D 00 48 61 72 64 20 44 69 73 6B 00 52 65 61
- 0X7DA0 64 00 20 45 72 72 6F 72 0D 0A 00 BB 01 00 B4 0E
- 0X7DB0 CD 10 AC 3C 00 75 F4 C3 55 6D 01 00 00 00 80 20
- 0X7DC0 21 00 83 B2 02 30 00 08 00 00 00 E8 0B 00 00 D2
- 0X7DD0 21 30 05 FE FF FF FE F7 0B 00 02 00 F4 01 00 00
- 0X7DE0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
- 0X7DF0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA
对照可以看出,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地址部分清的指令如下:
- ~# dd if=/dev/zero of=/dev/sda seek=440 count=6 bs=1
- ~# dd if=/dev/zero of=/dev/sda seek=447 count=3 bs=1
- ~# dd if=/dev/zero of=/dev/sda seek=463 count=3 bs=1
- ~# dd if=/dev/zero of=/dev/sda seek=451 count=3 bs=1
- ~# dd if=/dev/zero of=/dev/sda seek=467 count=3 bs=1
上述指令清零Windows NT兼容幻数,以及硬盘第一基本分区、第二基本分区的起始和结束CHS地址。如果你使用多系统(Windows/Linux/Etc.)请谨慎运行。