第十四章 探索虚拟内存
本章内容
14.1 系统信息
14.2 虚拟内存状态
14.3 NUMA机器中的内存管理
14.4 确定地址空间的状态
本章通过分析一些windows函数来了解与系统内存管理和进程中虚拟地址空间相关的信息。
14.1 系统信息
操作系统有许多值是由运行主机决定的。如页面大小和分配粒度。不应该将这些参数写死(硬编码到代码中)而是应该在进程初始化时取得这些值,然后在代码中使用。
以下函数取得与主机相关的值。
WINBASEAPI
VOID
WINAPI
GetSystemInfo(
_Out_ LPSYSTEM_INFO lpSystemInfo
);
我们需要传递SYSTEM_INFO结构的地址给这个函数。
typedef struct _SYSTEM_INFO {
union {
DWORD dwOemId; // Obsolete field...do not use
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;
如果想要得到机器中与处理器有关的详细信息,可以调用GetLogicalProcessorInformation函数。如下面代码所示:
void ShowProcessors() {
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION pBuffer = NULL;
DWORD dwSize = 0;
DWORD procCoreCount;
BOOL bResult = GetLogicalProcessorInformation(pBuffer, &dwSize);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
_tprintf(TEXT("Impossible to get processor information\n"));
return;
}
pBuffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)malloc(dwSize);
bResult = GetLogicalProcessorInformation(pBuffer, &dwSize);
if (!bResult) {
free(pBuffer);
_tprintf(TEXT("Impossible to get processor information\n"));
return;
}
procCoreCount = 0;
DWORD lpiCount = dwSize / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
for (DWORD current = 0; current < lpiCount; current++) {
if (pBuffer[current].Relationship == RelationProcessorCore) {
if (pBuffer[current].ProcessorCore.Flags == 1) {
_tprintf(TEXT(" + one CPU core (HyperThreading)\n"));
}
else {
_tprintf(TEXT(" + one CPU socket\n"));
}
procCoreCount++;
}
}
_tprintf(TEXT(" -> %d active CPU(s)\n"), procCoreCount);
free(pBuffer);
}
运行结果
为了让32位应用在64位版本的windows上运行。Microsoft提供一个Windows 32-bit On Windows 64-bit的模拟层。也称为WOW64.当32位应用通过WOW64运行时,GetSystemInfo返回值和他在64位应用程序中所取得的值可能会有不同。例如在32位下SYSTEM_INFO结构的dwPageSize字段值是4KB而在64位下是8KB
如果想知道是否在WOW64上运行,可以调用下面函数。
WINBASEAPI
BOOL
WINAPI
IsWow64Process(
_In_ HANDLE hProcess,
_Out_ PBOOL Wow64Process
);
第一个参数是要查看的目标进程句柄。
第二个参数Wow64Process指向返回值,若运行在WOW64模式下返回TRUE。
也可以调用IsOs并传递OS_WOW6432来判断。返回TRUE 则运行在WOW64下。
系统信息示例程序
代码如下:
/******************************************************************************
Module: SysInfo.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/
#include "..\CommonFiles\CmnHdr.h"
#include <windowsx.h>
#include <tchar.h>
#include <stdio.h>
#include <StrSafe.h>
#include "Resource.h"
//
// This function accepts a number and converts it to a
// string, inserting commas where appropriate.
PTSTR BigNumToString(LONG lNum, PTSTR szBuf, DWORD chBufSize) {
TCHAR szNum[100];
StringCchPrintf(szNum, _countof(szNum), TEXT("%d"), lNum);
NUMBERFMT nf;
nf.NumDigits = 0;
nf.LeadingZero = FALSE;
nf.Grouping = 3;
nf.lpDecimalSep = TEXT(".");
nf.lpThousandSep = TEXT(",");
nf.NegativeOrder = 0;
GetNumberFormat(LOCALE_USER_DEFAULT, 0, szNum, &nf, szBuf, chBufSize);
return szBuf;
}
//
void ShowCPUInfo(HWND hWnd, WORD wProcessorArchitecture, WORD wProcessorLevel,
WORD wProcessorRevision) {
TCHAR szCPUArch[64] = TEXT("(unknown)");
TCHAR szCPULevel[64] = TEXT("(unknown)");
TCHAR szCPURev[64] = TEXT("(unknown)");
switch (wProcessorArchitecture) {
// Notice that AMD processors are seen as PROCESSOR_ARCHITECTURE_INTEL.
// In the Registry, the content of the "VendorIdentifier" key under
// HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0
// is either "GenuineIntel" or "AuthenticAMD"
//
// Read http://download.intel.com/design/Xeon/applnots/24161831.pdf
// for Model numeric codes.
// http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/20734.pdf
// should be used for AMD processors Model numeric codes.
//
case PROCESSOR_ARCHITECTURE_INTEL:
_tcscpy_s(szCPUArch, _countof(szCPUArch), TEXT("Intel"));
switch (wProcessorLevel) {
case 3: case 4:
StringCchPrintf(szCPULevel, _countof(szCPULevel), TEXT("80%c86"), wProcessorLevel + '0');
StringCchPrintf(szCPURev, _countof(szCPURev), TEXT("%c%d"),
HIBYTE(wProcessorRevision) + TEXT('A'),
LOBYTE(wProcessorRevision));
break;
case 5:
_tcscpy_s(szCPULevel, _countof(szCPULevel), TEXT("Pentium"));
StringCchPrintf(szCPURev, _countof(szCPURev), TEXT("Model %d, Stepping %d"),
HIBYTE(wProcessorRevision), LOBYTE(wProcessorRevision));
break;
case 6:
switch (HIBYTE(wProcessorRevision)) { // Model
case 1:
_tcscpy_s(szCPULevel, _countof(szCPULevel),
TEXT("Pentium Pro"));
break;
case 3:
case 5:
_tcscpy_s(szCPULevel, _countof(szCPULevel),
TEXT("Pentium II"));
break;
case 6:
_tcscpy_s(szCPULevel, _countof(szCPULevel),
TEXT("Celeron"));
break;
case 7:
case 8:
case 11:
_tcscpy_s(szCPULevel, _countof(szCPULevel),
TEXT("Pentium III"));
break;
case 9:
case 13:
_tcscpy_s(szCPULevel, _countof(szCPULevel),
TEXT("Pentium M"));
break;
case 10:
_tcscpy_s(szCPULevel, _countof(szCPULevel),
TEXT("Pentium Xeon"));
break;
case 15:
_tcscpy_s(szCPULevel, _countof(szCPULevel),
TEXT("Core 2 Duo"));
break;
default:
_tcscpy_s(szCPULevel, _countof(szCPULevel),
TEXT("Unknown Pentium"));
break;
}
StringCchPrintf(szCPURev, _countof(szCPURev), TEXT("Model %d, Stepping %d"),
HIBYTE(wProcessorRevision), LOBYTE(wProcessorRevision));
break;
case 15:
_tcscpy_s(szCPULevel, _countof(szCPULevel), TEXT("Pentium 4"));
StringCchPrintf(szCPURev, _countof(szCPURev), TEXT("Model %d, Stepping %d"),
HIBYTE(wProcessorRevision), LOBYTE(wProcessorRevision));
break;
}
break;
case PROCESSOR_ARCHITECTURE_IA64:
_tcscpy_s(szCPUArch, _countof(szCPUArch), TEXT("IA-64"));
StringCchPrintf(szCPULevel, _countof(szCPULevel), TEXT("%d"), wProcessorLevel);
StringCchPrintf(szCPURev, _countof(szCPURev), TEXT("Model %c, Pass %d"),
HIBYTE(wProcessorRevision) + TEXT('A'),
LOBYTE(wProcessorRevision));
break;
case PROCESSOR_ARCHITECTURE_AMD64:
_tcscpy_s(szCPUArch, _countof(szCPUArch), TEXT("AMD64"));
StringCchPrintf(szCPULevel, _countof(szCPULevel), TEXT("%d"), wProcessorLevel);
StringCchPrintf(szCPURev, _countof(szCPURev), TEXT("Model %c, Pass %d"),
HIBYTE(wProcessorRevision) + TEXT('A'),
LOBYTE(wProcessorRevision));
break;
case PROCESSOR_ARCHITECTURE_UNKNOWN:
default:
_tcscpy_s(szCPUArch, _countof(szCPUArch), TEXT("Unknown"));
break;
}
SetDlgItemText(hWnd, IDC_PROCARCH, szCPUArch);
SetDlgItemText(hWnd, IDC_PROCLEVEL, szCPULevel);
SetDlgItemText(hWnd, IDC_PROCREV, szCPURev);
}
void ShowBitness(HWND hWnd) {
TCHAR szFullTitle[100];
TCHAR szTitle[32];
GetWindowText(hWnd, szTitle, _countof(szFullTitle));
#if defined(_WIN64)
// 64-bit applications can only run on 64-bit Windows,
// so there is nothing special to check except the
// _WIN64 symbol set by the compiler.
StringCchPrintf(szFullTitle, _countof(szFullTitle),
TEXT("64-bit %s"), szTitle);
#else
BOOL bIsWow64 = FALSE;
if (!IsWow64Process(GetCurrentProcess(), &bIsWow64)) {
chFAIL("Failed to get WOW64 state.");
return;
}
if (bIsWow64) {
StringCchPrintf(szFullTitle, _countof(szFullTitle),
TEXT("32-bit %s on WOW64"), szTitle);
}
else {
StringCchPrintf(szFullTitle, _countof(szFullTitle),
TEXT("32-bit %s on 32-bit Windows"), szTitle);
}
#endif
SetWindowText(hWnd, szFullTitle);
}
//
BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam) {
chSETDLGICONS(hWnd, IDI_SYSINFO);
SYSTEM_INFO sinf;
GetSystemInfo(&sinf);
ShowCPUInfo(hWnd, sinf.wProcessorArchitecture,
sinf.wProcessorLevel, sinf.wProcessorRevision);
TCHAR szBuf[50];
SetDlgItemText(hWnd, IDC_PAGESIZE,
BigNumToString(sinf.dwPageSize, szBuf, _countof(szBuf)));
StringCchPrintf(szBuf, _countof(szBuf), TEXT("%p"),
sinf.lpMinimumApplicationAddress);
SetDlgItemText(hWnd, IDC_MINAPPADDR, szBuf);
StringCchPrintf(szBuf, _countof(szBuf), TEXT("%p"),
sinf.lpMaximumApplicationAddress);
SetDlgItemText(hWnd, IDC_MAXAPPADDR, szBuf);
StringCchPrintf(szBuf, _countof(szBuf), TEXT("0x%016I64X"),
(__int64)sinf.dwActiveProcessorMask);
SetDlgItemText(hWnd, IDC_ACTIVEPROCMASK, szBuf);
SetDlgItemText(hWnd, IDC_NUMOFPROCS,
BigNumToString(sinf.dwNumberOfProcessors, szBuf, _countof(szBuf)));
SetDlgItemText(hWnd, IDC_ALLOCGRAN,
BigNumToString(sinf.dwAllocationGranularity, szBuf, _countof(szBuf)));
ShowBitness(hWnd);
return TRUE;
}
//
void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify) {
switch (id) {
case IDCANCEL:
EndDialog(hWnd, id);
break;
}
}
//
INT_PTR WINAPI Dlg_Proc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
chHANDLE_DLGMSG(hDlg, WM_INITDIALOG, Dlg_OnInitDialog);
chHANDLE_DLGMSG(hDlg, WM_COMMAND, Dlg_OnCommand);
}
return FALSE;
}
//
int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR, int) {
DialogBox(hInstExe, MAKEINTRESOURCE(IDD_SYSINFO), NULL, Dlg_Proc);
return 0;
}
End of File //
14.2 虚拟内存状态
GlobalMemoryStatus可以用来取得当前内存状态的动态信息:
WINBASEAPI
VOID
WINAPI
GlobalMemoryStatus(
_Out_ LPMEMORYSTATUS lpBuffer
);
(作者认为应该叫VirtualMemoryStatus)
调用此函数需要传递一个MEMORYSTATUS的结构
typedef struct _MEMORYSTATUS {
DWORD dwLength;
DWORD dwMemoryLoad;
SIZE_T dwTotalPhys;
SIZE_T dwAvailPhys;
SIZE_T dwTotalPageFile;
SIZE_T dwAvailPageFile;
SIZE_T dwTotalVirtual;
SIZE_T dwAvailVirtual;
} MEMORYSTATUS, *LPMEMORYSTATUS;
调用之前必须初始化dwLength成员设置为MEMORYSTATUS结构的大小。(为了后续添加新的成员)
GlobalMemoryStatus会对其他成员进行初始化,然后返回。
如果预计程序会在装有4GB内存以上的机器运行,或者页交换文件大于4GB ,就应调用GlobalMemoryStatusEx
WINBASEAPI
BOOL
WINAPI
GlobalMemoryStatusEx(
_Out_ LPMEMORYSTATUSEX lpBuffer
);
并传递一个MEMORYSTATUSEX结构
typedef struct _MEMORYSTATUSEX {
DWORD dwLength;
DWORD dwMemoryLoad;
DWORDLONG ullTotalPhys;
DWORDLONG ullAvailPhys;
DWORDLONG ullTotalPageFile;
DWORDLONG ullAvailPageFile;
DWORDLONG ullTotalVirtual;
DWORDLONG ullAvailVirtual;
DWORDLONG ullAvailExtendedVirtual;
} MEMORYSTATUSEX, *LPMEMORYSTATUSEX;
这个结构就是加你个成员的大小换成了64位,因此能容纳大于4GB的值
ullAvailExtendedVirtual 指在当前进程的虚拟地址空间中尚未被预定的那一大块内存地址空间的大小。只对特定配置中的特定cpu体系结构才有意义。
14.3 NUMA机器中的内存管理
NUMA(Non-uniform Memory Access非统一内存访问)体系中,cpu既能访问自己节点的内存也能访问其他节点的内存。通常为了提高性能,尽量会使用自己节点的内存来支持物理存储器以提高内存访问的性能,但是如果没有足够的内存,windows也会使用外节点的内存来支持物理存储器。
GlobalMemoryStatusEx函数返回的结构中,ullAvailPhys参数返回的是所有节点可用内存的总量。如果想知道特定NUMA节点的内存数量,调用一下函数:
WINBASEAPI
BOOL
WINAPI
GetNumaAvailableMemoryNode(
_In_ UCHAR Node,
_Out_ PULONGLONG AvailableBytes
);
Node标识节点
AvailableBytes指向LONGLONG变量用来返回该节点的内存数量。
调用一下函数获得某个CPU驻留在哪个NUMA节点中。
WINBASEAPI
BOOL
WINAPI
GetNumaProcessorNode(
_In_ UCHAR Processor,
_Out_ PUCHAR NodeNumber
);
通过以下函数获得系统中的节点总数
WINBASEAPI
BOOL
WINAPI
GetNumaHighestNodeNumber(
_Out_ PULONG HighestNodeNumber
);
对于任何节点来说, 它的值介于0~HighestNodeNumber之间。
调用一下函数来获得驻留在某个节点中的CPU列表
WINBASEAPI
BOOL
WINAPI
GetNumaNodeProcessorMask(
_In_ UCHAR Node,
_Out_ PULONGLONG ProcessorMask
);
Node是节点标识符, ProcessorMask用来返回掩码。如果某一位被设置,那么该位对应的CPU就属于该节点。
Windows提供了一些函数,供开发人员手工控制线程和内存关联(memory affinity)
想进一步了解NUMA可以查阅MSDN
示例程序:虚拟内存状态
笔者自己编译了一个64位版本的并使用了GlobalMemoryStatusEx来获取数据,64位下的虚拟地址空间高达8TB。
VMStat应用程序会显示调用GlobalMemoryStatus函数的结构。
dwMemoryLoad成员表明内存管理系统有多忙,(0~100)仅供参考
dwTotalPhys成员 物理内存的总量以字节为单位。 不过其值总是会少于真实的物理内存,因为系统在启动过程会为非页面缓冲池保留一部分内存。甚至连内核也无法使用这部分内存。
dwAvailPhys 当前可用内存(RAM)总量
dwTotalPageFile 显示硬盘上的交换文件最多能存放多少字节的数据。当前值仅供参考,系统会根据需要增减页交换文件。
dwAvailPageFile 表明在页面文件中尚有多少空间为被调拨给进程使用。
dwTotalVirtual成员 表示地址空间中为各进程私有的那部分的字节数。 通常比2GB 少了128KB 因为减去了空指针区(0x00000000~0x0000FFFF)和64KB禁入分区(0x7FFF0000~0x7FFFFFFF)
dwAvailVirtual 是该数据结构中唯一与当前进程有关的成员,所有其他成员适用整个系统。GlobalMemoryStatus会把调用进程地址空间中所有闲置的区域都加起来。
如果用dwTotalVirtual 减去 dwAvailVirtual 可以得到当前进程总共在地址空间中预定了多少内存。
没有哪个成员能表示当前进程正在使用的物理存储器的数量。把一个进程地址空间中被保存在内存(RAM)里的那些页面称为它的工作集(Working Set)
以下函数可以获得当前进程工作集大小和最大工作集大小,定义于psapi.h中:
BOOL
WINAPI
GetProcessMemoryInfo(
HANDLE Process,
PPROCESS_MEMORY_COUNTERS ppsmemCounters,
DWORD cb
);
hProcess是想要访问的进程句柄,(必须具备 PROCESS_QUERY_INFORMATION 和 PROCESS_VM_READ访问权限)例如GetCurrentProcess
ppsmemCounters是一个指向PROCESS_MEMORY_COUNTERS的结构。结构大小通过cbSize来指定。如果函数返回TRUE,下面结构会包含于指定进程有关的详细信息。
typedef struct _PROCESS_MEMORY_COUNTERS_EX {
DWORD cb;
DWORD PageFaultCount;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
SIZE_T QuotaPeakPagedPoolUsage;
SIZE_T QuotaPagedPoolUsage;
SIZE_T QuotaPeakNonPagedPoolUsage;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
SIZE_T PrivateUsage;
} PROCESS_MEMORY_COUNTERS_EX;
typedef PROCESS_MEMORY_COUNTERS_EX *PPROCESS_MEMORY_COUNTERS_EX;
WorkingSetSize字段包含了hProcess所表示的进程正在使用的工作集(内存)字节数。PeakWorkingSetSize包含了进程开始运行以来使用过的最大内存数量。
PrivateUsage字段:在对应用程序性能进行调整时,除了工作集之外,还应该知道应用程序通过new, malloc或者VirtualAlloc函数显示分配了多少内存。
如果希望进一步了解现有可以用来监视操作系统和进程内存的API,参阅Memory Performance Information
https://msdn.microsoft.com/en-us/library/aa965225.aspx
14.4 确定地址空间的状态
VirtualQuery 函数可以用于查询地址空间中的内存地址有关的特定信息(比如大小,存储器类型以及保护属性)。
WINBASEAPI
SIZE_T
WINAPI
VirtualQuery(
_In_opt_ LPCVOID lpAddress,
_Out_writes_bytes_to_(dwLength, return) PMEMORY_BASIC_INFORMATION lpBuffer,
_In_ SIZE_T dwLength
);
还有另一个函数允许一个进程来查询另外一个进程的内存信息。
WINBASEAPI
SIZE_T
WINAPI
VirtualQueryEx(
_In_ HANDLE hProcess,
_In_opt_ LPCVOID lpAddress,
_Out_writes_bytes_to_(dwLength, return) PMEMORY_BASIC_INFORMATION lpBuffer,
_In_ SIZE_T dwLength
);
传递一个进程的句柄,以此来查询哪个进程的地址空间。(通常用于调试器和一些工具软件)
lpAddress 要查询的虚拟地址
pmbi必须由调用者创建的MEMORY_BASIC_INFORMATION结构
typedef struct _MEMORY_BASIC_INFORMATION {
PVOID BaseAddress;
PVOID AllocationBase;
DWORD AllocationProtect;
SIZE_T RegionSize;
DWORD State;
DWORD Protect;
DWORD Type;
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
最后一个参数dwLength用来指定MEMORY_BASIC_INFORMATION结构的大小。 VirtualQuery(Ex)的返回值是其复制到缓存中的字节数。
关于VirtualQuery在MEMORY_BASIC_INFORMATION结构中填入的信息参考如下:
14.4.1 VMQuery函数
由于系统提供的VirtualQuery具有一定的局限性,不能知道已预订区域的大小,或者某个区域中块的数量,或者某个区域是否包含线程栈,等等。
作者自己创建了VMQuery函数以及一个VMQUERY结构体
typedef struct {
// Region information
PVOID pvRgnBaseAddress;
DWORD dwRgnProtection; // PAGE_*
SIZE_T RngSize;
DWORD dwRgnStorage; // MEM_*: Free, Image, Mapped, Private
DWORD dwTgnBlocks;
DWORD dwRgnGuardBlks; // If > 0, region contains thread stack
BOOL bRgnIsAStack; // TRUE if region contains thread stack
// Block information
PVOID pvBlkBaseAddress;
DWORD dwBlkProtection; // PAGE_*
SIZE_T BlkSize;
DWORD dwBlkStorage; // MEM_*: Free, Image, Mapped, Private
} VMQUERY, *PVMQUERY;
BOOL VMQuery(
HANDLE hProcess,
LPCVOID pvAddress,
PVMQUERY pVMQ);
VMQuery.cpp
/******************************************************************************
Module: VMQuery.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/
#include "..\CommonFiles\CmnHdr.h"
#include <windowsx.h>
#include "VMQuery.h"
//
// Helper structure
typedef struct {
SIZE_T RgnSize;
DWORD dwRgnStorage; // MEM_*: Free, Image, Mapped, Private
DWORD dwRgnBlocks;
DWORD dwRgnGuardBlks; // If > 0, region contains thread stack
BOOL bRgnIsAStack; // TRUE if region contains thread stack
} VMQUERY_HELP;
// This global, static variable holds the allocation granularity value for
// this CPU platform. Initialized the first time VMQuery is called.
static DWORD gs_dwAllocGran = 0;
//
// Iterates through a region's blocks and returns finding in VMQUERY_HELP
static BOOL VMQueryHelp(HANDLE hProcess, LPCVOID pvAddress,
VMQUERY_HELP *pVMQHelp) {
ZeroMemory(pVMQHelp, sizeof(*pVMQHelp));
// Get address of region containing passed memory address.
MEMORY_BASIC_INFORMATION mbi;
BOOL bOk = (VirtualQueryEx(hProcess, pvAddress, &mbi, sizeof(mbi))
== sizeof(mbi));
if (!bOk)
return bOk; // Bad memory address, return failure
// Walk starting at the region's base address (which never changes)
PVOID pvRgnBaseAddress = mbi.AllocationBase;
// Walk starting at the first block in the region (changes in the loop)
PVOID pvAddressBlk = pvRgnBaseAddress;
// Save the memory type of the physical storage block.
pVMQHelp->dwRgnStorage = mbi.Type;
for (;;) {
// Get info about the current block.
bOk = (VirtualQueryEx(hProcess, pvAddressBlk, &mbi, sizeof(mbi))
== sizeof(mbi));
if (!bOk)
break; // Couldn't get the information; end loop.
// Is this block in the same region?
if (mbi.AllocationBase != pvRgnBaseAddress)
break; // Found a block in the next region; end loop.
// We have a block contained in the region.
pVMQHelp->dwRgnBlocks++; // Add another block to the region
pVMQHelp->RgnSize += mbi.RegionSize; // Add block's size to region size
// If block has PAGE_GUARD attribute, add 1 to this counter
if ((mbi.Protect & PAGE_GUARD) == PAGE_GUARD)
pVMQHelp->dwRgnGuardBlks++;
// Take a guess as to the type of physical storage committed to the
// block. This is a guess because some blocks can convert from MEM_IMAGE
// to MEM_PRIVATE or from MEM_MAPPED to MEM_PRIVATE; MEM_PRIVATE can
// always be overridden by MEM_IMAGE or MEM_MAPPED.
if (pVMQHelp->dwRgnStorage == MEM_PRIVATE)
pVMQHelp->dwRgnStorage = mbi.Type;
// Get the address of the next block.
pvAddressBlk = (PVOID)((PBYTE)pvAddressBlk + mbi.RegionSize);
}
// After examing the region, check to see whether it is a thread stack
// Windows Vista: Assume stack if region has at least 1 PAGE_GUARD block
pVMQHelp->bRgnIsAStack = (pVMQHelp->dwRgnGuardBlks > 0);
return TRUE;
}
//
BOOL VMQuery(HANDLE hProcess, LPCVOID pvAddress, PVMQUERY pVMQ) {
if (gs_dwAllocGran = 0) {
// Set allocation granularity if this is the first call
SYSTEM_INFO sinf;
GetSystemInfo(&sinf);
gs_dwAllocGran = sinf.dwAllocationGranularity;
}
ZeroMemory(pVMQ, sizeof(*pVMQ));
// Get the MEMORY_BASIC_INFORMATION for the passed address.
MEMORY_BASIC_INFORMATION mbi;
BOOL bOk = (VirtualQueryEx(hProcess, pvAddress, &mbi, sizeof(mbi))
== sizeof(mbi));
if (!bOk)
return bOk; // Bad memory address; return failure
// The MEMORY_BASIC_INFORMATION structure contains valid information.
// Time to start setting the members of our own VMQUERY structure.
// First, fill in the block members. We'll fill the region members later.
switch (mbi.State) {
case MEM_FREE: // Free block (not reserved)
pVMQ->pvBlkBaseAddress = NULL;
pVMQ->BlkSize = 0;
pVMQ->dwBlkProtection = 0;
pVMQ->dwBlkStorage = MEM_FREE;
break;
case MEM_RESERVE: // Reserved block without committed storage in it.
pVMQ->pvBlkBaseAddress = mbi.BaseAddress;
pVMQ->BlkSize = mbi.RegionSize;
// For an uncommitted block, mbi.Protect is invalid. So we will
// show that the reserved block inherits the protection attribute
// of the region in which it is contained.
pVMQ->dwBlkProtection = mbi.AllocationProtect;
pVMQ->dwBlkStorage = MEM_RESERVE;
break;
case MEM_COMMIT: // Reserve block with committed storage in it.
pVMQ->pvBlkBaseAddress = mbi.BaseAddress;
pVMQ->BlkSize = mbi.RegionSize;
pVMQ->dwBlkProtection = mbi.Protect;
pVMQ->dwBlkStorage = mbi.Type;
break;
default:
DebugBreak();
break;
}
// Now fill in the region data members.
VMQUERY_HELP VMQHelp;
switch (mbi.State) {
case MEM_FREE: // Free block (not reserved)
pVMQ->pvRgnBaseAddress = mbi.BaseAddress;
pVMQ->dwRgnProtection = mbi.AllocationProtect;
pVMQ->RgnSize = mbi.RegionSize;
pVMQ->dwRgnStorage = MEM_FREE;
pVMQ->dwRgnBlocks = 0;
pVMQ->dwRgnGuardBlks = 0;
pVMQ->bRgnIsAStack = FALSE;
break;
case MEM_RESERVE: // Reserved block without committed storage in it.
pVMQ->pvRgnBaseAddress = mbi.AllocationBase;
pVMQ->dwRgnProtection = mbi.AllocationProtect;
// Iterate through all blocks to get complete region information.
VMQueryHelp(hProcess, pvAddress, &VMQHelp);
pVMQ->RgnSize = VMQHelp.RgnSize;
pVMQ->dwRgnStorage = VMQHelp.dwRgnStorage;
pVMQ->dwRgnBlocks = VMQHelp.dwRgnBlocks;
pVMQ->dwRgnGuardBlks = VMQHelp.dwRgnGuardBlks;
pVMQ->bRgnIsAStack = VMQHelp.bRgnIsAStack;
break;
case MEM_COMMIT: // Reserved block with committed storage in it.
pVMQ->pvRgnBaseAddress = mbi.AllocationBase;
pVMQ->dwRgnProtection = mbi.AllocationProtect;
// Iterate through all blocks to get complete region information.
VMQueryHelp(hProcess, pvAddress, &VMQHelp);
pVMQ->RgnSize = VMQHelp.RgnSize;
pVMQ->dwRgnStorage = VMQHelp.dwRgnStorage;
pVMQ->dwRgnBlocks = VMQHelp.dwRgnBlocks;
pVMQ->dwRgnGuardBlks = VMQHelp.dwRgnGuardBlks;
pVMQ->bRgnIsAStack = VMQHelp.bRgnIsAStack;
break;
default:
DebugBreak();
break;
}
return bOk;
}
End of File //
14.4.2 示例程序:虚拟内存映射
/******************************************************************************
Module: VMMap.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/
#include "..\CommonFiles\CmnHdr.h"
#include "..\CommonFiles\Toolhelp.h"
#include <psapi.h>
#include <windowsx.h>
#include <tchar.h>
#include <strsafe.h>
#include "VMQuery.h"
#include "Resource.h"
// Look for the .lib corresponding to psapi.dll
#pragma comment(lib, "psapi")
//
DWORD g_dwProcessId = 0; // Which process to walk?
BOOL g_bExpandRegions = FALSE;
CToolhelp g_toolhelp;
//
// I use this function to obtain the dump figures in the book.
void CopyControlToClipboard(HWND hWnd) {
TCHAR szClipData[128 * 1024] = { 0 };
int nCount = ListBox_GetCount(hWnd);
for (int nNum = 0; nNum < nCount; nNum++) {
TCHAR szLine[1000];
ListBox_GetText(hWnd, nNum, szLine);
_tcscat_s(szClipData, _countof(szClipData), szLine);
_tcscat_s(szClipData, _countof(szClipData), TEXT("\r\n"));
}
OpenClipboard(NULL);
EmptyClipboard();
// Clipboard accepts only data that is in a block allocated
// with GlobalAlloc using the GMEM_MOVEABLE and GMEM_DDESHARE flags.
HGLOBAL hClipData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
sizeof(TCHAR) * (_tcslen(szClipData) + 1));
PTSTR pClipData = (PTSTR)GlobalLock(hClipData);
_tcscpy_s(pClipData, _tcslen(szClipData) + 1, szClipData);
#ifdef UNICODE
BOOL bOk = (SetClipboardData(CF_UNICODETEXT, hClipData) == hClipData);
#else
BOOL bOk = (SetClipboardData(CF_TEXT, hClipData) == hClipData);
#endif
CloseClipboard();
if (!bOk) {
GlobalFree(hClipData);
chMB("Error putting text on the clipboard");
}
}
//
PCTSTR GetMemStorageText(DWORD dwStorage) {
PCTSTR p = TEXT("Unknown");
switch (dwStorage) {
case MEM_FREE: p = TEXT("Free "); break;
case MEM_RESERVE: p = TEXT("Reserve"); break;
case MEM_IMAGE: p = TEXT("Image "); break;
case MEM_MAPPED: p = TEXT("Mapped "); break;
case MEM_PRIVATE: p = TEXT("Private"); break;
}
return(p);
}
//
PTSTR GetProtectText(DWORD dwProtect, PTSTR szBuf, size_t chSize,
BOOL bShowFlags) {
PCTSTR p = TEXT("Unknown");
switch (dwProtect & ~(PAGE_GUARD | PAGE_NOCACHE | PAGE_WRITECOMBINE)) {
case PAGE_READONLY: p = TEXT("-R--"); break;
case PAGE_READWRITE: p = TEXT("-RW-"); break;
case PAGE_WRITECOPY: p = TEXT("-RWC"); break;
case PAGE_EXECUTE: p = TEXT("E---"); break;
case PAGE_EXECUTE_READ: p = TEXT("ER--"); break;
case PAGE_EXECUTE_READWRITE: p = TEXT("ERW-"); break;
case PAGE_EXECUTE_WRITECOPY: p = TEXT("ERWC"); break;
case PAGE_NOACCESS: p = TEXT("----"); break;
}
_tcscpy_s(szBuf, chSize, p);
if (bShowFlags) {
_tcscat_s(szBuf, chSize, TEXT(" "));
_tcscat_s(szBuf, chSize, (dwProtect & PAGE_GUARD)
? TEXT("G") : TEXT("-"));
_tcscat_s(szBuf, chSize, (dwProtect & PAGE_NOCACHE)
? TEXT("N") : TEXT("-"));
_tcscat_s(szBuf, chSize, (dwProtect & PAGE_WRITECOMBINE)
? TEXT("W") : TEXT("-"));
}
return(szBuf);
}
///
void ConstructRgnInfoLine(HANDLE hProcess, PVMQUERY pVMQ,
PTSTR szLine, int cchMaxLen) {
StringCchPrintf(szLine, cchMaxLen, TEXT("%p\t%s\t%12u\t"),
pVMQ->pvRgnBaseAddress,
GetMemStorageText(pVMQ->dwRgnStorage),
pVMQ->RgnSize);
if (pVMQ->dwRgnStorage != MEM_FREE) {
StringCchPrintf(_tcschr(szLine, 0), cchMaxLen - _tcslen(szLine),
TEXT("%u\t"), pVMQ->dwRgnBlocks);
GetProtectText(pVMQ->dwRgnProtection, _tcschr(szLine, 0),
cchMaxLen - _tcslen(szLine), FALSE);
}
else {
_tcscat_s(szLine, cchMaxLen, TEXT("\t"));
}
_tcscat_s(szLine, cchMaxLen, TEXT("\t"));
// Try to obtain the module pathname for this region.
if ((pVMQ->dwRgnStorage != MEM_FREE) && (pVMQ->pvRgnBaseAddress != NULL)) {
MODULEENTRY32 me = { sizeof(me) };
if (g_toolhelp.ModuleFind(pVMQ->pvRgnBaseAddress, &me)) {
_tcscat_s(szLine, cchMaxLen, me.szExePath);
}
else {
int cchLen = _tcslen(szLine);
// This is not a module; see if it's a memory-mapped file
DWORD dwLen = GetMappedFileName(hProcess,
pVMQ->pvRgnBaseAddress, szLine + cchLen, cchMaxLen - cchLen);
if (dwLen == 0) {
// NOTE: GetMappedFileName modifies the string when it fails
szLine[cchLen] = 0;
}
}
}
if (pVMQ->bRgnIsAStack) {
_tcscat_s(szLine, cchMaxLen, TEXT("Thread Stack"));
}
}
//
void ConstructBlkInfoLine(PVMQUERY pVMQ, PTSTR szLine, int cchMaxLen) {
_stprintf_s(szLine, cchMaxLen, TEXT(" %p\t%s\t%12u\t"),
pVMQ->pvBlkBaseAddress,
GetMemStorageText(pVMQ->dwBlkStorage),
pVMQ->BlkSize);
if (pVMQ->dwBlkStorage != MEM_FREE) {
// add an empty cell for number of regions
_tcscat_s(szLine, cchMaxLen, TEXT("\t"));
GetProtectText(pVMQ->dwBlkProtection, _tcschr(szLine, 0),
cchMaxLen - _tcslen(szLine), TRUE);
}
else {
_tcscat_s(szLine, cchMaxLen, TEXT("\t"));
}
}
//
void Refresh(HWND hWndLB, DWORD dwProcessId, BOOL bExpandRegions) {
// Delete contents of list box & add a horizontal scroll bar
ListBox_ResetContent(hWndLB);
ListBox_SetHorizontalExtent(hWndLB, 300 * LOWORD(GetDialogBaseUnits()));
// Is the process still running?
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION,
FALSE, dwProcessId);
if (hProcess == NULL) {
ListBox_AddString(hWndLB, TEXT("")); // Blank line, looks better
ListBox_AddString(hWndLB,
TEXT(" The process ID identifies a process that is not running"));
return;
}
// Grab a new snapshot of the process
g_toolhelp.CreateSnapshot(TH32CS_SNAPALL, dwProcessId);
// Walk the virtual address space, adding entries to the list box.
BOOL bOk = TRUE;
PVOID pvAddress = NULL;
SetWindowRedraw(hWndLB, FALSE);
while (bOk) {
VMQUERY vmq;
bOk = VMQuery(hProcess, pvAddress, &vmq);
if (bOk) {
// Construct the line to be displayed, and add it to the list box.
TCHAR szLine[1024];
ConstructRgnInfoLine(hProcess, &vmq, szLine, _countof(szLine));
ListBox_AddString(hWndLB, szLine);
if (bExpandRegions) {
for (DWORD dwBlock = 0; bOk && (dwBlock < vmq.dwRgnBlocks);
dwBlock++) {
ConstructBlkInfoLine(&vmq, szLine, _countof(szLine));
ListBox_AddString(hWndLB, szLine);
// Get the address of the next region to test.
pvAddress = ((PBYTE)pvAddress + vmq.BlkSize);
if (dwBlock < vmq.dwRgnBlocks - 1) {
// Don't query the memory info after the last block.
bOk = VMQuery(hProcess, pvAddress, &vmq);
}
}
}
// Get the address of the next region to test.
pvAddress = ((PBYTE)vmq.pvRgnBaseAddress + vmq.RgnSize);
}
}
SetWindowRedraw(hWndLB, TRUE);
CloseHandle(hProcess);
}
//
BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam) {
chSETDLGICONS(hWnd, IDI_VMMAP);
// Show which process we're walking in the window's caption
TCHAR szCaption[MAX_PATH * 2];
GetWindowText(hWnd, szCaption, _countof(szCaption));
g_toolhelp.CreateSnapshot(TH32CS_SNAPALL, g_dwProcessId);
PROCESSENTRY32 pe = { sizeof(pe) };
StringCchPrintf(&szCaption[_tcslen(szCaption)],
_countof(szCaption) - _tcslen(szCaption), TEXT(" (PID=%u \"%s\")"),
g_dwProcessId, g_toolhelp.ProcessFind(g_dwProcessId, &pe) ?
pe.szExeFile : TEXT("unknown"));
SetWindowText(hWnd, szCaption);
// VMMap has so much info to show, let's maximize it by default
ShowWindow(hWnd, SW_MAXIMIZE);
// Set the columns width
int aTabs[5];
aTabs[0] = 48;
aTabs[1] = aTabs[0] + 40;
aTabs[2] = aTabs[1] + 52; // count
aTabs[3] = aTabs[2] + 12; // flags
aTabs[4] = aTabs[3] + 20; // description
ListBox_SetTabStops(GetDlgItem(hWnd, IDC_LISTBOX), _countof(aTabs), aTabs);
// Force the list box to refresh itself
Refresh(GetDlgItem(hWnd, IDC_LISTBOX), g_dwProcessId, g_bExpandRegions);
return TRUE;
}
//
void Dlg_OnSize(HWND hWnd, UINT state, int cx, int cy) {
// The list box alswys fills the whole client area
SetWindowPos(GetDlgItem(hWnd, IDC_LISTBOX), NULL, 0, 0, cx, cy,
SWP_NOZORDER);
}
//
void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify) {
switch (id) {
case IDCANCEL:
EndDialog(hWnd, id);
break;
case ID_REFRESH:
Refresh(GetDlgItem(hWnd, IDC_LISTBOX),
g_dwProcessId, g_bExpandRegions);
break;
case ID_EXPANDREGIONS:
g_bExpandRegions = g_bExpandRegions ? FALSE : TRUE;
Refresh(GetDlgItem(hWnd, IDC_LISTBOX),
g_dwProcessId, g_bExpandRegions);
break;
case ID_COPYTOCLIPBOARD:
CopyControlToClipboard(GetDlgItem(hWnd, IDC_LISTBOX));
break;
}
}
//
INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
chHANDLE_DLGMSG(hWnd, WM_INITDIALOG, Dlg_OnInitDialog);
chHANDLE_DLGMSG(hWnd, WM_COMMAND, Dlg_OnCommand);
chHANDLE_DLGMSG(hWnd, WM_SIZE, Dlg_OnSize);
}
return FALSE;
}
//
int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine, int) {
CToolhelp::EnablePrivilege(SE_DEBUG_NAME, TRUE);
g_dwProcessId = _ttoi(pszCmdLine);
if (g_dwProcessId == 0) {
g_dwProcessId = GetCurrentProcessId();
MessageBox(NULL,
TEXT("No Process Id passed on command line, Defaulting to this process"),
TEXT("VMMap"), MB_ICONINFORMATION);
}
DialogBox(hInstExe, MAKEINTRESOURCE(IDD_VMMAP), NULL, Dlg_Proc);
return 0;
}
End of File //