Introduction

This article describes a tool I wrote to show, pictorially and dynamically, the consumption of virtual memory by a Windows process. 

Background 

Recently, I needed to investigate the way in which a Windows process was consuming Virtual Memory (VM).  I wanted to get a picture in my head of the available VM and how it was being allocated, freed and mapped by the process, and phenomena such as virtual memory fragmentation. 

I came across this tool by Charles Bailey: http://hashpling.org/asm/.   It works well and it helped, but I wanted more information about particular types of allocation (those corresponding to memory-mapped files and managed and unmanaged assemblies loaded by the process), and I wanted to be able to understand for myself what was going on 'under the hood'.  So I wrote my own tool.

Using the tool 

Simply run the executable.  It requires the .Net framework 4 Client profile, but otherwise needs no installation.

Select a running process from the dropdown list.  Alternatively, type the name of a process (e.g. "Excel"), and wait for it to start; Mnemonic will automatically scan the process for as long as it runs.

You'll see a screen such as this:

Screenshot_chrome.png

Note that the 'scale' at the right-hand side of the graph shows 3071 MB - approximately 3GB. This is because this screenshot was running on a 32-bit Windows machine with the /3GB switch, which extends the user-mode virtual address space to nearly 3GB.  On a standard 32-bit Windows environment this limit would be 2GB, whereas under 64-bit Windows it would be 4GB (because Chrome is a Win32 process). 

Note the large (approximately 1GB) yellow region to the right, which is reserved but not committed.  You'll see this if you run a Win32 process under 32-bit Windows with /3GB, or under 64-bit Windows, unless your process is marked as 'large address aware'.  If your process is marked with LARGEADDRESSAWARE, this region will be marked as Free (initially, at least); if not, Windows reserves it to prevent the process from accessing it.

Run your mouse over the graph to see details about the allocations, including address range and any module loaded (managed or unmanaged) at that range. 

Control-click the graph to save it as a file.  Specify the root name (and folder) for the files; a sequence number and the .png extension will be appended.  Simply click the graph to save subsequent snapshots; the sequence number will increment. 

Using the code  

The code consists of a reusable class for enumerating the contents of Virtual Memory: VirtualMemoryExplorer, and a simple front-end interface that allows the selection of a process and the graphical presentation of the information. 

Two 'enumeration' loops take place within VirtualMemoryExplorer.Scan that build lists of VM regions that are of interest, and their characteristics.

The first looks at VM allocations: 

// Use UInt32 so that we can cope with addresses above 2GB in a /3GB or "4GT" environment, or 64-bit Windows
UInt32 address = 0;
for (; ; address = (UInt32)m.BaseAddress + (UInt32)m.RegionSize)
{
    if (0 == VirtualQueryEx(processHandle, (UIntPtr)address, out m, (uint)Marshal.SizeOf(m)))
    {
        // Record the 'end' of the address scale
        // (Expect 2GB in the case of a Win32 process running under 32-bit Windows, but may be
        // extended to up to 3GB if the OS is configured for "4 GT tuning" with the /3GB switch
        // Expect 4GB in the case of a Win32 process running under 64-bit Windows)
        addressLimit = address;
        break;
    }
    VMChunkInfo chunk = new VMChunkInfo();
    chunk.regionAddress = (UInt32)m.BaseAddress;
    chunk.regionSize = (UInt32)m.RegionSize;
    chunk.type = (PageType)m.Type;
    chunk.state = (PageState)m.State;
    if ((chunk.type == PageType.Image) || (chunk.type == PageType.Mapped))
    {
        // .Net 4 maps assemblies into memory using the memory-mapped file mechanism;
        // they don't show up in Process.Modules list
        string fileName = GetMappedFileName(processHandle, chunk.regionAddress);
        if (fileName.Length > 0)
        {
            fileName = Path.GetFileName(fileName);
            chunk.regionName = fileName;
        }
    }
    chunkInfos.Add(chunk);
};

The second looks at the modules (dlls) loaded by the process.  I used the Process.Modules method.  As per this page, from .Net 4 this list no longer includes managed assemblies - only unmanaged ones.  (The only way to discover the managed assemblies is to use GetMappedFileName in conjunction with VirtualQueryEx, which is done in the snippet above).

mappingInfos = new List<VMRegionInfo>();
foreach (ProcessModule module in process.Modules)
{
    VMRegionInfo mappingInfo = new VMRegionInfo();
    mappingInfo.regionAddress = (UInt32)module.BaseAddress;
    mappingInfo.regionSize = (UInt32)module.ModuleMemorySize;
    mappingInfo.regionName = Path.GetFileName(module.FileName);
    mappingInfos.Add(mappingInfo);
}
// Sort by address
mappingInfos.Sort(delegate(VMRegionInfo map1, VMRegionInfo map2)
{
    return Comparer<UInt32>.Default.Compare(map1.regionAddress, map2.regionAddress);
}); 

Points of Interest 

I struggled with the process selection mechanism, which uses a DropDown.  If the dropdown button is pressed, I wanted to show the list of running processes.  If a name is typed into the textbox, I wanted Mnemonic to start scanning a process matching the typed name as soon as it starts.  To achieve the behaviour I wanted, I eventually opted to scan for available processes in a background thread. 

It was interesting to see that .Net applications are not, by default, marked as LARGEADDRESSAWARE and that mainstream applications such as the Office 2007 suite are not so marked, either.  I'd be interested to know why this is.

I'm curious about the possibility of using memory-mapped files in managed applications (there is new support for this technology in .Net 4), so I was interested to see that the CLR uses memory-mapped files to access assemblies (dlls) it loads into a process.  These show up in purple in Mnemonic, and the assembly name is shown in the status bar. 

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"