3.系統機制
微軟提供了一些基本組件讓內核模式的組件使用:
1.陷阱分發,包括終端,延遲的過程調用(DPC),異步過程調用(APC),異常分發以及系統服務分發
2.執行體對象管理器
3.同步,包括自旋鎖,內核分發器對象,以及等待是如何實現的。
4.系統輔助線程
5.其他的機制,比如Windows全局標記
6.本地過程調用
7.內核事件跟蹤
8.Wow64
3.1陷阱分發
中斷和異常是導致處理器轉向正常控制流之外代碼的兩種系統條件。陷阱(trap)是指當異常或者中斷發生時,處理器捕捉到一個執行線程,並將控制權轉移到操作系統中某處固定地址處的機制。
在Windows中處理器將控制權轉給一個陷阱處理器 (trap handle)。所謂陷阱處理器是指與某個特殊的中斷或者一場相關的一個函數。
內核對待中斷和異常是有區別的,中斷是異步事件,並且與當前正在運行的任務毫無關系。中斷主要由I/O設備,處理器時鍾,定時器產生。中斷可以允許和禁止。異常是一個同步過程,它是一個特殊指令執行的結果。
異常可以在同樣數據在一個程序里重現。異常的例子:內存訪問違例,特定的調試器指令,以及除0錯誤。內核把系統服務調用異常(從技術上講,他們是系統陷阱(trap))。
當一個硬件異常或者中斷產生的時候,處理器在被中斷的線程的內核棧中記錄機器狀態信息,當它可以回到控制流中該點處繼續執行。如果該線程在用戶模式下執行,那么windows就切換到該線程的內核模式棧。然后windows在被中斷的線程的內核棧上創建一個陷阱幀(trap frame),並把線程的執行狀態保存在陷阱幀里。在內核調試器中輸入dtnt!_ktrap_frame就可以看到陷阱定義。
多數情況下內核安裝了前端陷阱處理函數,在內核將控制權交給與改陷阱香瓜的其他函數之后或者之前,由這些前段陷阱來執行一些常規的陷阱任務。
如陷阱條件是一個設備中斷,則內核硬件中斷陷阱處理器將控制權轉交給一個由設備驅動程序提供給改中斷設備的中斷服務例程(ISR)。
若陷阱條件是因為調用了一個系統服務引發,那么通用的系統服務陷阱處理器將控制前交給執行體中指定的系統服務。內核不會為不處理的陷阱安裝陷阱處理器。陷阱處理器一般使用KeBugCheckEx,當內核檢測到可能導致數據被破壞的行為時,改函數會停止計算機。
3.1.1 中斷分發
硬件產生的中斷往往是有I/O設置激發的。當設備需要服務就會以中斷的方式通知處理器。中斷驅動的設置可以一步的進行I/O處理。
系統可以產生軟中斷,如內核可能觸發一個軟中斷,觸發線程分發過程,同時也以異步的方式打斷一個線程的執行。
內核安裝了中斷陷阱處理器來響應設備中斷,中斷陷阱處理器將控制權遞給一個負責該中斷的外部例程(ISR)或者傳遞給一個響應中斷的內部內核例程。
下面介紹硬件如何向處理器通知中斷,內核支持中斷類型,設備驅動如何與內核交互,以及內核如何識別軟中斷。
3.1.1.1 硬件中斷
在windows鎖支持的平台上,外部I/O中斷進入中斷控制器的一個引腳,該控制器在cpu的引腳上中斷cpu。中斷控制器將IRQ(中斷請求)翻譯成中斷號,利用該中斷號作為中斷分發表的索引。並將控制權傳遞給恰當的中斷分發例程。
在引導時,windows填充IDT(中斷分發表),其中包含了指向內核中負責處理每個中斷和異常的指針。
windows將硬件IRQ映射到IDT上,同時它利用IDT來為異常配置陷阱處理器。雖然windows支持最多256個IDT項,但是支持的IRQ數據量由中斷控制機設計決定。
3.1.1.2 軟中斷請求級別(IRQL)
雖然中斷控制器已經實現一層中斷優先級,但是windows仍然強迫使用它自己的中斷優先級方案,稱為中斷請求級別(IRQL)。
X86,X64,IA64中斷請求級別:
中斷是按優先級別來處理的高優先會搶占低優先級中斷的執行權,當一個高優先級中斷發送,處理器會把中斷線程上下文保存起來,並調用與中斷相關的陷阱分發器,陷阱分發器提升IRQL,並調用中斷服務例程,調用完成后降低IRQL,回到中斷發送前,被中斷線程運行。但是當有其他,低優先級中斷時,當IRQL降低,低優先級中斷出現。這樣,內核會恢復到上述過程來處理中斷。
線程優先是線程的屬性,IRQL是中斷源的屬性。每個處理器的IRQL設置可以隨系統代碼的執行變化。
每個處理器的IRQL設置決定了該處理器可以接收哪些中斷。當一個內核模式線程運行時,可以通過KeRaiseIrql和KeLowerIrql來提升和降低處理器IRQL或通過調用內核同步對象的函數間接提高或者降低IRQL。當處理器的IRQL高於中斷源則被屏蔽,否則被中斷打斷。
訪問。
因為訪問PIC(中斷控制器)比較慢所以引入了優化技術延遲IRQL以避免訪問PIC。當IRQL被提升,HAL記下新的IRQL而不是去修改中斷屏蔽值。當一個較低中斷發生則HAL將中斷屏蔽值設置為對於第一個中斷正常的值。這樣當IRQL被提升的時候沒有更低優先級中斷,則HAL需要修改PIC。
一個內核模式線程根據請求,來降低和升高處理器IRQL。當中斷發生時,陷阱處理器(或處理器本身)將改處理器的IRQL提升到中斷源的IRQL.這樣會把等於或者低於它的所有中斷都屏蔽。保證了不被低級中斷截掉。被屏蔽的中斷由其他處理器處理或者被保存下來知道IRQL下降。
因此系統組件包括內核和設備驅動,都試圖讓IRQL保持在被動級別。這樣可以提高設備啟動可以更加及時的響應硬件中斷。
每個中斷級別都有特定的目的,如內核發出一個處理器間的中斷,以請求另外一個處理器執行一個動作。
將中斷映射到IRQL:IRQL級別和中斷控制器定義的中斷請求並不相同,在hal中決定一個中斷分配給那個IRQL。然后調用HAL函數HalGetSystemInterruptVector把中斷映射到對應的IRQL。
預定義的IRQL:以下介紹一下預定義的IRQL
1.只有當內核在KeBugCheckEx中停止了系統並屏蔽所有中斷的時候,內核才會使用高級別的IRQL。
2.電源失敗,出現在NT文檔中,但是從來沒有使用過。
3.處理器間的中斷,用於向另外一個處理器請求執行一個動作。
4.時鍾,主要用於系統時鍾,內核利用該黃總段級別來跟蹤具體時刻,以及現場測量或者分配cpu時間。
5.性能剖析(Profile),當內核的性能剖析功能被打開的時候,內核性能剖析陷阱處理器會記錄下中斷發生時被執行的代碼的地址。(性能剖析器Kernrate)
6.設備IRQL,用來對設別中斷優先級分區
7.DPC/Dispath級別和APC級別是由內核和設備驅動程序產生的軟中斷。
8.被動級別,最低的IRQL優先級別,它不是一個中斷級別,它是普通線程運行時設置的,允許所有中斷發生。
對於DPC/Dispatch級別或者更高級別上的代碼,一個重要的限制是它不能等待一個對象。另外一個限制DPC/Dispatch或者更高級別的IRQL只能訪問非換出頁內存。若2個限制都違反了系統會崩潰,代碼為IRQL_NOT_LESS_OR_EQUAL。在驅動上違反限制是一種常見的錯誤。在驅動上違反限制上一種創建的錯誤。
中斷對象,內核提供了一種可移植的機制使得設備驅動程序可以為它們的設備注冊ISR。這是一個稱為中斷對象的內核控制對象。
中斷對象包含了所有“供內核將一個設別的ISR與一個特定級別的中斷關聯起來的所有信息”,包含該ISR的地址,該設備中斷時所在的IRQL級別,以及內核中該ISR關聯的IDT項。
駐留在中斷對象中的代碼調用了實際的中斷分發器,通用是內核的KiInterruptDispatch或者KiChainedDispatch例程,並將指向中斷對象的指針傳遞給它。
下圖顯示了中斷控制流:
將ISR與特定中斷級別關聯起來的稱為連接一個中斷對象,將ISR與IDT項斷開關聯稱為斷開一個中斷對象。這些操作通過內核函數IoconnectInterrupt和IoDisconnectInterrupt完成。
3.1.1.3 軟中斷
雖然大多數中斷都是硬件產生,但是windows內核也為各種各樣的任務產生軟中斷。包括:
1.激發線程分發
2.非時間緊急中斷處理
3.處理器定時到期
4.特定線程的環境中異步執行一個過程
5.支持異步I/O操作
分發或者延遲過程調用(DPC)中斷,當一個線程不能繼續執行的時候,比如因為線程已經終止了或者主動進入等待狀態,內核就會直接調用分發器,從而立即導致一個環境切換。但是有時檢測到線程已深入到許多層代碼中,這時應該進行重新調度,這種情況下內核請求分發,但是將它推遲到完成了當前的行為之后再進行。
當嚙合對共享的內核數據訪問,會把IRQL拉到DPC/Dispatch級別當內核檢查到需要分發的時候,請求一個DPC/Dispatch中斷。所以只有當內核完成了當前的活動,把IRQL拉低,分發中斷才能處理。
延遲事務也在這個IRQL上運行,DPC是完成一項系統任務,但是不是那么緊迫,這些函數被稱為延遲的,是因為不會了立即執行。
DPC賦予操作系統一種能力,產生一個中斷並且在內核模式下執行系統函數。內核利用dpc來處理定時到期,以及一個線程的時限到期以后重新調度處理器。為了給硬件中斷提供及時的服務,windows視圖把IRQL保持在低於設備IRQL之下。為了達到這個目的,讓設備驅動程序ISR執行最少必要的工作來響應他們的設備,將異變的中斷狀態保存起來,並將數據傳輸非時間緊迫的中斷處理推遲到 DPC/Dispatch IRQL級別上的DPC中在執行。
DPC是通過DPC對象來表示的,DPC對象是內核控制對象,對於用戶模式不可見,對於設備驅動和內核代碼是可見的。DPC對象包含最重要的信息是DPC中斷將要調用哪個系統函數地址。正在等待的DPC被存放在隊列中,每個處理器都有一個隊列稱為DPC隊列。要想請求一個DPC,系統會初始化DPC對象,然后放入DPC隊列中。
默認情況下內核把DPC對象放在發生該DPC請求的處理器的DPC隊列末尾。在設備驅動程序只需指定一個DPC優先級別和指定特定CPU,就可以改變這種默認方式。指定在某個CPU上叫定向DPC。如果一個DPC的優先級為低級或者中級則放入隊列尾,否則放入隊列頭部。
當處理器的IRQL從DPC/Dispatch或更高降到某個更低的級別時,內核處理DPC。在處理DPC是IRQL在DPC/Dispatch級別上,並且將DPC來出來運行直到隊列為空。當隊列為空內核才讓IRQL降低到DPC/Dispatch以下。讓正常的線程執行過程繼續執行。
DPC優先級可以以另一種方式影響到系統行為。內核通常通過一個DPC/Dispatch級別的中斷來激發隊列“抽干”的動作。只有當一個DPC被定為在ISR所在的處理器上,且改DPC的優先級是高級或者中級時,內核才產生一個中斷,若為低級只有當DPC請求到一個閥值或一段時間后,內核才會請求中斷。
若DPC被定為在一個不同於其ISR運行的CPU上,並DPC為高級。內核立即用一個信號通知CPU,以便”抽干”它的DPC隊列。若優先級為中級或者低級則DPC數據超過閥值,內核才會激發一個DPC/Dispatch中斷。
DPC中斷產生的規則:
DPC優先級別 |
DPC被定為在ISR的處理器上 |
DPC被定為在另一個處理器上 |
低級 |
DPC隊列長度超過最大的DPC隊列長度值,或者DPC請求率小於最小的DPC請求率。 |
DPC隊列長度超過最大的DPC隊列長度或者系統空閑 |
中級 |
總是激發 |
DPC隊列長度超過最大的DPC隊列長度或者系統空閑 |
高級 |
總是激發 |
總是激發 |
DPC主要為設備驅動提供的,但是內核也使用DPC,內核使用DPC來處理限時到期事件。在系統時鍾每個”嘀嗒”點上,就發生一個時鍾IRQL級別的中斷。時鍾中斷處理器對系統時間進行更新,將一個記錄了當前線程運行多長時間的計數器遞減。當計數器減到0,線程到期,內核可能需要重新調度該處理器,這個任務在DPC/Dispatch IRQL上完成。
時鍾中斷處理器將一個DPC插入到隊列中以便激發分發過程。然后結束他的工作並且降低IRQL,因為DPC中斷級別較低,所以在時鍾中斷完成前出現尚未處理的設備中斷,都在DPC中斷之前被處理。
APC異步調用,異步過程調用提供了一種在特定用戶線程環境中執行用戶程序和系統代碼的途徑。APC經過排隊以便在特定線程的環境中執行。
APC是由一個內核控制對象(APC對象)來描述的,正在等待執行的APC駐留在一個由內核管理的APC隊列中。APC隊列是特定線程相關的,即每個線程有它自己的APC隊列,當內核請求要將APC排隊時,它將一個APC排隊,她將APC插入到將來執行此APC例程的那個線程的隊列中。當內核請求APC級別中斷,當該線程最終開始執行的時候,會執行此APC。
有2種APC:內核和用戶模式。內核模式的APC並不要求從目標獲取許可就可以運行在改線程的環境中,而用戶模式必須先獲取許可。內核模式的APC有普通和特別2種,將IRQL提升到APC級別或調用KeEnterGuardRegion,就可以靜止這兩種類型的內核模式APC。
執行體使用內核模式的APC來完成那些必須要在特定線程的地址空間(執行環境)中才能完成的操作系統任務。它可以利用特殊的內核模式APC來指示某個線程停止執行一個可中斷的系統服務。
用戶模式APC(ReadFileEX,WriteFileEx和QueueUserApc),如ReadfileEx,WritefileEx允許調用者指定一個完成例程,當I/O完成是例程就會被調用。I/O完成機制是通過I/O的線程插入一個APC來實現的。內核APC運行在APC級別上,用戶模式APC運行在被動級別上。
APC交付會導致等跌隊列重新排序,如APC用來把等待資源的線程掛起,那么該線程就會進入等待訪問這個資源隊列的末尾。
3.1.2 異常分發
中斷可以在任何時候發生,異常則是直接由當前正在運行的程序產生。windows引入了一種稱為結構化異常處理的設施,應用程序可以在異常發生時獲得控制,然后應用程序可以修正條件,並返回到異常發生處,將棧展開(使引發異常的子例程執行過程中止),或想系統報告,改異常不可識別,因為系統應該繼續搜索一個有可能處理此異常的異常處理器。
x86上所有異常都在預定義的中斷號,這些中斷號對應IDT項。每個項指向了某個特定異常的陷阱處理器。
所有異常,除了簡單的通過陷阱處理器,可以解決的之外,其他都由異常分發器的內核模塊服務。異常分發器就是找到一個異常處理器,處理要處理的異常。
異常處理對用戶來說都是透明的,有些異常也允許原封不動的回到用戶模式。如內存訪問違例,算法溢出,操作系統不對他們處理。環境子系統可以建立起基於幀的異常處理器來處理異常。
基於幀是將一個異常處理與一個特定的過程激活動作關聯起來。當一個過程被調用,代表該過程的幀被壓到棧中。一個棧幀可以關聯多個異常處理器,每個異常處理器保護源程序中一塊特定代碼。當發生一個異常時,內核查找與當前幀關聯在一起的某個異常處理器。如果沒有找到內核繼續查找與上一個棧幀關聯在一起的某個處理。如果最終還是沒有找到異常處理器,內核會調用自己默認的異常處理器。
異常發生,CPU硬件將控制權遞交給內核陷阱處理器,內核陷阱處理器創建一個陷阱幀。正由於陷阱幀處理完異常后,系統可以從停止的地方恢復。
如果在內核模式下的異常,異常分發器調用一個例程來找到一個基於幀的異常處理器。由它來處理異常。
在用戶模式下,windows子系統有一個調試器端口和異常端口,通過它們來接收windows進程中用戶模式異常的通知。內核在它默認的異常處理器中用了這些端口。
調試器端口是最常見的異常來源,因此異常分發器采取動作,
1.查看引發該異常的進程是否有一個相關的調試器進程。若存在異常分發器發送一個調試器對象信息到調試對象相關的進程。
2.若該進程沒有附載的調試器進程或調試器並沒有處理該異常,那么異常分發器切換到用戶模式下。將陷阱幀按照Context數據結構的格式拷貝到用戶棧中,並調用一個例程來找到一個基於幀的異常處理器。
3.如果沒有找到或者雖然找到了但是它不處理該異常,則異常分發器切換到內核模式下,並且再次調用調試器,以便讓用戶做更多的調試。
4.若調試器不在運行,並沒有找到基於幀的處理器,那么內核向與該線程的進程關聯在一起的異常端口發送一個信息。該異常端口如果存在的話,則一定是由控制該線程的環境子系統注冊的。環境子系統監聽該端口,在恰當的時機把一個異常轉化為一個與環境相關的信號或異常。客戶/服務器運行時子系統(CSRSS)簡單的彈出一個消息框來通知用戶發生了錯誤,並且終止進程。
5.當POSIX從內核收到一個消息,指定的一個線程產生了異常,當內核在處理異常過程走得比較深了,而子系統並沒有處理該異常,那么內核執行一個默認的異常處理器,它只是簡單的將引發該異常的線程所在的進程終止掉。
未處理的異常
所有windows線程都有一個異常處理器來處理未被處理的異常。該異常處理器是在windows內部的進程啟動函數或線程啟動函數中聲明。如:
如果一個線程的異常沒有被處理,則windows的未處理異常過濾器將會被調用。這個函數目的是,當一個異常未被處理時,可以提供一種系統統一的行為和方法。
3.1.3 系統服務分發
內核陷阱處理器分發中斷,異常和系統服務調用
3.1.3.1 32位系統服務分發
在x86 Pentium II處理器以上,使用windows使用sysenter執行觸發一個陷阱,這個是intel特別為快速系統服務定義的。為了支持這一指令,windows在引導時刻把內核的服務分發器的地址保存與該指令相關的寄存器中。
執行該指令會導致變化到內核模式下,並且執行系統服務分發器,為了返回到用戶模式,系統服務分發器通常執行sysexit執行(當處理器單步標記被打開,系統分發器改而使用iretd指令。)
在K6和更高的32位AMD處理器上,windows使用syscall類似於sysenter,系統嗲用號也在EAX上,而調用者參數則保存在棧中。在完成了分發之后,內核執行sysret指令。
3.1.3.2 64位系統服務分發
在64位體系結構上,windows使用syscall指令進行系統分發(和AMD處理器上syscall類似),系統調用號存在EAX寄存器,前4個參數存放在寄存器匯總其他參數存放在棧中。
在IA64上使用EPC指令,前8個系統調用參數通過寄存器來傳遞,其他參數通過棧傳遞。
3.1.3.3 內核模式的系統服務分發
內核利用傳遞進來的參數找到系統分發表中的服務信息(類似IDT表)。
系統服務分發器KiSystemService將調用的參數從用戶模式棧中復制到內核模式,然后執行服務。如果換地給一個系統服務的參數指向了用戶空間中的緩沖區,那么在內核模式代碼復制到緩沖區或從緩沖區讀前先要查明緩沖區是否可以訪問。
每個線程都有一個指針指向它的系統服務表,windows有2個系統服務表,最多可以支持4個。系統服務分發器確定哪個表包含了所有請求的服務,它將32位系統服務號中的其中2個位解釋成一個索引表。系統服務號低12位被用在該表索引所指定的表中進行的索引。
3.1.3.4 服務描述符表
一個主要的默認數組表(KeDescriptorTable)定義了Ntosrknl.exe中實現的核心執行體系統服務。另一個默認數組表(KeserviceDeseriptorTableShadow)包含了在windows子系統的內核模式部分win32.sys中實現的windows user和GDI服務。
當windows線程第一次調用一個windows user和GDI服務,該線程的系統服務表的地址被指向一個包含windows user和GDI服務的表格。KeAddSystemSericeTable可以讓win32.sys加入到系統服務表中。
針對windows執行體服務的系統服務分發指令位於NTdll.dll中。子系統調用Ntdll.dll來實現。windows user和GDI函數,在這些函數中,系統分發指令是直接在user32.dll和GDI.dll中實現,沒有涉及ntdll.dll。
3.2 對象管理器
windows實現了一個對象模型,以便為執行體的實現各種內部服務提供了一致的,安全的訪問路徑。執行體內部復制創建,刪除,保護和跟蹤對象的組件(windows對象管理器)。
對象管理器把原本可能散落在整個系統各處的資源控制操作集中在一起。對象管理器的設計意圖是為了實現本章稍后列出的一些功能。
1.提供一種公共,同一個的機制來使用系統資源
2.將對象保護隔離到操作系統統一的區域中,從而可以做到c2安全等級
3.提供一種來記錄進程使用對象數量的機制,從而可以對系統資源的使用上加限制。
4.簡歷一套對象命名方案,可以很方便的融合現有對象。
5.支持各種操作系統環境的需要
6.簡歷統一的規則來維護對象的保持力。
windows內部有兩種類型的對象:執行體對象和內核對象。
執行體對象是指執行體的各種組件所實現的對象(如,進程管理器,內存管理器,I/O子系統)內核對象是指windows內核實現的一組更為基本的對象。這些對象在執行體內部被創建和使用。
3.2.1 執行體對象
每個windows環境子系統總是把操作系統的不同面貌呈現給它的應用程序。執行體對象和對象服務是環境子系統用於構建其自己版本的對象和其他資源基礎。
執行體對象往往由在用戶應用程序中一般的環境子系統或由操作系統的組件作為他們常規操作的一部分而創建。如為了創建一個文件,windows應用程序調用windows的createfile函數,該函數在windows子系統DLL kernel32.dll中現實中實現的,在經過了一些驗證和初始化工作以后,createfile會調用原生的windows服務ntcreatefile來創建一個執行體文件對象。
windows子系統使用執行體對象來導出它自己對象集合,其中許多對象直接對應於執行體對象。
暴露給windows api的執行體對象:
對象類型 |
所代表的含義 |
符號鏈接(Symbolic link) |
間接的引用一個對象名字的機制 |
進程(Process) |
虛擬地址空間,以及為了執行一組線程對象而必需的控制信息 |
線程(Thread) |
進程內部的一個可執行實體 |
作業(Job) |
指一組進程,通過作業機制,可以像單個實體那樣來管理他們 |
內存區(section) |
共享內存的一個區域(在windows中也稱為文件映射對象) |
文件(File) |
一個已打開的文件或者I/O設備的實例 |
訪問令牌(Access token) |
一個進程或者線程的安全輪廓(安全ID,用戶權限等) |
事件(Event) |
一個具有持久狀態(有信號,或者無信號的)的對象,可被用於同步或者通知。 |
信號量(Semaphore) |
信號量是一個計數器,它提供了資源門控能力,對該信號量所保護的資源只允許某個最大數目的線程來訪問它。 |
互斥體(Mutex) |
用於順序訪問一個資源的一種同步機制 |
定時器(Timer) |
這是一種當固定長時間過去時,通知一個線程的機制 |
IO完成(IoCompletion) |
使線程能夠將”I/O操作完成通知”進出隊列的一種方法,在windows中稱為IO完成端口。 |
鍵(Key) |
這是一種引用注冊表中數據的機制。 |
窗口站(WindowStation) |
該對象包含了一個剪貼板,一組全局原子和一組桌面對象 |
桌面(Desktop) |
這是一個被包含在窗口站內部的對象。桌面對象有一個邏輯顯示器表面,其中包含了窗口,菜單和鈎子。 |
3.2.2 對象結構
每個對象都有一個對象頭和對象體,對象管理器控制了對象頭,而執行體組件則控制了由它們創建的對象類型的對象體。每個對象頭指向一個進程列表,類表中每個進程都打開了此對象。對象類型指向了稱為類型對象的特殊對象。
3.2.2.1 對象頭和對象體
對象管理器使用對象頭中保存的數據來管理這些對象,而無須關系它們的類型。
標准對象頭的屬性:
屬性 |
用途 |
對象名稱 |
使一個對象對於其他的進程也是可見的,以便於共享 |
對象目錄 |
提供了一個層次結構來存儲對象名稱 |
安全描述符 |
決定誰可以使用該對象,以及允許它們如何使用它(注:對於沒有名稱的對象來說,安全描述符是空[null]) |
配額花費 |
列出了當一個進程打開一個指向該對象的句柄時,針對該進程收取的資源花費額 |
已打開句柄的計數 |
記錄了“打開一個句柄來指向該對象”的次數 |
已打開句柄的列表 |
指向一個進程列表,其中每個進程都打開了指向該對象的句柄。 |
對象類型 |
指向一個類型對象,該對象包含了正對這個類型的對象都是公共屬性 |
引用計數 |
記錄了一個內核模式組件引用該對象地址的次數 |
除了對象頭以外,每個對象也有一個對象體,並且其格式和內容只有這種對象類型才有,同一類型的所有對象共享同樣的對象體格式。一個執行體組件通過創建一個對象類型,並且為它提供一些服務,就可以控制和維護所有這個類型的對象體中的數據。
對象管理器提供了少量通過服務,通過這些服務可以對一個對象頭中保存的屬性進行操作,通過服務可以用再任何類型的對象上。
雖然通用的對象服務都支持所有對象類型,但是每個對象都有它自己的創建,打開和查詢服務。
3.2.2.2 對象類型
對象頭中包含的數據對於所有對象都是公共的,但是每個對象實例可以取不同的值。
為了節省內存,對象管理器只在創建一個新的對象類型時才會存儲靜態的,特定於對象類型的屬性。
進程對象和進程類型對象:
類型對象的屬性
屬性 |
用途 |
類型名稱 |
此種類型的對象名稱(“process”,”event”,”port”) |
池類型 |
指明了這種類型的對象是從換頁的還是非換頁的內存中分配 |
默認的配額花費 |
默認從進程配額中扣除的換頁內存池值和非換頁內存池值 |
訪問類型 |
當一個線程打開某個指向該類型的對象時可以請求的訪問類型(“讀”,”寫”,”終止”,”掛起”) |
通用訪問權限的映射關系 |
在4種通用的訪問權限和屬於該類型的訪問權限之間的映射關系。 |
同步 |
指明了一個線程是否可以等待這種的對象 |
方法 |
在一個對象的生命周期的特定點上,對象管理器自動調用的一個或者多個例程。 |
一個對象能否支持同步,取決於該對象是否包含了一個內嵌的分發器對象,在“低IRQL的同步”中有介紹。
3.2.2.3 對象方法
上面的表中,最后一個屬性就是方法。方法是由一組內部例程構成的,這些例程類似構造和解析函數(在創建和銷毀時被使用)。
當一個執行體組件創建了一個新的對象類型時,它可以像對象管理器注冊一個或多個方法,對象管理器在此種類型的對象生命周期中,某些明確定義的點上調用這些方法。
對象方法:
方法 |
何時調用 |
Open |
當一個對象句柄被打開 |
Close |
當一個對象句柄被關閉 |
Delete |
在對象管理器中刪除一個對象之前 |
Query name |
當一個線程在一個從屬名字空間中查詢一個對象的名稱時 |
Parse |
當對象管理器在一個從屬名字空間中搜索一個對象名稱時 |
Security |
當一個進程讀寫(如文件)在其從屬名字空間中的保護屬性時。 |
3.2.2.3.1 Open函數
對象管理器創建一個指向對象的句柄時會調用open方法,在對象被創建或者打開時候運行。只有一個對象類型(windowstation)定義了open方法。這樣win32.sys能夠與服務於桌面相關內存池的進程共享內存。
3.2.2.3.2 Close函數
close方法的例子是IO中,對象管理器關閉一個句柄使用close方法。close方法先檢查看正在關閉該文件句柄進程是否有任務用於該文件並且未完成的鎖,如果有則除去鎖。
3.2.2.3.3 Delete函數
對象管理器在內存中刪除臨時對象以前,調用delete方法。內存管理器為內存區對象類型注冊了delete方法,它會釋放該內存區使用的物理頁面。並在刪除內存區對象前驗證一下內存管理器為該內存區所分配的任何內部數據結構已被刪除了。
3.2.2.3.4 Parse函數(類似於Query name)
若發現對象存在於對象管理器名字空間外,允許對象管理器把查找一個對象的控制權交給一個從屬的對象管理器。若在搜索路徑上碰到一個關聯了parse的對象,會暫停搜索。對象管理器調用parse方法。將正在搜索的對象名稱的剩余部分傳給parse方法。除了對象管理器方法外在windows中還有注冊表名字空間和文件系統名字空間。例如打開一個名為\Device\Floppy0\docs\resume.doc的文件句柄,對象管理器遍歷它的名稱樹,直到到達Floppy0。調用parse,把\docs\resume.doc傳入。I/O管理器的parse例程接受名稱,並且傳給文件系統,文件系統找到文件並打開。
3.2.2.3.5 Security方法
Security也是I/O系統使用方法,類似parse。一旦一個線程視圖查詢或改變那些用於保護一個文件的安全信息時,該方法就會被調用。安全信息是存儲在文件對象中,而不是內存,因此必須調用I/O系統才能找到安全信息,並將它們讀出來或進行修改。
3.2.2.4 對象句柄和進程句柄表
當進程根據名稱來創建或者打開一個對象時,它會接受到一個句柄,通過句柄來訪問一個對象,要比使用名稱訪問快得多。因為對象管理器可以跳過名稱查找過程,直接找到目標對象。進程也可以在其創建時刻通過繼承句柄的方式獲得句柄或從另一個進程接收一個復制的句柄。
所有的用戶模式進程在其線程使用一個對象以前,必須先擁有一個指向該對象的句柄。句柄被用做指向系統資源的間接指針,這樣可以讓應用程序不與系統數據結構直接交互。
對象句柄還是提供了額外的一些好處:第一,不同句柄沒有什么區別可以使用統一的接口來引用。第二,對象管理器有獨立的權利來創建句柄,查找句柄。也就是對象管理器可以仔細地審查每個可能會影響對象的用戶模式動作。
對象句柄是索引與進程相關的句柄表中的項相關。執行體進程(EPROCESS)塊中一個域指向句柄表。句柄表實現方式是3層和虛擬地址到物理地址映射類似。
當進程被創建對象管理器分配了句柄表的最高層結構,其中包含了指向中間層表的指針;同時也創建了中間層,其中包含了第一個指向子句柄表的指針數組,還分配了最底層,其中包含了第一個子句柄表。
把低24位看成3個8位,分別索引到3層結構中的一層。在xp,2003在進程創建時,最底層句柄表被分配,其他的都會被按需分配。在windows 2000中一個子句柄表是255個可用表項。在xp,2003表項=(頁大小/表項大小)-1。在windows xp,2003上句柄表項:
P:說明了調用者是否允許關閉句柄。I:該進程創建的子進程是否在它們句柄表中有一份該句柄的拷貝。A:關閉該對象時是否應該產生一個升級信息(對象管理器內部使用該標記)。
系統組件和設備驅動程序通常需要打開一些不應該讓用戶訪問的對象。可以通過內核句柄表來表示。內核句柄表只有在內核模式下可以飛昂文,可以在任何進程環境下。
對象管理器看到一個句柄的高位被設置時,就會將它識別為內核句柄表中的句柄。也就是說內核句柄表中的句柄的引用值大於0x80000000。在windwos 2000中內核句柄表是一張獨立的句柄表,但是在xp和2003中,內核句柄表也被用做system進程的句柄表。
3.2.2.5 對象安全性
當一個進程打開一個句柄,對象管理器調用安全引用監視器,監視該對象描述符是否允許該進程所請求的訪問類型,若允許,引用監視器返回一組准許的訪問權限,同時對象管理器放入它創建的對象句柄中。
當下次進程要使用句柄時可以快速的檢查這一組句柄中的准許訪問權限。
3.2.2.6 對象保持力
對象保持力,分為暫時和永久的。暫時是當需要的時候使用,不需要的時候釋放。永久是一直保持知道被顯式釋放。
對象管理器通過兩個階段來實現對象保持力。第一階段稱為名稱保持力,第二個階段,不再有用時,停止保留對象本身(也就是刪除)。
名稱保持力:當一個進程打開一個對象的句柄。會在該對象頭信息中的已打開句柄計數器+1。當用完關閉句柄,對象管理器已打開句柄-1,。當計數器為0,對象管理器從全局名字空間中刪除該對象名稱。
不再有用時刪除:對象專門提供一個引用計數來記錄。
已打開句柄計數器:當進程打開一個對象句柄+1,關閉句柄-1
引用計數:提供一個對象指針+1,用完了-1
當已打開計數器為0,引用計數大於0.表示對象還在使用。當引用計數為0,對象管理器會從內存中將它刪除。
進程A,進程B和內核結構引用了一個對象,因此handlecount=2,referencecount=3。
3.2.2.7 資源記賬
windows對象管理器提供了一個中心設施來實現資源記賬。每個對象頭都包含了配額花費。
windows每個進程都指向一個配額的數據結構,配額為0表示不限制。
3.2.2.8 對象名稱
對象名稱可以滿足1.區分對象之間的方法。2.找到並獲得特定對象的方法。3.允許程序間共享。
只有2種情況會使用名稱進行查找,1.創建一個命名對象時,會通過名稱查找,驗證全局名稱空間中不存在。2.當打開一個句柄,句柄指向一個命名對象時,對象管理器查找該名稱並返回一個對象句柄。
對象的名稱存儲位置取決於對象類型
目錄 |
所存儲對象名稱的類型 |
\GLOBAL?? |
Ms-dos設備名(\DosDevices是指向此目錄的符號鏈接) |
\BaseNameObjects |
互斥體,時間,信號量,可等待的定時器和內存區對象 |
\Callback |
回調對象 |
\Device |
設備對象 |
\Driver |
驅動程序對象 |
\FileSystem |
文件系統驅動程序對象和文件系統識別器對象 |
\KnowDlls |
已知DLL(在啟動時候由系統映射的DLL)的內存區名稱和路徑 |
\Nls |
已映射的國家語言支持表的內存區名稱 |
\ObjectTyoes |
對象類型名稱 |
\RPC Control |
遠程過程調用(RPC)所使用的端口對象 |
\Security |
與安全子系統相關的對象的名稱 |
\Windows |
Windows子系統的端口和窗口站 |
對象的名稱相對於一台計算機而言是全局的,但是他們的跨越網絡是不可見的。但是對象管理器解析名稱的方法使得有可能訪問其他機器上的命名對象。
對象目錄是對象管理器支持這種層次型命名結構手段。對象目錄解析對象名稱為指向對象的指針。對象管理器利用指針來構建對應的句柄,將這些對象句柄返回給用戶模式的調用者。
符號鏈接,在某些文件系統中,通過符號鏈接,用戶可以創建一個文件名或一個目錄名,當被使用的時候,實際上被操作系統轉譯成另外一個不同的文件或文件名。使用符號鏈接,是一種讓用戶間接的共享一個文件或目錄的內容。
符號鏈接對象,完成的功能類似於對象名稱的功能一樣。當對象名稱中有符號鏈接,對象管理器遍歷它的對象名稱空間,找到該符號鏈接對象,並找到一個取代該符號鏈接名的字符串。
3.2.2.9 會話名稱空間
一個登陸到控制台會話上,用戶可以訪問全局名稱空間,另外會話可以獲得該名稱空間的私有名稱空間實例。\DosDevices,\Windows,\BaseNamedObjects屬於會話局部的名稱空間,都會被放在私有名稱空間中。將名稱空間中相同部分復制,來初始化名稱空間。
對象管理器在\session\X下創建私有版本,對象管理器以透明的方法,將對象的名稱從\BaseNameObjects重定向到\session\2\BaseNamedObjects。
windows子系統DLL將windows應用程序傳過來的位於\DosDevice中對象引用加上\??前綴(c:\windows變成\??\c:\windows)。依賴於EPROCESS中DeviceMap。DeviceMap結構中DosDevicesDirectory域所指的對象目錄管理器代表了進程的局部DosDevices。
在windows 2003和xp上,系統沒有將全局對象拷貝到局部DosDevices目錄中,當看到\??會通過DeviceMap中DosDevicesDirectory找到該進程局部\DosDevices目錄。若在局部中沒有,並且DeviceMap有效,則會在GlobalDosDevicesDirectory查找對象。
當會話中的應用程序要與其他會話的實例同步在任何對象名稱前加入\Global\ApplicationInitialized被重定向到\BasedNameObjects\ApplicationInitized而不是\Sessions\2\BaseNamedObjects\ApplicationInitialized。
在2003和xp中應用程序只要在\DosDevices中沒有這樣的對象就會訪問全局,不需要使用\Global。
3.3 同步
當一個資源不允許共享訪問,或共享訪問導致不可預測的后果則需要互斥。若一段代碼訪問了一個不可共享的資源,則這樣的代碼區成為臨界區。
3.3.1 高IRQL的同步
在內核執行的各個階段,內核必須保證,在臨界區內部同一時刻只有一個處理器在執行。
在中斷發生,內核可能在更新一個全局數據結構,而中斷的處理例程可能也要修改此數據結構。在單處理器,可以用以下方式來避免。如,當線程修改全局數據結構時,禁止所有中斷。windows的作法是執行臨界區時,把IRQL拉高會用到該數據結構的最高IRQL。
3.3.1.1 互鎖操作
這個最簡單的同步方式,依賴於支持多處理器硬件,操作一個整型值來進行比較。
3.3.1.2 自旋鎖
自旋鎖是內核用來實現多處理器互斥的機制
在windows中,所有內核模式自旋鎖都有一個與之關聯的IRQL。當運行自旋鎖就會拉高IRQL。
3.3.1.3 排隊自旋鎖
工作方式:當一個處理器要獲得已被其他處理器持有的隊列自旋鎖鎖時,把自己的標示符放入一個與該自旋鎖關聯的一個隊列中。
當自旋鎖被釋放,將鎖交給隊列中第一個標示符對於的cpu。
在同時處理器等待一個較忙的自旋鎖不是檢查自旋鎖本身而是每個處理器的標志。在隊列中位於它之前的處理器會對它設置,表明輪到這個等待的處理器了。
排隊的自旋鎖的自然結果是,他們在每個處理器標識符上旋轉,而不是全局自旋鎖上旋轉有2個效果:
1.多處理器總線不會因為處理器之間的同步招致繁重的流量。
2.排隊加強了先進先出的順序,處理器之間性能更加一致。
在windows中定義了很多全局隊列自旋鎖,並且在每個處理器的“處理器控制區域(PCR)”包含了一組數組(保存了指向這些全局隊列的自旋鎖指針)。當調用KeAcquireQueueSpinLock的時候將一個PCR的索引傳進去,可以獲得對應的全局自旋鎖。
3.3.1.4 棧內排隊自旋鎖
除了使用全局定義的靜態排隊自旋鎖,xp和2003還提供了KeAcquireInstackQueuedSpinlock和KeReleaseInstackQueuedSpinlock。來支持動態分配的排隊自旋鎖。
3.3.1.5 執行體的互鎖操作
內核提供了很多簡單的建立在自旋鎖基礎上的同步函數。
3.3.2 低IRQL的同步
自旋鎖使用有嚴格的限制:
1.對於受保護的資源,必須快速訪問,不要與其他代碼有復雜的交互關系
2.臨界區代碼的內存頁不能換出去,不能引用可被換頁的數據,不能調用外部過程,不能中斷或異常。
在自旋鎖不適合是可用:內核分發器對象,快速互斥體和受限互斥體,壓棧鎖,執行體資源。
|
是否暴露給設備驅動程式使用 |
禁止常規的內核模式APC |
禁止特殊的內核模式APC |
支持遞歸獲取操作 |
支持共享的和獨占的獲取操作 |
內核分發器互斥體 |
是 |
是 |
否 |
是 |
否 |
內核分發器信號量 |
是 |
否 |
否 |
否 |
否 |
快速互斥體 |
是 |
是 |
是 |
否 |
否 |
受限互斥體 |
否 |
是 |
是 |
否 |
否 |
壓棧鎖 |
否 |
否 |
否 |
否 |
是 |
執行體資源 |
是 |
是 |
否 |
是 |
是 |
3.3.2.1 內核分發對象
內核以內核對象的形式,向執行體提供了額外的同步機制,這些內核對象合起來統稱為分發器對象。每個支持同步的用戶可見對象都封裝了至少一個內核分發器對象。
3.3.2.1.1 等待分發器對象
一個用戶模式的線程等待一個事件對象的句柄。
內核將該線程的調度狀態從就緒狀態改變成等待狀態,然后將該線程加入到正在等待該事件的線程列表中。
另外一個線程設置了該事件,內核沿着該事件的等待線程隊列向前搜索。若有一個線程等待條件滿足將線程狀態從等待改為就緒。若是一個可變優先級的線程,則內核可能也要提升它的執行優先級。
因為一個新線程已經變成就緒執行狀態,所以進行重新調度。如果它找到一個正在運行的線程。其優先級低於就緒線程的優先級。那么會搶占此低優先級的線程,並且發出一個軟中斷,以便激發一個環境切換,切換到高優先級的線程中。
如果沒有處理器可以搶占的話,則分發器將該就緒線程放到分發器就緒隊列中,以后再被調度。
3.3.2.1.2 如果Signals一個對象
略
3.3.2.1.3 數據結構
typedef struct _DISPATCHER_HEADER {
UCHAR Type;
UCHAR Absolute;
UCHAR Size;
UCHAR Inserted;
LONG SignalState;
LIST_ENTRY WaitListHead;
} DISPATCHER_HEADER;
typedef struct _KWAIT_BLOCK {
LIST_ENTRY WaitListEntry;
struct _KTHREAD *RESTRICTED_POINTER Thread;
PVOID Object;
struct _KWAIT_BLOCK *RESTRICTED_POINTER NextWaitBlock;
USHORT WaitKey;
USHORT WaitType;
} KWAIT_BLOCK, *PKWAIT_BLOCK, *RESTRICTED_POINTER PRKWAIT_BLOCK;
每個分發器對象都有一個等待列表,列表中一項代表一個等待該對象的線程。所以當線程向一個分發器對象發出信號,內核可以很快的確定誰在等待對象。
等待快結構WaitListEntry指向被等待對象,Thread指向等待線程,NextWaitBlock指向下一個等待塊。
3.3.2.2 快速互斥體和受限互斥體
快速互斥體也成為執行體互斥體,比互斥體對象提供了更好的性能。盡管他們也是建立在分發器事件對象基礎上的,但是如果對於快速互斥體沒有競爭的話,他們無須等待事件對象。
受限互斥體,本質上它與快速互斥體是相同的。使用了KGATE同步對象,通過調用KeEnterGuardedRegion來進制所有內核模式APC的事務,主要用戶內存管理器。
3.3.2.3 執行體資源
執行體資源是一種支持共享和獨占訪問的同步機制,要求APC事務禁止,如果一個線程正在等待獲得一個共享訪問權,則它應該等待一個與該資源相關聯的信號量,如果一個線程正在等待獲得一個資源的獨占訪問權,則應該等待一個事件。
當一個獨占持有者通過給信號量發型號來釋放一個資源喚醒共享訪問者。
當一個線程在等待獨占訪問一個資源,而該資源正在被其他線程擁有,該線程等待一個同步事件對象。
3.3.2.4 壓棧鎖
壓棧鎖是建立在KGATE同步對象基礎之上,相比快速互斥體好處是可以按照共享的方式獨占的模式來獲得。
有兩種類型壓棧鎖:普通壓棧鎖和能感知緩存的壓棧鎖。
普通壓棧鎖:當一個線程想要獲得一個普通的壓棧鎖,若尚未被使用則壓棧鎖代碼標記為已被占用。若已被占有(共享,獨占),線程在自己棧上分配一個等待塊,將初始化等待塊中的事件對象,等待塊加入到與壓棧鎖相關聯的等待列表中。向該等待着的等待塊中的事件發出信號。
能感知緩存的壓棧鎖簡歷在基本壓棧鎖之上。為每個處理器分配一個壓棧鎖,然后將這些壓棧鎖和處理器關聯起來。當一個線程希望以共享方式獲得壓棧鎖時,簡單的獲得對應於當前處理器的那個壓棧鎖以獨占方式獲得獨占鎖時,以獨占模式獲得每個處理器的壓棧鎖。
壓棧鎖的使用范圍包括對象管理器和內存管理器,對象管理器中,可以保護全局對象管理器結構和對象安全描述符,在內存管理器,他們可以保護awe數據結構。
3.4 系統輔助線程
windows在system進程中創建了幾個線程,這些線程稱為系統輔助線程它們代表其他線程來完成一些工作。如DPC級別的IRQL不能運行更低IRQL級別才能執行的函數,必須將這樣的處理過程傳遞給一個低於DPC級別的IRQL的執行線程上。
設備驅動程序或執行體組件通過ExQueueWorkItem和IoQueueWorkItem把工作放到一個隊列分發器對象上,系統復制線程在該對象上尋找工作。工作包含一個例程以及一個參數,當輔助線程處理該工作,會把參數傳遞給例程。
系統輔助線程有以下三類:
延遲型輔助線程:優先級12上,處理器非緊急工作項目,當它在等待工作項目時允許棧頁面被換出到頁面文件中。
緊急型輔助線程:優先級13,處理一些緊急工作項目,始終在內存上
超緊急型輔助線程:優先級15,總在內存中。
執行體函數ExpWorkerThreadBalanceManager確定是否創建新的緊急型輔助線程,新的線程被稱為動態的輔助線程。創建時必須滿足下列條件:
1.在緊急工作隊列下有工作項目
2.不活動的緊急型輔助線程的數目必須少於系統處理器個數
3.動態輔助線程數據少於16個。
3.5 windows全局標志
略
3.6 本地過程調用(LPC)
LPC用於進程間通信,LPC為了以下3中方式通信而設計:
1.短於256字節的信息可以通過LPC發送。從發送進程拷貝到系統地址空間中,再從系統地址空間中拷貝到接收進程地址空間中。
2.若多於256字節則復制到共享內存區。
3.若超過了內存共享區,可以直接在地址空間中讀取或寫入。
LPC有多種端口:服務器連接端口,服務器通信端口,客戶通信端口,未命名的通信端口
3.7 內核事件跟蹤
一個公共的基礎設施,向內核和ETW提供痕跡數據應用程序要使用到ETW,要屬於以下3類:
1.控制器,啟動或停止,也管理緩沖區
2.提供者,為它所能產生的事件類定義GUID,並注冊到ETW以上,並接受控制器命令,啟動,停止它所負責的事件類跟蹤。
3.消費者,選擇一個或多個會話,讀取數據。
控制器啟動內核記錄器(ETW庫)向WMI發送一個IO請求說明要開始跟蹤哪些事件類。當WMI接受到已啟動跟蹤源接收到數據就會寫入buffer,每隔一秒觸發一次寫入日志文件。
3.8 Wow64
64位windows上win32仿真,也就是可以在64位上執行32位,x86應用程序。以DLL形式來實現。
wow64.dll實現了文件系統重定向,以及注冊表重定向和反射
wow64cpu.dll實現了cpu從32到64,從64到32之間的切換
wow64win.dll截取了win32k.sys導出GUI系統調用
3.8.1 Wow64進程地址空間布局結構
wow64進程可以是2G,也可以是4G虛擬空間。若沒有設置大地址空間感知標志則最多保留2G,若開啟大地之空間感知標志最多保留4G。
3.8.2 系統調用
Wow64.dll鈎住從32位代碼變值原生64為系統代碼路徑,也鈎住64位原生系統需要調用至32位用戶模式代碼的所有代碼路徑。
啟動應用程序,64位ntdll.dll映射到地址空間,初始化判斷映像頭為32位x86則加載wow64.dll映射32位ntdll.dll,建立ntdll內部啟動環境切換到32位,執行32位加載器。
64位和32位間通過wow64在之間轉換。
3.8.3 異常分發
通過wow64來轉化異常分發。
3.8.4 用戶回調
通過wow64來轉化
3.8.5 文件系統重定向
為了降低應用程序移植代價,所有相關API將windows\system32替換為windows\syswow64。使用文件系統重定向來實現。
線程使用wow64enable,wow64FsRedirection函數進制文件系統重定向。
3.8.6 注冊表重定向和反射
注冊表也使用重定向在注冊表上創建原生和wow642中,視圖為了允許32和64位com組件互操作,在注冊表中某些特定部分被更新,wow64也會將這些更新映射到迎來一個視圖。
3.8.7 I/O請求
IO除了讀寫硬盤還可以用於程序通信,驅動程序通IoIs32bitProcess來檢測是否從一個wow64進程發出的。
3.9總結
主要介紹執行體建立起來的基本系統機制。