Skip to content

Commit

Permalink
ClrMD post
Browse files Browse the repository at this point in the history
  • Loading branch information
maartenba committed Dec 26, 2016
1 parent 47e9bc6 commit 4ff432f
Show file tree
Hide file tree
Showing 12 changed files with 601 additions and 0 deletions.
6 changes: 6 additions & 0 deletions ClrMd/ClrMd.Explorer/App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
</configuration>
14 changes: 14 additions & 0 deletions ClrMd/ClrMd.Explorer/ClrHeapObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace ClrMd.Explorer
{
public class ClrHeapObject
{
public int Generation { get; }
public ulong Ptr { get; }

public ClrHeapObject(int generation, ulong ptr)
{
Generation = generation;
Ptr = ptr;
}
}
}
66 changes: 66 additions & 0 deletions ClrMd/ClrMd.Explorer/ClrMd.Explorer.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A370D1FA-C986-41F2-9AE3-5E9FD4A06DDF}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ClrMd.Explorer</RootNamespace>
<AssemblyName>ClrMd.Explorer</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Diagnostics.Runtime, Version=0.8.31.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Diagnostics.Runtime.0.8.31-beta\lib\net40\Microsoft.Diagnostics.Runtime.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ClrHeapObject.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
306 changes: 306 additions & 0 deletions ClrMd/ClrMd.Explorer/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.Diagnostics.Runtime;

namespace ClrMd.Explorer
{
class Program
{
static void Main(string[] args)
{
// Start the ClrMd.Target process
var demoProcess = StartDemoProcess();

// Give it a few seconds to run
Thread.Sleep(TimeSpan.FromSeconds(2));

// Attach ClrMd to our process
using (var dataTarget = DataTarget.AttachToProcess(demoProcess.Id, 10000, AttachFlag.Invasive)) // invasive, pausing our target process
{
// Dump CLR info
var clrVersion = dataTarget.ClrVersions.First();
var dacInfo = clrVersion.DacInfo;

Console.WriteLine("# CLR Info");
Console.WriteLine("Version: {0}", clrVersion.Version);
Console.WriteLine("Filesize: {0:X}", dacInfo.FileSize);
Console.WriteLine("Timestamp: {0:X}", dacInfo.TimeStamp);
Console.WriteLine("Dac file: {0}", dacInfo.FileName);
Console.WriteLine("");

// Dump runtime info
var runtime = clrVersion.CreateRuntime();
var appDomain = runtime.AppDomains.First();

Console.WriteLine("# Runtime Info");
Console.WriteLine("AppDomain: {0}", appDomain.Name);
Console.WriteLine("Address: {0}", appDomain.Address);
Console.WriteLine("Configuration: {0}", appDomain.ConfigurationFile);
Console.WriteLine("Directory: {0}", appDomain.ApplicationBase);
Console.WriteLine("");

// Dump thread info
Console.WriteLine("## Threads");
Console.WriteLine("Thread count: {0}", runtime.Threads.Count);
Console.WriteLine("");
foreach (var thread in runtime.Threads)
{
Console.WriteLine("### Thread {0}", thread.OSThreadId);
Console.WriteLine("Thread type: {0}", thread.IsBackground ? "Background"
: thread.IsGC ? "GC"
: "Foreground");
Console.WriteLine("");
Console.WriteLine("Stack trace:");
foreach (var stackFrame in thread.EnumerateStackTrace())
{
Console.WriteLine("* {0}", stackFrame.DisplayString);
}
Console.WriteLine("");
}

// Dump heap info
var heap = runtime.GetHeap();

if (heap.CanWalkHeap)
{
Console.WriteLine("## What's the retention path of the Clock object?");
Console.WriteLine("");
foreach (var ptr in heap.EnumerateObjectAddresses())
{
var type = heap.GetObjectType(ptr);
if (type == null || type.Name != "ClrMd.Target.Clock")
{
continue;
}


// Enumerate roots and try to find the current object
var stack = new Stack<ulong>();
foreach (var root in heap.EnumerateRoots())
{
stack.Clear();
stack.Push(root.Object);
if (GetPathToObject(heap, ptr, stack, new HashSet<ulong>()))
{
// Print retention path
var depth = 0;
foreach (var address in stack)
{
var t = heap.GetObjectType(address);
if (t == null)
{
continue;
}

Console.WriteLine("{0} {1} - {2} - {3} bytes", new string('+', depth++), address, t.Name, t.GetSize(address));
}

break;
}
}
}

Console.WriteLine("## Heap info");
Console.WriteLine("Heap count: {0}", runtime.HeapCount);
Console.WriteLine("");

foreach (var generation in heap.EnumerateObjectAddresses()
.Select(ptr => new ClrHeapObject(heap.GetGeneration(ptr), ptr))
.GroupBy(item => item.Generation)
.OrderBy(item => item.Key))
{
Console.WriteLine("### Generation {0}", generation.Key);
foreach (var objectAddress in generation)
{
var type = heap.GetObjectType(objectAddress.Ptr);
if (type == null)
{
continue;
}

Console.WriteLine("* {0} - {1} - {2} bytes", objectAddress.Ptr, type.Name, type.GetSize(objectAddress.Ptr));
if (type.HasSimpleValue)
{
Console.WriteLine("** Value: {0}", type.GetValue(objectAddress.Ptr));
}
}
Console.WriteLine("");
}
}


Console.WriteLine("## Heap info");
Console.WriteLine("Heap count: {0}", runtime.HeapCount);
Console.WriteLine("");
if (heap.CanWalkHeap)
{
foreach (var generation in heap.EnumerateObjectAddresses()
.Select(ptr => new ClrHeapObject(heap.GetGeneration(ptr), ptr))
.GroupBy(item => item.Generation)
.OrderBy(item => item.Key))
{
Console.WriteLine("### Generation {0}", generation.Key);
foreach (var objectAddress in generation)
{
var type = heap.GetObjectType(objectAddress.Ptr);
if (type == null)
{
continue;
}

Console.WriteLine("* {0} - {1} - {2} bytes", objectAddress.Ptr, type.Name, type.GetSize(objectAddress.Ptr));
if (type.HasSimpleValue)
{
Console.WriteLine("** Value: {0}", type.GetValue(objectAddress.Ptr));
}

if (type.Name == "ClrMd.Target.Clock")
{
// Enumerate roots and try to find the current object
var stack = new Stack<ulong>();
foreach (var root in heap.EnumerateRoots())
{
stack.Clear();
stack.Push(root.Object);
if (GetPathToObject(heap, objectAddress.Ptr, stack, new HashSet<ulong>()))
{
// Print retention path
var depth = 0;
foreach (var address in stack)
{
var t = heap.GetObjectType(address);
if (t == null)
{
continue;
}

Console.WriteLine("{0} {1} - {2} - {3} bytes", new string('+', depth++), address, t.Name, t.GetSize(address));
}

break;
}
}
}
}
Console.WriteLine("");
}
}

// Dump strings
if (heap.CanWalkHeap)
{
var numberOfStrings = 0;
var uniqueStrings = new Dictionary<string, int>();

foreach (var ptr in heap.EnumerateObjectAddresses())
{
var type = heap.GetObjectType(ptr);

// Skip if not a string
if (type == null || type.IsString == false)
{
continue;
}

// Count total
numberOfStrings++;

// Get value
var text = (string)type.GetValue(ptr);
if (uniqueStrings.ContainsKey(text))
{
uniqueStrings[text]++;
}
else
{
uniqueStrings[text] = 1;
}
}

Console.WriteLine("## String info");
Console.WriteLine("String count: {0}", numberOfStrings);
Console.WriteLine("");

Console.WriteLine("Most duplicated strings: (top 5)");
foreach (var keyValuePair in uniqueStrings.OrderByDescending(kvp => kvp.Value).Take(5))
{
Console.WriteLine("* {0} usages: {1}", keyValuePair.Value, keyValuePair.Key);
}
Console.WriteLine("");
}
}

// Wait
Console.WriteLine("Press <enter> to quit");
Console.ReadLine();

// Kill demo process
try
{
demoProcess.Kill();
}
catch
{
}
}

private static Process StartDemoProcess()
{
// Start the ClrMd.Target process
var processStartInfo = new ProcessStartInfo(Assembly.GetExecutingAssembly().Location
.Replace("ClrMd.Explorer", "ClrMd.Target"));

return Process.Start(processStartInfo);
}

private static bool GetPathToObject(ClrHeap heap, ulong objectPointer, Stack<ulong> stack, HashSet<ulong> touchedObjects)
{
// Start of the journey - get address of the first objetc on our reference chain
var currentObject = stack.Peek();

// Have we checked this object before?
if (!touchedObjects.Add(currentObject))
{
return false;
}

// Did we find our object? Then we have the path!
if (currentObject == objectPointer)
{
return true;
}


// Enumerate internal references of the object
var found = false;
var type = heap.GetObjectType(currentObject);
if (type != null)
{
type.EnumerateRefsOfObject(currentObject, (innerObject, fieldOffset) =>
{
if (innerObject == 0 || touchedObjects.Contains(innerObject))
{
return;
}
// Push the object onto our stack
stack.Push(innerObject);
if (GetPathToObject(heap, objectPointer, stack, touchedObjects))
{
found = true;
return;
}
// If not found, pop the object from our stack as this is not the tree we're looking for
stack.Pop();
});
}

return found;
}
}
}
Loading

0 comments on commit 4ff432f

Please sign in to comment.