Windows 引导过程
Windows 内核中的各个组件和各种机制在起作用以前,必须首先被初始化。此初始化工作是在系统引导时完成的。当用户打开计算机的电源开关时,计算机便开始运行,但操作系统并不立即获得控制权,而是BIOS 代码首先获得控制,它执行必要的硬件检测工作,并允许用户通过一些功能键来配置当前系统中的硬件设置,甚至诊断硬件问题,然后才将控制权交给操作系统。
1.1 内核加载
在Intel x86 系统上,Windows 操作系统获得控制首先从硬盘的主引导记录(MBR,Master Boot Record)开始,Windows Setup 程序在安装Windows 时填充MBR(其他的磁盘管理程序也可能填充MBR)。MBR 包含代码和数据,其代码称为引导代码,在系统引导时首先获得控制;MBR 中的数据是一张分区表,指定了每个分区在磁盘上的位置和大小,以及分区的类型。当MBR 中的引导代码被执行时,它检查分区表中的每一个分区,若找到一个已被标记为可引导的分区(称为引导分区),则将该分区的第一个扇区(称为引导扇区)读到内存中。由于分区表包含了每一个分区的磁盘位置,所以,引导扇区的位置很容易被确定。然后MBR 的代码将控制权交给引导扇区中的代码。
;此处代码摘自NT4代码的\private\ntos\boot\bootcode\x86mboot.asm
relocated_org equ 0600h
buildtime_org equ 0100h
org_delta equ (relocated_org - buildtime_org)
_data segment public
assume cs:_data,ds:_data
org buildtime_org
; 这段代码读出位于主引导记录末尾的分区表,找到标志为可引导的分区,把它的引导扇区拷贝到内存中并执行
start:
cli ;开始的时候并没有中断
xor ax,ax
mov ss,ax
mov sp,7c00h ;位于地址0:7c00处的新堆栈
mov si,sp ; 0:7c00为标准引导地址
push ax
pop es
push ax
sti ;允许中断
cld
mov di,relocated_org mov cx,100h
rep movsw
;重定位到地址 0000:0600,跳到这里从分区表中读取可引导分区的入口,把引导分区拷贝到内存的标准引导地址(0000:7C00)
; jmp entry2 + org_delta
db 0eah
dw $+4+org_delta,0
entry2:
mov si,(offset tab) + org_delta ;表示分区表
mov bl,4 ;分区表项的个数
next:
cmp byte ptr[si],80h ;判断是否是可以引导的入口
je boot ;yes
cmp byte ptr[si],0 ;再次判断是否为0
jne bad ;不是,只有 x"00" 或者x"80" 是有效的
add si,16 ;执行到下一个入口点
dec bl
jnz next
int 18h ;未检测到可引导的入口,返回
boot:
mov dx,[si] ;引导开始处
mov cx,[si+2]
mov bp,si ;保存表入口地址并传给分区引导记录
next1:
add si,16 ;下一个表项
dec bl ;表项数目递减
jz tabok
cmp byte ptr[si],0 ;所有剩余的表入口都要从0开始
je next1 ;满足上述判断条件
bad:
mov si,(offset m1) + org_delta ;无法找到一个从0开始的表项入口,该表为坏表
msg:
lodsb ;获取显示信息的字符
cmp al,0
je hold
push si
mov bx,7
mov ah,14
int 10h ;显示信息
pop si
jmp msg ;循环打印完整信息
hold: jmp hold ;此处自旋,不做任何事
tabok:
mov di,5 ;计数值
rdboot:
mov bx,7c00h ;读取系统引导记录的位置
mov ax,0201h ;读取一个扇区
push di
int 13h ; 获取引导记录
pop di
jnc goboot ;成功得到引导记录,交与控制权
xor ax,ax ;出现错误
int 13h ;重新校准
dec di ;递减计数值
jnz rdboot ;只要计数值仍大于0,就继续尝试
mov si,(offset m2) + org_delta ;所有的入口都已检测完毕,错误无法避免
jmp msg ;跳转到显示错误信息
goboot:
mov si,(offset m3) + org_delta
mov di,07dfeh
cmp word ptr [di],0aa55h ;判断引导记录是否有效
jne msg ;无效,则显示无效的系统引导记录信息
mov si,bp ;有效,则将分区表入口地址传给它
db 0eah
dw 7c00h,0
include x86mboot.msg
org 2beh ;此处显示了主引导记录的结构
tab: ;partition table
dw 0,0 ;partition 1 begin
dw 0,0 ;partition 1 end
dw 0,0 ;partition 1 relative sector (low, high parts)
dw 0,0 ;partition 1 # of sectors (low, high parts)
dw 0,0 ;partition 2 begin
dw 0,0 ;partition 2 end
dw 0,0 ;partition 2 relative sector
dw 0,0 ;partition 2 # of sectors
dw 0,0 ;partition 3 begin
dw 0,0 ;partition 3 end
dw 0,0 ;partition 3 relative sector
dw 0,0 ;partition 3 # of sectors
dw 0,0 ;partition 4 begin
dw 0,0 ;partition 4 end
dw 0,0 ;partition 4 relative sector
dw 0,0 ;partition 4 # of sectors
signa db 55h,0aah ;引导区有效签名值
_data ends
end start
Windows Setup 程序在确定了要将Windows 系统安装到哪个分区中以后,除了可能会写入MBR 以外,还会写入该分区的引导扇区。所以,严格意义上讲,Windows 操作系统的真正入口点应该是引导扇区中的代码。引导分区必须被格式化成Windows 所支持的文件系统,典型的文件系统格式是NTFS 和FAT,其中NTFS 是Windows NT 的原生文件系统,而FAT 则是从MS-DOS 时代继承和发展过来的。
引导扇区中的代码随硬盘文件系统格式的不同而有所不同,其职责是,给Windows提供有关该硬盘上卷的结构和格式方面的信息,并且从该卷的根目录中读入Windows 的加载程序,即ntldr 文件;然后将控制权交给ntldr 的入口函数。为了能够从根目录中读入加载程序,引导扇区包含了能理解文件系统结构和读取文件的代码,这通常只是文件系统极其简单的一部分功能,而并非完整的实现。尽管引导扇区的职责相对简单,但是单个扇区(512 B)的代码和数据往往不足以完成其功能,为此,Windows 的做法是,让引导扇区中的代码读入其他扇区的数据,然后跳转到下一个扇区的代码区。这样就可以不受单个引导扇区长度的限制,这种做法相当于将第一个引导扇区当做一个加载器(loader),而真正完成引导扇区功能的扇区随后被加载进来并执行。这一过程对于MBR 是透明的,从而保持良好的兼容性。
; 此处代码摘自NT4代码的\private\ntos\boot\bootcode\ntfs\i386\ntfsboot.asm
MASM equ 1
.xlist
.286
A_DEFINED EQU 1
include ntfs.inc
DoubleWord struc
lsw dw ?
msw dw ?
DoubleWord ends
; 下面的代码显示了几个引导加载器使用的不同区段,最开始的两个分别是引导扇区最先加载的位置以及之后重定位的位置
; 第三个则是NTLDR加载的静态地址
BootSeg segment at 07c0h ; ROM 起先加载的位置.
BootSeg ends
NewSeg segment at 0d00h ; 重定位的位置.
NewSeg ends
LdrSeg segment at 2000h ; 将要在地址 2000:0000处加载加载器
LdrSeg ends
;/********************** START OF SPECIFICATIONS ************************/
;/* */
;/* SUBROUTINE NAME: ntfsboot */
;/* */
;/* DESCRIPTIVE NAME: Bootstrap loader */
;/* */
;/* FUNCTION: To load NTLDR into memory. */
;/* */
;/* NOTES: ntfsboot is loaded by the ROM BIOS (Int 19H) at */
;/* physical memory location 0000:7C00H. */
;/* ntfsboot runs in real mode. */
;/* This boot record is for NTFS volumes only. */
;/* */
;/* ENTRY POINT: ntfsboot */
;/* LINKAGE: Jump (far) from Int 19H */
;/* */
;/* INPUT: CS:IP = 0000:7C00H */
;/* SS:SP = 0030:00FAH (CBIOS dependent) */
;/* */
;/* EXIT-NORMAL: DL = INT 13 drive number we booted from */
;/* Jmp to main in NTLDR */
;/* */
;/* EXIT-ERROR: None */
;/* */
;/* EFFECTS: NTLDR is loaded into the physical memory */
;/* location 00020000H */
;/* MESSAGES: A disk read error occurred. */
;/* The file NTLDR cannot be found. */
;/* Insert a system diskette and restart the system. */
BootCode segment
assume cs:BootCode,ds:nothing,es:nothing,ss:nothing
org 0
public _ntfsboot
_ntfsboot proc far
jmp start
.errnz ($-_ntfsboot) GT (3),<FATAL PROBLEM: JMP is more than three bytes>
org 3
; 这是一个参数块的模版 – 任何调用者将引导代码写入磁盘以后都应该保存一个存在的参数块以及NTFS信息或者创建一个新的
Version db "NTFS " ; Must be 8 characters
BPB label byte
BytesPerSector dw 0 ; Size of a physical sector
SectorsPerCluster db 0 ; Sectors per allocation unit
ReservedSectors dw 0 ; Number of reserved sectors
Fats db 0 ; Number of fats
DirectoryEntries dw 0 ; Number of directory entries
Sectors dw 0 ; No. of sectors - no. of hidden sectors
Media db 0 ; Media byte
FatSectors dw 0 ; Number of fat sectors
SectorsPerTrack dw 0 ; Sectors per track
Heads dw 0 ; Number of surfaces
HiddenSectors dd 0 ; Number of hidden sectors
SectorsLong dd 0 ; Number of sectors iff Sectors = 0
; The following is the rest of the NTFS Sector Zero information.
; The position and order of DriveNumber and CurrentHead are especially important
; since those two variables are loaded into a single 16-bit register for the BIOS with one instruction.
DriveNumber db 80h ; Physical drive number (0 or 80h)
CurrentHead db ? ; Variable to store current head no.
SectorZeroPad1 dw 0
SectorsOnVolume db (size LARGE_INTEGER) dup (0)
MftStartLcn db (size LARGE_INTEGER) dup (0)
Mft2StartLcn db (size LARGE_INTEGER) dup (0)
ClustersPerFrs dd 0
DefClustersPerBuf dd 0
SerialNumber db (size LARGE_INTEGER) dup (0)
CheckSum dd 0
; The following variables are not part of the Extended BPB; they're just scratch variables for the boot code.
SectorBase dd ? ; next sector to read
CurrentTrack dw ? ; current track
CurrentSector db ? ; current sector
SectorCount dw ? ; number of sectors to read
start:
; 首先设置需要用到的区段(堆栈和数据).
cli
xor ax, ax ; 设置堆栈起点为该代码的前一句,在重定位之后将会被转移
mov ss, ax
mov sp, 7c00h .
Sti
; BIOS把可引导磁盘(磁道0,磁头0,扇区1)的第一个物理扇区映射到内存中物理地址为7C00处
mov ax, Bootseg
mov ds, ax
assume ds:BootCode
; 开始将引导块内容读入内存,然后跳转至新版本的引导块,位于第二扇区开始处
mov SectorBase.lsw, 0 ; 读取扇区0.
mov SectorBase.msw, 0
mov word ptr [SectorCount], 16 ; 读取引导区域代码
mov ax, NewSeg ; 在NewSeg处读取.
mov es, ax
sub bx, bx ; 定位NewSeg:0000.
call DoReadLL ; 调用底层的DoRead例程,该部分读取扇区的代码从略
push NewSeg ; 调整到 NewSeg:0200h.
push offset mainboot ; 压入第二个扇区的地址
ret ; 返回到第二个扇区
_ntfsboot endp
Intel x86 处理器支持实模式和保护模式,在实模式下,处理器的寄存器都是16 位的,而且不支持虚拟地址转译,只能访问物理内存空间中最低的1 MB 内存。计算机系统的BIOS 工作在实模式下,并且,当ntldr 获得控制权时,处理器仍然在实模式下运行。Ntldr文件实际上是由两部分组成的:第一部分是实模式代码,即首先获得控制的代码区;第二部分是一个标准的Windows 可执行二进制文件,在ntldr 中这部分被称为os loader。
; 此处代码摘自NT4代码的\private\\ntos\boot\startup\i386\su.asm
; _EnableProtectPaging
; 加载386保护模式寄存器
; 启用386保护模式
; 加载分页寄存器
; 启用386分页机制
public _EnableProtectPaging
_EnableProtectPaging proc near
push dword ptr 0
popfd
mov bx,sp
mov dx,[bx+2] ; 检测是否是第一次开启保护模式以及分页机制
xor ax,ax
mov gs,ax
mov es,ax
; 当调用内核的时候FS必须包含PCR的选择字
push PCR_Selector
pop fs
;加载gdtr和idtr
;在这里禁用中断,因为无法在转换到保护模式之前位于实模式且idt已被载入的情况下处理中断
cli
lgdt fword ptr [_GDTregister]
lidt fword ptr [_IDTregister]
; We have to stamp the segment portion of any real-mode far pointer with the corresponding selector values before we go protected.
mov si,offset _ScreenStart
mov word ptr [si+2],VideoSelector
mov si,offset _vp
mov word ptr [si+2],VideoSelector
; 开启保护模式和分页机制
mov eax,cr0
; 如果是第一次开启保护模式,那么无需开启分页机制,因为osloader已经做好一切
; 如果代码是返回保护模式,分页表已经设置完毕,同样无需开启
or dx,dx
jz only_prot
or eax,PROT_MODE + ENABLE_PAGING
mov cr0,eax
; 接下来代码中的JMP必须是双字对齐,为了避免触发一个i386的硬件bug
; 否则有可能使得预取队列混乱
ALIGN 4
jmp flush
only_prot:
or eax,PROT_MODE
mov cr0,eax
; 刷新预取队列
ALIGN 4
jmp flush
flush:
; 将寄存器CS作为SU模块的代码选择子
push SuCodeSelector
push offset cs:restart
retf
; 将寄存器DS和SS作为SU模块的保护模式数据选择子.
restart:
mov ax,SuDataSelector
mov ds,ax
mov ss,ax
; 加载LDT为0,因为从未被使用.
xor bx,bx
lldt bx
; 加载任务寄存器
or dx,dx
jnz epp10
mov bx,TSS_Selector
ltr bx
epp10:
ret ;返回之后介绍的su.asm中的模块
_EnableProtectPaging endp
…
…
public _RealMode
_RealMode proc near
; 转换到实模式
sgdt fword ptr [_GDTregister]
sidt fword ptr [_IDTregister]
push [saveDS] ; 将saveDS入栈,方便之后的跳转
mov ax,SuDataSelector
mov es,ax
mov fs,ax
mov gs,ax
mov eax,cr0
and eax, not (ENABLE_PAGING + PROT_MODE)
mov cr0,eax
; 刷新流水线
jmp far ptr here
here:
; Flush TLB
; We don't know where the page directory is, since it was allocated in the osloader.
; So we don't want to clear out cr3, but we DO want to flush the TLB....
mov eax,cr3
nop ; Fill - Ensure 13 non-page split
nop ; accesses before CR3 load
nop
nop
mov cr3,eax
; 转换为实模式地址
; 此处需要一个远跳转而不是指令retf,因为retf不能正确地重置访问权限为CS
db 0EAh ; JMP FAR PTR
dw offset _TEXT:rmode ; 2000:rmode
dw 02000h
rmode:
pop ax
mov ds,ax
mov ss,ax
; Stamp video pointers for real-mode use
mov si,offset _ScreenStart
mov word ptr [si+2],0b800h
mov si,offset _vp
mov word ptr [si+2],0b800h
lidt fword ptr [_IDTregisterZero]
sti
ret
_RealMode endp
Ntldr 的实模式代码首先获得控制,它的任务是,完成需在16 位模式下执行的初始化工作,例如清除键盘缓冲区,然后为切换到保护模式做好基本的环境准备,之后将处理器切换到保护模式(32 位模式)下,这样它就可以访问整个32 位地址空间了。最后它将控制权交给os loader。
; _TransferToLoader ;该子程序将控制权交给osloader
public _TransferToLoader
_TransferToLoader proc near
mov ebx,dword ptr [esp+2] ; 获取入口点参数
xor eax,eax
mov ax,[saveDS]
; 设置osloader的堆栈
mov cx,KeDataSelector
mov ss,cx
mov esp,LOADER_STACK
; 加载ds和es作为内核数据选择子
mov ds,cx
mov es,cx
; 设置指向文件系统和引导上下文记录的指针
shl eax,4
xor ecx,ecx
mov cx,offset _BootRecord
add eax,ecx
push eax
push 1010h ; 压入假的返回地址
; 将一个48位的地址传给loader的入口点
db OVERRIDE
push KeCodeSelector
push ebx
; 将控制权交还OS loader
db OVERRIDE
retf
_TransferToLoader endp
Os loader 刚接获控制时,处理器虽然已经工作在保护模式下,但是它的虚拟地址转译机制尚未开启,所以,处理器仍然直接使用物理地址。Os loader 首先做的工作是把物理内存管起来,用一个内存描述符(memory descriptor)数组把每一段内存的大小和用途记录下来,然后构造页目录和页表,使得16 MB 以下的内存能够通过页面映射(paging)机制进行访问,再设置好页目录寄存器,并打开页面映射机制。之后,os loader 继续执行其他的初始化工作,包括I/O 设备的初始化等。如果它还需要调用BIOS 中的服务(比如中断13h、中断15h 等),则必须保护好保护模式下的设置,并暂时切换回到实模式,待服务完成以后再切换到保护模式,并恢复设置。
Windows 的引导选项可以用来指示当前这次引导的各种参数,包括内核模块的文件名称、HAL 的文件名称、CPU 参数、各种内存参数、调试参数,等等。关于这些引导选项的全面列表和介绍,可参考[MSDN-BOOT]。接下来os loader 加载并执行NTDETECT.COM 程序,这是一个16 位实模式程序,它利用系统的BIOS 来查询系统的基本设备和配置信息,包括系统的日期和时间、总线的类型、磁盘的信息、输入/输出的接口信息等。这些信息被收集起来,在引导过程的后期被存放到注册表HKLM\HARDWARE\DESCRIPTION 键的下面。
代码摘自\ntos\boot\startup\i386\main.c
VOID
SuMain(
IN FPVOID BtRootDir,
IN FPDISKBPB BtBiosBlock,
IN SHORT BtBootDrive
)
/*++
Routine Description:
Main entrypoint of the SU module. Control is passed from the boot
sector to startup.asm which does some run-time fixups on the stack
and data segments and then passes control here.
Arguments:
BtRootDir - Address of root directory left in memory by boot sector
BtBiosBlock - Address of bios parameter block.
BtBootDrive - Drive that we booted from.
Returns:
Does not return. Passes control to the OS loader
--*/
{
ULONG LoaderEntryPoint;
ULONG EisaNumPages;
USHORT IsaNumPages;
MEMORY_LIST_ENTRY _far *CurrentEntry;
PIMAGE_OPTIONAL_HEADER OptionalHeader;
ULONG BlockEnd;
ULONG ImageSize;
ULONG ImageBase;
// 保存文件系统上下文信息
FsContext.BootDrive = (ULONG)BtBootDrive;
FsContext.PointerToBPB = MAKE_FLAT_ADDRESS(BtBiosBlock);
// 初始化视频子系统以使得错误和异常信息可以被显示
InitializeVideoSubSystem();
// 如果系统由软盘引导,那么关掉软盘驱动器
TurnMotorOff();
PatchDiskBaseTable();
// 基于总线类型设置机器类型.
if (BtIsEisaSystem()) {
MachineType = MACHINE_TYPE_EISA;
} else {
if (BtIsMcaSystem()) {
MachineType = MACHINE_TYPE_MCA;
} else {
MachineType = MACHINE_TYPE_ISA;
}
}
if (!ConstructMemoryDescriptors()) {
if (MachineType == MACHINE_TYPE_EISA) {
IsaNumPages = IsaConstructMemoryDescriptors();
EisaNumPages = EisaConstructMemoryDescriptors();
if (EisaNumPages + 0x80 < IsaNumPages) {
IsaConstructMemoryDescriptors();
}
} else {
if (MachineType == MACHINE_TYPE_MCA) {
McaConstructMemoryDescriptors();
} else {
IsaConstructMemoryDescriptors();
}
}
}
// 搜索内存描述符来表示低内存
CurrentEntry = MemoryDescriptorList;
while ((CurrentEntry->BlockBase != 0) &&
(CurrentEntry->BlockSize != 0)) {
CurrentEntry++;
}
if ((CurrentEntry->BlockBase == 0) &&
(CurrentEntry->BlockSize < (ULONG)512 * (ULONG)1024)) {
BlPrint(SU_NO_LOW_MEMORY,CurrentEntry->BlockSize/1024);
while (1) {
}
}
// 确保os loader映像文件包含一个内存描述符
OptionalHeader = (PIMAGE_OPTIONAL_HEADER)((PUCHAR)&edata + sizeof(IMAGE_FILE_HEADER));
ImageBase = OptionalHeader->ImageBase;
ImageSize = OptionalHeader->SizeOfImage;
OsLoaderBase = ImageBase;
OsLoaderExports = ImageBase + OptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
CurrentEntry = MemoryDescriptorList;
while (ImageSize > 0) {
while (CurrentEntry->BlockSize != 0) {
BlockEnd = CurrentEntry->BlockBase + CurrentEntry->BlockSize;
if ((CurrentEntry->BlockBase <= ImageBase) &&
(BlockEnd > ImageBase)) {
// 该描述符至少得包含osloader的一部分代码
if (BlockEnd-ImageBase > ImageSize) {
ImageSize = 0;
} else {
ImageSize -= (BlockEnd-ImageBase);
ImageBase = BlockEnd;
}
// 寻找剩余一部分的代码
CurrentEntry = MemoryDescriptorList;
break;
}
CurrentEntry++;
}
if (CurrentEntry->BlockSize == 0) {
break;
}
}
if (ImageSize > 0) {
// 不能将osloader重定位到高内存位置,否则输出错误信息
BlPrint(SU_NO_EXTENDED_MEMORY);
CurrentEntry = MemoryDescriptorList;
while (CurrentEntry->BlockSize != 0) {
BlPrint(" %lx - %lx\n",
CurrentEntry->BlockBase,
CurrentEntry->BlockBase + CurrentEntry->BlockSize);
CurrentEntry++;
}
while (1) {
}
}
// 启用A20线
EnableA20();
// 重定位保护模式中使用的IDT和GDT结构
Relocatex86Structures();
// 首次开启保护模式和分页模式
EnableProtectPaging(ENABLING);
// 重定位代码段并建立页面表项
LoaderEntryPoint = RelocateLoaderSections(&OsLoaderStart, &OsLoaderEnd);
// 将控制权交给OS loader
TransferToLoader(LoaderEntryPoint);
}
VOID
NtProcessStartup(
IN PBOOT_CONTEXT BootContextRecord
)
/*++
Routine Description:
Main entry point for setup loader. Control is transferred here by the
start-up (SU) module.
Arguments:
BootContextRecord - Supplies the boot context, particularly the
ExternalServicesTable.
Returns:
Does not return. Control eventually passed to the kernel.
--*/
{
ARC_STATUS Status;
// 初始化引导加载器的显示功能
DoGlobalInitialization(BootContextRecord);
BlFillInSystemParameters(BootContextRecord);
if (BootContextRecord->FSContextPointer->BootDrive == 0) {
// 从磁盘A:开始尝试引导
strcpy(BootPartitionName,"multi(0)disk(0)fdisk(0)");
GET_SECTOR(0,0,0,0,0,0,NULL);
#if defined(ELTORITO)
} else if (BlIsElToritoCDBoot(BootContextRecord->FSContextPointer->BootDrive)) {
// 从CD开始尝试引导
sprintf(BootPartitionName, "multi(0)disk(0)cdrom(%u)", BootContextRecord->FSContextPointer->BootDrive);
ElToritoCDBoot = TRUE;
#endif
} else {
//检查引导成功的分区是哪一个
BlGetActivePartition(BootPartitionName);
}
// 初始化内存描述符列表,OS loader的堆和参数块
Status = BlMemoryInitialize();
if (Status != ESUCCESS) {
BlPrint("Couldn't initialize memory\n");
while (1) {
}
}
// 初始化OS loader和I/O系统
Status = BlIoInitialize();
if (Status != ESUCCESS) {
BlPrint("Couldn't initialize I/O\n");
}
BlStartup(BootPartitionName);
// 永远不应该运行到这里!
do {
GET_KEY();
} while ( 1 );
}
BOOLEAN
BlDetectHardware(
IN ULONG DriveId,
IN PCHAR LoadOptions
)
/*++
Routine Description:
Loads and runs NTDETECT.COM to populate the ARC configuration tree.
NTDETECT is assumed to reside in the root directory.
Arguments:
DriveId - Supplies drive id where NTDETECT is located.
LoadOptions - Supplies Load Options string to ntdetect.
Return Value:
TRUE - NTDETECT successfully run.
FALSE - Error
--*/
{
ARC_STATUS Status;
PCONFIGURATION_COMPONENT_DATA TempFwTree;
ULONG TempFwHeapUsed;
extern BOOLEAN FwDescriptorsValid;
ULONG FileSize;
ULONG DetectFileId;
FILE_INFORMATION FileInformation;
PUCHAR DetectionBuffer;
PUCHAR Options;
UCHAR Buffer[100];
LARGE_INTEGER SeekPosition;
ULONG Read;
// 检查在根目录下是否存在文件ntdetect.com,如果有的话就将其加载到预定义的位置并将控制权转交给他
#if defined(ELTORITO)
if (ElToritoCDBoot) {
// 假设ntdetect.com在i386目录下
Status = BlOpen( DriveId,
"\\i386\\ntdetect.com",
ArcOpenReadOnly,
&DetectFileId );
} else {
#endif
Status = BlOpen( DriveId,
"\\ntdetect.com",
ArcOpenReadOnly,
&DetectFileId );
#if defined(ELTORITO)
}
#endif
DetectionBuffer = (PUCHAR)DETECTION_LOADED_ADDRESS;
if (Status != ESUCCESS) {
#if DBG
BlPrint("Error opening NTDETECT.COM, status = %x\n", Status);
BlPrint("Press any key to continue\n");
while (!GET_KEY()) {
}
#endif
return(FALSE);
}
// 获取ntdetect.com文件信息
Status = BlGetFileInformation(DetectFileId, &FileInformation);
if (Status != ESUCCESS) {
BlClose(DetectFileId);
#if DBG
BlPrint("Error getting NTDETECT.COM file information, status = %x\n", Status); //获取文件信息失败
BlPrint("Press any key to continue\n");
while (!GET_KEY()) {
}
#endif
return(FALSE);
}
FileSize = FileInformation.EndingAddress.LowPart;
if (FileSize == 0) {
BlClose(DetectFileId);
#if DBG
BlPrint("Error: size of NTDETECT.COM is zero.\n"); //获取文件末尾失败
BlPrint("Press any key to continue\n");
while (!GET_KEY()) {
}
#endif
return(FALSE);
}
SeekPosition.QuadPart = 0;
Status = BlSeek(DetectFileId,&SeekPosition,SeekAbsolute);
if (Status != ESUCCESS) {
BlClose(DetectFileId);
#if DBG
BlPrint("Error seeking to start of NTDETECT.COM file\n"); //获取文件开头失败
BlPrint("Press any key to continue\n");
while (!GET_KEY()) {
}
#endif
return(FALSE);
}
Status = BlRead(DetectFileId,DetectionBuffer,FileSize,&Read );
BlClose(DetectFileId);
if (Status != ESUCCESS) {
#if DBG
BlPrint("Error reading from NTDETECT.COM\n"); //读取文件失败
BlPrint("Read %lx bytes\n",Read);
BlPrint("Press any key to continue\n");
while (!GET_KEY()) {
}
#endif
return(FALSE);
}
// 必须传递小于1MB的NTDETECT指针,因此使用堆栈中的本地存储
if (LoadOptions) {
strcpy(Buffer, LoadOptions);
Options = Buffer;
} else {
Options = NULL;
}
DETECT_HARDWARE((ULONG)(TEMPORARY_HEAP_START - 0x10) * PAGE_SIZE,
(ULONG)0x10000, // 堆大小
(PVOID)&TempFwTree,
(PULONG)&TempFwHeapUsed,
(PCHAR)Options,
(ULONG)(LoadOptions ? strlen(LoadOptions) : 0)
);
FwConfigurationTree = TempFwTree;
FwHeapUsed = TempFwHeapUsed;
FwDescriptorsValid = FALSE;
return(TRUE);
}
VOID
DoGlobalInitialization(
IN PBOOT_CONTEXT BootContextRecord
)
/*++
Routine Description
This routine calls all of the subsytem initialization routines.
Arguments:
None
Returns:
Nothing
--*/
{
ARC_STATUS Status;
Status = InitializeMemorySubsystem(BootContextRecord); //初始化内存子系统
if (Status != ESUCCESS) {
BlPrint("InitializeMemory failed %lx\n",Status);
while (1) {
}
}
ExternalServicesTable=BootContextRecord->ExternalServicesTable;
MachineType = BootContextRecord->MachineType;
// 此处开启光标支持
HW_CURSOR(0,127);
BlpResourceDirectory = (PUCHAR)(BootContextRecord->ResourceDirectory);
BlpResourceFileOffset = (PUCHAR)(BootContextRecord->ResourceOffset);
OsLoaderBase = BootContextRecord->OsLoaderBase;
OsLoaderExports = BootContextRecord->OsLoaderExports;
InitializeMemoryDescriptors (); //初始化内存描述符
}
VOID
BlGetActivePartition(
OUT PUCHAR BootPartitionName
)
/*++
Routine Description:
Determine the ARC name for the partition NTLDR was started from
Arguments:
BootPartitionName - Supplies a buffer where the ARC name of the partition will be returned.
Return Value:
Name of the partition is in BootPartitionName.
Must always succeed.
--*/
{
UCHAR SectorBuffer[512];
UCHAR NameBuffer[80];
ARC_STATUS Status;
ULONG FileId;
ULONG Count;
int i;
// 尝试打开所有的分区并将其读为引导扇区,并与之前使用的引导扇区进行对比.
// 如果相同,则找到,否则尝试分区1
i=1;
do {
sprintf(NameBuffer, "multi(0)disk(0)rdisk(0)partition(%u)",i);
Status = ArcOpen(NameBuffer,ArcOpenReadOnly,&FileId);
if (Status != ESUCCESS) {
// 遍历所有分区未找到合适的对象,故设置默认值.
i=1;
break;
} else {
// 读取前512个字节
Status = ArcRead(FileId, SectorBuffer, 512, &Count);
ArcClose(FileId);
if (Status==ESUCCESS) {
// 只需要比较前36个字节
// 跳转标识 3 bytes
// Oem位段 8字节
// 参数块 25字节
if (memcmp(SectorBuffer, (PVOID)0x7c00, 36)==0) {
// 找到匹配对象.
break;
}
}
}
++i;
} while ( TRUE );
sprintf(BootPartitionName, "multi(0)disk(0)rdisk(0)partition(%u)",i);
return;
}
#if defined(ELTORITO)
接下来,os loader 从系统分区(即引导分区)的根目录下读入boot.ini 文件。注意,os loader 包含了读取当前文件系统的代码,它能够读取NTFS 文件系统的子目录。然后,os loader 清除屏幕,并检查在系统分区的根目录下是否有一个有效的hiberfil.sys 文件,如果存在的话,这一次引导过程转移到休眠系统的恢复过程。因此,os loader 将控制权交给一段能恢复休眠系统的内核代码。
如果当前这次引导不是休眠恢复,那么,os loader 解析boot.ini 文件,如果该文件中有多个引导选项,则os loader 显示一个引导选择菜单;如果boot.ini 文件中只包含一个引导选项,那么,此菜单不显示,而是立即应用该引导选项。
代码摘自\ntos\boot\bldr\i386\initx86.c
//负责打开驱动和读boot.ini文件
VOID
BlStartup(
IN PCHAR PartitionName
)
/*++
Routine Description:
Does x86-specific initialization, particularly presenting the boot.ini
menu and running NTDETECT, then calls to the common osloader.
Arguments:
PartitionName - Supplies the ARC name of the partition (or floppy) that
setupldr was loaded from.
Return Value:
Does not return
--*/
{
PUCHAR Argv[10];
ARC_STATUS Status;
ULONG BootFileId;
PCHAR BootFile;
ULONG Read;
PCHAR p;
ULONG i;
ULONG DriveId;
ULONG