|
(续)
GRLDR 的头部固定占用 16 扇区(8K)。但是,GRUB.EXE 的头部很长,而且不是固定的长度。因此首先需要确定这个头部的长度。
在 grub.exe 的偏移 0x1F0 处的一个字节,就表示头部所占用的扇区数。
偏移 0 处当然是 DOS 可执行文件的 MZ 标志。偏移 0x02 处的两个字节本来应该是 DOS 的 “EXE file last sector in bytes”,但是已经被 GRUB4DOS 用 EB XX 这条 JMP 指令覆盖掉了。这样做其实破坏了 EXE 的格式,但破坏得不严重,仍然能够被 DOS 认可。接下来的 24 个字节当然是 DOS 的 EXE 头部所需要的其它一些固定域。
从偏移 0x20 开始,是 DOS 设备驱动程序文件的一些格式域。再接下来就是第一扇区的可执行代码了。这个代码模拟 GRLDR 的第一扇区。其实,grub.exe 的开头的 16 扇区(8K)也模仿 GRLDR 的头部(即开头的 8K),完成类似于 GRLDR 的功能,这就使得 grub.exe 可以像 GRLDR 那样被 NTLDR 加载,即,在 boot.ini 中用 C:\GRUB.EXE="GRUB.EXE" 的方式也可以启动 GRUB4DOS。
在第一扇区的末尾处,从偏移 0x1F1 至 0x1FF,是 Linux 内核格式的一些域。这使得 GRUB.EXE 可以被别的软件当作 Linux 的内核来加载和启动。这很重要,因为从某种意义上说,Linux 内核格式也具有事实工业标准地位。所以,把 GRUB.EXE 装扮成 Linux 内核,可以方便其它软件调用 GRUB4DOS。Linux 内核的格式域其实还延伸到第二扇区开头的 48 个字节(参见 Linux 技术文档)。紧接着 Linux 内核的格式域之后,有很多 0x90 字节,这是机器指令 NOP(空操作),目的是适应不同版本的 NTLDR。不同版本的 NTLDR 在加载 GRLDR/GRUB.EXE 时,会把控制转移到这个区域中的不同位置(而不是跳转到 GRLDR/GRUB.EXE 的偏移 0x00 处),所以,我们才需要用很多 NOP 来适应这种情况。这些 NOP 一直延伸到偏移 0x269 处,紧接着在偏移 0x26A 处,当然就是一条跳转指令了,跳转到 GRLDR 的处理代码去执行,开始 GRLDR 的启动过程。这就是 GRUB.EXE 模仿 GRLDR 被 NTLDR 加载时的大致情况。
知道了头部的长度就是 0x1F0 处的一个字节所表示的头部的扇区数,这就等于说,已经可以知道主体部分起始于何处了。
相对于 GNU GRUB legacy 而言,头部是 grub4dos 特有的。头部的唯一作用就是加载主体到内存。而主体就是 GNU GRUB legacy 的核心代码 pre_stage2。因此,当主体获得控制时,头部就没有必要存在了。头部就像三级运载火箭中的一个级,它的目的和作用就是把主体送上轨道,然后自己也就废弃了。当主体获得控制以后,此时再来找头部就找不到了,因为它已经从内存中消失了。
grub4dos 也改造了 GNU GRUB legacy 的核心文件 pre_stage2(它就成为了 grub4dos 的主体部分)。grub4dos 在主体的开头安排了很多核心变量,可以让用户读取或写入。有些变量是不可以写入的,一旦写入,就破坏了 grub4dos 的运行环境。而有些变量是可以写入的,用户通过写入这些变量,来控制 grub4dos 的行为方式。
主体部分在内存中的位置是固定的,它被头部加载在物理地址 0x8200 处,此处可以看作是主体的固有 “轨道”。因此,很多变量都在 0x8200 之后的一个较小的区域之内。当然,grub4dos 内核中的许多结构都属于用户可访问的信息,它们分散在全部内存空间中。例如,0x800 处的 4K 字节是未压缩的内置菜单;0x110000 处的 256K 字节是压缩的内置菜单;而内存虚拟盘的映像通常位于内存的顶部。grub4dos 内核对于内存的使用似乎有些“破碎”的感觉,但这是有一个发展过程的,我们不希望后续的 grub4dos 版本会给用户带来麻烦,因此要尽量保持兼容性。而兼容性的要求,就使得 grub4dos 对内存的使用有些古怪。
头部都是用 assembly 写成的。与 GRLDR 的头部相对应的源程序文件是 grldrstart.S。与 grub.exe 的头部相对应的源程序文件是 dosstart.S。主体也需要一些 assembly 代码。主体的 assembly 代码大多都是在 asm.S 中,也有一些是内嵌于 C 语言的源程序文件中的。
asm.S (的编译结果)正好也就是主体部分从头部那里开始接管控制权的地方,它位于主体的最开头,即,它位于 pre_stage2 的最开头。asm.S 的第一条指令就是一个远跳转。asm.S 接管控制时,CPU 仍旧处于实模式。因此,接管控制后,asm.S 需要为保护模式的 C 语言代码建立运行环境,然后进入保护模式。在 stage2.c 中有一个叫做 cmain 的函数,它就是保护模式的主程序。保护模式的程序访问硬件,通常是先切换到实模式调用 BIOS 功能接口,完成硬件访问的任务,然后又回到保护模式下,继续执行保护模式的程序代码。
pre_stage2 被头部加载在固定地址 0x8200 处。下面对它的结构进行简要的描述。
偏移 0x05 处是一个控制字节,可以被用户改变,用来控制 grub4dos 的行为。
偏移 0x06 处的两个字节是 GNU GRUB legacy 的内部版本号。
偏移 0x08 处的四个字节是表示启动盘的启动分区的分区号。这里的分区号并不是我们通常理解的分区号,而是稍有不同。四字节中的最高字节应该总是 0x00,最低两个字节通常都是 0xFF,而真正的分区号就记录在次高的字节中。grub4dos 启动的时候,会自动把启动盘的启动分区号填写在这里。启动分区的编号是:0,1,2,3 为主分区,4,5,6,……,0xFE 为逻辑分区。分区号如果是 0xFF,则表示启动盘不含分区表,例如,当启动盘是软盘或光盘的时候就是如此。
偏移 0x11 处的一个字节也是一个控制字节。它的最低位(位 0)用来控制 grub4dos 是否强制认为所有的设备都支持 LBA。用户可以写入它,但通常是不需要写入的,它的默认值是 0,即并不强制认为所有的设备都支持 LBA。它的次低位(位 1)用来指示当前正在运行的 grub4dos 是否被(某个启动软件)当作 Linux 的内核来启动的。启动时,grub4dos 自己会写入它,用户不应该写入它。其它位目前未定义。
偏移 0x6C 处的 4 字节指示内置菜单在什么地方。外部程序可以据此确定内置菜单的起始位置,然后就可以导入、导出内置菜单了。(具体如何计算内置菜单的起始位置,此处暂不涉及。)
偏移 0x80 处的四个字节记录了启动盘的盘号,00 是软盘,0x80 是硬盘。光盘通常是 0x81 - 0xFF 之间的某个值。不同的 BIOS 为启动光盘赋予的盘号也不同。只有最低的一个字节不是 00,其它三个字节应该总是 00。grub4dos 在启动时自动记录启动盘的盘号,用户不需要写入它。启动盘的盘号以及分区号是有可能被改变的。configfile 命令就能够改变它们。BIOS 只使用 00 - 0xFF 之间的盘号,但是,grub4dos 的盘号采用 4 字节的整数。grub4dos 的 (md) 设备所对应的盘号就是 0xFFFF,因此,(md) 设备也可以用 (0xFFFF) 来表示。
偏移 0x138 处的 4 字节表示 DOS 启动盘的信息。最低字节表示 DOS 启动盘的盘号。00 是软盘,0x80 是硬盘。次低字节表示 DOS 启动盘的最大磁头号(即,磁头数 - 1)。次高字节表示 DOS 启动盘的每道扇区数。最高字节目前没有定义。
偏移 0x13C 处的 4 字节表示 DOS 启动盘的启动分区的隐藏扇区数(或者等价地说,是启动分区的起始扇区号)。
当 grub4dos 是从 DOS 下启动的时候,grub4dos 会自动设置这些信息。但是,外部程序加载 GRLDR 或 GRUB.EXE 的时候也可以设置这些信息。如果偏移 0x138 处的四字节整数不等于 0,那么 grub4dos 将强制把 0x138 处的一个字节所表示的盘号当作启动盘,也强制把 0x13C 处的值当作启动分区的起始扇区号(前提条件是,启动分区必须是一个主分区,而不能是逻辑分区)。此时,当然也根据偏移 0x139 和偏移 0x13A 处的值来强制设置启动盘的几何参数。
如果 0x138 处的四字节整数值保持 0 不变,那么还有另外一种办法来强制设置启动盘的信息。首先把软盘分区或者某个主分区(注:它不可以是逻辑分区)的第一扇区(即含有 BPB 的引导扇区)加载到 0000:7C00,然后再把控制权交给 grub4dos,这样,grub4dos 也会检测到 0000:7C00 处的启动盘的信息,并据此强制设置启动盘的信息(尤其是几何参数)。这是模仿 NTLDR 的启动逻辑。NTLDR 就是根据 0000:7C00 处的 BPB 信息来决定启动盘的。
如果其它软件需要加载 grldr,可以把 grldr 放置在一个 16 字节对齐的地址处,设置 DL 寄存器为启动盘的盘号(00 是第一软盘,0x80 是第一硬盘),设置 DH 为 GRLDR 文件所在的分区号(0,1,2,3 是主分区,4,5,6,……,0xFE 是逻辑分区,0xFF 表示启动设备是不含分区表的软盘或光盘),然后递交控制权。(DL 和 DH 寄存器合在一起就是 DX 寄存器)。
如果其他软件需要加载 grub.exe(并且是把 grub.exe 当作 GRLDR 的格式来加载,而不是当作 Linux 内核格式来加载),那么,除了需要像刚才所说设置好 DX 寄存器之外,还需要把 DX 压入堆栈(push DX),然后才可以递交控制权(用 jmp 指令跳转到 grub.exe 的偏移 00 处,即 MZ 标志处。此时的 MZ 标志,也被当作代码来使用了,这是一个奇技淫巧)。启动 GRLDR 时,也可以把 DX 压入堆栈,但这不是必须的,因为 GRLDR 获得控制后不需要从先前的堆栈中找回数据。但 GRUB.EXE 获得控制后需要立即从堆栈中弹出 DX 寄存器的值。
通过 DX 寄存器来传入启动设备的信息,这属于正常的传入方式。而前面介绍的偏移 0x138 处的启动盘信息(以及位于内存 0000:7C00 处的 BPB 信息)则是作为特殊控制手段(即用来强制指定启动盘信息)。当偏移 0x138 处存在启动盘信息时,它就强制指定了启动盘的信息。当偏移 0x138 处不存在启动盘信息(即此处的四字节整数为 0)时,grub4dos 就要检查内存 0000:7C00 处是否含有合法的 BPB 信息:如果内存 0000:7C00 处有合法的 BPB,就用这个 BPB 来决定启动盘的信息;如果内存 0000:7C00 处没有合法的 BPB,就用 DX 寄存器来决定启动盘信息。
[ 本帖最后由 不点 于 2012-10-26 13:55 编辑 ] |
|