Click here to Skip to main content
15,885,216 members
Articles / Desktop Programming / Windows Forms

Mnemonic: Assisting Your (virtual) Memory

Rate me:
Please Sign up or sign in to vote.
4.84/5 (10 votes)
9 Feb 2012CPOL4 min read 36.3K   1K   19  
A tool for visualizing the virtual memory used by Windows processes
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;
using System.ComponentModel;

namespace Mnemonic
{
    public class VMRegionInfo
    {
        public UIntPtr regionStartAddress;
        public UInt64 regionSize;
        public string regionName;

        public bool ContainsAddress(UIntPtr address)
        {
            return 
                ((UInt64)regionStartAddress <= (UInt64)address) &&
                ((UInt64)regionEndAddress >= (UInt64)address);
        }

        public UIntPtr regionEndAddress
        {
            get { return UIntPtr.Add(regionStartAddress, (int)(regionSize - 1)); }
        }
    }

    public class VMChunkInfo : VMRegionInfo
    {
        public PageState state;
        public PageType type;
    }

    public enum PageState
    {
        Committed = 0x1000,
        Free = 0x10000,
        Reserved = 0x2000
    }

    public enum PageType
    {
        Image = 0x01000000,     // Mapped into the view of an image section
        Mapped = 0x00040000,    // Mapped into the view of a section
        Private = 0x00020000    // Private (not shared with other processes)
    }

    static class ProcessExtensions
    {
        [DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool IsWow64Process([In] IntPtr processHandle, [Out, MarshalAs(UnmanagedType.Bool)] out bool wow64Process);

        public static bool Is64BitProcess(this Process process)
        {
            if (!Environment.Is64BitOperatingSystem)
                return false;

            bool isWow64Process;
            if (!IsWow64Process(process.Handle, out isWow64Process))
                throw new Win32Exception(Marshal.GetLastWin32Error());

            return !isWow64Process;
        }

    }

    class VirtualMemoryScanner
    {
#region P/Invoke
		[StructLayout(LayoutKind.Sequential)]
        public struct MEMORY_BASIC_INFORMATION
        {
            public UIntPtr BaseAddress;
            public UIntPtr AllocationBase;
            public uint AllocationProtect;
            public UIntPtr RegionSize;
            public uint State;
            public uint Protect;
            public uint Type;
        }

        [DllImport("kernel32.dll")]
        static extern int VirtualQueryEx(IntPtr hProcess, UIntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength);

        [StructLayout(LayoutKind.Sequential)]
        public struct SYSTEM_INFO
        {
            public uint dwOemId;
            public uint dwPageSize;
            public uint lpMinimumApplicationAddress;
            public uint lpMaximumApplicationAddress;
            public uint dwActiveProcessorMask;
            public uint dwNumberOfProcessors;
            public uint dwProcessorType;
            public uint dwAllocationGranularity;
            public uint dwProcessorLevel;
            public uint dwProcessorRevision;
        }

        [DllImport("kernel32")]
        static extern void GetSystemInfo(ref SYSTEM_INFO pSI);

        [DllImport("psapi.dll", SetLastError = true)]
        public static extern uint GetMappedFileName(IntPtr m_hProcess, UIntPtr lpv, StringBuilder
                lpFilename, uint nSize);

        public static string GetMappedFileName(IntPtr process, UIntPtr address)
        {
            StringBuilder fn = new StringBuilder(250);
            if (GetMappedFileName(process, address, fn, 250) > 0)
                return fn.ToString();
            else
                return string.Empty;
        }

        public static uint PageSize
        {
            get
            {
                SYSTEM_INFO pSI = new SYSTEM_INFO();
                GetSystemInfo(ref pSI);
                return pSI.dwPageSize;
            }
        }

        public static uint AllocationGranularity
        {
            get
            {
                SYSTEM_INFO pSI = new SYSTEM_INFO();
                GetSystemInfo(ref pSI);
                return pSI.dwAllocationGranularity;
            }
        }

 
	#endregion

        public Process process { get; set; }

        public VirtualMemoryScanner()
        {
        }

        public void Scan(Process process, out List<VMChunkInfo> chunkInfos, out List<VMRegionInfo> mappingInfos, out UIntPtr addressLimit)
        {
            this.process = process;
            IntPtr processHandle = process.Handle;
            MEMORY_BASIC_INFORMATION m = new MEMORY_BASIC_INFORMATION();

            chunkInfos = new List<VMChunkInfo>();

            const UInt64 GB = 1024 * 1024 * 1024;
            UInt64 maxRegionSize = (UInt64)2 * GB;

            UIntPtr memoryLimit;
            if (process.Is64BitProcess())
                memoryLimit = (UIntPtr)((UInt64)6 * GB);
            else
                memoryLimit = UIntPtr.Subtract(UIntPtr.Zero, 1);

            addressLimit = memoryLimit;

            // Use UIntPtr so that we can cope with addresses above 2GB in a /3GB or "4GT" environment, or 64-bit Windows
            UIntPtr address = (UIntPtr)0;
            while ((UInt64)address < (UInt64)memoryLimit)
            {
                int result = VirtualQueryEx(processHandle, address, out m, (uint)Marshal.SizeOf(m));
                if (0 == result || (UInt64)m.RegionSize > maxRegionSize)
                {
                    // 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.regionStartAddress = (UIntPtr)(UInt64)m.BaseAddress;
                chunk.regionSize = (UInt64)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.regionStartAddress);
                    if (fileName.Length > 0)
                    {
                        fileName = Path.GetFileName(fileName);
                        chunk.regionName = fileName;
                    }
                }

                chunkInfos.Add(chunk);

                UIntPtr oldAddress = address;
                // It's maddening, but UIntPtr.Add can't cope with 64-bit offsets under Win64!
                address = UIntPtr.Add(address, (int)m.RegionSize);
                if ((UInt64)address <= (UInt64)oldAddress)
                {
                    addressLimit = oldAddress;
                    break;
                }
            };

            mappingInfos = new List<VMRegionInfo>();
            try
            {
                foreach (ProcessModule module in process.Modules)
                {
                    VMRegionInfo mappingInfo = new VMRegionInfo();

                    mappingInfo.regionStartAddress = (UIntPtr)(UInt64)module.BaseAddress;
                    mappingInfo.regionSize = (UInt64)module.ModuleMemorySize;
                    mappingInfo.regionName = Path.GetFileName(module.FileName);

                    mappingInfos.Add(mappingInfo);
                }
            }
            catch { }

            // Sort by address
            mappingInfos.Sort(delegate(VMRegionInfo map1, VMRegionInfo map2)
            {
                return Comparer<UInt64>.Default.Compare((UInt64)map1.regionStartAddress, (UInt64)map2.regionStartAddress);
            });
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder Pixel Analytics Ltd.
United Kingdom United Kingdom
My expertise is concentrated on image processing and analysis; I've been doing it most of my working life. Now I offer consultancy through my own company, as well as developing software products.

Comments and Discussions