轉載自: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