



SU模块是NTLDR文件的前半部分,负责实模式下的初始化工作,为NTLDR文件的另一部分Loader模块提供支持。SU模块的主要工作流程如下:
1.检测物理地址(ConstructMemoryDescriptors)。
2.开启A20地址线(EnableA20)。
3.重定位GDT和IDT(Relocatex86Structures)。
4.开启保护模式(EnableProtectPaging)。
5.加载Loader模块(RelocateLoaderSections)。
6.转移控制权(TransferToLoader)。
在上一节讲MBR的时候,我们编写的MBR代码最后将NTLDR文件加载到物理地址0x2000:0000,即NTLDR文件的入口地址。SU作为NTLDR文件的前半部分,首先获得控制权,但其程序入口地址却不一定是物理地址0x2000:0000,这与SU的具体使用方式有关。在微软的设计中,SU的使用方式主要分为两种:
1.FAT Boot。采用这种方式时,由boot跳转到物理地址0x2000:0003,略过前面3B。这3B是一条跳转指令“jmp RealStart”。因此这条指令将不被执行,于是转入属于fat startup的代码里执行。
2.NTFS Boot。由boot跳转到0x2000:0000,刚好执行指令“jmp RealStart”,跳到属于ntfs startup的代码,而fat startup代码不会被执行。
所以在MBR或PBR中,必须决定启动分区是属于哪种文件系统,除非像我们整理的代码一样,默认就是NTFS,不提供FAT Boot。
调试原版startup.com,需要在boot.asm中指定链接incbin"‥\system32\startup.com",而不是我们编译的incbin"‥\Debug\startup.com"。
   
   首先设置寄存器的使用环境。_GDT是数据段开始的偏移,_edata是数据段结束的偏移,上面标注的一些具体数值是从调试过程中得出的。前面说过,NTLDR是由startup.com和osloader.exe合成的,在这里我们可以看到startup.com结束之后紧跟着的就是osloader.exe文件的开始。这种处理使得两个不同的文件合成为一个文件,在SU模块运行时又能找到osloader.exe文件的开头。RealStart完成寄存器上下文的初始化设置之后,SuMain正式进入为后续加载osloader.exe提供支持的初始化工作。
   我们主要分析的是ntfs startup,省略了一些无关紧要的代码,读者在调试过程中直接跳过就好。我们关注的是6大主要流程,即检测物理地址、开启A20地址线、重定位GDT和IDT、开启保护模式、加载osloader.exe和转移控制权。
在上述代码中,有两个指令可能大家不太熟悉。enter 1Eh,0指令实际上相当于以下3条指令:
   事实上,我们可以不必重新构建startup.com,原版startup.com已经可以替我们完成这些工作。现在我们根据原理重新构建startup.com,需要花许多功夫,但是很值得,主要出于以下方面的考虑:
1.我们在调试原版的过程中学习了相关知识,现在我们是否有能力根据原理写出来。
2.锻炼我们的编码能力,如何使用C语言转换为汇编,并学习如何调整指令。
3.重新编写的模块,肯定要去除无关的代码块,简洁易于定位,易于调试。
4.能让我们更深刻理解NTLDR是怎样设计的,为什么有这样的安排。
   
   尽管我们构建的代码和原版有些出入,但是也不会产生什么错误,反而更能清晰地了解执行流程,而且在我们调试时,也能从源头知道每个寄存器的值。对于startup.com调试,如果MBR部分已经学会了,可以直接跳过。下断点b 0x20000,再输入c,就来到了SU开头的指令jmp RealStart,如图2.3所示。
    图2.3
编写完SU代码后,如果不修改,基本上每次运行的物理地址和寄存器地址都不会改变。我们在跟随执行流程时,可以记住一些重要的地址,特别是函数的调用地址,这样在调试过程中如果需要直接运行到那个地址,就在那里下断点。我们的代码从一开始就设计得很简洁,所以不难定位,这也是我们需要重构的意义。
   
   以上每个函数前加下画线的地址是指程序执行时的函数地址,可以下断点。如果想直接看开启保护模式的函数,输入断点b 0x20233,再输入c就来到SuMain调用EnableProtectPaging(如上面的代码所示)。如果想跟入函数内部,使用s指令单步步入。注意,这些地址不是一成不变的,比如修改了代码,编译后不同的汇编代码都有可能改变,这里只是笔者写这一章节调试时记下的地址。如果这个地址和你真实运行的不同,做相应的修改即可。以后每次想看那个函数时,直接下断点,不必一步一步跟踪。下断点的地址除了在函数调用前,也可以在函数内,在任意想下断点的地方,具体取决于你学习的需要。在学习这部分内容的过程中,你可能需要一个记事本,写下学习的点滴,一些断点地址、寄存器值、内存状态等。记下一些东西有助于快速理解,实打实地学习,是不能省略的。这样做可以确保每一部分都学熟学懂,一定不要急躁,否则又要重复调试了。
对于startup来说主要的流程就这么多,下面我们分步解析各个函数。