使用VirtualQuery查看內存頁面信息

本文介紹了使用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的源代碼。

// meminfo.cpp, use VirtualQuery to get information about allocated memory pages
// 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章