使用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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值