Windows內核分析之一 —— 內核入口函數
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
NetRoc
前段時間和yuewang和一塊三毛錢商量着寫寫Windows分析的文章,我來開個頭吧,哈哈。既然是開頭,所以就選擇了內核入口點開始,我向來不怎麼會寫文章,也就當流水賬記記吧,看能不能引出他們更好的分析出來J
Ntoskrnl的入口點函數名是KiSystemStartup,這是bootloader執行了一些基本的初始化之後跳轉到的內核入口函數,用彙編語言實現。
一、KiSystemStartup功能介紹
KiSystemStartup第一次運行於processor 0,主要是初始化一些系統硬件狀態,調用一些系統初始化過程,然後就進入調度程序,開始系統調度過程。而對於其他processor,初始化的時候也是進入KiSystemStartup,但是做的工作有所區別而已。
二、Processor 0(以後簡稱P0)開始執行KiSystemStartup時的系統環境
這個運行環境是由bootloader準備好的:
1、 一個精簡版的IDT環境,從0到0x1F號中斷已經被準備好
2、 一個完整的GDT被初始化出來並且Load。
3、 完整的TSS被初始化並且Load。
4、 頁面映射經過了基本的初始化,並且設置好了初始化所需的最少的頁面。虛擬內存的最低4M被直接映射到物理內存中。
5、 ntoskrnl.exe被裝載到它內存描述符中的地址。也即編譯時確定的基地址。
6、 DS=ES=SS,ESP指向一個可用的棧中。
7、 中斷被關閉。
三、其他Processor開始執行KiSystemStartup的環境
IDT, GDT, TSS, stack, selectors, PCR全部初始化完成並可用,頁表設置爲當前運行的頁表(這一點偶也不太明白,可能還需要看看以後的代碼才能理解),具備一個LoaderBlock,作爲在該處理器上執行KiSystemStartup的參數。
四、大致流程
1、 KiSystemStartup將參數KissLoaderBlock放到全局變量_KeLoaderBlock中
2、 取出_KeNumberProcessors,並判斷是否是0。_KeNumberProcessors保存了系統中的處理器數目,這個變量被初始化爲0,所以當Ntoskrnl開始執行時,這個變量還沒有被填充。因此判斷_KeNumberProcessors是否是0,就可以知道當前是不是第一次執行KiSystemStartup。
3、 如果是P0,會將_KiInitialThread和P0BootStack的地址分別保存 到_KeLoaderBlock中的對應字段中。_KiInitialThread是系統啓動之後的初始線程,而P0BootStack應該是初始化時臨時使用的內核堆棧,定義爲db KERNEL_STACK_SIZE dup (?)。KERNEL_STACK_SIZE在i386中是0x3000,在AMD64中是0x6000。然後會設置fs爲0x30,這是內核_KPCR結構的在GDT中序號。最後,會將處理器序號,也就是0,保存到_KPCR中對應位置,這個位置在i386和AMD64中也是不同的。
4、 下面又是所有處理器都會執行的代碼了,設置初始線程的_ETHREAD:: Tcb:: ApcState:: ApcListHead[0],將_LIST_ENTRY的Flink和Blink都設置爲自身。
5、 調用_KiInitializeMachineType過程,會設置一下機器類型。不過這裏做得很簡單,這個函數可能在未來也會有較大更改。主要的機器類型信息可能包含了總線類型、CPU大致的系列等簡單信息。
6、 然後又判斷是否是P0,如果不是,會跳過一大段初始化代碼。
7、 在P0的情況下,調用GetMachineBootPointers函數,獲取由bootloader初始化過的一些信息。從這個函數返回後,edi中保存gdt基地址,esi中保存pcr基地址,edx保存tss基地址,eax保存idt基地址。KiSystemStartup接下來會將這些值保存到自己的局部變量中使用。
8、 Bootloader初始化的TSS是16位的,KiSystemStartup在這裏會將它的標誌改爲32位,然後連續調用_KiInitializeTSS2和_KiInitializeTSS初始化TSS。KiInitializeTSS2初始化了內核TSS結構在GDT中描述符的界限大小,以及初始化IOPM的相關結構。_KiInitializeTSS在TSS中首先設置不使用IOPM,然後設置Tss->Flags = 0,將EFLAGS清空。最後將LDT和ss0都設置爲0。設置完成後,重新裝載TR寄存器。
9、 接下來設置了double fault task gate。這裏會設置IDT中的08號中斷,設置成了一個任務門,並填充相應的TSS結構,是用於#DF異常時的。
10、 設置用於NMI fault 的task gate,設置IDT的02號中斷。這是用於不可屏蔽中斷的中斷號。同樣也調用_KiInitializeTSS填充了另外一個TSS結構。上面兩條詳細的原理參考Intel手冊關於IDT和task gate的描述。
11、 調用_KiInitializePcr初始化了當前的pcr。
12、 將初始進程的_EPROCESS地址,即_KiInitialProcess的地址設置到了初始線程的_ETHREAD:: Tcb:: ApcState:: Process中。
13、 設置PCR->Teb = 0。
14、 設置PCR->PrcbData.ProcessorState.SpecialRegisters.KernelDr6和PCR->PrcbData.ProcessorState.SpecialRegisters.KernelDr7爲0。這裏是爲了初始化內核調試器相關的東西,具體作用可能只有分析到相關代碼才能知道了。
15、 調用_KiSwapIDT轉換IDT描述符的格式。IDTENTRY定義如下:
typedef struct tagIDTENTRY
{
unsigned short OffsetLow;
unsigned short Selector;
unsigned char Reserved;
unsigned char Type:4;
unsigned char Always0:1;
unsigned char Dpl:2;
unsigned char Present:1;
unsigned short OffsetHigh;
} IDTENTRY, *PIDTENTRY;
這個函數將ntoskrnl定義的IDT表項數組中,選擇子的Selector和OffsetHigh字段對換。這裏估計是在初始化這些表項的時候,爲了方便直接將處理代碼的地址填到了&OffsetLow中,所以Selector保存了高位的地址,然後到後面來統一替換。詳細的原理參見Intel手冊。
16、 將ds和es的值設置爲0x23,也就是Ring3下的ds和es值。
17、 將ntoskrnl中_IDT數組的內容複製到當前的IDT表中。前面設置的double fault和 nmi fault的表項不會被覆蓋掉,而是使用新設置的內容。
18、 接下來又是所有處理器都會執行的操作了。調用_KiProcessorStart初始化處理器。這個函數會根據KiProcessorStartControl的不同值進行不同的操作,例如獲取一些處理器信息、啓動或者停止處理器等等。由於P0已經不需要初始化了,所以在P0階段這個函數直接返回。
19、 獲取_KiFreezeExecutionLock這個鎖,用於修改一些和處理器相關的全局資源。主要是_KPCR裏面的處理器相關的信息。然後調用了_HalInitializeProcessor函數,初始化該處理器的IDT。估計這個函數會繼續爲每個處理器調用KiSystemStartup函數。不過沒能確認。
20、 將IRQL的信息保存下來。這是hal由參數傳過來的。
21、 在_KeActiveProcessors中設置初始化完成的處理器MASK。
22、 調用_KiInitializeAbios初始化ABIOS結構。這裏的詳細原理就不太清楚了,因爲沒能分析過相關部分。
23、 將_KeNumberProcessors加1,增加已初始化完成的處理器數量。然後就會釋放掉_KiFreezeExecutionLock鎖了。
24、 接下來調用_KdInitSystem函數。這裏應該會初始化內核調試器。只在P0上調用。
25、 後面將會初始化內核了,首先會將IRQL提升到HIGH_LEVEL,並初始化調用內核初始化函數使用的寄存器,包括傳遞參數的eax,ebx,edx,以及用於堆棧訪問的esp和ebp。然後就調用_KiInitializeKernel進行內核初始化。這個函數相當複雜,也夠一篇文章,這裏就不寫了。呵呵
26、 出來之後設置idle thread的優先級爲0,開中斷,降低IRQL到DISPATCH_LEVEL。然後檢查並等待_KiBarrierWait這個鎖。對P0來說,由於_KiBarrierWait初始化爲0,所以直接就跳到idle線程了,其他處理器會一直等待_KiBarrierWait,直到允許他們運行。
27、 最後通過一個長跳轉到KiIdleLoop函數,開始系統的處理和調度,整個系統初始化過程就完成了。
五、後記
唉,真的寫起來才發現文章不好寫啊。自己再看的時候都感覺不清不楚的,呵呵。不過暫時就這樣吧J