本文摘自:http://www.mouseos.com/win7/mbr.html
前话: 引导磁盘的 MBR 由 int 0x19 加载到 0x7c00,int 0x19 最后的工作是跳到 0x7c00 执行。 |
一、 我的 bochs 上的 windows 7 的 disk images 介绍
在 bochs 上,我为 windows 7 分配了 10g 的磁盘空间, 这 10g 的分空间,bochs 是这样分配的:cylinders = 20805, heads = 16, spt = 63
cylinders(柱面或道)是 20805,heads(磁头或面)是 16,spt(扇区/柱面 - 每道扇区数)为 63
那么:磁盘空间为:disk size = cylinders * 63 * heads * 512 = 20805 * 63 * 16 * 512 = 10,737,377,280 bytes = 10g
二、硬盘的 MBR(主引导记录)
MBR 是位于:0 扇区(逻辑扇区) 即:0 柱面(0-cylinder),0 磁头(0-head),1 扇区(1-sector)
大小为 512 bytes。
整个 MBR 的结构如下:
位置(hex)
|
大小(bytes)
|
描述
|
000 - 162
|
354 bytes
|
硬盘 MBR 引导记录(代码区)
|
162 - 1BD
|
92 bytes
|
MBR 数据区域
|
1BE - 1CD
|
16 bytes
|
分区表 1
|
1CE - 1DD
|
16 bytes
|
分区表 2
|
1DE - 1ED
|
16 bytes
|
分区表 3
|
1EE - 1FD
|
16 bytes
|
分区表 4
|
1FE - 1FF
|
2 bytes
|
MBR 标志(55AA)
|
1、磁盘分区表(Disk Partition Table)
在 MBR 里的后 64 个字节里是磁盘的分区表结构,可定义 4 个分区,每个分区 16 bytes,从 0x1be ~ 0x1fe 共 64 bytes。
表1:磁盘分区表1(DPT1)结构
位置(hex)
|
大小(bytes)
|
意义
|
描述
| |
1BE
|
1
|
分区的启动标志
|
80 =
| 可启动分区 |
00 =
| 不可启动区 | |||
1BF - 1C1
|
3
|
分区的起始扇区
|
1BF =
| heads,起始 heads (1 个 bytes) |
1C0 =
| sector,低 6 bits 表示起始 sector,这里只用该节字的低 6 bits 来表示 sector | |||
1C1 =
| cylinder,1C0 的高 2 btis 加上 1C1 的 8 bits 组成 10 bits 表示起始 cylinder | |||
1C2
|
1
|
文件系统
| 如:07 表示 ntfs 系统,详见:文件系统 | |
1C3 - 1C5
|
3
|
分区的结束扇区
| 其意义和起始扇区一致 | |
1C6 - 1C9
|
4
|
此分区前扇区数
| 这 4 bytes 表示此分区前有多少扇区(实际上等于此分区的起始扇区号),以 little-endian 排列的。 | |
1CA - 1CD
|
4
|
此分区扇区数
| 这 4 bytes 用来表示此分区共有多少扇区,同样是以 little-endian 排列的。 |
上面是分区1 的结构,与分区2 ~ 分区4 结构一致 。
2、下面看看我的 bochs 上安装的 windows 7 分区表
(1)分区1结构
/***** DPT1 分区表1:从 0x1be 到 0x1cd ******/ 000001BE 80 /* boot indicator */ 000001BF 20 /* 起始 header 号 */ 000001C2 07 /* 系统属性 ID 标记 */ 000001C3 DF /* 结束 header 号 */
000001CA 00200300 /* 此分区的扇区总数 */ |
可以看出分区1是:
★ boot indicator = 80 表示:该分区是可启动的分区。
★ 起始扇区是:0 - cylinder, 0x20 - heads, 0x21 - sector
1 byte 的 head 最大可表示:0xFF 个 heads,即:255 个 heads
6 bits 的 sector 最大表示: 0x3F 个 sector 即:63 个 sectors
10 bits 的 cylinder 最大表示:0x3FF 个 cylinder 即: 1023 个 cylinders
每个 cylinder 的扇区为:heads * 63 = 255 * 63 = 16065 sectors
那么:分区1的起始扇区是第几扇分区呢? 它的逻辑分区 L 是:
L = cylinder * 16065 + heads * 63 + sector - 1
= 0 * 16065 + 0x20 * 63 + 21 - 1
= 0x20 * 63 + 0x20
= 2048
因此,分区1的起始扇区是第 2048 号扇区,即说明,此分区前面有 2048 个扇区被保留(扇区号从 0 开始编号)
★ 分区1 的文件系统是 ntfs 文件系统。
★ 分区1 的结束扇区是: 0xdf - heads, 0x13 - sectors, 0x0c - cylinder
那么,结束扇区是第几号扇区呢?
L = cylinder * 16065 + heads * 63 + sectors - 1
= 0x0c * 16065 + 0xdf * 63 + 0x13 - 1
= 192780 + 14049 + 18
= 206848
结束扇区是第 206848 号扇区。
因此:分区的扇区数是:206848 - 2048 = 204800 个扇区
★ 分区1 前的扇区数是: 00080000
由于是 little-endian 排列,它的 size 是 0x00000800 即:2048 个扇区
正好符合前面说的 2048 个扇区。
★ 分区1 的扇区数是: 00200300
同样是 little-endian 排列,它的 size 是 0x00032000 即: 204800 个扇区
正好符合前面说的 204800 个扇区数。
由此可见,分区1 共有 100M 的磁盘空间。
2、分区 2 的结构
/************** DPT2 分区表2:从 0x1CE 到 0x1DD *******************/
000001CF DF /* 起始 header */ 000001D2 07 /* file type flag */ 000001D3 FE /* 结束 header */ 000001D6 00280300 /* 分区前扇区数 */ 000001DA 00C83C01 /* 此分区扇区数 */ |
可以看出分区2 是:
★ 该分区是不可启动的分区。
★ 起始扇区是:L = 0x0c * 16065 + 0xdf * 63 + 0x14 - 1 = 206849 号扇区,正好是分区1的结束扇区的下一个扇区
★ 文件系统同样是 ntfs
★ 结束扇区是:L = 0x3ff * 16065 + 0xfe * 63 + 0x3f - 1 = 16450559 号扇区。
★ 分区前扇区数:00280300 = 0x00032800 = 206848 扇区。
★ 分区的扇区数:00c83c01 = 0x013cc800 = 20760576 扇区数
那么,分区2的大小是 ≈ 10g 约等于 10g, 由于分区 1 的大小是 100M bytes,分区2大小约为 10g
这里有一个现象:
前面说的结束分区是 16450559 号扇区,明显是不对的。这是因为,cylinder 最大只能表示 1023 个,超过部分没办法表示,这里就以分区扇数为准。
:(
由此可见: |
三、 windows 7 的 MBR 代码分析
下面是 windwos 7 MBR 的完整结构:
00000000 33C0 xor ax,ax 0000000B BE007C mov si,0x7c00 00000017 50 push ax 0000001C FB sti 0000002D 83C510 add bp,byte +0x10 00000034 885600 mov [bp+0x0],dl 00000056 FE4610 inc byte [bp+0x10] 00000059 6660 pushad
/********** 下面的区域是 mbr 常量符: mbr_str **************/
/***** DPT1 分区表1:从 0x1be 到 0x1cd ******/ 000001BE 80 /* boot indicator */ 000001BF 20 /* 起始 header 号 */ 000001C2 07 /* 系统属性 ID 标记 000001C6 00080000 /* 此分区前的扇区总数 */ 000001CA 00200300 /* 此分区的扇区总数 */
/************** DPT2 分区表2:从 0x1CE 到 0x1DD *******************/ 000001CF DF /* 起始 header */ 000001D2 07 /* file type flag */ 000001D3 FE /* 结束 header */ 000001D6 00280300 000001DA 00C83C01
/******** 1DE - 1ED = DPT3 **********************/
|
分区3 和分区4 都是空的。
上面的代码是用 nasm 中的 ndisasmw 反汇编出来的结果的。事实上,还有一部分不是代码部分,是数据区域
从 0x00 - 0x162 :这是 MBR 的主体代码区域
从 0x162 - 0x1bd:这是 MBR 用到的数据区域
从 0x1be - 0x1fd: 这是 MBR 的磁盘分区表区域
从 0x1fe - 0x1ff:这是 MBR 的标志 "55AA"
1、 mbr 前期准备工作
00000000 33C0 xor ax,ax 0000000B BE007C mov si,0x7c00 00000017 50 push ax |
代码中,把所有 segment 都设为 0,然后从 0x7c00 开始复制 512 bytes 到 0x600 区域,然后转到 0x61c 开始执行
实际上是把 mbr 重新复制到 0x600 处,然后从指令 retf 的下一条指令,即:指令 sti 处开始继续执行下去。
但是此时,ip 是位于 0x61c 处,0x61c 与 0x7c1c 代码是一样的。
关于 0x7c00:
再跳到 0x7c00 处交由 MBR 代码继续执行。 |
2、 读磁盘到 0x7c00
MBR 的下一步工作就是读磁盘,下面来看一看 MBR 是如何读磁盘。
(1)读磁盘前检查分区是否可启动
代码接着走到:
0000001C FB sti |
跳到 0x34 处是读磁盘的代码,而跳到 0x13b 处是打印出错信息,最终的结果是 hlt
打印出错信息,稍后再看,下一步先看一看 MBR 是如何读磁盘。
(2)读磁盘的方式
在 win7 的 MBR 中使用 int 0x13 扩展功能来读取磁盘,代码接着走到:
代码接着: 0000002D 83C510 add bp,byte +0x10 00000034 885600 mov [bp+0x0],dl /* 首次的 dl 值是 int 0x19 中断遗留下的,dl = 0x80 */ 00000037 55 push bp /* 如果不成功的反复读 5 次 */ /* 测试是否支持 int 0x13 扩展功能 */ 00000047 5D pop bp 00000048 720F jc 0x59 /* not support */ 00000056 FE4610 inc byte [bp+0x10] /* flags */
|
★ 从 0x34 处是开始读磁盘的工作。
★ MBR 使用 [bp + 0x11] 和 [bp + 0x10] 两处内存作为临时变量。
[bp + 0x11] 实际上是 DPT2 的 boot indicator 处,而 [bp + 0x10] 则是 DPT1 的最后一个 byte
★ 代码接下来测试是否支持 int 0x13 的扩展功能
★ 如果支持,则:置标志位为 1
★ 如果不支持,则跳到 0x87 处,而 0x87 是使用 int 0x13 原有功能来读取磁盘。(非扩展)
(3)使用 int 0x13 扩展来读取磁盘
代码接着: /* 以下是使用 int 0x13 扩展功能读 disk */ 00000061 666800000000 push dword 0x0 00000077 B442 mov ah,0x42
00000081 83C410 add sp,byte +0x10 /* skip buffer */ |
代码使用 int 0x13 的 0x42 号功能读扇区。
红色标注部分:这部分实际上是一个局部分变量,这个变量是个 struct(结构体),其定义用 c 描述如下:
下面是用 c 描述为: struct buffer_packet |
这个 struct 的大小为 16 bytes 即:0x10 bytes,对就于指令 push word 0x10
它将从 [bp + 0x8] 处得出从哪个 sector 开始读,这个 [bp + 0x8] 从前面分析得知:它是 DPT1 的“分区前面的扇区数”
实际上它是标明分区1从哪个逻辑扇区开始。从前面可知:这个值就是 2048,因此,代码是从扇区 2048 开始读,
这个 2048 就是分区1 的开始扇区,它是个逻辑扇区概念,而非(cylinder/head/sector)表示法。
★ 代码中将从 2048 号扇区开始读 1 个扇区到 0x7c00 处,最后,代码将跳到 0x9b 处
更详细的 int 0x13 / ah = 0x42 用法,见:int 0x13 扩展读(ax = 0x42)
(4)使用 int 0x13 原有功能来读取磁盘
代码接着: /* read sector into memory */ |
如果不支持 int 0x13 扩展则使用原有功能读磁盘。
原有的读磁盘模式是:指定 head、cylinder 以及 sector
使用 int 0x13 原有功能的缺点: 由于使用 cylinder/head/sector 的模式: |
因此,有必要引入扩展的 int 0x13 功能
int 0x13 扩展功能的优点: 显而易见:int 0x13 扩展功能可访问的容量远远超过原有功能,可以说访问多大都行, 它使用了 struct 的访问模式:提供 64 位的起始扇区值,16 位的一次访问扇区数。 理论上,可以说是非常惊人的访问容量。 |
(5)读后续处理
代码接着:
/* 不成功的话,反得读 5 次 */ /* 读完 5 次后,仍旧不成功,打印出错信息,htl */
|
这段代码功能是处理 int 0x13 后续工作,如果 int 0x13 读失败,则将 disk controller 复位,再重新读,反复读 5 次,
仍旧不成功的,将打印出错信息后 hlt
(6)成功读取后
代码接着: /* 读 sector 成功后 */ 000000C3 FF7600 push word [bp+0x0] 000000C9 7517 jnz 0xe2 /* 缓冲区满,不能写*/ 000000CB FA cli 000000D0 E88300 call word 0x156 000000E2 B800BB mov ax,0xbb00 000000E7 6623C0 and eax,eax |
代码中再次核对,是否 55aa 标志,如果不是将打印出错信息后 hlt
代码中还将测试键盘读/写缓冲区,然后往键盘端口 0x60 写命令字。
call word 0x156 这个子过程就是测试读/写缓冲区过程。
代码最终将转去 0x127 执行
(7)代码最终会返回到 0x7c00 继续执行
代码接着: 000000FB 666807BB0000 push dword 0xbb07 /* 最终代码会重新跳会到 0x7c00 处执行 */ |
在 0x127 处的代码是 MBR 代码的最终出口。
代码显示:MBR 最终的出口是:重新跳会到 0x7c00 继续执行,而此时,0x7c00 处已经是 分区1 的代码。(即:2048 扇区)
很抱歉,我暂时不了解 int 0x1a 第 0xbb00 的作用。如果您了解 int 0x1a 中断第 0xbb00 作用,请您留言。:)
3、 MBR 将分区1 的 1 个扇区读入 0x7c00 后,跳到 0x7c00 处继续执行。
下面看看 MBR 剩余的错误处理模块:
代码接着: /* 打印出错信息,hlt */ 0000013E 32E4 xor ah,ah
|
这里需要使用 MBR 的字符串信息:
代码接着: /********** 下面的区域是 mbr 常量符: mbr_str **************/ 000001B2 00 |
在出错处理过程里,根据索引值找到相应的出错信息,然后使用 int 0x10 打印出错信息,最后是 hlt 停机指令。
4、最后的 1BE ~ 1FD 是磁盘分区表。
四、总结 MBR 的作用
MBR 的作用主要为:检测分区的合法性, 然后,读活动分区(分区1)的引导记录到 0x7c00,重新返回到 0x7c00 执行。