实现 Windows 应用线上虚拟内存监控

您所在的位置:网站首页 vscode权限不够 实现 Windows 应用线上虚拟内存监控

实现 Windows 应用线上虚拟内存监控

2023-04-11 03:40| 来源: 网络整理| 查看: 265

背景在 Windows 内存管理知识总结 一文中,我提到了 Win32 程序由于虚拟内存不足导致的 OOM 问题,以及介绍了一些解决方案在 如何将 win32 程序虚拟内存扩展到 3GB? 一文中,我介绍了扩展虚拟内存的方案,在线下环境中,我们可以借助官方提供的工具 vmmap 查看具体内存布局 但是,线上环境中,如何验证程序是否应用了扩展方案?我们需要拿到线上具体的内存指标来证明本文将继续介绍,如何利用 Windows 相关 API 开发一个能够监控线上虚拟内存的工具虚拟内存线上监控方案1、直接使用 vmmap

可以看到 vmmap 提供了命令行 api,可以直接 dump 出一份 vmmap 文件,在应用中可以拿到文件后直接上传,然后再用 vmmap 打开分析,这个方案看起来天衣无缝,但是,

此方案有两个缺陷:

需要将 vmmap.exe 打包到应用中执行命令后,会弹出 vmmap 的 GUI(致命缺陷),并不是后台帮我们 dump 出一份文件,我们不希望用户能感知监控行为

所以,此方案无法使用

2、仿 vmmap 能力,实现自己的监控工具

此方案的成本相比 1 会大出很多,但好在经过一轮调研,有大佬已经实现了类似功能并开源twpol/vmmap,调研过程中,好像还看到有人吐槽 vmmap 作者 Mark Russinovich(微软 Azure CTO)比较吝啬,不想告诉大家 vmmap 是如何实现的(此处一个偷笑表情)

开源库是一个命令行工具,我们仍需要读懂源码,然后想办法集成到自己的应用中,由于我的应用是跑在 Windows 上的 Java 程序,所以还需要额外写 JNI 相关的代码

下面,我就详细介绍一下如何实现自己的监控工具

我们需要的指标是什么?

首先要想清楚我们到底需要什么指标?无论是 vmmap 还是开源库都提供了完整的虚拟内存布局信息,而我们其实只需要:

虚拟内存总大小,TotalSize当前可用的大小,FreeSize当前已用的大小,UsedSize当前不可用的内存大小(碎片),DisabledSize具体方案

流程其实不复杂,就是借助 Windows 提供的内存相关的数据结构和 API 来实现,具体看来分为如下步骤:

获取当前进程 id,CurrentProcessId通过 OpenProcess 获得当前进程 Handle打开 Debug 权限(如果没有这步,无法获得内存快照信息)遍历内存地址空间 获得 DisabledSize获取对应类型的内存信息 Disabled Memory Info Free Memory Info Used Memory Info关闭 Debug 权限(不要忘了这步)将程序打包成 .dll 供 Java 侧使用写 JNI 部分代码在 Java 中调用 .dll 获取 VirtualMemoryInfo

下面贴出关键部分代码,供大家参考:

核心数据结构// vm_query 是核心数据结构,定义了我们需要的指标 class vm_query { private: const DWORD _pid; unsigned long long _free = 0; unsigned long long _total_used = 0; unsigned long long _unusable = 0; const void collect_vm_result(); public: vm_query(); ~vm_query(); void enable_privilege(const tstring privilege_name); void disable_privilege(const tstring privilege_name); const unsigned long long get_free_size() { return _free; } const unsigned long long get_total_used_size() { return _total_used; } const unsigned long long get_unusable_size() { return _unusable; } }; 开启/关闭权限// 开启 Debug 权限 void vm_query::enable_privilege(const std::wstring privilege_name) { HANDLE h_token; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &h_token)) { return; } LUID luid; if (!LookupPrivilegeValue(NULL, privilege_name.c_str(), &luid)) { return; } TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(h_token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) { return; } if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { return; } } // 关闭 Debug 权限 void vm_query::disable_privilege(const std::wstring privilege_name) { HANDLE h_token; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &h_token)) { return; } LUID luid; if (!LookupPrivilegeValue(NULL, privilege_name.c_str(), &luid)) { return; } TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = 0; if (!AdjustTokenPrivileges(h_token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) { return; } if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { return; } } 遍历地址空间,并收集数据// Collect virtual memory allocations vm_info vm_info; { BOOL is_wow_64; IsWow64Process(h_process, &is_wow_64); SYSTEM_INFO system_info; GetNativeSystemInfo(&system_info); unsigned long long max_address = 0x100000000; //4GB+1 //后文会详细介绍此数据结构是什么 MEMORY_BASIC_INFORMATION mem_info = { 0 }; for (unsigned long long addr = 0; addr system_info.dwPageSize) { unsigned long long disable_end = ((addr + system_info.dwAllocationGranularity - 1) / system_info.dwAllocationGranularity) * system_info.dwAllocationGranularity; if (disable_end > addr) { memory_group group; group.process_disable_group(addr, disable_end - addr); vm_info.put_group(addr, group); addr = disable_end - mem_info.RegionSize; continue; } } if ((unsigned long long)mem_info.AllocationBase + mem_info.RegionSize > max_address) break; unsigned long long alloc_base = (unsigned long long)mem_info.AllocationBase; if (mem_info.State == MEM_FREE) { alloc_base = (unsigned long long)mem_info.BaseAddress; } // 将不同指标的内存分组存放 memory_group& group = vm_info.get_group(alloc_base); if (group.type() == VMGPT__LAST) { group = vm_info.get_group(alloc_base) = memory_group(&mem_info); } group.add_block(memory_block(&mem_info)); } } enum memory_group_type { VMGPT__FIRST = 0, VMGPT_TOTAL = VMGPT__FIRST, //已使用的 VMGPT_FREE, //可用的 VMGPT_UNUSABLE, //不可用的 VMGPT__LAST }; memory_block::memory_block(const PMEMORY_BASIC_INFORMATION p_info) { //MEM_COMMIT 表示【已提交的】 //MEM_RESERVED 表示【预留分配的】 //如果对上述内容有疑问,可以参考之前写过的【Windows 内存管理知识总结】 memory_block_type t = p_info->State == MEM_COMMIT ? VMBLT_COMMITTED : p_info->State == MEM_RESERVE ? VMBLT_RESERVED : VMBLT_FREE; for (int i = VMBLDT__FIRST; i BaseAddress; _data[VMBLDT_SIZE] = (unsigned long long) p_info->RegionSize; _type = t; } memory_block::memory_block(memory_block_type type, unsigned long long base, unsigned long long size) { for (int i = VMBLDT__FIRST; i

数据结构

MEMORY_BASIC_INFORMATION structure (winnt.h)

vitypedef struct _MEMORY_BASIC_INFORMATION { PVOID BaseAddress; // base address of the region of pages PVOID AllocationBase; DWORD AllocationProtect; // 内存保护权限(可执行、可读、可写) WORD PartitionId; SIZE_T RegionSize; // pages size in Bytes, from BaseAdress DWORD State; DWORD Protect; DWORD Type; } MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;

State,描述当前虚拟内存段的状态

StateMeaningMEM_COMMIT 0x1000Indicates committed pages for which physical storage has been allocated, either in memory or in the paging file on disk.MEM_FREE 0x10000Indicates free pages not accessible to the calling process and available to be allocated. For free pages, the information in the AllocationBase, AllocationProtect, Protect, and Type members is undefined.MEM_RESERVE 0x2000Indicates reserved pages where a range of the process's virtual address space is reserved without any physical storage being allocated. For reserved pages, the information in the Protect member is undefined.MEM_COMMIT,物理内存地址已经分配,无论数据是在内存中或是缓存在磁盘中MEM_FREE,还未分配的虚拟地址空间MEM_RESERVE,已经预留的虚拟内存地址空间,但还没有映射到物理内存中

Type,描述 region 中 pages 的类型

TypeMeaningMEM_IMAGE 0x1000000Indicates that the memory pages within the region are mapped into the view of an image section.MEM_MAPPED 0x40000Indicates that the memory pages within the region are mapped into the view of a section.MEM_PRIVATE 0x20000Indicates that the memory pages within the region are private (that is, not shared by other processes).MEM_IMAGE,镜像文件,一般指 DLLMEM_MAPPED,映射某个段,比如代码段、数据段等MEM_PRIVATE,进程私有的内存,比如堆和栈SYSTEM_INFO

SYSTEM_INFO structure (sysinfoapi.h)

Contains information about the current computer system. This includes the architecture and type of the processor, the number of processors in the system, the page size, and other such information.

typedef struct _SYSTEM_INFO { union { DWORD dwOemId; struct { WORD wProcessorArchitecture; WORD wReserved; } DUMMYSTRUCTNAME; } DUMMYUNIONNAME; DWORD dwPageSize; LPVOID lpMinimumApplicationAddress; LPVOID lpMaximumApplicationAddress; DWORD_PTR dwActiveProcessorMask; DWORD dwNumberOfProcessors; DWORD dwProcessorType; DWORD dwAllocationGranularity; WORD wProcessorLevel; WORD wProcessorRevision; } SYSTEM_INFO, *LPSYSTEM_INFO;

dwPageSize The page size and the granularity of page protection and commitment. This is the page size used by the VirtualAlloc function.

dwAllocationGranularity The granularity for the starting address at which virtual memory can be allocated. For more information, see VirtualAlloc.

ChatGPT 关于【Granularity】的解答: Granularity(粒度)是虚拟内存分配中重要的概念之一,它是指内存页面大小的单位。在 Windows 中,虚拟内存分配时,系统会把实际的物理内存空间分成大小相等的块,称为页面。这些页面的大小通常是2的幂次方,如4KB、8KB、16KB等。粒度越小,可以更精细地管理内存分配,但也会增加内存分配的开销;而粒度越大,则可以节省内存分配的开销,但可能会浪费部分内存空间。

在 Windows 中,虚拟内存的基本单位是虚拟页(Virtual Page),虚拟页与物理页(Physical Page)是一一对应的。当进程需要访问某个虚拟地址时,系统会根据该地址对应的虚拟页号,找到对应的物理页号,从而访问相应的物理内存。

在虚拟内存的分配和管理中,粒度的大小对系统性能和资源利用效率都有一定的影响。通常情况下,Windows 中的默认页面大小为4KB,这是一种比较折中的选择,能够平衡内存管理的效率和开销,同时也便于与其他操作系统进行交互。但在特定场景下,可能需要调整页面大小,以更好地适应应用程序的需求。

参考How do I get the information shown in vmmap programatically?Privilege Constants (Authorization)VirtualAllocVirtualQuery

本文使用 Zhihu On VSCode 创作并发布



【本文地址】


今日新闻


推荐新闻


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