《Undocumented Windows 2000 Secrets》翻譯 --- 第四章(5)
第四章 探索 Windows 2000 的內(nèi)存管理機(jī)制
翻譯: Kendiv( fcczj@263.net )
更新: Sunday, February 17, 2005
聲明:轉(zhuǎn)載請(qǐng)注明出處,并保證文章的完整性,本人保留譯文的所有權(quán)利。
IOCTL 函數(shù) SPY_IO_INTERRUPT
SPY_IO_INTERRUP 類似于 SPY_IO_SEGEMT ,不過該函數(shù)僅影響存儲(chǔ)在系統(tǒng)中斷描述符表( IDT )的中斷描述符,不會(huì)涉及 LDT 或 GDT 描述符。 IDT 最多可容納 256 個(gè)描述符,這些描述符可用來描述任務(wù)門、中斷門或陷阱門(參見 Intel 1999c, pp. 5-11ff )。順便說一下,中斷和陷阱在本質(zhì)上十分相似,二者只存在微小的差異:在進(jìn)入一個(gè)中斷處理例程后,總是會(huì)屏蔽其他中斷;而進(jìn)入陷阱處理例程卻不會(huì)修改中斷標(biāo)志。 SPY_IO_INTERRUPT 的調(diào)用者提供一個(gè) 0 到 255 之間的中斷號(hào),該中斷號(hào)將位于輸入緩沖區(qū)中,而一個(gè) SPY_INTERRUPT 結(jié)構(gòu)將作為輸出數(shù)據(jù)被存放到輸出緩沖區(qū)中,如果成功返回,該結(jié)構(gòu)中將包含對(duì)應(yīng)的中斷處理例程的屬性。由 Dispatcher 調(diào)用的幫助函數(shù) SpyOutputInterrupt() 只是一個(gè)簡(jiǎn)單的外包函數(shù),它實(shí)際上調(diào)用 SpyInterrupt() 函數(shù)并且將需要返回的數(shù)據(jù)復(fù)制到輸出緩沖區(qū)中。 列表 4-18 給出了這兩個(gè)函數(shù),以及它們操作的 SPY_INTERRUPT 結(jié)構(gòu)。稍后一些, SpyInterrupt() 函數(shù)將填充如下項(xiàng)目:
l Selector 用來指定一個(gè)任務(wù)狀態(tài)段( Task-State Segment, TSS )或代碼段( Code Segment )的選擇器。代碼段選擇器用來確定中斷或陷阱處理例程所在的段。
l Gate 用來表示一個(gè) 64 位的任務(wù)門、中斷門或陷阱門描述符,由 Selector 確定其地址。
l Segment 包含段的屬性,該段的地址由前面的 Gate 給出。
l pOffset 指定中斷或陷阱處理例程的入口地址相對(duì)基地址的偏移量。這里的基地址是指中斷或陷阱處理例程所在代碼段的起始地址。因?yàn)槿蝿?wù)門不包含偏移量,所以,如果輸入的選擇器指向一個(gè) TSS ,則忽略該成員。
l fOk 一個(gè)標(biāo)志變量,用來指示 SPY_INTERRUPT 結(jié)構(gòu)中的數(shù)據(jù)是否有效。
通常情況下, TSS 被用來保證一個(gè)錯(cuò)誤情況可以被一個(gè)有效的任務(wù)處理。這是一個(gè)特殊的系統(tǒng)段類型( system segment type ),它可以保存 104 個(gè)字節(jié)的進(jìn)程狀態(tài)信息,該信息在任務(wù)切換時(shí),用來進(jìn)行任務(wù)的恢復(fù),如 表 4-3 所示。當(dāng)與任務(wù)相關(guān)的中斷發(fā)生時(shí), CPU 總是強(qiáng)制切換該任務(wù),并將所有的 CPU 寄存器保存到 TSS 中。 Windows 2000 在中斷位置 0x02 (非屏蔽中斷 [NMI] , 0x08[Double Fault] 和 0x12[ 堆棧段故障 ] )處保存任務(wù)門。剩余的位置指向中斷處理例程。不使用的中斷由一個(gè)啞元例程 ---KiUnexpectedInterruptNNN() 處理,這里的 NNN 為一個(gè)十進(jìn)制數(shù)。這些啞元例程最后都匯集到內(nèi)部函數(shù) KIEndUnexpectedRange() ,在這里,這些例程將依次進(jìn)入 KiUnexpectedInterruptTail() 。
typedef struct _SPY_INTERRUPT
{
X86_SELECTOR Selector;
X86_GATE Gate;
SPY_SEGMENT Segment;
PVOID pOffset;
BOOL fOk;
}
SPY_INTERRUPT, *PSPY_INTERRUPT, **PPSPY_INTERRUPT;
#define SPY_INTERRUPT_ sizeof (SPY_INTERRUPT)
// -----------------------------------------------------------------
NTSTATUS SpyOutputInterrupt (DWord dInterrupt,
PVOID pOutput,
DWORD dOutput,
PDWORD pdInfo)
{
SPY_INTERRUPT si;
SpyInterrupt (dInterrupt, &si);
return SpyOutputBinary (&si, SPY_INTERRUPT_,
pOutput, dOutput, pdInfo);
}
// -----------------------------------------------------------------
BOOL SpyInterrupt (DWORD dInterrupt,
PSPY_INTERRUPT pInterrupt)
{
BOOL fOk = FALSE;
if (pInterrupt != NULL)
{
if (dInterrupt <= X86_SELECTOR_LIMIT)
{
fOk = TRUE;
if (!SpySelector (X86_SEGMENT_OTHER,
dInterrupt << X86_SELECTOR_SHIFT,
&pInterrupt->Selector))
{
fOk = FALSE;
}
if (!SpyIdtGate (&pInterrupt->Selector,
&pInterrupt->Gate))
{
fOk = FALSE;
}
if (!SpySegment (X86_SEGMENT_OTHER,
pInterrupt->Gate.Selector,
&pInterrupt->Segment))
{
fOk = FALSE;
}
pInterrupt->pOffset = SpyGateOffset (&pInterrupt->Gate);
}
else
{
RtlZeroMemory (pInterrupt, SPY_INTERRUPT_);
}
pInterrupt->fOk = fOk;
}
return fOk;
}
// -----------------------------------------------------------------
PVOID SpyGateOffset (PX86_GATE pGate)
{
return (PVOID) (pGate->Offset1 | (pGate->Offset2 << 16));
}
列表 4-18. 查詢中斷屬性
表 4-3. 任務(wù)狀態(tài)段( TSS )中的 CPU 狀態(tài)域
偏移量
位數(shù)
ID
描 述
0x00
16
前一個(gè)任務(wù)的鏈接
0x04
32
ESP0
Ring0 級(jí)的堆棧指針寄存器
0x08
16
SS0
Ring0 級(jí)的堆棧段寄存器
0x0C
32
ESP1
Ring1 級(jí)的堆棧指針寄存器
0x10
16
SS1
Ring1 級(jí)的堆棧段寄存器
0x14
32
ESP2
Ring2 級(jí)的堆棧指針寄存器
0x18
16
SS2
Ring2 級(jí)的堆棧段寄存器
0x1C
32
CR3
頁(yè)目錄基址寄存器( PDBR )
0x20
32
EIP
指令指針寄存器
0x24
32
EFLAGS
處理器標(biāo)志寄存器
0x28
32
EAX
通用寄存器
0x2C
32
ECX
通用寄存器
0x30
32
EDX
通用寄存器
0x34
32
EBX
通用寄存器
0x38
32
ESP
堆棧指針寄存器
0x3C
32
EBP
基地址指針寄存器
0x40
32
ESI
源索引寄存器
0x44
32
EDI
目標(biāo)索引寄存器
0x48
16
ES
擴(kuò)展段寄存器
0x4C
16
CS
代碼段寄存器
0x50
16
SS
堆棧段寄存器
0x54
16
DS
數(shù)據(jù)段寄存器
0x58
16
FS
附加的數(shù)據(jù)段寄存器 #1
0x5C
16
GS
附加的數(shù)據(jù)段寄存器 #2
0x60
16
LDT
本地描述符標(biāo)的段選擇器
0x64
1
1
調(diào)試陷阱標(biāo)志
0x66
16
I/O Map 的基地址
0x68
-
CPU 狀態(tài)信息結(jié)束
SpyInterrupt() 調(diào)用的 SpySegment() 、 SpySelector() 函數(shù)已經(jīng)在 列表 4-5 和 列表 4-16 中給出。 SpyGateOffset() 位于 列表 4-18 的末尾,它的工作和 SpyDescriptorBase() 、 SpyDescriptorLimit() 類似,從 X86_GATE 結(jié)構(gòu)中取出 Offset1 和 Offset2 位域,并適當(dāng)?shù)慕M織它們以構(gòu)成一個(gè) 32 位地址。 SpyIdtGaet() 定義于 列表 4-19 。它與 SpyDescriptor() 十分類似。匯編指令 SIDT 存儲(chǔ)一個(gè) 48 位的值,該值就是 CPU 的 IDT 寄存器的內(nèi)容,它由一個(gè) 16 位的表大小限制值和 IDT 的 32 位線性基地址構(gòu)成。 列表 4-19 中的剩余代碼將選擇器的描述符索引和 IDT 的大小限制值進(jìn)行比較,如果 OK ,則對(duì)應(yīng)的中斷描述符將被復(fù)制到調(diào)用者提供的 X86_GATE 結(jié)構(gòu)中。否則,門結(jié)構(gòu)的所有成員都將被設(shè)置為 0 。
BOOL SpyIdtGate (PX86_SELECTOR pSelector,
PX86_GATE pGate)
{
X86_TABLE idt;
PX86_GATE pGates = NULL;
BOOL fOk = FALSE;
if (pGate != NULL)
{
if (pSelector != NULL)
{
__asm
{
sidt idt.wLimit
}
if ((pSelector->wValue & X86_SELECTOR_INDEX)
<= idt.wLimit)
{
pGates = idt.pGates;
}
}
if (pGates != NULL)
{
RtlCopyMemory (pGate,
pGates + pSelector->Index,
X86_GATE_);
fOk = TRUE;
}
else
{
RtlZeroMemory (pGate, X86_GATE_);
}
}
return fOk;
}
列表 4-19. 獲取 IDT 門的值
IOCTL 函數(shù) SPY_IO_PHYSICAL
SPY_IO_PHYSICAL 函數(shù)很簡(jiǎn)單,它完全依賴于 ntoskrnl.exe 導(dǎo)出的 MmGetPhysicalAddress() 函數(shù)。該 IOCTL 函數(shù)通過簡(jiǎn)單的調(diào)用 SpyInputPointer() (參見 列表 4-10 )來獲取需要轉(zhuǎn)換的線性地址,然后讓 MmGetPhysicalAddress() 查找對(duì)應(yīng)的物理地址,最后將結(jié)果作為 PHYSICAL_ADDRESS 結(jié)構(gòu)返回給調(diào)用者。注意, PHYSICAL_ADDRESS 是一個(gè) 64 位的 LARGE_INTEGER 。在大多數(shù) i386 系統(tǒng)上,其高 32 位總是為 0 。不過,若系統(tǒng)啟用了物理地址擴(kuò)展( Physical Address Extension, PAE ),并且安裝的內(nèi)存大于 4GB ,這些位可能就是非 0 值了。
MmGetPhysicalAddress() 使用起始于線性地址 0xC0000000 的 PTE 數(shù)組,來進(jìn)行物理地址的查找。其基本的工作機(jī)制如下:
l 如果線性地址位于: 0x80000000----0x9FFFFFFF ,則其高 3 位將被設(shè)為零,最后產(chǎn)生的物理地址位于: 0x00000000-----0x1FFFFFFF 。
l 否則,線性地址的高 20 位將作為 PTE 數(shù)組(起始于 0xC0000000 )的索引。
l 如果目標(biāo) PTE 的 P 位已被設(shè)置,這表示其對(duì)應(yīng)得數(shù)據(jù)頁(yè)存在于物理內(nèi)存中。除了 20 位的 PFN 外,所有的 PTE 位都可以被剝離出來,線性地址最低的 12 位將作為在數(shù)據(jù)頁(yè)中的偏移量被加到最后的 32 位物理地址上去。
l 如果數(shù)據(jù)頁(yè)沒有存在于物理內(nèi)存中, MmGetPhysicalAddress() 返回 0 。
MmGetPhysicalAddress() 假設(shè)內(nèi)核內(nèi)存范圍: 0x80000000----0x9FFFFFF 之外的所有線性地址都使用 4KB 的頁(yè)。而其他函數(shù),如 MmIsAddressValid() ,會(huì)首先加載線性地址的 PDE ,并且檢查該 PDE 的 PS 位,以檢查頁(yè)大小是 4KB 還是 4MB 。這是一個(gè)非常通用的方法,可以處理任意的內(nèi)存配置。不過上述兩個(gè)函數(shù)都會(huì)返回正確的結(jié)果,這是因?yàn)?Windows 2000 僅針對(duì)內(nèi)存范圍: 0x80000000-----0x9FFFFFFF ,使用 4MB 頁(yè)。不過某些內(nèi)核 API 函數(shù),顯然設(shè)計(jì)的比其它的靈活許多。
IOCTL 函數(shù) SPY_IO_CPU_INFO
個(gè)別的 CPU 指令僅對(duì)運(yùn)行于 Ring 0 級(jí)的代碼有效, Ring 0 是五個(gè)特權(quán)級(jí)( Intel 系列的 CPU 只支持兩個(gè)特權(quán)級(jí): Ring0 和 Ring3 )中級(jí)別最高的一個(gè)。用 Windows 術(shù)語(yǔ)來說, Ring 0 意味著內(nèi)核模式( Kernel-mode )。這些被禁止的指令有:讀取控制寄存器 CR0 、 CR2 和 CR3 的內(nèi)容。因?yàn)檫@些寄存器中保存著非常有趣的信息,應(yīng)用程序可能想要找到一個(gè)辦法來訪問它們,解決方案就是 SPY_IO_CPU_INFO 函數(shù)。如 列表 4-20 所示, IOCTL 處理例程調(diào)用的 SpyOutputCpuInfo() 函數(shù)使用了一些嵌入式匯編來讀取控制寄存器,以及其他一些有價(jià)值的信息,比如 IDT 的內(nèi)容, GDT 和 LDT 寄存器以及存儲(chǔ)在寄存器 CS 、 DS 、 ES 、 FS 、 GS 、 SS 和 TR 中的段選擇器。任務(wù)寄存器( Task Register, TR )還包含一個(gè)涉及當(dāng)前任務(wù)的 TSS 的選擇器。
typedef struct _SPY_CPU_INFO
{
X86_REGISTER cr0;
X86_REGISTER cr2;
X86_REGISTER cr3;
SPY_SEGMENT cs;
SPY_SEGMENT ds;
SPY_SEGMENT es;
SPY_SEGMENT fs;
SPY_SEGMENT gs;
SPY_SEGMENT ss;
SPY_SEGMENT tss;
X86_TABLE idt;
X86_TABLE gdt;
X86_SELECTOR ldt;
}
SPY_CPU_INFO, *PSPY_CPU_INFO, **PPSPY_CPU_INFO;
#define SPY_CPU_INFO_ sizeof (SPY_CPU_INFO)
// -----------------------------------------------------------------
NTSTATUS SpyOutputCpuInfo (PVOID pOutput,
DWORD dOutput,
PDWORD pdInfo)
{
SPY_CPU_INFO sci;
PSPY_CPU_INFO psci = &sci;
__asm
{
push eax
push ebx
mov ebx, psci
mov eax, cr0
mov [ebx.cr0], eax
mov eax, cr2
mov [ebx.cr2], eax
mov eax, cr3
mov [ebx.cr3], eax
sidt [ebx.idt.wLimit]
mov [ebx.idt.wReserved], 0
sgdt [ebx.gdt.wLimit]
mov [ebx.gdt.wReserved], 0
sldt [ebx.ldt.wValue]
mov [ebx.ldt.wReserved], 0
pop ebx
pop eax
}
SpySegment (X86_SEGMENT_CS, 0, &sci.cs);
SpySegment (X86_SEGMENT_DS, 0, &sci.ds);
SpySegment (X86_SEGMENT_ES, 0, &sci.es);
SpySegment (X86_SEGMENT_FS, 0, &sci.fs);
SpySegment (X86_SEGMENT_GS, 0, &sci.gs);
SpySegment (X86_SEGMENT_SS, 0, &sci.ss);
SpySegment (X86_SEGMENT_TSS, 0, &sci.tss);
return SpyOutputBinary (&sci, SPY_CPU_INFO_,
pOutput, dOutput, pdInfo);
}
列表 4-20. 查詢 CPU 狀態(tài)信息
可使用幫助函數(shù) SpySegement() 獲取段選擇器,在前面,我們已討論過該函數(shù)。參見 列表 4-15 。
