《Undocumented Windows 2000 Secrets》翻譯 --- 第四章(8)
第四章 探索 Windows 2000 的內(nèi)存管理機(jī)制
翻譯: Kendiv( fcczj@263.net )
更新: Tuesday, February 22, 2005
聲明:轉(zhuǎn)載請(qǐng)注明出處,并保證文章的完整性,本人保留譯文的所有權(quán)利。
請(qǐng)求式分頁動(dòng)作
在討論 Spy 設(shè)備的 SPY_IO_MEMORY_DATA 函數(shù)時(shí),我提到過該函數(shù)可以讀取已被置換到頁面文件中的內(nèi)存頁。要證明這一點(diǎn),首先,必須讓系統(tǒng)處于低內(nèi)存狀態(tài),以強(qiáng)迫它將不馬上使用的數(shù)據(jù)置換到頁面文件中。我喜歡采用的方法如下:
1. 使用 PrintKey ,將 Windows 2000 的桌面復(fù)制到剪切板中。
2. 將該圖片粘貼到一個(gè)圖形處理程序中。
3. 將該圖片的尺寸放到最大。
現(xiàn)在,執(zhí)行命令: w2k_mem +d #16 0xC02800000 0xA0000000 0xA0001000 0xA0002000 0xC0280000 ,察看它在屏幕上的輸出。你可能會(huì)驚訝。在觸及某些 PTE 所引用的頁之前,它會(huì)獲取這些 PTE 的快照。在地址 0xC0280000 處發(fā)現(xiàn)的四個(gè) PTE 與地址范圍: 0xA0000000---0xA0003FFF 相關(guān),這是內(nèi)核模塊 win32k.sys 的一部分。如 示列 4-11 所示,該地址范圍已經(jīng)被置換出去了,因?yàn)樵诘刂?0xC0280000 的四個(gè) DWord 都是偶數(shù),這意味著它們的最低位(即 PTE 的 P 位)為零,這表示沒有存在于物理內(nèi)存中的頁。接下來的三塊 16 進(jìn)制 Dump 信息屬于 0xA0000000 、 0xA0001000 、 0xA0002000 , w2k_mem 可以毫無問題的訪問這些頁(系統(tǒng)會(huì)根據(jù)請(qǐng)求將它們?cè)俅螕Q入內(nèi)存)。
示列 4-11 觀察 PTE 的狀態(tài)變化
在開始下一節(jié)之前,請(qǐng)?jiān)俅窝芯恳幌?示列 4-11 中的第一欄。位于地址 0xC0280000 的四個(gè) PTE 看上去都很像。但事實(shí)上,它們僅有最低的三個(gè)位不同。如果你檢查所有位于頁面文件中的 PNPE ,你會(huì)發(fā)現(xiàn)它們的第 10 位都為 1 。這就是為什么我在 列表 4-3 中,將該位的名字取為 PageFile 。如果該位為 1 ,除 P 位外的所有位都將用來表示該頁在頁面文件中的位置。
更多的命令選項(xiàng)
示列 4-1 給出的某些命令選項(xiàng)還沒有解釋過。例如,系統(tǒng)狀態(tài)選項(xiàng): +o 、 +c 、 +g 、 +i 和 +b ,我會(huì)在本章的最后一節(jié)介紹它們,在那兒我們將發(fā)現(xiàn)幾個(gè) Windows 2000 內(nèi)存系統(tǒng)的秘密。
Spy 設(shè)備的接口
現(xiàn)在你已經(jīng)知道如何使用 w2k_mem 了,該是介紹它是如何工作的了。現(xiàn)在來看看這個(gè)程序是如何與 w2k_spy.sys 中的 Spy 設(shè)備通訊的。
回顧 ----- 設(shè)備 I/O 控制( Device I/O Control )
IOCTL 通訊的內(nèi)核模式端已經(jīng)由 列表 4-6 和 列表 4-7 給出了。 Spy 設(shè)備只是簡(jiǎn)單的等待 IRP 并處理其中的某些 IRP ,尤其是標(biāo)識(shí)為 IPR_MJ_DEVICE_CONTROL ,其中的一些請(qǐng)求在用戶模式下是被禁止的。調(diào)用 Win32 API 函數(shù) DeviceIoControl() , 列表 4-27 給出了該函數(shù)的原型。可能你已經(jīng)熟悉了 dwIocontrolCode 、 lpInBuffer 、 nInBufferSize 、 lpOutBuffer 、 nOutBufferSize 和 lpBytesReturned 參數(shù)。事實(shí)上,它們一一對(duì)應(yīng)于: SpyDispatcher() 的 dCode 、 pInput 、 dInput 、 pOutput 、 dOutput 和 pdInfo 參數(shù), SpyDispatcher 定義于 列表 4-7 。剩下的參數(shù)很快就會(huì)解釋。 hDevice 是 Spy 設(shè)備的句柄, lpOverlapped (可選的)指向一個(gè) OVERLAPPED 結(jié)構(gòu),異步 IOCTL 需要該結(jié)構(gòu)。我們不需要發(fā)送異步請(qǐng)求,所以該參數(shù)總是 NULL 。
列表 4-28 列出了所有執(zhí)行基本 IOCTL 操作的外包函數(shù)。最基本的一個(gè)是: IoControl() ,該函數(shù)調(diào)用 DeviceControl() 并測(cè)試返回的輸出數(shù)據(jù)的大小。因?yàn)?w2k_mem.exe 精確的提供了輸出緩沖區(qū)的大小,所以,輸出的字節(jié)數(shù)應(yīng)該總是等于緩沖區(qū)的大小。 ReadBinary() 是 IoControl() 的簡(jiǎn)單版本,它不需要輸入數(shù)據(jù)。 ReadCPUInfo() 、 ReadSegment() 和 ReadPhysical() 專用于 Spy 函數(shù) SPY_IO_CPU_INFO 、 SPY_IO_SEGEMNT 和 SPY_IO_PHYSICAL ,因?yàn)樗鼈儠?huì)經(jīng)常被用到。將它們封裝為 C 函數(shù),可讀性會(huì)更好些。
BOOL WINAPI DeviceIoControl( HANDLE hDevice,
DWORD dwIoControlCode,
PVOID lpInBuffer,
DWORD nInBufferSize,
PVOID lpOutBuffer,
DWORD nOutBufferSize,
PDWORD lpBytesReturned,
POVERLAPPED lpOverlapped);
列表 4-27. DeviceIoControl 函數(shù)的原型
BOOL WINAPI IoControl (HANDLE hDevice,
DWORD dCode,
PVOID pInput,
DWORD dInput,
PVOID pOutput,
DWORD dOutput)
{
DWORD dData = 0;
return DeviceIoControl (hDevice, dCode,
pInput, dInput,
pOutput, dOutput,
&dData, NULL)
&&
(dData == dOutput);
}
// -----------------------------------------------------------------
BOOL WINAPI ReadBinary (HANDLE hDevice,
DWORD dCode,
PVOID pOutput,
DWORD dOutput)
{
return IoControl (hDevice, dCode, NULL, 0, pOutput, dOutput);
}
// -----------------------------------------------------------------
BOOL WINAPI ReadCpuInfo (HANDLE hDevice,
PSPY_CPU_INFO psci)
{
return IoControl (hDevice, SPY_IO_CPU_INFO,
NULL, 0,
psci, SPY_CPU_INFO_);
}
// -----------------------------------------------------------------
BOOL WINAPI ReadSegment (HANDLE hDevice,
DWORD dSelector,
PSPY_SEGMENT pss)
{
return IoControl (hDevice, SPY_IO_SEGMENT,
&dSelector, DWORD_,
pss, SPY_SEGMENT_);
}
// -----------------------------------------------------------------
BOOL WINAPI ReadPhysical (HANDLE hDevice,
PVOID pLinear,
PPHYSICAL_ADDRESS ppa)
{
return IoControl (hDevice, SPY_IO_PHYSICAL,
&pLinear, PVOID_,
ppa, PHYSICAL_ADDRESS_)
&&
(ppa->LowPart || ppa->HighPart);
}
列表 4-28 幾個(gè) IOCTL 的外包函數(shù)
到目前為止,本節(jié)列出的所有函數(shù)都需要 Spy 設(shè)備的一個(gè)句柄。現(xiàn)在,我將介紹如何獲取該句柄。這實(shí)際上是一個(gè)非常簡(jiǎn)單的 Win32 操作,和打開文件類似。 列表 4-29 展示了 w2k_mem.exe 的命令處理例程的實(shí)現(xiàn)細(xì)節(jié)。該代碼使用 API 函數(shù) w2kFilePath() 、 w2kServiceLoad() 和 w2kServiceUnload() ,這幾個(gè)函數(shù)由 w2k_lib.dll 導(dǎo)出。如果你已經(jīng)讀過第三章中關(guān)于 Windows 2000 服務(wù)控制管理器的介紹,你應(yīng)該通過 列表 3-8 已了解了 w2kServiceLoad() 和 w2kServiceUnload() 。這些強(qiáng)大的函數(shù)可隨時(shí)加載或卸載內(nèi)核模式的設(shè)備驅(qū)動(dòng),并且能處理一些良性的錯(cuò)誤,如,妥善的處理加載一個(gè)已經(jīng)載入內(nèi)存的驅(qū)動(dòng)程序。 w2kFilePath() 是一個(gè)幫助函數(shù)。 w2k_mem.exe 調(diào)用它來獲取 Spy 驅(qū)動(dòng)程序的完整路徑。
WORD awSpyFile [] = SW(DRV_FILENAME);
WORD awSpyDevice [] = SW(DRV_MODULE);
WORD awSpyDisplay [] = SW(DRV_NAME);
WORD awSpyPath [] = SW(DRV_PATH);
// -----------------------------------------------------------------
void WINAPI Execute (PPWORD ppwArguments,
DWORD dArguments)
{
SPY_VERSION_INFO svi;
DWORD dOptions, dRequest, dReceive;
WORD awPath [MAX_PATH] = L'?';
SC_HANDLE hControl = NULL;
HANDLE hDevice = INVALID_HANDLE_VALUE;
_printf (L'rnLoading '%s' (%s) ...rn',
awSpyDisplay, awSpyDevice);
if (w2kFilePath (NULL, awSpyFile, awPath, MAX_PATH))
{
_printf (L'Driver: '%s'rn',
awPath);
hControl = w2kServiceLoad (awSpyDevice, awSpyDisplay,
awPath, TRUE);
}
if (hControl != NULL)
{
_printf (L'Opening '%s' ...rn',
awSpyPath);
hDevice = CreateFile (awSpyPath, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
}
else
{
_printf (L'Unable to load the spy device driver.rn');
}
if (hDevice != INVALID_HANDLE_VALUE)
{
if (ReadBinary (hDevice, SPY_IO_VERSION_INFO,
&svi, SPY_VERSION_INFO_))
{
_printf (L'rn%s V%lu.%02lu readyrn',
svi.awName,
svi.dVersion / 100, svi.dVersion % 100);
}
dOptions = COMMAND_OPTION_NONE;
dRequest = CommandParse (hDevice, ppwArguments, dArguments,
TRUE, &dOptions);
dOptions = COMMAND_OPTION_NONE;
dReceive = CommandParse (hDevice, ppwArguments, dArguments,
FALSE, &dOptions);
if (dRequest)
{
_printf (awSummary,
dRequest, (dRequest == 1 ? awByte : awBytes),
dReceive, (dReceive == 1 ? awByte : awBytes));
}
_printf (L'rnClosing the spy device ...rn');
CloseHandle (hDevice);
}
else
{
_printf (L'Unable to open the spy device.rn');
}
if ((hControl != NULL) && gfSpyUnload)
{
_printf (L'Unloading the spy device ...rn');
w2kServiceUnload (awSpyDevice, hControl);
}
return;
}
列表 4-29. 控制 Spy 設(shè)備
請(qǐng)注意 列表 4-29 頂部給出的四個(gè)全局字符串的定義。常量 DRV_FILENAME 、 DRV_MODULE 、 DRV_NAME 和 DRV_PATH 來自 Spy 驅(qū)動(dòng)的頭文件 w2k_spy.h 。 表 4-4 列出了它們的當(dāng)前值。你不會(huì)在 w2k_mem.exe 的源代碼中發(fā)現(xiàn)設(shè)備相關(guān)的定義, w2k_spy.h 提供了客戶端程序所需的一切。這非常重要:如果以后改變了任何設(shè)備相關(guān)的定義,就不需要更新任何程序文件了。只需要以新的頭文件編譯、鏈接程序即可。
列表 4-29 頂部調(diào)用的 w2kFilePath() 可以保證由全局變量 awSpyFile (見 表 4-4 )指定的 w2k_spy.sys 總是從 w2k_mem.exe 所在目錄中加載。接下來, 列表 4-29 中的代碼將全局字符串 awSpyDevice 和 awSpyDisplay ()傳遞給 w2kServiceLoad() ,以加載 Spy 設(shè)備的驅(qū)動(dòng)。如果驅(qū)動(dòng)沒有被加載,這些字符串將被保存在驅(qū)動(dòng)的屬性列表中,可以由其他程序取出;否則,將保留當(dāng)前的屬性設(shè)置。盡管 列表 4-29 中的 w2kServiceLoad() 調(diào)用可返回一個(gè)句柄,但這并不是一個(gè)可用于任何 IOCTL 函數(shù)的句柄。要獲取 Spy 設(shè)備的句柄,必須使用 Win32 的多用途函數(shù) CreateFile() 。該函數(shù)可打開或創(chuàng)建 Windows 2000 中幾乎所有可被打開和創(chuàng)建的東西。如果提供了內(nèi)核設(shè)備的符號(hào)鏈接名,形如 .<SymbolicLink > 給 CreateFile() 的 lpFileName 參數(shù),那么該函數(shù)就可打開這個(gè)內(nèi)核設(shè)備。 Spy 設(shè)備的符號(hào)鏈接名是: w2k_spy ,因此, CreateFile() 的第一個(gè)參數(shù)必須是 .w2k_spy ,這正是 表 4-4 中的 awSpyPath 的值。
表 4-4. 設(shè)備相關(guān)的字符串定義
w2k_spy 常量
w2k_mem 變量
值
DRV_FILENAME
awSpyFile
w2k_spy.sys
DRV_MODULE
awSpyDevice
w2k_spy
DRV_NAME
awSpyDisplay
SBS Windows 2000 Spy Device
DRV_PATH
awSpyPath
. w2k_spy
如果 CreateFile() 成功,它將返回一個(gè)設(shè)備的句柄,該句柄可傳遞給 DeviceIoControl() 。 列表 4-29 中的 Execute() 函數(shù)使用該句柄來查詢 Spy 設(shè)備的版本信息,如果 IOCTL 調(diào)用成功,該信息將會(huì)在屏幕上顯示出來。接下來, CommandParser() 函數(shù)將被調(diào)用兩次,第一次調(diào)用只是簡(jiǎn)單的檢查命令行中是否有無效的參數(shù),并顯示任何可能的錯(cuò)誤。第二次調(diào)用則執(zhí)行所有的命令。我不想討論該函數(shù)的細(xì)節(jié)。 列表 4-29 中的剩余代碼是為了進(jìn)行清理工作,如關(guān)閉句柄和卸載 Spy 驅(qū)動(dòng)(該功能是可選的)。 w2k_mem.exe 的源代碼中還有一些有趣的代碼片斷,但我不在這里討論它們了。請(qǐng)參考本書光盤的 srcw2k_mem 目錄下的 w2k_mem.c 和 w2k_mem.h 。
現(xiàn)在唯一需要注意的就是 gfSpyUnload 標(biāo)志,該標(biāo)志決定是否卸載 Spy 驅(qū)動(dòng)。我已經(jīng)將這個(gè)全局標(biāo)志設(shè)為了 FALSE ,因此不會(huì)自動(dòng)卸載該驅(qū)動(dòng)。這提高 w2k_mem.exe 或 w2k_spy.sys 的任何客戶端的性能,因?yàn)榧虞d一個(gè)驅(qū)動(dòng)需要花費(fèi)一定的時(shí)間。只有第一個(gè)客戶端會(huì)產(chǎn)生加載開銷。這種設(shè)置還可避免多個(gè)客戶端間的競(jìng)爭(zhēng),如,一個(gè)客戶試圖卸載該驅(qū)動(dòng)而此時(shí)另一個(gè)還在使用這個(gè)驅(qū)動(dòng)。當(dāng)然, Windows 2000 不會(huì)卸載一個(gè)驅(qū)動(dòng),除非該驅(qū)動(dòng)的所有句柄都被關(guān)閉了,但系統(tǒng)會(huì)將驅(qū)動(dòng)置于 STOP_PENDING 狀態(tài),這樣新的客戶端將無法訪問此設(shè)備。不過,如果你不在一個(gè)多客戶端的環(huán)境下運(yùn)行 w2k_spy.sys ,而且你需要經(jīng)常更新設(shè)備的驅(qū)動(dòng)程序,你就應(yīng)該將 gfSpyUnload 標(biāo)志設(shè)為 TRUE 。
深入 Windows 2000 內(nèi)存
引入用戶模式和內(nèi)核模式的獨(dú)立 4GB 地址空間被再次劃分為多個(gè)更小的塊。正如你可能猜到的,它們中的大多數(shù)都包含未文檔化的結(jié)構(gòu),而且服務(wù)于未文檔化的地目的。其中某些東西對(duì)于任何開發(fā)系統(tǒng)診斷或調(diào)試軟件的人來說都是真正的金礦。
基本的操作系統(tǒng)信息
如果你注意過 示列 4-1 下半部分的幫助信息,你會(huì)發(fā)該節(jié)的標(biāo)題是:“系統(tǒng)狀態(tài)選項(xiàng)”。現(xiàn)在試試名為“顯示操作系統(tǒng)信息”的選項(xiàng): +o 。 示列 4-12 給出了在我的機(jī)器上使用該選項(xiàng)的輸出結(jié)果。這里顯示的信息都是 SPY_OS_INFO 結(jié)構(gòu)的內(nèi)容,該結(jié)構(gòu)定義與 列表 4-13 ,由 Spy 設(shè)備函數(shù) SpyOutputOsInfo() 實(shí)際創(chuàng)建該結(jié)構(gòu),此函數(shù)也包含在 列表 4-13 中。在 示列 4-12 中,你可以看到位于 4GB 地址空間中的進(jìn)程的一些典型地址。例如,有效的用戶地址范圍是: 0x00010000 ---- 0x7FFFFFFF 。你可能閱讀過其他有關(guān) Windows NT 或 2000 的程序設(shè)計(jì)書籍,用戶模式的第一個(gè)和最后一個(gè) 64KB 線性內(nèi)存區(qū)域是“不能訪問區(qū)域”,訪問這一區(qū)域?qū)⒁l(fā)一個(gè)錯(cuò)誤(參見第五章, Solomon 1998 ), W2k_mem.exe 輸出證明了這一點(diǎn)。
示列 4-12. 顯示操作系統(tǒng)信息
示列 4-12 中的最后三行包含的信息非常有趣,它們都是有關(guān)系統(tǒng)的。這些信息大部分都取自位于地址 0xFFDF0000 處的 SharedUserData 區(qū)域中。系統(tǒng)在該處維護(hù)一個(gè)名為 KUSER_SHARED_DATA 的結(jié)構(gòu),該結(jié)構(gòu)定義于 DDK 頭文件 ntddk.h 。
