本文介紹了使用VirtualQuery函數查看進程內存地址空間的頁面分配情況,並給出了一個例程。
1 Win32內存佈局簡介
在16位CPU的時代,受尋址範圍的限制,系統所能使用的內存空間是非常少的。據說當年Bill Gates曾說過“640K內存對任何人來說都夠用了”,現在看來似乎很好笑,不過做過DOS編程的人大概都還會懷念那個時代。後來有了386,有了保護模式,有了虛擬內存,多任務終於成爲現實。
在Win32環境下,尋址不再需要遵照“段首址:偏移”的格式(當然這樣也不算錯),每個進程擁有獨立的4GB地址空間,當然物理內存總是寶貴的,因此並不會給每個進程分配那麼多空間。段寄存器不再用來存放段首址,而是存放所謂的選擇子(Segment Selector),所有的段共用00000000到FFFFFFFF的4GB地址空間,內存分段概念已經過時了,至少在絕大多數應用中你不用和段寄存器打交道了。
4GB內存空間被分爲用戶空間(00000000到7FFFFFFF)和系統空間(80000000到FFFFFFFF),這兩部分各佔2GB,當然如果你願意的話也可以把用戶空間設爲3GB系統空間設爲1GB。用戶空間的可訪問範圍是00010000到7FFEFFFF,這兩個值是調用GetSystemInfo函數得到的,這個範圍比上面提到的2GB小,是因爲系統在兩端預留了一部分空間作爲“隔離帶”。
本文中用VirtualQuery查看內存頁面信息其實查看的就是從00010000到7FFEFFFF這段空間,這不足2GB的空間也被劃分爲不同的段落,用於存放可執行映像、全局數據、棧、堆、DLL等。
2 VirtualQuery與VirtualQueryEx
VirutalQuery提供某一段內存區域的頁面信息。函數原型如下:
DWORD VirtualQuery(
LPCVOID lpAddress, // address of region
PMEMORY_BASIC_INFORMATION lpBuffer, // address of information buffer
DWORD dwLength // size of buffer
);
Windows給應用程序分配內存時是以頁爲單位的,頁的大小通常是4KB。而VirtualQuery函數會給出lpAddress這個地址所在的頁面以及與它相鄰的具有相同屬性的頁面的信息,你可以通過查看MEMORY_BASIC_INFORMATION結構的內容來檢索這些信息,MEMORY_BASIC_INFORMATION定義如下:
typedef struct _MEMORY_BASIC_INFORMATION { // mbi
PVOID BaseAddress; // base address of region
PVOID AllocationBase; // allocation base address
DWORD AllocationProtect; // initial access protection
DWORD RegionSize; // size, in bytes, of region
DWORD State; // committed, reserved, free
DWORD Protect; // current access protection
DWORD Type; // type of pages
} MEMORY_BASIC_INFORMATION;
其中,BaseAddress是lpAddress所在頁面的基地址,AllocationBase是用VirtualAlloc函數分配此頁面時的基地址,可以小於等於BaseAddress,原因是VirtualAlloc可以一次分配很多頁。AllocationProtect是分配時的保護屬性,比如只讀、讀寫、可執行等,Protect是現在的保護屬性。State可以取MEM_COMMIT,MEM_FREE,MEMRESERVE三者之一,其中只有MEM_COMMIT狀態的頁面是實際分配了物理內存的可以訪問。Type描述有關頁面共享的屬性。用過金山遊俠的都知道,我們在遊戲內存中找數據時只需要搜索具有MEM_COMMIT和writable屬性的頁面。
VirtualQuery的侷限是一次只能得到一組連續頁面的信息,然而這樣連續的頁面通常有幾十組甚至上百組之多。要查看完整的地址空間狀況,需要循環調用VirtualQuery函數。
VirtualQueryEx和 VirtualQuery的用法一樣,只是多了一個參數——進程句柄,因此你可以用它來查看其它進程的地址空間。Win32 API裏還有很多像這樣成對存在的函數。
3 程序實例
這個例程用一個循環搜索用戶地址空間並打印出VirtualQuery得到的頁面信息,用過CheatEngine的應該知道CheatEngine也有這個功能,我在寫這個程序時參考了CheatEngine的源代碼。
// VirtualQuery,在winbase.h中聲明,引入庫爲kernel32.lib
// 本程序在WinXP,MinGW G++ 3.4.5測試通過
// 龍第九子 2008/05/03
#include <windows.h>
#include <stdio.h>
#include <string>
std::string FormatMemInfo(MEMORY_BASIC_INFORMATION &meminfo)
{
// this function gives you a human-readable formatted output of struct meminfo
// use local variables for short
PVOID base_address = meminfo.BaseAddress; // ***
PVOID alloc_base = meminfo.AllocationBase;
DWORD alloc_protect = meminfo.AllocationProtect;
DWORD region_size = meminfo.RegionSize; // ***
DWORD state = meminfo.State; // ***
DWORD protect = meminfo.Protect; // ***
DWORD type = meminfo.Type;
// format the AllocationProtect field
std::string s_alloc_protect;
if(alloc_protect & PAGE_NOACCESS) // 0x0001
s_alloc_protect = "NoAccess";
if(alloc_protect & PAGE_READONLY) // 0x0002
s_alloc_protect = "Readonly";
else if(alloc_protect & PAGE_READWRITE) // 0x0004
s_alloc_protect = "ReadWrite";
else if(alloc_protect & PAGE_WRITECOPY) // 0x0008
s_alloc_protect = "WriteCopy";
else if(alloc_protect & PAGE_EXECUTE) // 0x0010
s_alloc_protect = "Execute";
else if(alloc_protect & PAGE_EXECUTE_READ) // 0x0020
s_alloc_protect = "Execute_Read";
else if(alloc_protect & PAGE_EXECUTE_READWRITE) // 0x0040
s_alloc_protect = "Execute_ReadWrite";
else if(alloc_protect & PAGE_EXECUTE_WRITECOPY) // 0x0080
s_alloc_protect = "Execute_WriteCopy";
if(alloc_protect & PAGE_GUARD) // 0x0100
s_alloc_protect += "+Guard";
if(alloc_protect & PAGE_NOCACHE) // 0x0200
s_alloc_protect += "+NoCache";
// format the State field
std::string s_state;
if(state == MEM_COMMIT) // accessible, physical memory is allocated.
s_state = "Commit ";
else if(state == MEM_FREE) // unaccessible, AllocationBase, AllocationProtect, Protect, and Type are undefined.
s_state = "Free ";
else if(state == MEM_RESERVE) // unaccessible, Protect is undefined.
s_state = "Reserve";
else // this case is not expected to happen
s_state = "Damned ";
// format the Protect field
std::string s_protect;
if(protect & PAGE_NOACCESS)
s_protect = "NoAccess";
if(protect & PAGE_READONLY)
s_protect = "Readonly";
else if(protect & PAGE_READWRITE)
s_protect = "ReadWrite";
else if(protect & PAGE_WRITECOPY)
s_protect = "WriteCopy";
else if(protect & PAGE_EXECUTE)
s_protect = "Execute";
else if(protect & PAGE_EXECUTE_READ)
s_protect = "Execute_Read";
else if(protect & PAGE_EXECUTE_READWRITE)
s_protect = "Execute_ReadWrite";
else if(protect & PAGE_EXECUTE_WRITECOPY)
s_protect = "Execute_WriteCopy";
if(protect & PAGE_GUARD)
s_protect += "+Guard";
if(protect & PAGE_NOCACHE)
s_protect += "+NoCache";
// format the Type field
std::string s_type;
if(type == MEM_IMAGE)
s_type = "Image ";
else if(type == MEM_MAPPED)
s_type = "Free ";
else if(type == MEM_PRIVATE) // most cases
s_type = "Private";
else
s_type = "- ";
// final string
char buf[128] = {'/0'};
sprintf(buf, "%8X %8X %25s %7s %25s %7s %8X", base_address, alloc_base,
s_alloc_protect.c_str(), s_state.c_str(), s_protect.c_str(), s_type.c_str(), region_size);
return std::string(buf);
}
int main()
{
SYSTEM_INFO info;
GetSystemInfo(&info);
//DWORD pagesize = info.dwPageSize; // page size, usually 4k
DWORD lowerbound = (DWORD)info.lpMinimumApplicationAddress; // starting address, normally 0x10000
DWORD upperbound = (DWORD)info.lpMaximumApplicationAddress; // end address, normally 0x7FFEFFFF
MEMORY_BASIC_INFORMATION meminfo;
DWORD ptr = lowerbound;
while(ptr <= upperbound)
{
if(VirtualQuery((void*)ptr, &meminfo, sizeof(meminfo)) == 0) // if the queried address unaccessible
break;
std::string s_meminfo = FormatMemInfo(meminfo); // format
puts(s_meminfo.c_str()); // output
int oldptr = ptr;
ptr = (DWORD)meminfo.BaseAddress + meminfo.RegionSize;
if(ptr <= oldptr) // prevent unendable iteration
break;
}
return 0;
}
參考資料:
1 《Windows彙編語言程序設計教程》電子工業出版社 2005.4
2 Win32 Developer's References, by Microsoft
3 從進程中獲取QQ號碼 http://blog.163.com/dave22@126/blog/static/238654112007812104236800/
4 CheatEngine 5.4源代碼 http://www.cheatengine.org/downloads/CheatEngine54src.rar