Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Mnemonic: Assisting Your (virtual) Memory

, 9 Feb 2012
A tool for visualizing the virtual memory used by Windows processes
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Drawing.Drawing2D;
using System.IO;

namespace Mnemonic
{
    public partial class MnemonicForm : Form
    {
        // The list of process names, filled by the background worker thread
        private List<Process> processes = new List<Process>();

        private Process currentProcess = null;
        private int currentProcessID = 0;

        private VirtualMemoryScanner scanner = new VirtualMemoryScanner();

        const UInt64 MB = 1024 * 1024;
        private UInt64 chunkSizeB = 4 * MB;

        private List<VMChunkInfo> vmRegionInfos;
        private List<VMRegionInfo> unmanagedRegionInfos;

        const int graphGrid = 20;
        const int graphMargin = 4;

        private Color colorCommitted = Color.Red;
        private Color colorReserved = Color.Yellow;
        private Color colorFree = Color.Green;
        private Color colorMapped = Color.Blue;
        private Color colorMappedFile = Color.Purple;
        private Color colorUnmanagedModule = Color.Cyan;

        private string filename = string.Empty;
        private int filenameSequence = 0;

        public MnemonicForm()
        {
            InitializeComponent();

#if DEBUG
            // Easier to debug if TopMost is not set
            this.TopMost = false;
#endif
            labelTotalCommitted.ForeColor = colorCommitted;
            labelTotalReserved.ForeColor = colorReserved;
            labelTotalFree.ForeColor = colorFree;
            labelLargestFree.ForeColor = colorFree;

            SetPBoxTooltip();

            // Start background thread enumerating processes
            processWorker.RunWorkerAsync();
        }

#region Helpers
        private void SetPBoxTooltip()
        {
            if (filename.Length > 0)
                toolTip.SetToolTip(pboxGraph, String.Format("Click to save to {0} {1}.png", filename, filenameSequence));
            else
                toolTip.SetToolTip(pboxGraph, "Control-click to start saving to file");
        }

        private int graphXFromAddress(UIntPtr address)
        {
            return (int)((ulong)address / chunkSizeB);
        }

        private UIntPtr addressFromGraphX(int graphX)
        {
            return (UIntPtr)((ulong)graphX * chunkSizeB);
        }

        private void UpdateCurrentProcess()
        {
            if (currentProcess != null)
            {
                // We have a selected process.  Check it's still valid
                if (!currentProcess.HasExited &&
                    (comboProcesses.Text == currentProcess.ProcessName) &&
                    (currentProcessID == currentProcess.Id))
                {
                    UpdateCurrentProcessDetails();
                    return;
                }
            }

            currentProcess = null;

            // Search for a process that matches what we're looking for
            string processName = comboProcesses.Text.ToLower();
            lock (processes)
            {
                foreach (Process process in processes)
                {
                    if (process.ProcessName.ToLower().StartsWith(processName))
                    {
                        currentProcess = process;
                        UpdateCurrentProcessDetails();
                        return;
                    }
                }
            }

            UpdateCurrentProcessDetails();
        }

        private void UpdateCurrentProcessDetails()
        {
            if (currentProcess != null)
            {
                currentProcessID = currentProcess.Id;

                txtWindow.Text = currentProcess.MainWindowTitle;
                txtBitness.Text = currentProcess.Is64BitProcess() ? "64 bit" : "32 bit";
                txtPID.Text = currentProcess.Id.ToString();
            }
            else
            {
                currentProcessID = 0;

                txtWindow.Text = string.Empty;
                txtBitness.Text = string.Empty;
                txtPID.Text = string.Empty;
                vmRegionInfos = null;
                unmanagedRegionInfos = null;

                labelTotalCommitted.Text = string.Empty;
                labelTotalReserved.Text = string.Empty;
                labelTotalFree.Text = string.Empty;
                labelLargestFree.Text = string.Empty;
            }

            pboxGraph.Invalidate();
        }

        private string FormatAddressMB(UInt64 size)
        {
            return String.Format("{0} MB", size / MB);
        }

        // Return the index of the first value in addresses that is >= address, or -1
        private int IndexOfAddress(UInt64[] addresses, UInt64 address)
        {
            int index = Array.BinarySearch(addresses, address);
            if (index < 0)
                index = ~index;

            if (index >= addresses.Length)
                return addresses.Length;

            if (index == 0)
                return -1;

            return index;
        }
#endregion

#region Drawing
        void DrawRegion(VMRegionInfo info, int gridRow, Graphics graphics, Color colour)
        {
            int startX = graphXFromAddress(info.regionStartAddress);
            int endX = graphXFromAddress(info.regionEndAddress);
            Rectangle rect = new Rectangle(startX, graphGrid * gridRow + graphMargin, endX - startX, graphGrid - graphMargin);

            SolidBrush brush = new SolidBrush(colour);

            // Draw "large" continguous blocks shaded, so their extents are visible
            if (endX > startX + 10)
            {
                LinearGradientBrush gradient = new LinearGradientBrush(rect, Color.White, colour, LinearGradientMode.ForwardDiagonal);
                graphics.FillRectangle(gradient, rect);
            }
            else
            {
                graphics.FillRectangle(brush, rect);
            }
        }

        void DrawVMMapping(VMRegionInfo mappingInfo, Graphics graphics)
        {
            DrawRegion(mappingInfo, 2, graphics, colorUnmanagedModule);
        }

        void DrawVMChunk(VMChunkInfo chunkInfo, Graphics graphics)
        {
            int startX = graphXFromAddress(chunkInfo.regionStartAddress);
            int endX = graphXFromAddress(chunkInfo.regionEndAddress);

            Color colour;
            if (chunkInfo.state == PageState.Reserved)
                colour = colorReserved;
            else if (chunkInfo.state == PageState.Committed)
                colour = colorCommitted;
            else
                colour = colorFree;

            DrawRegion(chunkInfo, 0, graphics, colour);

            if (chunkInfo.type == PageType.Mapped)
            {
                DrawRegion(chunkInfo, 1, graphics,
                    (chunkInfo.regionName != null) ? colorMappedFile : colorMapped);
            }
        }

        private void DrawVM(Graphics graphics)
        {
            if (vmRegionInfos != null)
            {
                foreach (VMChunkInfo chunkInfo in vmRegionInfos)
                    DrawVMChunk(chunkInfo, graphics);
            }

            if (unmanagedRegionInfos != null)
            {
                foreach (VMRegionInfo mappingInfo in unmanagedRegionInfos)
                    DrawVMMapping(mappingInfo, graphics);
            }
        }
#endregion

#region processWorker thread
        // Fill the list of processes in a background thread
        private void processWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            while (true)
            {
                BackgroundWorker worker = sender as BackgroundWorker;

                List<Process> processes = new List<Process>();
                foreach (Process process in Process.GetProcesses())
                {
                    try
                    {
                        IntPtr handle = process.Handle;
                        processes.Add(process);
                    }
                    catch
                    {
                        // Will see an exception if we don't have permission to get the process handle
                    }
                }
                worker.ReportProgress(0, processes);
                System.Threading.Thread.Sleep(500);
            }
        }

        private void processWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            processes = (List<Process>)e.UserState;
        }
 
#endregion    

#region Control event handlers
        private void refreshTimer_Tick(object sender, EventArgs e)
        {
            UpdateCurrentProcess();
            if (currentProcess == null)
                return;

            UIntPtr addressLimit;
            scanner.Scan(currentProcess, out vmRegionInfos, out unmanagedRegionInfos, out addressLimit);

            chunkSizeB = (UInt64)addressLimit / (UInt64)pboxGraph.Width;

            txtAddressLimit.Text = FormatAddressMB((UInt64)addressLimit);

            UInt64 totalCommitted = 0;
            UInt64 totalReserved = 0;
            UInt64 totalFree = 0;
            UInt64 largestFree = 0;
            foreach (VMChunkInfo chunkInfo in vmRegionInfos)
            {
                if (chunkInfo.state == PageState.Reserved)
                    totalReserved += chunkInfo.regionSize;
                else if (chunkInfo.state == PageState.Committed)
                    totalCommitted += chunkInfo.regionSize;
                else
                {
                    totalFree += chunkInfo.regionSize;
                    if (chunkInfo.regionSize > largestFree)
                        largestFree = chunkInfo.regionSize;
                }
            }

            labelTotalCommitted.Text = "Committed: " + FormatAddressMB(totalCommitted);
            labelTotalReserved.Text = "Reserved: " + FormatAddressMB(totalReserved);
            labelTotalFree.Text = "Free: " + FormatAddressMB(totalFree);
            labelLargestFree.Text = "Largest free block: " + FormatAddressMB(largestFree);

            pboxGraph.Invalidate();
        }

        private void comboProcesses_DropDown(object sender, EventArgs e)
        {
            // Fill the comboProcesses dropdown only when required, to stop it flashing and banging
            comboProcesses.Items.Clear();
            lock (processes)
                comboProcesses.Items.AddRange(processes.ToArray<object>());
        }

        private void comboProcesses_SelectedIndexChanged(object sender, EventArgs e)
        {
            currentProcess = (Process)comboProcesses.SelectedItem;
            UpdateCurrentProcessDetails();
        }

        private void comboProcesses_TextUpdate(object sender, EventArgs e)
        {
            // Dropdown's text has been edited.
            UpdateCurrentProcess();
        }

        private void pnlGraph_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.FillRectangle(new SolidBrush(((Control)sender).BackColor), e.ClipRectangle);

            DrawVM(e.Graphics);
        }

        private void pnlGraph_MouseEnter(object sender, EventArgs e)
        {
            statusStripTotals.Visible = false;
            statusStrip.Visible = true;
        }

        private void pnlGraph_MouseLeave(object sender, EventArgs e)
        {
            statusStripTotals.Visible = true;
            statusStrip.Visible = false;
        }

        private void pnlGraph_MouseMove(object sender, MouseEventArgs e)
        {
            labelAddress.Text = string.Empty;
            labelRegionName.Text = string.Empty;

            if (currentProcess == null)
                return;

            // Address corresponding to the position of the mouse
            UIntPtr address = addressFromGraphX(e.X);

            // Search for a virtual memory region that contains the address
            int vmRegionIndex = -1;
            bool vmRegionNamed = false;
            if (vmRegionInfos != null)
            {
                UInt64[] addresses = vmRegionInfos.Select(chunkInfo => (UInt64)chunkInfo.regionStartAddress).ToArray();
                int index = IndexOfAddress(addresses, (UInt64)address) - 1;
                if (index >= 0 && vmRegionInfos[index].ContainsAddress(address))
                {
                    vmRegionIndex = index;
                    vmRegionNamed = (vmRegionInfos[index].regionName != null);
                }
            }

            // Search for an unmanaged module at the address
            int unmanagedRegionIndex = -1;
            bool unmanagedRegionNamed = false;
            if (unmanagedRegionInfos != null)
            {
                UInt64[] addresses = unmanagedRegionInfos.Select(mappingInfo => (UInt64)mappingInfo.regionStartAddress).ToArray();
                int index = IndexOfAddress(addresses, (UInt64)address) - 1;
                if (index >= 0 && unmanagedRegionInfos[index].ContainsAddress(address))
                {
                    unmanagedRegionIndex = index;
                    unmanagedRegionNamed = (unmanagedRegionInfos[index].regionName != null);
                }
            }

            if (vmRegionIndex >= 0)
            {
                // Report the address range of the VM region
                VMRegionInfo info = vmRegionInfos[vmRegionIndex];
                labelAddress.Text = String.Format("0x{0:X8}:0x{1:X8} [0x{2:X8}]", 
                    info.regionStartAddress, info.regionEndAddress, info.regionSize);
            }

            // If the VM region is named, but there's no unmanaged region, it's a managed assembly
            // Otherwise if there's a ProcessModule at this address, it's unmanaged
            if (vmRegionNamed && !unmanagedRegionNamed)
            {
                labelRegionName.ForeColor = colorMappedFile;
                labelRegionName.Text = "Managed: " + vmRegionInfos[vmRegionIndex].regionName;
            }
            else if (unmanagedRegionNamed)
            {
                labelRegionName.ForeColor = colorUnmanagedModule;
                labelRegionName.Text = "Unmanaged: " + unmanagedRegionInfos[unmanagedRegionIndex].regionName;
            }
        }

        private void pboxGraph_Click(object sender, EventArgs e)
        {
            if (currentProcess != null)
            {
                if (ModifierKeys == Keys.Control)
                {
                    filename = string.Empty;
                    filenameSequence = 0;
                    if (saveFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                    {
                        filename = Path.Combine(
                            Path.GetDirectoryName(saveFileDialog.FileName),
                            Path.GetFileNameWithoutExtension(saveFileDialog.FileName));
                    }
                }

                if (filename != string.Empty)
                {
                    // Draw the graph to a Bitmap and save
                    Bitmap bm = new Bitmap(pboxGraph.Width, pboxGraph.Height);
                    using (Graphics bmGraphics = Graphics.FromImage(bm))
                        DrawVM(bmGraphics);

                    bm.Save(string.Format("{0} {1}.png", filename, filenameSequence++));
                }

                SetPBoxTooltip();
            }
        }
#endregion

    }
}

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)

Share

About the Author

Phil Atkin
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.

| Advertise | Privacy | Mobile
Web02 | 2.8.140827.1 | Last Updated 9 Feb 2012
Article Copyright 2011 by Phil Atkin
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid