public void FindAllPaths() { using DataTarget dataTarget = TestTargets.GCRoot.LoadFullDump(); ClrRuntime runtime = dataTarget.ClrVersions.Single().CreateRuntime(); GCRoot gcroot = new GCRoot(runtime.Heap); gcroot.ClearCache(); Assert.False(gcroot.IsFullyCached); FindAllPathsImpl(gcroot); gcroot.BuildCache(CancellationToken.None); Assert.True(gcroot.IsFullyCached); FindAllPathsImpl(gcroot); }
public void GCRoots() { using (DataTarget dataTarget = TestTargets.GCRoot.LoadFullDump()) { ClrRuntime runtime = dataTarget.ClrVersions.Single().CreateRuntime(); GCRoot gcroot = new GCRoot(runtime.Heap); gcroot.ClearCache(); Assert.IsFalse(gcroot.IsFullyCached); GCRootsImpl(gcroot); gcroot.BuildCache(CancellationToken.None); gcroot.AllowParallelSearch = false; Assert.IsTrue(gcroot.IsFullyCached); GCRootsImpl(gcroot); gcroot.AllowParallelSearch = true; Assert.IsTrue(gcroot.IsFullyCached); GCRootsImpl(gcroot); } }
public void GCStaticRoots() { using DataTarget dataTarget = TestTargets.GCRoot.LoadFullDump(); ClrRuntime runtime = dataTarget.ClrVersions.Single().CreateRuntime(); ClrHeap heap = runtime.Heap; heap.StackwalkPolicy = ClrRootStackwalkPolicy.SkipStack; GCRoot gcroot = new GCRoot(runtime.Heap); gcroot.ClearCache(); Assert.False(gcroot.IsFullyCached); GCStaticRootsImpl(gcroot); gcroot.BuildCache(CancellationToken.None); gcroot.AllowParallelSearch = false; Assert.True(gcroot.IsFullyCached); GCStaticRootsImpl(gcroot); gcroot.AllowParallelSearch = true; Assert.True(gcroot.IsFullyCached); GCStaticRootsImpl(gcroot); }
public void BuildCacheCancel() { using (DataTarget dataTarget = TestTargets.GCRoot.LoadFullDump()) { ClrRuntime runtime = dataTarget.ClrVersions.Single().CreateRuntime(); ClrHeap heap = runtime.Heap; heap.StackwalkPolicy = ClrRootStackwalkPolicy.SkipStack; GCRoot gcroot = new GCRoot(heap); ulong target = gcroot.Heap.GetObjectsOfType("TargetType").Single(); CancellationTokenSource source = new CancellationTokenSource(); source.Cancel(); try { gcroot.BuildCache(source.Token); Assert.Fail("Should have been cancelled!"); } catch (OperationCanceledException) { } } }
static void Main() { Helpers.TestWorkingDirectory = Environment.CurrentDirectory; // Please see src\Microsoft.Diagnostics.Runtime.Tests\Targets\GCRoot.cs for the debuggee we // are currently debugging. DataTarget dt = TestTargets.GCRoot.LoadFullDump(); ClrRuntime runtime = dt.ClrVersions.Single().CreateRuntime(); ClrHeap heap = runtime.Heap; // This is the object we are looking for. Let's say the address is 0x1234. The following // demo will show you how to build output similar to SOS's "!gcroot 0x1234". ulong target = GetTarget(heap); /* At the most basic level, to implement !gcroot, simply create a GCRoot object. Then call * EnumerateGCRoots to find unique paths from the root to the target object. This runs similarly * to !gcroot. Please note you will receive all paths each time this code is run, but the order * of roots from run to run are NOT guaranteed. Note that all operations take a cancellation token * which you can use to cancel GCRoot at any time (it will throw an OperationCancelledException). */ GCRoot gcroot = new GCRoot(heap); foreach (GCRootPath rootPath in gcroot.EnumerateGCRoots(target, CancellationToken.None)) { Console.Write($"{rootPath.Root} -> "); Console.WriteLine(string.Join(" -> ", rootPath.Path.Select(obj => obj.Address))); } Console.WriteLine(); // ========================================== /* Since GCRoot can take a long time to run, ClrMD provides an event to get progress updates on how far * along GCRoot is. There are some caveats here, in that you only get updates if you have cached the * gc heap locally (more on that later). This is because without pre-fetching and compiling data ahead * of time, we have no real way to know how many objects are on the heap. The intent of this delegate is * to allow you to report to the user a rough percentage of how far along things are (such as a progress * bar or console output). */ gcroot.ProgressUpdated += delegate(GCRoot source, long current, long total) { // Note that sometimes we don't know how many total items will be processed in the current // phase. In that case, total will be -1. (For example, we don't know the total number // of handles on the handle table until we enumerate them all.) if (total > 0) { Console.WriteLine($"heap searched={(int)(100 * current / (float)total)}%"); } else { Console.WriteLine($"objects inspected={current}"); } }; // ========================================== /* The next important concept is that GCRoot allows you to trade memory for a faster !gcroot. * Due to how memory reads and the clr debugging layer are written, it can take 30-60 minutes * to run the full GCRoot algorithm on large dumps using the code above! To fix that, ClrMD * allows you to copy all relevant data into local memory, at which point all GCRoot runs should * complete very fast. Even the worst crash dumps should take <60 seconds to complete. The * downside is this will use a lot of memory (in some cases, multiple gigs of memory), so you have * to wrap the cach function in a try/catch for OOM exceptions. */ try { gcroot.BuildCache(CancellationToken.None); // Now GCRoot will run MUCH, MUCH faster for the following code. } catch (OutOfMemoryException) { // Whoops, the crash dump in question was too big to read all data into memory. We will continue // on without cached GC data. } // ========================================== /* The next thing to know about GCRoot is there are two ways we can walk the stack of threads looking for * roots: Exact and Fast. You change this setting with ClrHeap.StackwalkPolicy. Using a stackwalk policy * of exact will show you exactly what the GC sees, eliminating false roots, but it can be slow. Using the * Fast stackwalk policy is very fast, but can over-report roots that are actually just old GC references * left on the stack. * Unfortunately due to the way that CLR's debugging layer is implemented, using Exact can take a *long* * time (10s of minutes if the dump has 1000s of threads). The default is to let ClrMD decide which policy * to use. Lastly, note that you can choose to omit stack roots entirely by setting StackwalkPolicy to * "SkipStack". */ // Let's set a concrete example: heap.StackwalkPolicy = ClrRootStackwalkPolicy.Exact; // ========================================== /* Finally, GCRoot can use parallel processing to use more of the CPU and work in parallel. This is done * automatically (and is on by default)...but only if you use .BuildCache and build the *complete* GC heap * in local memory. Since limitations in ClrMD and the APIs it's built on do not allow multithreading, * we only do multithreaded processing if we never need to call into ClrMD while walking objects. * * One downside is that multithreaded processing means the order of roots enumerated is not stable from * run to run. You can turn off multithreaded processing so that the results of GCRoot are stable between * runs (though slower to run), but GCRoot makes NO guarantees about the order of roots you receive, and * they may change from build to build of ClrMD or change depending on the other flags set on the GCRoot * object. */ // This can be used to turn off multithreaded processing, this property is true by default: // gcroot.AllowParallelSearch = false; // You can set the maximum number of tasks using this. The default is Environment.ProcessorCount * 2: gcroot.MaximumTasksAllowed = 20; // ========================================== /* Finally, we will run through the enumeration one more time, but this time using parallel processing * and printing out a bunch of status messages for demonstration. */ Console.WriteLine("Get ready for a lot of output, since we are raw printing status updates!"); Console.WriteLine(); foreach (GCRootPath rootPath in gcroot.EnumerateGCRoots(target, CancellationToken.None)) { Console.Write($"{rootPath.Root} -> "); Console.WriteLine(string.Join(" -> ", rootPath.Path.Select(obj => obj.Address))); } }
/// <summary> /// Builds a cache of the GC heap and roots. This will consume a LOT of memory, so when calling it you must wrap this /// in /// a try/catch for OutOfMemoryException. /// Note that this function allows you to choose whether we have exact thread callstacks or not. Exact thread /// callstacks /// will essentially force ClrMD to walk the stack as a real GC would, but this can take 10s of minutes when the thread /// count gets /// into the 1000s. /// </summary> /// <param name="cancelToken">The cancellation token used to cancel the operation if it's taking too long.</param> /// <inheritdoc /> public void BuildCache(CancellationToken cancelToken) => Root.BuildCache(cancelToken);