Windows內核分析之一 —— 內核入口函數

Windows內核分析之一 —— 內核入口函數

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 

 

NetRoc

http://www.DbgTech.net/

 

前段時間和yuewang和一塊三毛錢商量着寫寫Windows分析的文章,我來開個頭吧,哈哈。既然是開頭,所以就選擇了內核入口點開始,我向來不怎麼會寫文章,也就當流水賬記記吧,看能不能引出他們更好的分析出來J

Ntoskrnl的入口點函數名是KiSystemStartup,這是bootloader執行了一些基本的初始化之後跳轉到的內核入口函數,用彙編語言實現。

一、KiSystemStartup功能介紹

KiSystemStartup第一次運行於processor 0,主要是初始化一些系統硬件狀態,調用一些系統初始化過程,然後就進入調度程序,開始系統調度過程。而對於其他processor,初始化的時候也是進入KiSystemStartup,但是做的工作有所區別而已。

 

二、Processor 0(以後簡稱P0)開始執行KiSystemStartup時的系統環境

這個運行環境是由bootloader準備好的:

1、 一個精簡版的IDT環境,從00x1F號中斷已經被準備好

2、 一個完整的GDT被初始化出來並且Load

3、 完整的TSS被初始化並且Load

4、 頁面映射經過了基本的初始化,並且設置好了初始化所需的最少的頁面。虛擬內存的最低4M被直接映射到物理內存中。

5、 ntoskrnl.exe被裝載到它內存描述符中的地址。也即編譯時確定的基地址。

6、 DS=ES=SSESP指向一個可用的棧中。

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,會將_KiInitialThreadP0BootStack的地址分別保存 _KeLoaderBlock中的對應字段中。_KiInitialThread是系統啓動之後的初始線程,而P0BootStack應該是初始化時臨時使用的內核堆棧,定義爲db      KERNEL_STACK_SIZE dup (?)KERNEL_STACK_SIZEi386中是0x3000,在AMD64中是0x6000。然後會設置fs0x30,這是內核_KPCR結構的在GDT中序號。最後,會將處理器序號,也就是0,保存到_KPCR中對應位置,這個位置在i386AMD64中也是不同的。

4、 下面又是所有處理器都會執行的代碼了,設置初始線程的_ETHREAD:: Tcb:: ApcState:: ApcListHead[0],將_LIST_ENTRYFlinkBlink都設置爲自身。

5、 調用_KiInitializeMachineType過程,會設置一下機器類型。不過這裏做得很簡單,這個函數可能在未來也會有較大更改。主要的機器類型信息可能包含了總線類型、CPU大致的系列等簡單信息。

6、 然後又判斷是否是P0,如果不是,會跳過一大段初始化代碼。

7、 P0的情況下,調用GetMachineBootPointers函數,獲取由bootloader初始化過的一些信息。從這個函數返回後,edi中保存gdt基地址,esi中保存pcr基地址,edx保存tss基地址,eax保存idt基地址。KiSystemStartup接下來會將這些值保存到自己的局部變量中使用。

8、 Bootloader初始化的TSS16位的,KiSystemStartup在這裏會將它的標誌改爲32位,然後連續調用_KiInitializeTSS2_KiInitializeTSS初始化TSSKiInitializeTSS2初始化了內核TSS結構在GDT中描述符的界限大小,以及初始化IOPM的相關結構。_KiInitializeTSSTSS中首先設置不使用IOPM,然後設置Tss->Flags = 0,將EFLAGS清空。最後將LDTss0都設置爲0。設置完成後,重新裝載TR寄存器。

9、 接下來設置了double fault task gate。這裏會設置IDT中的08號中斷,設置成了一個任務門,並填充相應的TSS結構,是用於#DF異常時的。

10、             設置用於NMI fault task gate,設置IDT02號中斷。這是用於不可屏蔽中斷的中斷號。同樣也調用_KiInitializeTSS填充了另外一個TSS結構。上面兩條詳細的原理參考Intel手冊關於IDTtask gate的描述。

11、             調用_KiInitializePcr初始化了當前的pcr

12、             將初始進程的_EPROCESS地址,即_KiInitialProcess的地址設置到了初始線程的_ETHREAD:: Tcb:: ApcState:: Process中。

13、             設置PCR->Teb = 0

14、             設置PCR->PrcbData.ProcessorState.SpecialRegisters.KernelDr6PCR->PrcbData.ProcessorState.SpecialRegisters.KernelDr70。這裏是爲了初始化內核調試器相關的東西,具體作用可能只有分析到相關代碼才能知道了。

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表項數組中,選擇子的SelectorOffsetHigh字段對換。這裏估計是在初始化這些表項的時候,爲了方便直接將處理代碼的地址填到了&OffsetLow中,所以Selector保存了高位的地址,然後到後面來統一替換。詳細的原理參見Intel手冊。

16、             dses的值設置爲0x23,也就是Ring3下的dses值。

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、             _KeNumberProcessors1,增加已初始化完成的處理器數量。然後就會釋放掉_KiFreezeExecutionLock鎖了。

24、             接下來調用_KdInitSystem函數。這裏應該會初始化內核調試器。只在P0上調用。

25、             後面將會初始化內核了,首先會將IRQL提升到HIGH_LEVEL,並初始化調用內核初始化函數使用的寄存器,包括傳遞參數的eax,ebx,edx,以及用於堆棧訪問的espebp。然後就調用_KiInitializeKernel進行內核初始化。這個函數相當複雜,也夠一篇文章,這裏就不寫了。呵呵

26、             出來之後設置idle thread的優先級爲0,開中斷,降低IRQLDISPATCH_LEVEL。然後檢查並等待_KiBarrierWait這個鎖。對P0來說,由於_KiBarrierWait初始化爲0,所以直接就跳到idle線程了,其他處理器會一直等待_KiBarrierWait,直到允許他們運行。

27、             最後通過一個長跳轉到KiIdleLoop函數,開始系統的處理和調度,整個系統初始化過程就完成了。

 

五、後記

唉,真的寫起來才發現文章不好寫啊。自己再看的時候都感覺不清不楚的,呵呵。不過暫時就這樣吧J

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章