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
}
}