《Undocumented Windows 2000 Secrets》翻譯 --- 第四章(9)
第四章 探索 Windows 2000 的內(nèi)存管理機(jī)制
翻譯: Kendiv( [email protected] )
更新: Tuesday, February 22, 2005
聲明:轉(zhuǎn)載請(qǐng)注明出處,并保證文章的完整性,本人保留譯文的所有權(quán)利。
Windows 2000 的分段和描述符
w2k_mem.exe 的另一個(gè)很棒的選項(xiàng)是 +e ,該選項(xiàng)將顯示和說明處理器的段寄存器和描述表的內(nèi)容。 示列 4-13 給出了其典型輸出。 CS 、 DS 和 ES 段寄存器的內(nèi)容非常清晰的證明了 Windows 2000 為每個(gè)進(jìn)程提供了平坦的 4GB 地址空間:起始于 0x00000000 ,終止于 0xFFFFFFFF 。 示列 4-13 中最右邊的標(biāo)志符用來表示段的類型,該段的類型由它的描述符的 Type 成員給出。代碼和數(shù)據(jù)段的 Type 屬性可分別符號(hào)化為“ cra ”和“ ewa ”。省略號(hào)“ - ”意味著相應(yīng)的屬性沒有設(shè)置。一個(gè)任務(wù)狀態(tài)段( Task State Segment , TSS )僅能有“ a ”(可用)和“ b ”(忙)兩種屬性。 表 4-5 給出了所有可用的屬性。 示列 4-13 展示了 Windows 2000 的 CS 段的不一致性, CS 段允許執(zhí)行和讀取,而 DS 、 ES 、 FS 和 SS 段的屬性則是可擴(kuò)展和讀 / 寫訪問。另一個(gè)不明顯但十分重要的細(xì)節(jié)是 CS 、 FS 和 SS 段的 DPL 在用戶模式和內(nèi)核模式并不相同。 DPL 是描述符特權(quán)級(jí)別( Descriptor Privilege Level )。對(duì)于代碼段( CS ),僅當(dāng)調(diào)用者位于其 DPL 指定的特權(quán)級(jí)時(shí)才能調(diào)用該段中的代碼(參考 Intel 1999c, pp. 4-8f )。在用戶模式, CS 段的 DPL 為 3 ;在內(nèi)核模式,其 DPL 為 0 。對(duì)于數(shù)據(jù)段( DS ),其 DPL 是最低的特權(quán)級(jí),在用戶模式下,所有特權(quán)級(jí)都可訪問它,而在內(nèi)核模式下,僅允許特權(quán) 0 訪問。
示列 4-13. 顯示 CPU 信息
IDT 和 GDT 寄存器的內(nèi)容顯示了 GDT 的范圍是: 0x8003F000 --- 0x8003F3FF ,緊隨其后的就是 IDT ,其地址范圍是: 0x8003F400 --- 0x8003FBFF 。由于每個(gè)描述符占用 64 位,故 GDT 和 IDT 分別包含 128 和 256 個(gè)項(xiàng)。注意, GDT 可容納 8,192 個(gè)項(xiàng),但 Windows 2000 僅使用了其中的一小部分。
表 4-5 代碼和數(shù)據(jù)段的 Type 屬性
段
屬 性
描 述
CODE
c
使段一致(低特權(quán)的代碼可能進(jìn)入)
CODE
r
允許讀訪問(和僅執(zhí)行訪問相斥)
CODE
a
段可以訪問
DATA
e
向下擴(kuò)展段(堆棧段的典型屬性)
DATA
w
允許寫訪問(和僅讀取訪問相斥)
DATA
a
段可以訪問
TSS32
a
任務(wù)狀態(tài)段可用
TSS32
b
任務(wù)狀態(tài)段繁忙
W2k_mem.exe 還提供了兩個(gè)很有特色的選項(xiàng) ----+g 和 +i ,這兩個(gè)選項(xiàng)可顯示 GDT 和 IDT 的更多細(xì)節(jié)。 示列 4-14 示范了 +g 選項(xiàng)的輸出。它很類似于 示列 4-13 中的“ kernel-model segment: ”一節(jié),但列出了在內(nèi)核模式下所有可用的段選擇子( selector ),而不僅僅是存儲(chǔ)在段寄存器中的那些。 W2k_mem.exe 通過遍歷整個(gè) GDT 來獲取所有的段選擇子,可通過 IOCTL 函數(shù) SPY_IO_SEGMENT 來指示 Spy 設(shè)備查詢段信息。僅顯示有效的選擇子。比較 示列 4-13 和 4-14 中的 GDT 選擇子將十分有趣, GDT 的選擇子定義于 ntddk.h 中,匯總在 表 4-6 。顯然,它們與 w2k_mem.exe 的輸出是一致的。
示列 4-14. 顯示 GDT 描述符
表 4-6. 定義于 ntddk.h 中的 GDT 選擇子( selector )
符 號(hào)
值
注 釋
KGDT_NULL
0x0000
空的段選擇子(無效)
KGDT_R0_CODE
0x0008
內(nèi)核模式的 CS 寄存器
KGDT_R0_DATA
0x0010
內(nèi)核模式的 SS 寄存器
KGDT_R3_CODE
0x0018
用戶模式的 CS 寄存器
KGDT_R3_DATA
0x0020
用戶模式的 DS 、 ES 和 SS 寄存器,內(nèi)核模式的 DS 和 ES 寄存器
KGDT_TSS
0x0028
位于用戶和內(nèi)核的任務(wù)狀態(tài)段
KGDT_R0_PCR
0x0030
內(nèi)核模式的 FS 寄存器(處理器控制區(qū)域)
KGDT_R3_TEB
0x0038
用戶模式的 FS 寄存器(線程環(huán)境塊)
KGDT_VDM_TILE
0x0040
基地址 0x00000400 ,限制 0x0000FFFF ( Dos 虛擬機(jī))
KGDT_LDT
0x0048
本地描述符表
KGDT_DF_TSS
0x0050
Ntoskrnl.exe 變量 KiDoubleFaultTSS
KGDT_NMI_TSS
0x0058
Ntoskrnl.exe 變量 KiNMITSS
示列 4-14 中的選擇子( selector )沒有在 表 4-6 中列出,其中的某些選擇子可以通過查找熟悉的基地址或其內(nèi)存內(nèi)容來確認(rèn)它們。使用內(nèi)核調(diào)試器可查找其中某些選擇子的基地址對(duì)應(yīng)的符號(hào)。 表 4-7 給出了我已經(jīng)確認(rèn)的選擇子。
W2k_mem.exe 的 +i 選項(xiàng)可轉(zhuǎn)儲(chǔ) IDT 中的門描述符( Gate Descriptor )。 示列 4-15 給出了 IDT 的門描述符的部分內(nèi)容, Intel 僅定義了 IDT 中的前 20 個(gè)門描述符( Intel 1999c, pp. 5-6 )。 IDT 中的中斷 0x14 到 0x1F 由 Intel 保留;剩余的 0x20 到 0xFF 由操作系統(tǒng)使用。
在 表 4-8 中,我給出了所有可確認(rèn)的特殊的中斷、陷阱和任務(wù)門。大多數(shù)用戶自定義的中斷都指向啞元例程 ---KiUnexpectedinterruptnNNN() ,在前面我們已經(jīng)解釋過它。對(duì)于某些中斷處理例程的地址,內(nèi)核調(diào)試器也無法解析其地址對(duì)應(yīng)的符號(hào)。
表 4-7. 更多的 GDT 選擇子( selector )
值
基地址
描 述
0x0078
0x80400000
Ntoskrnl.exe 的代碼段
0x0080
0x80400000
Ntoskrnl.exe 的數(shù)據(jù)段
0x00A0
0x814985A8
TSS ( EIP 成員指向 HalpMcaExceptionHandlerWrapper )
0x00E0
0xF0430000
ROM BIOS 代碼段
0x00F0
0x8042DCE8
Ntoskrnl.exe 函數(shù) KiI386CallAbios
0x0100
0xF0440000
ROM BIOS 數(shù)據(jù)段
0x0108
0xF0440000
ROM BIOS 數(shù)據(jù)段
0x0110
0xF0440000
ROM BIOS 數(shù)據(jù)段
示列 4-15. 顯示 IDT 門描述符
表 4-8. Windows 2000 中斷、陷阱和任務(wù)門
INT
Intel 定義的描述符
擁有者
處理例程 /TSS
0x00
整除錯(cuò)誤( DE )
ntoskrnl.exe
KiTrap00
0x01
調(diào)試( DB )
ntoskrnl.exe
KiTrap01
0x02
NMI 中斷
ntoskrnl.exe
KiNMITSS
0x03
斷點(diǎn)( BP )
ntoskrnl.exe
KiTrap03
0x04
溢出( OF )
ntoskrnl.exe
KiTrap04
0x05
越界( BR )
ntoskrnl.exe
KiTrap05
0x06
未定義的操作碼( UD )
ntoskrnl.exe
KiTrap06
0x07
沒有數(shù)學(xué)協(xié)處理器( NM )
ntoskrnl.exe
KiTrap07
0x08
Double Fault ( DF )
ntoskrnl.exe
KiDouble
0x09
協(xié)處理器段溢出
ntoskrnl.exe
KiTrap09
0x0A
無效的 TSS ( TS )
ntoskrnl.exe
KiTrap0A
0x0B
段不存在( NP )
ntoskrnl.exe
KiTrap0B
0x0C
堆棧段故障( SS )
ntoskrnl.exe
KiTrap0C
0x0D
常規(guī)保護(hù)( GP )
ntoskrnl.exe
KiTrap0D
0x0E
頁(yè)故障( PF )
ntoskrnl.exe
KiTrap0E
0x0F
Intel 保留
ntoskrnl.exe
KiTrap0F
0x10
Math Fault ( MF )
ntoskrnl.exe
KiTrap10
0x11
對(duì)齊檢查( AC )
ntoskrnl.exe
KiTrap11
0x12
Machine Check ( MC )
?
?
0x13
流 SIMD 擴(kuò)展
ntoskrnl.exe
KiTrap0F
0x14-0x1F
Intel 保留
ntoskrnl.exe
KiTrap0F
0x2A
用戶自定義
ntoskrnl.exe
KiGetTickCount
0x2B
用戶自定義
ntoskrnl.exe
KiCallbackReturn
0x2C
用戶自定義
ntoskrnl.exe
KiSetLowWaitHighThread
0x2D
用戶自定義
ntoskrnl.exe
KiDebugSerice
0x2E
用戶自定義
ntoskrnl.exe
KiSystemService
0x2F
用戶自定義
ntoskrnl.exe
KiTrap0F
0x30
用戶自定義
hal.dll
HalpClockInterrupt
0x38
用戶自定義
hal.dll
HalpProfileInterrupt
Windows 2000 的內(nèi)存區(qū)域
W2k_mem.exe 的最后一個(gè)還未討論的選項(xiàng)是: +b 選項(xiàng)。該選項(xiàng)會(huì)產(chǎn)生 4GB 地址空間中相鄰內(nèi)存區(qū)域的列表,這個(gè)列表非常大。 W2k_mem.exe 使用 Spy 設(shè)備的 IOCTL 函數(shù) SPY_IO_PAGE_ENTRY 遍歷整個(gè) PTE 數(shù)組(位于地址 0xC0000000 )來生成這個(gè)列表。在作為結(jié)果的每個(gè) SPY_PAGE_ENTRY 結(jié)構(gòu)中,通過將它們的 dSize 成員與其對(duì)應(yīng)的 PTE 線性地址相加即可得到下一個(gè) PTE 的地址。 列表 4-30 給出了該選項(xiàng)的實(shí)現(xiàn)方式。
DWord WINAPI DisplayMemoryBlocks (HANDLE hDevice)
{
SPY_PAGE_ENTRY spe;
PBYTE pbPage, pbBase;
DWORD dBlock, dPresent, dTotal;
DWORD n = 0;
pbPage = 0;
pbBase = INVALID_ADDRESS;
dBlock = 0;
dPresent = 0;
dTotal = 0;
n += _printf (L'rnContiguous memory blocks:'
L'rn-------------------------rnrn');
do {
if (!IoControl (hDevice, SPY_IO_PAGE_ENTRY,
&pbPage, PVOID_,
&spe, SPY_PAGE_ENTRY_))
{
n += _printf (L' !!! Device I/O error !!!rn');
break;
}
if (spe.fPresent)
{
dPresent += spe.dSize;
}
if (spe.pe.dValue)
{
dTotal += spe.dSize;
if (pbBase == INVALID_ADDRESS)
{
n += _printf (L'%5lu : 0x%08lX ->',
++dBlock, pbPage);
pbBase = pbPage;
}
}
else
{
if (pbBase != INVALID_ADDRESS)
{
n += _printf (L' 0x%08lX (0x%08lX bytes)rn',
pbPage-1, pbPage-pbBase);
pbBase = INVALID_ADDRESS;
}
}
}
while (pbPage += spe.dSize);
if (pbBase != INVALID_ADDRESS)
{
n += _printf (L'0x%08lXrn', pbPage-1);
}
n += _printf (L'rn'
L' Present bytes: 0x%08lXrn'
L' Total bytes: 0x%08lXrn',
dPresent, dTotal);
return n;
}
列表 4-30. 查找相鄰的線性內(nèi)存塊
示列 4-16 摘錄了在我的機(jī)器上使用 +b 選項(xiàng)的輸出列表,可以看出其中的幾個(gè)區(qū)域非常有趣。一些非常明顯的地址是: 0x00400000 ,這是 w2k_mem.exe 內(nèi)存映像的起始地址(第 13 號(hào)塊),還有一個(gè)是 0x10000000 ,此處是 w2k_lib.dll 的基地址(第 23 號(hào)塊)。 TEB 和 PEB 頁(yè)也很容易認(rèn)出(第 104 號(hào)塊), hal.dll (第 105 號(hào)塊), ntoskrnl.exe (第 105 號(hào)塊), win32k.sys (第 106 號(hào)塊)。第 340---350 號(hào)塊是系統(tǒng) PTE 數(shù)組的一小段,第 347 號(hào)塊是頁(yè)目錄的一部分。第 2122 號(hào)塊包含 SharedUserData 區(qū)域,第 2123 號(hào)塊由 KPCR 、 KPRCB 和包含線程和進(jìn)程狀態(tài)信息的 CONTEXT 結(jié)構(gòu)組成。
示列 4-16. 相鄰內(nèi)存塊列表示列
還需要補(bǔ)充一下, W2k_mem.exe 的 +b 選項(xiàng)會(huì)報(bào)告有大量的內(nèi)存被使用,這可能超出了一個(gè)合理的值(比如,你機(jī)器上的物理內(nèi)存數(shù))。請(qǐng)注意 示列 4-16 底部給出的匯總信息。我現(xiàn)在真的使用了 700MB 的內(nèi)存嗎? Windows 2000 的任務(wù)管理器顯示是 150MB ,那么這兒的又是什么呢?這種奇特的效果都是由第 105 號(hào)內(nèi)存塊產(chǎn)生的,該內(nèi)存塊表示的范圍: 0x80000000----0xA01A5FFF 占用了 0x201A6000 字節(jié),也就是說占用了 538,599,424 字節(jié)。這顯然是不可能的。問題是整個(gè)線性地址空間: 0x80000000 ---- 0x9FFFFFFF 都被映射到了物理內(nèi)存: 0x00000000 ---- 0x1FFFFFFF ,在前面我已經(jīng)提及過這一點(diǎn)。該區(qū)域中的所有 4MB 頁(yè)都對(duì)應(yīng)地址 0xC0300000 處的頁(yè)目錄中的一個(gè)有效的 PDE ,我們可以使用 w2k_mem +d #0x200 0xC0300800 命令來證明這一點(diǎn)( 示列 4-17 )。因?yàn)榻Y(jié)果列表中的所有 PDE 都是奇數(shù)( 譯注:如果 PDE 為奇數(shù),證明其 P 位肯定為 1 ),所以它們對(duì)應(yīng)的頁(yè)都必須存在;不過,它們并不需真正占用物理內(nèi)存。事實(shí)上,這一內(nèi)存區(qū)域的大部分都是“空洞( hole )”,如果將其復(fù)制到緩沖區(qū)中,可發(fā)現(xiàn)它們都被 0xFF 填充。因此,對(duì)于 w2k_mem.exe 輸出的內(nèi)存使用情況,你不需要過于認(rèn)真。
示列 4-17. 地址范圍是: 0x80000000 --- 0x9FFFFFFF 的 PDE
Windows 2000 的內(nèi)存布局 本章的最后一部分將給出在一個(gè) Windows 2000 進(jìn)程“看”來, 4GB 線性地址空間的總體布局是什么樣子。 表 4-9 給出了多個(gè)基本數(shù)據(jù)結(jié)構(gòu)的內(nèi)存范圍。它們之間的“大洞( big hole )” 有不同的用途,如,用于進(jìn)程模塊和設(shè)備驅(qū)動(dòng)程序的加載區(qū)域,內(nèi)存池,工作集鏈表等等。注意,有些內(nèi)存地址和內(nèi)存塊的大小在不同的系統(tǒng)之間有很大的差異,這 取決于物理內(nèi)存和硬件的配置情況、進(jìn)程的屬性以及其他一些系統(tǒng)變量。因此,這里給出的僅僅是一個(gè)草圖而已,并不是精確的布局圖。
有些物理內(nèi)存塊在線性地址空間中出現(xiàn)的兩次或更多次。例如, SharedUserData 區(qū)域位于線性地址 0xFFDF0000 ,并且并鏡像到 0x7FFE0000 。這兩個(gè)地址都指向物理內(nèi)存中的同一個(gè)頁(yè),這意味著,如果向 0xFFDF0000+n 處寫入一個(gè)字節(jié),那么 0x7FFE0000+n 處的值也會(huì)隨之改變。這是一個(gè)虛擬內(nèi)存的世界 ---- 一個(gè)物理地址可以被映射到線性地址空間中的任何地方,即使一個(gè)物理地址在同一時(shí)間映射到多個(gè)線性地址也是可以的。回憶一下 圖 4-3 和 圖 4-4 ,它們清楚地展示了線性地址的這種“虛假行為”。它們的目錄和表位域正確的指向用來確定數(shù)據(jù)實(shí)際位置的結(jié)構(gòu)體。如果兩個(gè) PTE 的 PFN 恰好是相同的,那么它們對(duì)應(yīng)的線性地址將指向物理內(nèi)存相同位置。
表 4-9. 進(jìn)程地址空間中的可確認(rèn)的內(nèi)存區(qū)域
起始地址
結(jié)束地址
十六進(jìn)制大小
類型 / 描述
0x00000000
0x0000FFFF
10000
底部的受保護(hù)塊( Lower guard block )
0x00010000
0x0001FFFF
10000
WCHAR[]/ 環(huán)境字符串,在一個(gè) 4KB 頁(yè)中分配
0x00020000
0x0002FFFF
10000
PROCESS_PARAMETERS/ 在一個(gè) 4KB 頁(yè)中分配
0x00030000
0x0012FFFF
1000000
DWORD[4000]/ 進(jìn)程堆棧(默認(rèn); 1MB )
0x7FFDD000
0x7FFDDFFF
1000
TEB/1# 線程的線程環(huán)境塊
0x7FFDE000
0x7FFDEFFF
1000
TEB/2# 線程的線程環(huán)境塊
0x7FFDF000
0x7FFDFFFF
1000
PEB/ 進(jìn)程環(huán)境塊
0x7FFE0000
0x7FFE02D7
2D8
KUSER_SHARED_DATA/ 用戶模式下的 SharedUserData
0x7FFF0000
0x7FFFFFFF
10000
頂部的受保護(hù)塊( Upper guard block )
0x80000000
0x800003FF
400
IVT/ 中斷向量表
0x80036000
0x800363FF
400
KGDTENTRY[80]/ 全局描述符表
0x80036400
0x80036BFF
800
KIDTENTRY[100]/ 中斷描述符表
0x800C0000
0x800FFFFF
40000
VGA/ROM BIOS
0x80244000
0x802460AA
20AB
KTSS/ 內(nèi)核任務(wù)狀態(tài)段(繁忙)
0x8046AB80
0x8046ABBF
40
KeServiceDescriptorTable
0x8046AB
0x8046ABFF
40
KeServiceDescriptorTableShadow
0x80470040
0x804700A7
68
KTSS/KiDoubleFaultTSS
0x804700A8
0x8047010F
68
KTSS/KiNMITSS
0x804704D8
0x804708B7
3E0
PROC[F8]/KiServiceTable
0x804708B8
0x804708BB
4
DWORD/KiServiceLimit
0x804708BC
0x804709B3
F8
BYTE[F8]/KiArgumentTable
0x814C6000
0x82CC5FFF
1800000
PFN[100000]/MmPfnDatabase (最大為 4GB )
0xA01859F0
0xA01863EB
9FC
PROC[27F]/W32pServiceTable
0xA0186670
0x A01863EE
27F
BYTE[27F]W32pArgumentTable
0xC0000000
0xC03FFFFF
400000
X86_PE[100000]/ 頁(yè)目錄和頁(yè)表
0xC1000000
0xE0FFFFFF
20000000
系統(tǒng)緩存( MmSystemCacheStart, MmSystemCacheEnd )
0xE1000000
0xE77FFFFF
6800000
頁(yè)池( Paged Pool )( MmPagedPoolStart, MmPagedPoolEnd )
0xF0430000
0xF043FFFF
10000
ROM BIOS 代碼段
0xF0440000
0xF044FFFF
10000
ROM BIOS 數(shù)據(jù)段
0xFFDF0000
0xFFDF02D7
2D8
KUSER_SHARED_DATA/ 內(nèi)核模式下的 SharedUserData
0xFFDFF000
0xFFDFF053
54
KPCR/ 處理器控制區(qū)(內(nèi)核模式 FS 段)
0xFFDFF120
0xFFDFF13B
1C
KPRCB/ 處理器控制塊
0xFFDFF13C
0xFFDFF407
2CC
CONTEXT/ 線程 CONTEXT ( CPU 狀態(tài))
0xFFDFF620
0xFFDFF71F
100
后備鏈表目錄( Lookaside list DirectorIEs )
