windows进程堆内存列表遍历

您所在的位置:网站首页 内存遍历工具使用教程 windows进程堆内存列表遍历

windows进程堆内存列表遍历

2024-07-13 07:21| 来源: 网络整理| 查看: 265

一、简介

    前面我们通过用户模式堆栈跟踪数据库,查找并打印指定内存的堆栈,这个过程首先是拿到一个感兴趣的内存块地址,然后再去查找他的堆栈。上一篇我们用的是一个测试地址,那么实际工作中什么是我们感兴趣的堆内存块呢?一般来说是一些非常大的内存,分配次数非常多的内存,这些都是可以(需要)优化的内存。从这个角度说,首先就得分析整个进程中的堆内存块,得出最大的内存块列表,这就需要遍历整个堆了。今天就简单介绍下如何遍历堆列表,并写一个demo。

二、遍历本进程的堆内存列表 2.1、获取堆列表

    每个windows进程都可能会有很多堆,首先每个进程会有自己的默认堆,其次,很多版本的crt库都会有自己建立的堆,另外开发者可能会自己主动调用HeapCreate创建自建堆。所以一个进程是可能存在有多个堆的。我们的第一步是获取进程的堆列表。

DWORD GetProcessHeaps( [in] DWORD NumberOfHeaps, [out] PHANDLE ProcessHeaps );

参数

[in] NumberOfHeaps

可以存储在 ProcessHeaps 指向的缓冲区中的最大堆句柄数。

[out] ProcessHeaps

指向接收堆句柄数组的缓冲区的指针。

getProcessHeaps 函数 (heapapi.h) - Win32 apps | Microsoft Learnicon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/windows/win32/api/heapapi/nf-heapapi-getprocessheaps这里我们可以通过传空来获取堆个数,然后再分配缓存来获取真正的堆列表

DWORD dwNum = GetProcessHeaps(0, nullptr); HANDLE* pHeapHandle = new HANDLE[dwNum]; dwNum = GetProcessHeaps(dwNum, pHeapHandle); 2.2、枚举指定堆中的内存块

    获取了堆以后,我们就需要枚举这个堆中的内存块了

BOOL HeapWalk( [in] HANDLE hHeap, [in, out] LPPROCESS_HEAP_ENTRY lpEntry );

 参数

[in] hHeap

堆的句柄。 此句柄由 HeapCreate 或 GetProcessHeap 函数返回。

[in, out] lpEntry

指向 PROCESS_HEAP_ENTRY 结构的指针,该结构维护特定堆枚举的状态信息。

如果 HeapWalk 函数成功并返回值 TRUE,则此结构的成员包含有关堆中下一个内存块的信息。

若要启动堆枚举,请将 PROCESS_HEAP_ENTRY 结构的 lpData 字段设置为 NULL。 若要继续特定的堆枚举,请重复调用 HeapWalk 函数,不更改 hHeap、 lpEntry 或 PROCESS_HEAP_ENTRY 结构的任何成员。

heapWalk 函数 (heapapi.h) - Win32 apps | Microsoft Learnicon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/windows/win32/api/heapapi/nf-heapapi-heapwalk

for (size_t i = 0; i < dwNum; i++) { HANDLE h = pHeapHandle[i]; HeapLock(h); PROCESS_HEAP_ENTRY phe = { 0 }; while (HeapWalk(h, &phe)) { if ((phe.wFlags & PROCESS_HEAP_ENTRY_BUSY)) { printf("\t%s!\n", (char*)phe.lpData); } } HeapUnlock(h); }

我们这里就简单地只统计PROCESS_HEAP_ENTRY_BUSY标记的堆内存,这些是已经分配的堆内存。

将所有busy的  PROCESS_HEAP_ENTRY 保存下来,就可以做进一步统计分析了,这个以后再说。这里我们完成了堆内存的遍历。

三、遍历其他进程的堆列表

        上面的遍历接口是非常高效的,不过他只能获取本进程的堆列表,而使用ToolHelp32库,可以获取其他进程的资源,包括枚举进程,枚举线程,枚举模块,枚举内存列表。不过这个接口在枚举内存列表时非常低效。

CreateToolhelp32Snapshot 函数 (tlhelp32.h) - Win32 apps | Microsoft Learnicon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot

HANDLE CreateToolhelp32Snapshot( [in] DWORD dwFlags, [in] DWORD th32ProcessID );

这个接口很有意思,从接口名称便可以看出,它是获取和创建了系统/进程某一时刻某一些资源的快照,这个快照是只读的,你可以通过他获取系统中所有的进程信息,可以通过他获取某个进程中所有的线程,也可以通过他获取某个进程中所有的模块信息,包括我们这里使用他获取堆列表。

参数

[in] dwFlags

要包含在快照中的系统部分。 此参数可使用以下一个或多个值。

值含义

TH32CS_INHERIT

0x80000000

指示快照句柄是可继承的。

TH32CS_SNAPALL

包括系统中的所有进程和线程,以及 th32ProcessID 中指定的进程的堆和模块。 等效于指定使用 OR 操作 (“|”组合的TH32CS_SNAPHEAPLIST、TH32CS_SNAPMODULE、TH32CS_SNAPPROCESS和TH32CS_SNAPTHREAD值) 。

TH32CS_SNAPHEAPLIST

0x00000001

包括快照 th32ProcessID 中指定的进程的所有堆。 若要枚举堆,请参阅 Heap32ListFirst。

TH32CS_SNAPMODULE

0x00000008

包括快照 th32ProcessID 中指定的进程的所有模块。 若要枚举模块,请参阅 Module32First。 如果函数失败并 出现ERROR_BAD_LENGTH,请重试该函数,直到成功。

64 位 Windows: 在 32 位进程中使用此标志包括 th32ProcessID 中指定的进程的 32 位模块,而在 64 位进程中使用它包括 64 位模块。 若要从 64 位进程包括 th32ProcessID 中指定的进程的 32 位模块,请使用 TH32CS_SNAPMODULE32 标志。

TH32CS_SNAPMODULE32

0x00000010

从 64 位进程调用时,包括快照中 th32ProcessID 中指定的进程的所有 32 位模块。 此标志可以与 TH32CS_SNAPMODULE 或 TH32CS_SNAPALL结合使用。 如果函数失败并 出现ERROR_BAD_LENGTH,请重试该函数,直到成功。

TH32CS_SNAPPROCESS

0x00000002

包括系统中快照中的所有进程。 若要枚举进程,请参阅 Process32First。

TH32CS_SNAPTHREAD

0x00000004

包括快照系统中的所有线程。 若要枚举线程,请参阅 Thread32First。

若要标识属于特定进程的线程,请在枚举线程时将其进程标识符与 THREADENTRY32 结构的 th32OwnerProcessID 成员进行比较。

[in] th32ProcessID

要包含在快照中的进程的进程标识符。 此参数可以为零以指示当前进程。 指定 TH32CS_SNAPHEAPLIST、 TH32CS_SNAPMODULE、 TH32CS_SNAPMODULE32或 TH32CS_SNAPALL 值时,使用此参数。 否则,将忽略它,并且所有进程都包含在快照中。

如果指定的进程是空闲进程或 CSRSS 进程之一,则此函数将失败,并且最后一个错误代码 ERROR_ACCESS_DENIED ,因为它们的访问限制阻止用户级代码打开它们。

如果指定的进程是 64 位进程,调用方是 32 位进程,则此函数将失败,最后一个错误代码 ERROR_PARTIAL_COPY (299) 。

3.1、获取堆内存列表快照

使用TH32CS_SNAPHEAPLIST来指定抓取指定进程id的堆内存列表快照

HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPHEAPLIST, dwPID); if (INVALID_HANDLE_VALUE == hSnapShot) { printf("CreateToolhelp32Snapshot failed! error:%d\n", GetLastError()); return -1; } 3.2、遍历快照中的堆列表

从快照中遍历堆列表

// 遍历堆 HEAPLIST32 hplt = {sizeof(hplt)}; if (TRUE == Heap32ListFirst(hSnapShot, &hplt)) { do { printf("HeapId:%Id\n", hplt.th32HeapID); } while (TRUE == Heap32ListNext(hSnapShot, &hplt)); }

通过接口Heap32ListFirst和Heap32ListNext可以遍历所有的堆。

3.3、遍历指定堆中的内存块 

通过接口Heap32First和Heap32Next,传入上面遍历的堆id,可以遍历堆中所有的内存块。

// 遍历堆中的内存块 HEAPENTRY32 hp = { sizeof(hp) }; if (TRUE == Heap32First(&hp, hplt.th32ProcessID, hplt.th32HeapID)) { do { if (!(hp.dwFlags & LF32_FREE)) { // printf("\t%Id\n", hp.dwAddress); } } while (TRUE == Heap32Next(&hp)); } 3.4、完整函数  HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPHEAPLIST, dwPID); if (INVALID_HANDLE_VALUE == hSnapShot) { printf("CreateToolhelp32Snapshot failed! error:%d\n", GetLastError()); return -1; } // 遍历堆 HEAPLIST32 hplt = {sizeof(hplt)}; if (TRUE == Heap32ListFirst(hSnapShot, &hplt)) { do { printf("HeapId:%Id\n", hplt.th32HeapID); // 遍历堆中的内存块 HEAPENTRY32 hp = { sizeof(hp) }; if (TRUE == Heap32First(&hp, hplt.th32ProcessID, hplt.th32HeapID)) { do { if (!(hp.dwFlags & LF32_FREE)) { // TODO // printf("\t%Id\n", hp.dwAddress); } } while (TRUE == Heap32Next(&hp)); } } while (TRUE == Heap32ListNext(hSnapShot, &hplt)); } 四、总结

正如官网所说,HeapWalk性能远高于快照中的Heap32Next接口,实际测试第二种方式也非常拉跨。

使用 HeapWalk 函数行走堆的大小大致是线性的,而使用 Heap32Next 函数遍走堆的大小大致为二次。 即使对于具有 10,000 个分配的适度堆, HeapWalk 的运行速度也比 Heap32Next 快 10,000 倍,同时提供更详细的信息。 随着堆大小的增加,性能差异变得更加明显。

遍历堆列表 - Win32 apps | Microsoft Learnicon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/windows/win32/toolhelp/traversing-the-heap-list



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3