Ejemplo n.º 1
0
        private void GCRootsImpl(GCRoot gcroot)
        {
            ClrHeap heap   = gcroot.Heap;
            ulong   target = heap.GetObjectsOfType("TargetType").Single();

            RootPath[] rootPaths = gcroot.EnumerateGCRoots(target, false, CancellationToken.None).ToArray();

            Assert.IsTrue(rootPaths.Length >= 2);

            foreach (RootPath rootPath in rootPaths)
            {
                AssertPathIsCorrect(heap, rootPath.Path.ToArray(), rootPath.Path.First().Address, target);
            }

            bool hasThread = false, hasStatic = false;

            foreach (RootPath rootPath in rootPaths)
            {
                if (rootPath.Root.Kind == GCRootKind.Pinning)
                {
                    hasStatic = true;
                }
                else if (rootPath.Root.Kind == GCRootKind.LocalVar)
                {
                    hasThread = true;
                }
            }

            Assert.IsTrue(hasThread);
            Assert.IsTrue(hasStatic);
        }
Ejemplo n.º 2
0
 /// <summary>
 ///     Initializes a new instance of the <see cref="GcRootAdapter" /> class.
 /// </summary>
 /// <param name="root">The root.</param>
 /// <exception cref="ArgumentNullException">root</exception>
 /// <inheritdoc />
 public GcRootAdapter(IConverter converter, GCRoot root) : base(converter)
 {
     Root = root ?? throw new ArgumentNullException(nameof(root));
     AllowParallelSearch = Root.AllowParallelSearch;
     IsFullyCached       = Root.IsFullyCached;
     MaximumTasksAllowed = Root.MaximumTasksAllowed;
 }
Ejemplo n.º 3
0
        public void FindAllPaths()
        {
            using DataTarget dataTarget = TestTargets.GCRoot.LoadFullDump();
            using ClrRuntime runtime    = dataTarget.ClrVersions.Single().CreateRuntime();
            GCRoot gcroot = new GCRoot(runtime.Heap);

            FindAllPathsImpl(gcroot);
        }
Ejemplo n.º 4
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="objects"></param>
        /// <param name="topn"></param>
        /// <returns></returns>
        private IEnumerable <(string objectAddress, string GCRoot)> GetGCRoot
            (IEnumerable <ClrObject> objects, int topn = 3)
        {
            GCRoot gCRoot  = new GCRoot(Runtime.Heap);
            var    topObjs = objects.Take(topn);

            return(topObjs.Select(o => (objectAddress: o.Address.ToString(),
                                        GCRoot: gCRoot.EnumerateGCRoots(o, false, Environment.ProcessorCount).First().ToString())));
        }
Ejemplo n.º 5
0
        private void GCRootsImpl(GCRoot gcroot, ClrHeap heap, ulong target, int parallelism, bool unique)
        {
            GCRootPath[] rootPaths = gcroot.EnumerateGCRoots(target, unique: unique, parallelism, CancellationToken.None).ToArray();

            // In the case where we say we only want unique rooting chains AND we want to look in parallel,
            // we cannot guarantee that we will pick the static roots over the stack ones.  Hence we don't
            // ensure that the static variable is enumerated when unique == true.
            CheckRootPaths(heap, target, rootPaths, mustContainStatic: !unique);
        }
Ejemplo n.º 6
0
        public void GCRoots()
        {
            using DataTarget dataTarget = TestTargets.GCRoot.LoadFullDump();
            using ClrRuntime runtime    = dataTarget.ClrVersions.Single().CreateRuntime();
            ClrHeap heap = runtime.Heap;

            GCRoot gcroot = new GCRoot(heap);

            GCRootsImpl(gcroot);
        }
Ejemplo n.º 7
0
        private void FindSinglePathImpl(GCRoot gcroot)
        {
            ClrHeap heap = gcroot.Heap;

            GetKnownSourceAndTarget(heap, out ulong source, out ulong target);

            LinkedList <ClrObject> path = gcroot.FindSinglePath(source, target, CancellationToken.None);

            AssertPathIsCorrect(heap, path.ToImmutableArray(), source, target);
        }
Ejemplo n.º 8
0
        private void GCStaticRootsImpl(GCRoot gcroot)
        {
            ulong target = gcroot.Heap.GetObjectsOfType("TargetType").Single();

            RootPath[] paths = gcroot.EnumerateGCRoots(target, false, CancellationToken.None).ToArray();
            Assert.AreEqual(1, paths.Length);
            RootPath rootPath = paths[0];

            AssertPathIsCorrect(gcroot.Heap, rootPath.Path.ToArray(), rootPath.Path.First().Address, target);
        }
Ejemplo n.º 9
0
        private void GCRootsImpl(GCRoot gcroot)
        {
            ClrHeap heap   = gcroot.Heap;
            ulong   target = heap.GetObjectsOfType("TargetType").Single();

            GCRootsImpl(gcroot, heap, target, parallelism: 1, unique: false);
            GCRootsImpl(gcroot, heap, target, parallelism: 16, unique: false);

            GCRootsImpl(gcroot, heap, target, parallelism: 1, unique: true);
            GCRootsImpl(gcroot, heap, target, parallelism: 16, unique: true);
        }
Ejemplo n.º 10
0
        public void FindSinglePath()
        {
            using DataTarget dataTarget = TestTargets.GCRoot.LoadFullDump();
            using ClrRuntime runtime    = dataTarget.ClrVersions.Single().CreateRuntime();
            ClrHeap heap   = runtime.Heap;
            GCRoot  gcroot = new GCRoot(heap);

            GetKnownSourceAndTarget(heap, out ulong source, out ulong target);

            LinkedList <ClrObject> path = gcroot.FindSinglePath(source, target, CancellationToken.None);

            AssertPathIsCorrect(heap, path.ToImmutableArray(), source, target);
        }
Ejemplo n.º 11
0
        public void EnumerateAllPathCancel()
        {
            using DataTarget dataTarget = TestTargets.GCRoot.LoadFullDump();
            using ClrRuntime runtime    = dataTarget.ClrVersions.Single().CreateRuntime();
            ClrHeap heap   = runtime.Heap;
            GCRoot  gcroot = new GCRoot(heap);

            CancellationTokenSource cancelSource = new CancellationTokenSource();

            cancelSource.Cancel();

            GetKnownSourceAndTarget(runtime.Heap, out ulong source, out ulong target);
            Assert.Throws <OperationCanceledException>(() => gcroot.EnumerateAllPaths(source, target, false, cancelSource.Token).ToArray());
        }
Ejemplo n.º 12
0
        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);
        }
Ejemplo n.º 13
0
        public void GCRootsIndirectHandles()
        {
            using DataTarget dataTarget = TestTargets.GCRoot2.LoadFullDump();

            ClrRuntime runtime = dataTarget.ClrVersions.Single().CreateRuntime();
            ClrHeap    heap    = runtime.Heap;
            GCRoot     gcroot  = new GCRoot(heap);

            ulong target = heap.GetObjectsOfType("IndirectTarget").Single();

            _ = Assert.Single(gcroot.EnumerateGCRoots(target, unique: true, CancellationToken.None));

            Assert.Equal(2, gcroot.EnumerateGCRoots(target, unique: false, CancellationToken.None).Count());
        }
Ejemplo n.º 14
0
        public void GCRoots()
        {
            using DataTarget dataTarget = TestTargets.GCRoot.LoadFullDump();
            using ClrRuntime runtime    = dataTarget.ClrVersions.Single().CreateRuntime();
            ClrHeap heap   = runtime.Heap;
            GCRoot  gcroot = new GCRoot(heap);

            ulong target = heap.GetObjectsOfType("TargetType").Single();

            GCRootsImpl(gcroot, heap, target, parallelism: 1, unique: false);
            GCRootsImpl(gcroot, heap, target, parallelism: 16, unique: false);

            GCRootsImpl(gcroot, heap, target, parallelism: 1, unique: true);
            GCRootsImpl(gcroot, heap, target, parallelism: 16, unique: true);
        }
Ejemplo n.º 15
0
        private void FindAllPathsImpl(GCRoot gcroot)
        {
            ClrHeap heap = gcroot.Heap;

            GetKnownSourceAndTarget(heap, out ulong source, out ulong target);

            LinkedList <ClrObject>[] paths = gcroot.EnumerateAllPaths(source, target, false, CancellationToken.None).ToArray();

            // There are exactly three paths to the object in the test target
            Assert.Equal(3, paths.Length);

            foreach (LinkedList <ClrObject> path in paths)
            {
                AssertPathIsCorrect(heap, path.ToImmutableArray(), source, target);
            }
        }
Ejemplo n.º 16
0
        public void FindAllPaths()
        {
            using DataTarget dataTarget = TestTargets.GCRoot.LoadFullDump();
            using ClrRuntime runtime    = dataTarget.ClrVersions.Single().CreateRuntime();
            ClrHeap heap   = runtime.Heap;
            GCRoot  gcroot = new GCRoot(heap);

            GetKnownSourceAndTarget(heap, out ulong source, out ulong target);

            LinkedList <ClrObject>[] paths = gcroot.EnumerateAllPaths(source, target, false, CancellationToken.None).ToArray();

            // There are exactly three paths to the object in the test target
            Assert.Equal(3, paths.Length);

            foreach (LinkedList <ClrObject> path in paths)
            {
                AssertPathIsCorrect(heap, path.ToImmutableArray(), source, target);
            }
        }
Ejemplo n.º 17
0
        private void GCRootsImpl(GCRoot gcroot)
        {
            ClrHeap heap   = gcroot.Heap;
            ulong   target = heap.GetObjectsOfType("TargetType").Single();

            GCRootPath[] rootPaths = gcroot.EnumerateGCRoots(target, unique: false, 1, CancellationToken.None).ToArray();
            CheckRootPaths(heap, target, rootPaths, mustContainStatic: true);

            rootPaths = gcroot.EnumerateGCRoots(target, unique: false, 16, CancellationToken.None).ToArray();
            CheckRootPaths(heap, target, rootPaths, mustContainStatic: true);

            rootPaths = gcroot.EnumerateGCRoots(target, unique: true, 1, CancellationToken.None).ToArray();
            CheckRootPaths(heap, target, rootPaths, mustContainStatic: true);

            // In the case where we say we only want unique rooting chains AND we want to look in parallel,
            // we cannot guarantee that we will pick the static roots over the stack ones.  Hence we don't
            // ensure that the static variable is enumerated.
            rootPaths = gcroot.EnumerateGCRoots(target, unique: true, 16, CancellationToken.None).ToArray();
            CheckRootPaths(heap, target, rootPaths, mustContainStatic: false);
        }
Ejemplo n.º 18
0
        public void FindSinglePathCancel()
        {
            using DataTarget dataTarget = TestTargets.GCRoot.LoadFullDump();
            using ClrRuntime runtime    = dataTarget.ClrVersions.Single().CreateRuntime();
            ClrHeap heap   = runtime.Heap;
            GCRoot  gcroot = new GCRoot(runtime.Heap);

            CancellationTokenSource cancelSource = new CancellationTokenSource();

            cancelSource.Cancel();

            GetKnownSourceAndTarget(runtime.Heap, out ulong source, out ulong target);
            try
            {
                gcroot.FindSinglePath(source, target, cancelSource.Token);
                Assert.True(false, "Should have been cancelled!");
            }
            catch (OperationCanceledException)
            {
            }
        }
Ejemplo n.º 19
0
        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);
            }
        }
Ejemplo n.º 20
0
        public IEnumerable <dynamic> ExtractData(ClrRuntime clr, Action <string> actionLog = null)
        {
            if (!clr.Heap.CanWalkHeap)
            {
                throw new InvalidOperationException("Cannot extract gc roots, heap is not in a walkable state");
            }
            var gcRootHandle      = new GCRoot(clr.Heap);
            var currentObjectName = string.Empty;

            try
            {
                gcRootHandle.ProgressUpdated += ReportGcRootEnumerationProgress;
                foreach (var obj in clr.Heap.EnumerateObjects())
                {
                    if (obj.Type == null)
                    {
                        continue;
                    }
                    currentObjectName = $"{obj.Type.Name} - #{obj.Address}";
                    actionLog?.Invoke($"Enumerating roots for ${currentObjectName}");
                    foreach (var rootPath in gcRootHandle.EnumerateGCRoots(obj.Address, true, Environment.ProcessorCount))
                    {
                        yield return(AutoMapper.Instance.Map <GcRoot>(rootPath,
                                                                      opts => opts.AfterMap((_, dst) => {
                            var segment = clr.Heap.GetSegmentByAddress(rootPath.Root.Object);
                            dst.Generation = segment.GetGeneration(rootPath.Root.Object.Address);
                        })));
                    }
                }
            }
            finally
            {
                gcRootHandle.ProgressUpdated -= ReportGcRootEnumerationProgress;
            }

            void ReportGcRootEnumerationProgress(GCRoot source, long processed) =>
            actionLog?.Invoke($"Finding GC Roots for {currentObjectName}: {processed} objects processed.");
        }
Ejemplo n.º 21
0
        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);
        }
Ejemplo n.º 22
0
        public void EnumerateAllPathshCancel()
        {
            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);

                CancellationTokenSource cancelSource = new CancellationTokenSource();
                cancelSource.Cancel();

                GetKnownSourceAndTarget(runtime.Heap, out ulong source, out ulong target);
                try
                {
                    gcroot.EnumerateAllPaths(source, target, false, cancelSource.Token).ToArray();
                    Assert.Fail("Should have been cancelled!");
                }
                catch (OperationCanceledException)
                {
                }
            }
        }
Ejemplo n.º 23
0
        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.True(false, "Should have been cancelled!");
            }
            catch (OperationCanceledException)
            {
            }
        }
Ejemplo n.º 24
0
        public void EnumerateGCRootsCancel()
        {
            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);

                ulong target = gcroot.Heap.GetObjectsOfType("TargetType").Single();

                CancellationTokenSource source = new CancellationTokenSource();
                source.Cancel();

                try
                {
                    gcroot.EnumerateGCRoots(target, false, source.Token).ToArray();
                    Assert.Fail("Should have been cancelled!");
                }
                catch (OperationCanceledException)
                {
                }
            }
        }
Ejemplo n.º 25
0
        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)));
            }
        }
Ejemplo n.º 26
0
    public static void Main(string[] args)
    {
        ulong target = 0;

        if (args.Length != 2)
        {
            PrintUsage();
            Environment.Exit(1);
        }
        else if (!File.Exists(args[0]))
        {
            PrintUsage();
            Console.WriteLine($"File not found: '{args[0]}'.");
            Environment.Exit(1);
        }
        else if (!ulong.TryParse(args[1], System.Globalization.NumberStyles.HexNumber, null, out target))
        {
            PrintUsage();
            Console.WriteLine($"Could not parse object address '{args[1]}'.");
            Environment.Exit(1);
        }

        using DataTarget dt      = DataTarget.LoadDump(args[0]);
        using ClrRuntime runtime = dt.ClrVersions.Single().CreateRuntime();
        ClrHeap heap = runtime.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))
        {
            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.
         */

        gcroot.ProgressUpdated += (GCRoot source, int current) =>
        {
            Console.WriteLine($"Current root {source} objects inspected={current:n0}");
        };

        // ==========================================

        /* 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;

        // ==========================================

        /* 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, false, Environment.ProcessorCount))
        {
            Console.Write($"{rootPath.Root} -> ");
            Console.WriteLine(string.Join(" -> ", rootPath.Path.Select(obj => obj.Address)));
        }
    }