原文链接:http://www.mouseos.com/win7/
前话: 这部分是由 MBR 代码从磁盘的 2048 号扇区读 1 扇区取到 0x7c00,然后跳到 0x7c00 继续执行。 因此,这部分代码仍是在 0x7c00 区域里运行,而原来的 MBR 已经被覆盖。 |
一、 DBR 的结构
下面来看一看 DBR 的结构,如下:
位置(hex)
|
大小(bytes)
|
描述
|
000 - 001
|
2 bytes
| 跳转代码:jmp short 0x54 它将跳到 0x54 处执行 |
002 - 053
|
82 bytes
| 这部分是 DBR 执行期间变量区域。 |
054 - 18B
|
312 bytes
| 这部分是 DBR 代码部分 |
18C - 1FD
|
114 bytes
| 这部分是 DBR 的数据区,字符串常量 |
1FE - 1FF
|
2 bytes
| DBR 的标志(55AA) |
第二部分的变量区域,是 DBR 被加载到 0x7c00 执行时,由于代码中需要使用临时的地方存储数据。
因此,开辟了这部分作为局部变量区,但其中也包含了一些常量数据。
二、 DBR 代码分析
现在来看一看 DBR 的代码部分。
(1)DBR 前期准备工作
代码如下: 00000000 EB52 jmp short 0x54 ... ... 00000054 FA cli |
由第 1 条指令 jmp short 0x54 跳到 0x54 处执行。
这段代码目的是,设置 sp 为 0x7c00,并且 cs 为 0x07c0,然后继续往下执行。但此时 cs 已经变为 0x07c0,而不是之前的 0x0000
(2)检测文件系统
代码接着: 00000066 88160E00 mov [0xe],dl /* 驱动器 ID,即:0x80 */ |
这里使用了变量 [0x0e],这个变量就位于前面所说的变量区域,用来放置 驱动器(硬盘)的 ID 值。
代码将检测分区是否为 NTFS 文件系统。这个 [0x03] 是常量,在本例 windows 7 中,它的值是 "NTFS" 字符串。
不是 NTFS 系统的话,将打印错误信息,然后 hlt
(3)检测是否支持 int 0x13 扩展
代码接着: 00000075 B441 mov ah,0x41 0000007C 720C jc 0x8a /* not support */ 0000008A E9DD00 jmp word 0x16a /* 出错处理 */ |
和 MBR 代码一样,这里也要检测 BIOS 是否支持 int 0x13 扩展功能,如果不支持的话,将打印出错信息,最终将 hlt 停机处理
(4)下一步,获取磁盘参数
这些磁盘参数,包括:
★ 磁盘的 cylinders 数量
★ 磁盘的 heads 数量
★ 每道的 sectors 数
★ 磁盘总共有多少扇区
★ 每个扇区有多少字节。
代码接着: 0000008D 1E push ds 000000A0 9F lahf /* 测试获取的 drivers 参数中的 "每扇区字节数" 是否与 DBR 中既定的 "每扇区字节数" 相等 */ |
先来看一看,蓝色部分标注的代码,这里有一个巧妙之处。
代码片断1: |
这里实际上是预留了 0x18 + 0x02 = 0x1A 个字节,即:26 个字节。
为什么不直接用 sub sp, 0x1a 来预留 0x1a 个字节呢,下面继续解读下去。
这里使用了 int 0x13 的第 0x48 号功能来获取磁盘(硬盘)的参数,这些磁盘参数在上面已经列出。
int 0x13 的 0x48 号功能需要一个缓冲区来存放获取的参数值
那么,现在来看一看这个缓冲区结构的定义,用 c 来描述如下:
用 c 描述如下: struct driver_parameters } buffer; |
这个磁盘参数结构总共是 26 个字节,sectors 数是个 64 位值。
回到前面的代码:push word 0x1a 执行完后,sp 实际上对应着的 buf_size 这个变量,表示 driver_parametes 这个结构体的字节数,即:sizeof(buffer)。
因此,通过 push 来定位到 buf_size 变量,实际上等于 buf_size = 0x1a 将 buf_size 变量赋值为 0x1a
而后面经过 add sp, 0x1a 修复 stack 后,代码:pop ax,正好用来取出 buffer 的最后一个 word 值,即:ax = bytes_per_sector
这段代码,玩弄了一点小技俩。
最后,ds:si 指向这块缓冲区的地址。
代码片断2: 0000009A 8BF4 mov si,sp |
从上面看出,这块缓冲区就是在 stack 开辟出来的 26 个字节的空间,即:0x00:sp
执行完 int 0x13 后,恢复了 stack,如果有错,则转到 0x8a,最终是打印错误信息,然后 hlt
代码片断3: /* 测试获取的 drivers 参数中的 "每扇区字节数" 是否与 DBR 中既定的 "每扇区字节数" 相等 */ |
此时,ax 的值就是 bytes_per_sector,即:每扇区有多少字节。
[0x0b] 地址是 DBR 中存放的值,在本例中它就是 0x200,也就是 512
(5)以实例看一看,我的 bochs 上为 windows 7 分配的磁盘,它的参数是什么?
bochs 上的 windows 7 实例: <bochs:40> x /26b si |
可以看出:
buf_size = 0x001a = 26 bytes
flags = 0x0000
cylinders = 0xaa553fff = 2857713663 个 cylinders
heads = 0x00000010 = 16 个 heads
sectors_per_track = 0x0000003f = 63 个 sectors
sectors = 0x00000000_013fffb0 = 20971440 个 sectors
bytes_per_sector = 0x0200 = 512 bytes
那么,这块磁盘大小为:sectors * bytes_per_sector = 209771440 * 512 = 10g
但是,cylinders 居然有 0xaa553fff 这么大的数量,这显然不对的。
注意: 这很显然,cylinders 是按照 10 bits 来表达,它是属于原有的表达方法,32 位的 cylinder 实际上虚的,超过 10 bits 是不能表达出来。 |
(5)读分区1 剩余的扇区数
分区1 的 DBR 引导记录,由 MBR 读入,接下着 DBR 引导记录负责将其余的扇区读到内存 0x7c00 下一个扇区的位置。
0x7c00 + 512 = 0x7e00 这就是下一个扇区将要读入的存放地方。
由上一篇文章中得知,分区1 位于磁盘的第 2048 扇区,即:0x800 号扇区。
因此,下一步将以 0x800 为起始,接着读 0x801 扇区到 0x7e00,以此类推:0x802 扇区读到 0x8000 ... ...
接着看代码,如下:
代码接着: 000000AF A30F00 mov [0xf],ax /* 0x200 值写入 [0xf] 中 */ 000000C5 03160F00 add dx,[0xf] /* 目的是设置 buffer 的 segment 值 */ 000000D2 2BC8 sub cx,ax /* 此处作用是:控制读 16 个扇区 */ |
这个 ax 值正是 0x200 即:512 bytes, 内存 [0x0f] 是 DBR 使用的变量。
代码: shr word [0x0f], 0x4 这里作用是调整为 segment 的增量,即:0x200 调整为 0x20 那么,加上 0x7c0 这个 segment 值变成为 0x7e0,将作为 segment
接着,将 dx = ds,此时,dx 为 0x07c0,将要加上 0x20 作为 segment 值使用。
这段代码将要读 0x2000/0x200 也就是 16 个扇区到内存。
内存 [0x11],用于记录,扇区 offset 值,这个值就是基于 0x800(2048),而 [0x16] 用来记每次读多少次。
实际的读扇区工作交由过程 func_0x11d() 执行。 下面将看一看 func_0x11d() 是如何读取
(6)读扇区子过程 func_0x11d()
代码接着: /*读扇区子过程 func_0x11d(): */ 00000121 66A11100 mov eax,[0x11] 0000012B 666800000000 push dword 0x0 0000013B B442 mov ah,0x42 00000147 6659 pop ecx 00000165 07 pop es |
这个 func 有点长,但是逻辑很清晰:就是读 n 个扇区,这个 n 值来自 [0x16],上面已经看到 [0x16] 被初始置为 1
代码:mov eax, [0x11] 这个 [0x11] 是前面说的 offset 值
而 add eax, [0x1c] 这个 [0x1c] 就是 0x800 值,它是随着 DBR 被一起读到内存,这是常量值。这里算出将要读哪个扇区
这段代码读入 16 个扇区到内存。
到此为止,DBR 做的工作是 : 将分区1 的第 2 扇区开始读 16 个扇区到地址 0x7e00 - 0x9fff 上,而扇区1(即,DBR)是由 MBR 读入到 0x7c00 |
(7)接下来,看看读完扇区后,DBR 做些什么工作。
代码接着: 000000EF 16 push ss /* 从 0x7c00+0x1028 地址开始清 0 */ |
很抱歉,我并不了解 int 0x13 的 ah = 0xbb00 和 ah = 0xbb07 的用途,这里无法做出分析。如果您知道,请不吝留言赐教。
代码后面部分,是:从 0x8c28(0x7c00 + 0x1028)开始到 0x9c00 清 0
最后转到 0x7c0:0x27a 处继续执行。
地址 0x7c0:0x27a 实际上是分区1 的第 2 个扇区的偏移 0x7a 处。
到此为止,DBR 的工作就完成了。下一步工作将是走到 下一个扇区继续执行。
(8)下面看一看,DBR 的错误处理代码
代码接着: 00000177 EBFD jmp short 0x176 /* 打印信息,hlt */ |
代码根据错误的类型,选择相应的错误信息打印出来,最后是 hlt 处理。
下面是错误的信息及标志位:
代码接着: /*
|
三、最后总结一下 DBR 的大致工作
它的主要工作是:先判断一下磁盘的参数符不符合,然后,从下一扇区开始读 16 个扇区到 0x7e00 处。