在Windows操作系统中,每个进程的虚拟地址空间都被划分为若干内存块,每个内存块都具有一些属性,如内存大小、保护模式、类型等。这些属性可以通过VirtualQueryEx函数查询得到。
该函数可用于查询进程虚拟地址空间中的内存信息的函数。它的作用类似于Windows操作系统中的Task Manager中的进程选项卡,可以显示出一个进程的内存使用情况、模块列表等信息。使用VirtualQueryEx函数,可以枚举一个进程的所有内存块。该函数需要传入要查询的进程的句柄、基地址和一个MEMORY_BASIC_INFORMATION结构体指针。它会返回当前内存块的基地址、大小、状态(free/commit/reserve)、保护模式等信息。
在实现对内存块的枚举之前,我们先通过ReadProcessMemory函数实现一个内存远程内存读取功能,如下代码所示,首先,通过OpenProcess函数打开进程句柄,获得当前进程的操作权限。然后,调用EnumMemory函数,传入进程句柄以及起始地址和终止地址参数,依次读取每一页内存,通过循环打印其内存数据。- #include <iostream>
- #include <windows.h>
- // 枚举内存实现
- void EnumMemory(HANDLE Process, DWORD BeginAddr, DWORD EndAddr)
- {
- // 每次读入长度
- const DWORD pageSize = 1024;
- BYTE page[pageSize];
- DWORD tmpAddr = BeginAddr;
- while (tmpAddr <= EndAddr)
- {
- ReadProcessMemory(Process, (LPCVOID)tmpAddr, &page, pageSize, 0);
- for (int x = 0; x < pageSize; x++)
- {
- if (x % 15 == 0)
- {
- printf("| 0x%08X \n", tmpAddr + x);
- }
- printf("0x%02X ", page[x]);
- }
- tmpAddr += pageSize;
- }
- }
- int main(int argc, char* argv[])
- {
- HANDLE process;
- // 打开当前进程
- process = OpenProcess(PROCESS_ALL_ACCESS, false, GetCurrentProcessId());
- // 枚举内存 从0x401000 - 0x7FFFFFFF
- EnumMemory(process, 0x00401000, 0x7FFFFFFF);
- system("pause");
- return 0;
- }
复制代码 运行上述代码片段则首先通过GetCurrentProcessId()得到自身进程的PID号,接着通过调用ScanProcessMemory函数实现对自身进程内存块的枚举功能,最终输出如下图所示的效果;

当然了虽然上述代码可以实现对内存块的枚举功能,但是在实际的开发场景中我们还是需要将枚举结果存储起来以便后期调用,此时我们可以考虑在全局定义vector容器,容器的属性为每一个内存块的MEMORY_BASIC_INFORMATION属性,当需要查询时只需要枚举这个容器并循环输出该容器内的数据即可,改进后的代码如下所示;- typedef struct _SYSTEM_INFO {
- union {
- DWORD dwOemId; // 兼容性保留
- struct {
- WORD wProcessorArchitecture; // 操作系统处理器体系结构
- WORD wReserved; // 保留
- } DUMMYSTRUCTNAME;
- } DUMMYUNIONNAME;
- DWORD dwPageSize; // 页面大小和页面保护和承诺的粒度
- LPVOID lpMinimumApplicationAddress; // 指向应用程序和dll可访问的最低内存地址的指针
- LPVOID lpMaximumApplicationAddress; // 指向应用程序和dll可访问的最高内存地址的指针
- DWORD_PTR dwActiveProcessorMask; // 处理器掩码
- DWORD dwNumberOfProcessors; // 当前组中逻辑处理器的数量
- DWORD dwProcessorType; // 处理器类型,兼容性保留
- DWORD dwAllocationGranularity; // 虚拟内存的起始地址的粒度
- WORD wProcessorLevel; // 处理器级别
- WORD wProcessorRevision; // 处理器修订
- } SYSTEM_INFO, *LPSYSTEM_INFO;
复制代码 读者可编译并自行运行上述代码,观察输出效果其与第一个案例中的效果保持一致,此处仅仅只是通过容器中转了参数传递,输出效果图如下所示;

对于内存块中的范围区间同样可实现继续查询,例如在开始地址0x5DF00000-0x5DF01000这个内存区间内,可能灰灰划分为更多的子块,当Basicinfo.State内存属性中的子块属性为MEM_COMMIT时,我们还可以继续调用VirtualQuery函数对这个大内存块内的子内存块进行更加细致的解析效果,这段代码如下所示;- SIZE_T VirtualQueryEx(
- HANDLE hProcess,
- LPCVOID lpAddress,
- PMEMORY_BASIC_INFORMATION lpBuffer,
- SIZE_T dwLength
- );
复制代码 当上述代码运行后,我们就可以获取到当前内存中有多少个内存块,以及每一个内存块的属性信息,如下图所示;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/c09766a2.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |