转载自:https://blog.csdn.net/cosmoslife/article/details/7855425
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
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
;
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
)
{
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
)
{
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
)
{
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
)
{
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
)
{
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
)
{
PUCHAR Argv[10];
ARC_STATUS Status;
ULONG BootFileId;
PCHAR BootFile;
ULONG Read;
PCHAR p;
ULONG i;
ULONG DriveId;
ULONG FileSize;
ULONG Count;
LARGE_INTEGER SeekPosition;
PCHAR LoadOptions = NULL;
BOOLEAN UseTimeOut=TRUE;
BOOLEAN AlreadyInitialized = FALSE;
extern BOOLEAN FwDescriptorsValid;
// 打开引导分区以便加载引导驱动.
Status = ArcOpen(PartitionName, ArcOpenReadOnly, &DriveId);
if (Status != ESUCCESS) {
BlPrint("Couldn't open drive %s\n",PartitionName);
BlPrint(BlFindMessage(BL_DRIVE_ERROR),PartitionName);
goto BootFailed;
}
// 初始化双字节内码系统以及显示支持.
TextGrInitialize(DriveId);
do {
Status = BlOpen( DriveId,
"\\boot.ini",
ArcOpenReadOnly,
&BootFileId ); //此处开始打开boot.ini
BootFile = MyBuffer;
RtlZeroMemory(MyBuffer, SECTOR_SIZE+32);
if (Status != ESUCCESS) {
BootFile[0]='\0';
} else {
// 通过从头到尾读取boot.ini文件获取大小
FileSize = 0;
do {
Status = BlRead(BootFileId, BootFile, SECTOR_SIZE, &Count);
if (Status != ESUCCESS) {
BlClose(BootFileId);
BlPrint(BlFindMessage(BL_READ_ERROR),Status);
BootFile[0] = '\0'; //结束符
FileSize = 0;
Count = 0;
goto BootFailed;
}
FileSize += Count;
} while (Count != 0);
if (FileSize >= SECTOR_SIZE) {
// 如果boot.ini文件大于一个扇区的大小,那么就需要重新分配更大的缓冲区
BootFile=FwAllocateHeap(FileSize);
}
if (BootFile == NULL) {
BlPrint(BlFindMessage(BL_READ_ERROR),ENOMEM);
BootFile = MyBuffer;
BootFile[0] = '\0';
goto BootFailed;
} else {
SeekPosition.QuadPart = 0;
Status = BlSeek(BootFileId,
&SeekPosition,
SeekAbsolute);
if (Status != ESUCCESS) {
BlPrint(BlFindMessage(BL_READ_ERROR),Status);
BootFile = MyBuffer;
BootFile[0] = '\0';
goto BootFailed;
} else {
Status = BlRead( BootFileId,
BootFile,
FileSize,
&Read );
SeekPosition.QuadPart = 0;
Status = BlSeek(BootFileId,
&SeekPosition,
SeekAbsolute);
if (Status != ESUCCESS) {
BlPrint(BlFindMessage(BL_READ_ERROR),Status);
BootFile = MyBuffer;
BootFile[0] = '\0';
goto BootFailed;
} else {
BootFile[Read]='\0';
}
}
}
// 搜索Ctrl-Z
p=BootFile;
while ((*p!='\0') && (*p!=26)) {
++p;
}
if (*p != '\0') {
*p='\0';
}
BlClose(BootFileId);
}
if (!AlreadyInitialized) {
AbiosInitDataStructures();
}
MdShutoffFloppy(); //关闭软驱
TextClearDisplay(); //清除显示文本
p=BlSelectKernel(DriveId,BootFile, &LoadOptions, UseTimeOut);
if (!AlreadyInitialized) {
BlPrint(BlFindMessage(BL_NTDETECT_MSG));
if (!BlDetectHardware(DriveId, LoadOptions)) {
BlPrint(BlFindMessage(BL_NTDETECT_FAILURE));
return;
}
TextClearDisplay();
// 初始化SCSI引导驱动
if(!_strnicmp(p,"scsi(",5)) {
AEInitializeIo(DriveId);
}
ArcClose(DriveId);
// 设置标志位表示内存描述符不能被改变.
FwDescriptorsValid = FALSE;
} else {
TextClearDisplay();
}
//设置该标志位用于表示ntdetect和abios的初始化例程已经运行
AlreadyInitialized = TRUE;
//设置引导菜单无等待操作的时间,即用户如不操作则一直停留
UseTimeOut=FALSE;
i=0;
while (*p !='\\') {
KernelBootDevice[i] = *p;
i++;
p++;
}
KernelBootDevice[i] = '\0';
strcpy(OsLoadFilename,"osloadfilename=");
strcat(OsLoadFilename,p);
// 这里只能使用参数”osloader= variable ”来指定从哪里加载HAL.DLL.
// 因为x86系统未指定”系统分区 ” 所以将从路径\nt\system\HAL.DLL加载HAL.DLL
strcpy(OsLoaderFilename,"osloader=");
strcat(OsLoaderFilename,p);
strcat(OsLoaderFilename,"\\System32\\NTLDR");
strcpy(SystemPartition,"systempartition=");
strcat(SystemPartition,KernelBootDevice);
strcpy(OsLoadPartition,"osloadpartition=");
strcat(OsLoadPartition,KernelBootDevice);
strcpy(OsLoadOptions,"osloadoptions=");
if (LoadOptions) {
strcat(OsLoadOptions,LoadOptions);
}
strcpy(ConsoleInputName,"consolein=multi(0)key(0)keyboard(0)");
strcpy(ConsoleOutputName,"consoleout=multi(0)video(0)monitor(0)");
strcpy(X86SystemPartition,"x86systempartition=");
strcat(X86SystemPartition,PartitionName);
Argv[0]="load";
Argv[1]=OsLoaderFilename;
Argv[2]=SystemPartition;
Argv[3]=OsLoadFilename;
Argv[4]=OsLoadPartition;
Argv[5]=OsLoadOptions;
Argv[6]=ConsoleInputName;
Argv[7]=ConsoleOutputName;
Argv[8]=X86SystemPartition;
Status = BlOsLoader(9,Argv,NULL);
BootFailed:
if (Status != ESUCCESS) {
// 引导失败就重新启动
while (TRUE) {
GET_KEY();
}
} else {
//重新打开设备
Status = ArcOpen(BootPartitionName, ArcOpenReadOnly, &DriveId);
if (Status != ESUCCESS) {
BlPrint(BlFindMessage(BL_DRIVE_ERROR),BootPartitionName);
goto BootFailed;
}
}
} while (TRUE);
}
然后,os loader 加载内核模块映像文件,默认为ntoskrnl.exe,以及HAL 映像文件,默认为hal.dll。再加载注册表的SYSTEM 储巢,即\WINDOWS\system32\config\system 文件。通过检查SYSTEM 储巢中的设置信息,它可以知道哪些设备驱动程序必须被加载进来,即被标记为“引导-启动”(SERVICE_BOOT_START)的设备驱动程序。然后它加载所有这些被标记为“引导-启动”的设备驱动程序,以及访问系统目录所必需的文件系统驱动程序。注意,在此之前os loader 也可以访问系统分区,但并非通过文件系统驱动程序。
代码摘自\ntos\boot\bldr\osloader.c
// 定义扩展静态数据
ULONG BlConsoleOutDeviceId = 0;
ULONG BlConsoleInDeviceId = 0;
ULONG BlDcacheFillSize = 32;
#if DBG
BOOLEAN BlOutputDots=FALSE;
#else
BOOLEAN BlOutputDots=TRUE;
#endif
CHAR KernelFileName[8+1+3+1]="ntoskrnl.exe";
CHAR HalFileName[8+1+3+1]="hal.dll";
//此处定义名为"ntoskrnl.exe"和"hal.dll"的文件
ARC_STATUS
BlOsLoader (
IN ULONG Argc,
IN PCHAR Argv[],
IN PCHAR Envp[]
)
{
CHAR BootDirectoryPath[256];
ULONG CacheLineSize;
PCHAR ConsoleOutDevice;
PCHAR ConsoleInDevice;
ULONG Count;
PCONFIGURATION_COMPONENT_DATA DataCache;
CHAR DeviceName[256];
CHAR DevicePrefix[256];
PCHAR DirectoryEnd;
CHAR DllName[256];
CHAR DriverDirectoryPath[256];
PCHAR FileName;
ULONG FileSize;
PLDR_DATA_TABLE_ENTRY HalDataTableEntry;
CHAR HalDirectoryPath[256];
CHAR KernelDirectoryPath[256];
PVOID HalBase;
PVOID SystemBase;
ULONG Index;
ULONG Limit;
ULONG LinesPerBlock;
PCHAR LoadDevice;
ULONG LoadDeviceId;
PCHAR LoadFileName;
PCHAR LoadOptions;
ULONG i;
CHAR OutputBuffer[256];
ARC_STATUS Status;
PLDR_DATA_TABLE_ENTRY SystemDataTableEntry;
PCHAR SystemDevice;
ULONG SystemDeviceId;
PTRANSFER_ROUTINE SystemEntry;
PIMAGE_NT_HEADERS NtHeaders;
PWSTR BootFileSystem;
PCHAR LastKnownGood;
BOOLEAN BreakInKey;
CHAR BadFileName[128];
PBOOTFS_INFO FsInfo;
// 获取控制台输出设备的名字并获取写入权限
ConsoleOutDevice = BlGetArgumentValue(Argc, Argv, "consoleout");
if (ConsoleOutDevice == NULL) {
return ENODEV;
}
Status = ArcOpen(ConsoleOutDevice, ArcOpenWriteOnly, &BlConsoleOutDeviceId);
if (Status != ESUCCESS) {
return Status;
}
// 获取控制台输入设备的名字并获取读取权限
ConsoleInDevice = BlGetArgumentValue(Argc, Argv, "consolein");
if (ConsoleInDevice == NULL) {
return ENODEV;
}
Status = ArcOpen(ConsoleInDevice, ArcOpenReadOnly, &BlConsoleInDeviceId);
if (Status != ESUCCESS) {
return Status;
}
// 声明OS Loader.
strcpy(&OutputBuffer[0], "OS Loader V4.00\r\n");
ArcWrite(BlConsoleOutDeviceId, &OutputBuffer[0],
strlen(&OutputBuffer[0]),
&Count);
// 初始化内存描述符列表,OSloader系统堆和参数块
Status = BlMemoryInitialize();
if (Status != ESUCCESS) {
BlFatalError(LOAD_HW_MEM_CLASS,
DIAG_BL_MEMORY_INIT,
LOAD_HW_MEM_ACT);
goto LoadFailed;
}
// 计算数据缓存大小.该值用来对齐I/O缓冲区以防主机系统不支持连续的缓存
DataCache = KeFindConfigurationEntry(BlLoaderBlock->ConfigurationRoot,
CacheClass,
SecondaryCache,
NULL);
if (DataCache == NULL) {
DataCache = KeFindConfigurationEntry(BlLoaderBlock->ConfigurationRoot,
CacheClass,
SecondaryDcache,
NULL);
if (DataCache == NULL) {
DataCache = KeFindConfigurationEntry(BlLoaderBlock->ConfigurationRoot,
CacheClass,
PrimaryDcache,
NULL);
}
}
if (DataCache != NULL) {
LinesPerBlock = DataCache->ComponentEntry.Key >> 24;
CacheLineSize = 1 << ((DataCache->ComponentEntry.Key >> 16) & 0xff);
BlDcacheFillSize = LinesPerBlock * CacheLineSize;
}
// 初始化OS loader的I/O系统
Status = BlIoInitialize();
if (Status != ESUCCESS) {
BlFatalError(LOAD_HW_DISK_CLASS,
DIAG_BL_IO_INIT,
LOAD_HW_DISK_ACT);
goto LoadFailed;
}
// 初始化资源节
Status = BlInitResources(Argv[0]);
if (Status != ESUCCESS) {
BlFatalError(LOAD_HW_DISK_CLASS,
DIAG_BL_IO_INIT,
LOAD_HW_DISK_ACT);
}
// 初始化NT配置树.
BlLoaderBlock->ConfigurationRoot = NULL;
Status = BlConfigurationInitialize(NULL, NULL);
if (Status != ESUCCESS) {
BlFatalError(LOAD_HW_FW_CFG_CLASS,
DIAG_BL_CONFIG_INIT,
LOAD_HW_FW_CFG_ACT);
goto LoadFailed;
}
// 复制osloadoptions参数给LoaderBlock
LoadOptions = BlGetArgumentValue(Argc, Argv, "osloadoptions");
if (LoadOptions != NULL) {
FileSize = strlen(LoadOptions)+1;
FileName = (PCHAR)BlAllocateHeap(FileSize);
strcpy(FileName, LoadOptions);
BlLoaderBlock->LoadOptions = FileName;
//检测标志值判断是否应该输出文件名而不是单独的原点
if ((strstr(FileName,"SOS")!=NULL) ||
(strstr(FileName,"sos")!=NULL)) {
BlOutputDots=FALSE;
}
FileName=strstr(BlLoaderBlock->LoadOptions,"HAL=");
if (FileName) {
for (i=0; i<sizeof(HalFileName); i++) {
if (FileName[4+i]==' ') {
HalFileName[i]='\0';
break;
}
HalFileName[i]=FileName[4+i];
}
}
HalFileName[sizeof(HalFileName)-1]='\0';
FileName=strstr(BlLoaderBlock->LoadOptions,"KERNEL=");
if (FileName) {
for (i=0; i<sizeof(KernelFileName); i++) {
if (FileName[7+i]==' ') {
KernelFileName[i]='\0';
break;
}
KernelFileName[i]=FileName[7+i];
}
}
KernelFileName[sizeof(KernelFileName)-1]='\0';
} else {
BlLoaderBlock->LoadOptions = NULL;
}
// 获取加载设备的名称以及读取的权限.
LoadDevice = BlGetArgumentValue(Argc, Argv, "osloadpartition");
if (LoadDevice == NULL) {
Status = ENODEV;
BlFatalError(LOAD_HW_FW_CFG_CLASS,
DIAG_BL_FW_GET_BOOT_DEVICE,
LOAD_HW_FW_CFG_ACT);
goto LoadFailed;
}
Status = ArcOpen(LoadDevice, ArcOpenReadOnly, &LoadDeviceId);
if (Status != ESUCCESS) {
BlFatalError(LOAD_HW_DISK_CLASS,
DIAG_BL_OPEN_BOOT_DEVICE,
LOAD_HW_DISK_ACT);
goto LoadFailed;
}
// 获取系统设备的名称以及读取的权限.
SystemDevice = BlGetArgumentValue(Argc, Argv, "systempartition");
if (SystemDevice == NULL) {
Status = ENODEV;
BlFatalError(LOAD_HW_FW_CFG_CLASS,
DIAG_BL_FW_GET_SYSTEM_DEVICE,
LOAD_HW_FW_CFG_ACT);
goto LoadFailed;
}
Status = ArcOpen(SystemDevice, ArcOpenReadOnly, &SystemDeviceId);
if (Status != ESUCCESS) {
BlFatalError(LOAD_HW_FW_CFG_CLASS,
DIAG_BL_FW_OPEN_SYSTEM_DEVICE,
LOAD_HW_FW_CFG_ACT);
goto LoadFailed;
}
// 初始化调试系统
BlLogInitialize(SystemDeviceId);
//显示系统提示符,给用户更多的准备时间
BlStartConfigPrompt();
#if defined(_PPC_)
Status = BlPpcInitialize();
if (Status != ESUCCESS) {
goto LoadFailed;
}
#endif // defined(_PPC_)
// 获取系统根目录的路径名称.
LoadFileName = BlGetArgumentValue(Argc, Argv, "osloadfilename");
if (LoadFileName == NULL) {
Status = ENOENT;
BlFatalError(LOAD_HW_FW_CFG_CLASS,
DIAG_BL_FW_GET_BOOT_DEVICE,
LOAD_HW_FW_CFG_ACT);
goto LoadFailed;
}
// 生成SYSTEM32目录名称
strcpy(BootDirectoryPath, LoadFileName);
strcat(BootDirectoryPath, "\\System32\\");
// 生成ntoskrnl.exe的全路径
// "\winnt\system32\ntoskrnl.exe"
strcpy(KernelDirectoryPath, BootDirectoryPath);
strcat(KernelDirectoryPath, KernelFileName);
// 加载内核模块映像文件.
BlOutputLoadMessage(LoadDevice, KernelDirectoryPath);
Status = BlLoadImage(LoadDeviceId,
LoaderSystemCode,
KernelDirectoryPath,
TARGET_IMAGE,
&SystemBase);
if (Status != ESUCCESS) {
BlFatalError(LOAD_SW_MIS_FILE_CLASS,
DIAG_BL_LOAD_SYSTEM_IMAGE,
LOAD_SW_FILE_REINST_ACT);
goto LoadFailed;
}
// 无论文件系统是什么,都需要与引导驱动一起加载
FsInfo = BlGetFsInfo(LoadDeviceId);
if (FsInfo != NULL) {
BootFileSystem = FsInfo->DriverName;
} else {
BlFatalError(LOAD_SW_MIS_FILE_CLASS,
DIAG_BL_LOAD_SYSTEM_IMAGE,
LOAD_SW_FILE_REINST_ACT);
goto LoadFailed;
}
// 获取OS loader文件的路径名称,并除去目录名称,用来加载HAL.DLL
FileName = BlGetArgumentValue(Argc, Argv, "osloader");
if (FileName == NULL) {
Status = ENOENT;
BlFatalError(LOAD_HW_FW_CFG_CLASS,
DIAG_BL_FIND_HAL_IMAGE,
LOAD_HW_FW_CFG_ACT);
goto LoadFailed;
}
DirectoryEnd = strrchr(FileName, '\\');
FileName = strchr(FileName, '\\');
HalDirectoryPath[0] = 0;
if (DirectoryEnd != NULL) {
Limit = (ULONG)DirectoryEnd - (ULONG)FileName + 1;
for (Index = 0; Index < Limit; Index += 1) {
HalDirectoryPath[Index] = *FileName++;
}
HalDirectoryPath[Index] = 0;
}
// 生成完整的路径名称并将HAL加载入系统内存中去.
strcpy(&DllName[0], &HalDirectoryPath[0]);
strcat(&DllName[0], HalFileName);
BlOutputLoadMessage(SystemDevice, &DllName[0]);
Status = BlLoadImage(SystemDeviceId,
LoaderHalCode,
&DllName[0],
TARGET_IMAGE,
&HalBase);
if (Status != ESUCCESS) {
BlFatalError(LOAD_SW_MIS_FILE_CLASS,
DIAG_BL_LOAD_HAL_IMAGE,
LOAD_SW_FILE_REINST_ACT);
goto LoadFailed;
}
// 为ntoskrnl.exe生成数据表入口.
Status = BlAllocateDataTableEntry("ntoskrnl.exe",
KernelDirectoryPath,
SystemBase,
&SystemDataTableEntry);
if (Status != ESUCCESS) {
BlFatalError(LOAD_SW_INT_ERR_CLASS,
DIAG_BL_LOAD_SYSTEM_IMAGE,
LOAD_SW_INT_ERR_ACT);
goto LoadFailed;
}
// 为hal.dll生成数据表入口.
Status = BlAllocateDataTableEntry("hal.dll",
&DllName[0],
HalBase,
&HalDataTableEntry);
if (Status != ESUCCESS) {
BlFatalError(LOAD_SW_INT_ERR_CLASS,
DIAG_BL_LOAD_HAL_IMAGE,
LOAD_SW_INT_ERR_ACT);
goto LoadFailed;
}
#if defined(_ALPHA_)
Status = BlLoadPal(SystemDeviceId,
LoaderSystemCode,
&HalDirectoryPath[0],
TARGET_IMAGE,
&BlLoaderBlock->u.Alpha.PalBaseAddress,
SystemDevice);
if (Status != ESUCCESS) {
BlFatalError(LOAD_SW_MIS_FILE_CLASS,
DIAG_BL_LOAD_SYSTEM_DLLS,
LOAD_SW_FILE_REINST_ACT);
goto LoadFailed;
}
#endif // _ALPHA_
// 扫描系统映像的导入表以及加载相关的dll
Status = BlScanImportDescriptorTable(LoadDeviceId,
LoadDevice,
&BootDirectoryPath[0],
SystemDataTableEntry);
if (Status != ESUCCESS) {
BlFatalError(LOAD_SW_INT_ERR_CLASS,
DIAG_BL_LOAD_SYSTEM_DLLS,
LOAD_SW_INT_ERR_ACT);
goto LoadFailed;
}
// 扫描HAL.dll的导入表以及加载相关的dll
Status = BlScanImportDescriptorTable(SystemDeviceId,
SystemDevice,
&HalDirectoryPath[0],
HalDataTableEntry);
if (Status != ESUCCESS) {
BlFatalError(LOAD_SW_INT_ERR_CLASS,
DIAG_BL_LOAD_HAL_DLLS,
LOAD_SW_INT_ERR_ACT);
goto LoadFailed;
}
// 重定位系统入口点并设置系统指定的信息
NtHeaders = RtlImageNtHeader(SystemBase);
SystemEntry = (PTRANSFER_ROUTINE)((ULONG)SystemBase +
NtHeaders->OptionalHeader.AddressOfEntryPoint);
#ifdef MIPS
BlLoaderBlock->u.Mips.GpBase = (ULONG)SystemBase +
NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_GLOBALPTR].VirtualAddress;
#endif
#if defined(_ALPHA_)
BlLoaderBlock->u.Alpha.GpBase = (ULONG)SystemBase +
NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_GLOBALPTR].VirtualAddress;
#endif
// 复制所有的设备驱动的目录路径
strcpy(&DriverDirectoryPath[0], &BootDirectoryPath[0]);
strcat(&DriverDirectoryPath[0], "\\Drivers\\");
// 为NLS数据分配结构体. 这将通过BlLoadAndScanSystemHive函数填充并加载.
BlLoaderBlock->NlsData = BlAllocateHeap(sizeof(NLS_DATA_BLOCK));
if (BlLoaderBlock->NlsData == NULL) {
Status = ENOMEM;
BlFatalError(LOAD_HW_MEM_CLASS,
DIAG_BL_LOAD_SYSTEM_HIVE,
LOAD_HW_MEM_ACT);
goto LoadFailed;
}
// 加载注册表的SYSTEM储巢
Status = BlLoadAndScanSystemHive(LoadDeviceId,
LoadDevice,
LoadFileName,
BootFileSystem,
BadFileName);
if (Status != ESUCCESS) {
if (BlRebootSystem) {
Status = ESUCCESS;
} else {
BlBadFileMessage(BadFileName);
}
goto LoadFailed;
}
// 生成符合ARC命名规范的引导设备名称以及NT路径名.
Status = BlGenerateDeviceNames(LoadDevice, &DeviceName[0], &DevicePrefix[0]);
if (Status != ESUCCESS) {
BlFatalError(LOAD_HW_FW_CFG_CLASS,
DIAG_BL_ARC_BOOT_DEV_NAME,
LOAD_HW_FW_CFG_ACT);
goto LoadFailed;
}
FileSize = strlen(&DeviceName[0]) + 1;
FileName = (PCHAR)BlAllocateHeap(FileSize);
strcpy(FileName, &DeviceName[0]);
BlLoaderBlock->ArcBootDeviceName = FileName;
FileSize = strlen(LoadFileName) + 2;
FileName = (PCHAR)BlAllocateHeap( FileSize);
strcpy(FileName, LoadFileName);
strcat(FileName, "\\");
BlLoaderBlock->NtBootPathName = FileName;
// 生成符合ARC命名规范的HAL设备名称以及NT路径名.
#ifdef i386
strcpy(&DeviceName[0], BlGetArgumentValue(Argc, Argv, "x86systempartition"));
#else
Status = BlGenerateDeviceNames(SystemDevice, &DeviceName[0], &DevicePrefix[0]);
if (Status != ESUCCESS) {
BlFatalError(LOAD_HW_FW_CFG_CLASS,
DIAG_BL_ARC_BOOT_DEV_NAME,
LOAD_HW_FW_CFG_ACT);
goto LoadFailed;
}
#endif //i386
FileSize = strlen(&DeviceName[0]) + 1;
FileName = (PCHAR)BlAllocateHeap(FileSize);
strcpy(FileName, &DeviceName[0]);
BlLoaderBlock->ArcHalDeviceName = FileName;
#ifdef i386
//此处硬编码osloader的地址
FileName = (PCHAR)BlAllocateHeap(2);
FileName[0] = '\\';
FileName[1] = '\0';
#else
FileSize = strlen(&HalDirectoryPath[0]) + 1;
FileName = (PCHAR)BlAllocateHeap(FileSize);
strcpy(FileName, &HalDirectoryPath[0]);
#endif //i386
BlLoaderBlock->NtHalPathName = FileName;
// 获取NTFS设备的签名信息以允许内核创建正确的ARC名称
BlGetArcDiskInformation();
// 此处执行特定的安装代码.
Status = BlSetupForNt(BlLoaderBlock);
if (Status != ESUCCESS) {
BlFatalError(LOAD_SW_INT_ERR_CLASS,DIAG_BL_SETUP_FOR_NT,LOAD_SW_INT_ERR_ACT);
goto LoadFailed;
}
// 关闭调试系统
BlLogTerminate();
// 将控制权转换给已加载的映像
(SystemEntry)(BlLoaderBlock);
Status = EBADF;
BlFatalError(LOAD_SW_BAD_FILE_CLASS,DIAG_BL_KERNEL_INIT_XFER,LOAD_SW_FILE_REINST_ACT);
LoadFailed:
return Status;
}
至此,引导系统所需的模块(包括内核映像、HAL,以及必要的设备驱动程序)都已经被加载到内存中。而且,在此过程中os loader 也已经构造了一个参数块,记录下了这次引导过程中加载器所获得的各种参数信息。参数块的类型为LOADER_PARAMETER_BLOCK,Windows 内核在初始化过程中将会用到这些参数信息。WRK 中包含有它的定义,如下(见public\internal\base\inc\arc.h 文件):
typedef struct _LOADER_PARAMETER_BLOCK {
LIST_ENTRY LoadOrderListHead; //加载的模块链表,每个元素都为KLDR_DATA_TABLE_ENTRY
LIST_ENTRY MemoryDescriptorListHead;
//内存描述符链表,每个元素都为MEMORY_ALLOCATION_DESCRIPTOR
LIST_ENTRY BootDriverListHead; //引导驱动程序链表,每个元素都为BOOT_DRIVER_LIST_ENTRY
ULONG_PTR KernelStack; //内核栈顶
ULONG_PTR Prcb; //进程环境,指向一个进程控制块
ULONG_PTR Process; //初始进程,EPROCESS
ULONG_PTR Thread; //初始线程,ETHREAD
ULONG RegistryLength; //系统储巢的长度
PVOID RegistryBase; //系统储巢的基地址
PCONFIGURATION_COMPONENT_DATA ConfigurationRoot;
//配置树,包含ISA,磁盘和ACPI的配置数据
PCHAR ArcBootDeviceName; //引导分区的ARC名称
PCHAR ArcHalDeviceName; //系统分区的ARC名称
PCHAR NtBootPathName; //OS目录的路径名称,比如"\Windows"
PCHAR NtHalPathName; //OD加载器的路径名称,比如"\"
PCHAR LoadOptions; //引导选项,来自boot.ini
PNLS_DATA_BLOCK NlsData; //包含ANSI代码页,OEM代码页和Unicode码表
PARC_DISK_INFORMATION ArcDiskInformation; //所有磁盘的签名结构
PVOID OemFontFile; //OEM字体文件
struct _SETUP_LOADER_BLOCK *SetupLoaderBlock; //网络引导或文字模式安装引导
PLOADER_PARAMETER_EXTENSION Extension; //扩展部分
union {
I386_LOADER_BLOCK I386;
// ALPHA_LOADER_BLOCK Alpha;
// IA64_LOADER_BLOCK Ia64;
} u;
} LOADER_PARAMETER_BLOCK, *PLOADER_PARAMETER_BLOCK;
由上述代码的注解可以看出,LOADER_PARAMETER_BLOCK 参数块中包含了有关这次引导的各种参数信息和系统配置,这里ARC 名称是指符合ARC(Advanced RISCComputing)命名规范的字符串,例如“multi(0)disk(0)rdisk(0)partition(1)”是指0 号磁盘控制器第一块硬盘上的第一个分区。注意,参数块中的绝大多数信息由os loader 来填充,而在接下来的内核初始化过程中使用,但也有例外,比如有关线程和进程的信息需要在内核初始化过程中填充。
最后,os loader 将控制权交给内核模块的入口函数,该函数将不再返回,所以,接下来的引导过程由内核模块继续进行,引导扇区和系统加载器(ntldr 或os loader)的使命已经完成。下图显示了以上介绍的引导步骤。
我们已经看到,ntldr 构造了一个类型为LOADER_PARAMETER_BLOCK 的参数块,把与系统初始化相关的参数信息包装到此结构中,然后将控制权传递给内核模块ntoskrnl.exe 的入口函数。因此,内核的初始化从内核模块的入口函数开始,WRK 包含了内核初始化过程的绝大多数代码。此入口函数为KiSystemStartup,它是一个汇编函数,位于base\ntos\ke\i386\newsysbg.asm 文件中。
cPublicProc _KiSystemStartup ,1
push ebp
mov ebp, esp
sub esp, 32 ;分配空间给全局变量
mov ebx, dword ptr KissLoaderBlock
mov _KeLoaderBlock, ebx ; 获取加载器的参数
movzx ecx, _KeNumberProcessors ;获取处理器的个数
mov KissPbNumber, ecx
or ecx, ecx ;判断是否为引导处理器
jnz @f ;不是引导处理器
; 初始化0阶段使用静态的内存
mov dword ptr [ebx].LpbThread, offset _KiInitialThread ;初始化线程
mov dword ptr [ebx].LpbKernelStack, offset P0BootStack ;内核堆栈
push KGDT_R0_PCR ; P0 needs FS set
pop fs
; 在Prcb中存储处理器序号
mov byte ptr PCR[PcPrcbData+PbNumber], cl
;此处开始构造PCR (Processor Control Region)
@@:
mov eax, dword ptr [ebx].LpbThread
mov dword ptr KissIdleThread, eax
lea ecx, [eax].ThApcState.AsApcListHead ;初始化内核APC链表头
mov [eax].ThApcState.AsApcListHead, ecx ;
mov [eax].ThApcState.AsApcListHead+4, ecx ;
mov eax, dword ptr [ebx].LpbKernelStack
mov dword ptr KissIdleStack, eax
stdCall _KiInitializeMachineType
cmp byte ptr KissPbNumber, 0
jne kiss_notp0
; 初始化GDT,PCR,TSS,IDT
stdCall GetMachineBootPointers
; (edi) -> gdt
; (esi) -> pcr
; (edx) -> tss
; (eax) -> idt
; 保存相关参数到相应的寄存器
mov KissGdt, edi
mov KissPcr, esi
mov KissTss, edx
mov KissIdt, eax
;将TSS转换为32位,因为ntloader传递的tss为16位
lea ecx,[edi]+KGDT_TSS ; (ecx) -> TSS descriptor
mov byte ptr [ecx+5],089h ; 32bit, dpl=0, present, TSS32, not busy
; KiInitializeTSS2(
; TSS的线性地址
; TSS描述符的线性地址
; );
stdCall _KiInitializeTSS2, <KissTss, ecx>
stdCall _KiInitializeTSS, <KissTss>
mov cx,KGDT_TSS
ltr cx ;从GDT中取出相应的TSS段描述符
; 设置32位双重故障任务门去获取双重故障异常
mov eax,KissIdt
lea ecx,[eax]+40h ; 异常向量号8
mov byte ptr [ecx+5],085h ; 描述符特权级别dpl=0, present, taskgate
mov word ptr [ecx+2],KGDT_DF_TSS
lea ecx,[edi]+KGDT_DF_TSS
mov byte ptr [ecx+5],089h ; 32位, 描述符特权级别dpl=0, present, TSS32, not busy
mov edx,offset FLAT:_KiDoubleFaultTSS
mov eax,edx
mov [ecx+KgdtBaseLow],ax
shr eax,16
mov [ecx+KgdtBaseHi],ah
mov [ecx+KgdtBaseMid],al
mov eax, MINIMUM_TSS_SIZE
mov [ecx+KgdtLimitLow],ax
; KiInitializeTSS(
; 双重故障任务状态段
; );
push edx
stdCall _KiInitializeTSS, <edx>
pop edx
mov eax,cr3
mov [edx+TssCr3],eax
mov eax, offset FLAT:_KiDoubleFaultStack
mov dword ptr [edx+TssEsp],eax
mov dword ptr [edx+TssEsp0],eax
mov dword ptr [edx+020h],offset FLAT:_KiTrap08
mov dword ptr [edx+024h],0 ; eflags
mov word ptr [edx+04ch],KGDT_R0_CODE ; 设置CS的值
mov word ptr [edx+058h],KGDT_R0_PCR ; 设置FS的值
mov [edx+050h],ss
mov word ptr [edx+048h],KGDT_R3_DATA OR RPL_MASK ; Es
mov word ptr [edx+054h],KGDT_R3_DATA OR RPL_MASK ; Ds
; 设置32位不可屏蔽中断任务门去获取不可屏蔽中断异常
mov eax,KissIdt
lea ecx,[eax]+10h ; Descriptor 2
mov byte ptr [ecx+5],085h ; dpl=0, present, taskgate
mov word ptr [ecx+2],KGDT_NMI_TSS
lea ecx,[edi]+KGDT_NMI_TSS
mov byte ptr [ecx+5],089h ; 32bit, dpl=0, present, TSS32, not busy
mov edx,offset FLAT:_KiNMITSS
mov eax,edx
mov [ecx+KgdtBaseLow],ax
shr eax,16
mov [ecx+KgdtBaseHi],ah
mov [ecx+KgdtBaseMid],al
mov eax, MINIMUM_TSS_SIZE
mov [ecx+KgdtLimitLow],ax
push edx
stdCall _KiInitializeTSS,<edx>
; KiInitializeTSS(
; TSS地址
; );
pop edx
; We are using the DoubleFault stack as the DoubleFault stack and the NMI Task Gate stack and briefly, it is the DPC stack for the first
; processor.
mov eax,cr3
mov [edx+TssCr3],eax
mov eax, offset FLAT:_KiDoubleFaultTSS
mov eax, dword ptr [eax+038h] ; get DF stack
mov dword ptr [edx+TssEsp0],eax ; use it for NMI stack
mov dword ptr [edx+038h],eax
mov dword ptr [edx+020h],offset FLAT:_KiTrap02
mov dword ptr [edx+024h],0 ; eflags
mov word ptr [edx+04ch],KGDT_R0_CODE ; set value for CS
mov word ptr [edx+058h],KGDT_R0_PCR ; set value for FS
mov [edx+050h],ss
mov word ptr [edx+048h],KGDT_R3_DATA OR RPL_MASK ; Es
mov word ptr [edx+054h],KGDT_R3_DATA OR RPL_MASK ; Ds
stdCall _KiInitializePcr, <KissPbNumber,KissPcr,KissIdt,KissGdt,KissTss,KissIdleThread,offset FLAT:_KiDoubleFaultStack>
; 在当前线程对象中设置当前进程指针
mov edx, KissIdleThread
mov ecx, offset FLAT:_KiInitialProcess ; (ecx)-> idle process obj
mov [edx]+ThApcState+AsProcess, ecx ; set addr of thread's process
; 设置 PCR: Teb, Prcb 指针.
; Prcb 相关参数将在函数 _KiInitializeKernel中设置
mov dword ptr PCR[PcTeb], 0 ; PCR->Teb = 0
; 初始化KernelDr7和KernelDr6为0. 该操作必须在调试器调用前完成.
mov dword ptr PCR[PcPrcbData+PbProcessorState+PsSpecialRegisters+SrKernelDr6],0
mov dword ptr PCR[PcPrcbData+PbProcessorState+PsSpecialRegisters+SrKernelDr7],0
; 内核IDT重新设置,转换成i386可以识别的次序.该操作只能由引导处理器完成
stdCall _KiSwapIDT
mov eax,KGDT_R3_DATA OR RPL_MASK ; 设置请求特权级RPL为ring 3
mov ds,ax
mov es,ax
; 复制陷阱处理器替换内核调试处理器
mov eax, KissIdt ; (eax)-> Idt
push dword ptr [eax+40h] ; 保存双重故障描述符
push dword ptr [eax+44h]
push dword ptr [eax+10h] ; 保存不可屏蔽中断故障描述符
push dword ptr [eax+14h]
mov edi,KissIdt
mov esi,offset FLAT:_IDT
mov ecx,offset FLAT:_IDTLEN ;
shr ecx,2
rep movsd
pop dword ptr [eax+14h] ; 恢复双重故障描述符
pop dword ptr [eax+10h]
pop dword ptr [eax+44h] ; 恢复不可屏蔽中断故障描述符
pop dword ptr [eax+40h]
ifdef QLOCK_STAT_GATHER
EXTRNP KiCaptureQueuedSpinlockRoutines,0,,FASTCALL
fstCall KiCaptureQueuedSpinlockRoutines
endif
kiss_notp0:
ifndef NT_UP
; 告知引导处理器该处理器已经开始运行.
stdCall _KiProcessorStart
endif
; A new processor can't come online while execution is frozen
; Take freezelock while adding a processor to the system
; NOTE: don't use SPINLOCK macro - it has debugger stuff in it
@@: test _KiFreezeExecutionLock, 1
jnz short @b
lock bts _KiFreezeExecutionLock, 0
jc short @b
; 添加当前处理器到活动列表,并更新BroadcastMasks
mov ecx, dword ptr KissPbNumber ; 标记该处理器为活动的
mov byte ptr PCR[PcNumber], cl
mov eax, 1
shl eax, cl ; 关联字段
mov PCR[PcSetMember], eax
mov PCR[PcSetMemberCopy], eax
mov PCR[PcPrcbData.PbSetMember], eax
; 初始化处理器间的中断向量表并自增就绪处理器计数值以开启内核调试器.
stdCall _HalInitializeProcessor, <dword ptr KissPbNumber, KissLoaderBlock>
//为当前处理器初始化其HAL中的PCR和处理器间中断向量
ifdef _APIC_TPR_
; 通过hal记录IRQL表,并传递过来
mov eax, KissLoaderBlock
mov eax, [eax]+LpbExtension
mov ecx, [eax]+LpeHalpIRQLToTPR
mov _HalpIRQLToTPR, ecx
mov ecx, [eax]+LpeHalpVectorToIRQL
mov _HalpVectorToIRQL, ecx
endif
mov eax, PCR[PcSetMember]
or _KeActiveProcessors, eax ; 活动处理器的新关联值
; 初始化ABIOS数据结构.
; KiInitializeAbios例程必须在KeLoaderBlock初始化完成之后调用
stdCall _KiInitializeAbios, <dword ptr KissPbNumber>
inc _KeNumberProcessors ; 又有新的处理器被激活
xor eax, eax ; 释放执行锁
mov _KiFreezeExecutionLock, eax
cmp byte ptr KissPbNumber, 0
jnz @f
; 不能在调试器中暂停
stdCall _KdInitSystem, <0,_KeLoaderBlock>
if DEVL
; 给予调试器获得控制权的机会.
POLL_DEBUGGER
endif ; DEVL
@@:
nop ; 留一个位置给int3指令
; 设置初始化的IRQL = HIGH_LEVEL
RaiseIrql HIGH_LEVEL
mov KissIrql, al
or _KiBootFeatureBits, KF_CMPXCHG8B ; CMPXCHG8B是XP的一个标识
; 初始化ebp,esp和其他参数寄存器,为初始化内核做准备
mov ebx, KissIdleThread
mov edx, KissIdleStack
mov eax, KissPbNumber
and edx, NOT 3h ; 4字节边界对齐
xor ebp, ebp ; (ebp) = 0. 没有更多的栈帧了
mov esp, edx
; 为空闲线程栈NPX_SAVE_AREA预留空间并初始化
sub esp, NPX_FRAME_LENGTH+KTRAP_FRAME_LENGTH+KTRAP_FRAME_ALIGN
push CR0_EM+CR0_TS+CR0_MP ; 为Cr0NpxState预留空间
; arg6 - LoaderBlock
; arg5 - processor number
; arg4 - addr of prcb
; arg3 - idle thread's stack
; arg2 - addr of current thread obj
; arg1 - addr of current process obj
; 初始化系统数据结构和HAL
stdCall _KiInitializeKernel,<offset _KiInitialProcess,ebx,edx,dword ptr PCR[PcPrcb],eax,_KeLoaderBlock>
//执行内核初始化
; 设置空闲线程的优先级.
mov ebx,PCR[PcPrcbData+PbCurrentThread] ; 设置空闲线程的地址
mov byte ptr [ebx]+ThPriority, 0 ; 设置优先级为0
; Control is returned to the idle thread with IRQL at HIGH_LEVEL. Lower IRQL
; to DISPATCH_LEVEL and set wait IRQL of idle thread.
sti
LowerIrql DISPATCH_LEVEL
mov byte ptr [ebx]+ThWaitIrql, DISPATCH_LEVEL
// KiInitializeKernel函数返回以后,启动中断,将IRQL降低为DISPATCH_LEVEL
// 从而允许线程调度器选择新的线程
mov ebx, PCR[PcSelfPcr] ; 获取PCR的地址
; 在一个多处理器系统中,引导处理器直接进入空闲循环.而其他的处理器不会直接进入空闲循环;
; 而是等到所有处理器已经开启并且引导主扇区允许进入为止;
; 屏障KiBarrierWait对于系统的第一个处理器并不起作用,而仅对后续的处理器起作用
ifndef NT_UP
@@: cmp _KiBarrierWait, 0 ; 判断是否设置了KiBarrierWait
YIELD
jnz short @b
endif
push 0
jmp @[email protected] ; 进入空闲循环
stdENDP _KiSystemStartup