35

In java it is possible to get a snapshot of the stacktraces of all running threads. This is done with java.lang.Thread.getAllStackTraces() (it returns Map<Thread,StackTraceElement[]>).

How can this be done with .net?

JleruOHeP
  • 10,106
  • 3
  • 45
  • 71
Daniel Sperry
  • 4,381
  • 4
  • 31
  • 41

8 Answers8

39

So I actually just had to figure out how to do this -- haven't used this solution extensively in production yet, but theres a relatively new library called ClrMd.

http://blogs.msdn.com/b/dougste/archive/2013/05/04/clrmd-net-crash-dump-and-live-process-inspection.aspx

Using it, I'm able to attach to my own process and get a stack trace for all live threads. Using this when a deadlock is detected before restarting our app like so:

var result = new Dictionary<int, string[]>();

var pid = Process.GetCurrentProcess().Id;

using (var dataTarget = DataTarget.AttachToProcess(pid, 5000, AttachFlag.Passive))
{
    ClrInfo runtimeInfo = dataTarget.ClrVersions[0];
    var runtime = runtimeInfo.CreateRuntime();

    foreach (var t in runtime.Threads)
    {
        result.Add(
            t.ManagedThreadId,
            t.StackTrace.Select(f =>
            {
                if (f.Method != null)
                {
                    return f.Method.Type.Name + "." + f.Method.Name;
                }

                return null;
            }).ToArray()
        );
    }
}

var json = JsonConvert.SerializeObject(result);

zip.AddEntry("_threads.json", json);

The really important thing to get that to work from the same process is AttachFlag.Passive

If you just do DataTarget.AttachToProcess(pid, 5000), it'll do an "invasive" attach which attempts to pause the process. This throws an exception when you try to attach to your own process, I'm assuming because you can't pause your application while trying to attach from your application or something like that.

Joshua Evensen
  • 1,544
  • 1
  • 15
  • 33
  • just got back some debug dumps from a client location and this worked for us. pretty cool. – Joshua Evensen Jul 09 '14 at 20:02
  • Great code, thank you! I could suggest to add method signature as with f.Method.GetFullSignature(). It may be helpful in case of overrides – Andrey Jul 14 '14 at 04:51
  • Just an update -- i've gotten results from this from a pretty large number of client installs and it's always worked and been decently helpful. – Joshua Evensen Mar 15 '15 at 17:53
  • This code throws an exception for me on the line that calls CreateRuntime(dacLocation) with the message "This runtime is not initialized and contains no data." – Drake Jul 26 '15 at 05:27
  • Just a guess, sounds like you're trying to debug a non .NET process? Or maybe hmmm does ClrVersions have multiple elements in it? – Joshua Evensen Jul 27 '15 at 18:51
  • 1
    It was because ClrMD currently doesn't support .NET 4.6. And this post has a solution: http://stackoverflow.com/questions/31633541/clrmd-throws-exception-when-creating-runtime/31745689#31745689 – Drake Aug 02 '15 at 04:26
  • This is awesome. However, I would just `Select` on `f.DisplayString` - more reliable and informative. – RobSiklos Oct 14 '15 at 01:30
  • I get incomplete stack traces for asp.net MVC actions. Visual studio shows managed-to-native transition, followed by native-to-managed, and then the rest of the managed stack, which I can't see with ClrMd. It stops at IL_STUB_PInvoke. Anyone knows how to fix this? Or maybe there is another library that would work better in my case? – youen Oct 13 '16 at 13:43
  • if you're just using the stack traces for debugging, might be a good idea to just get memory dumps honestly. you can actually generate those from code too. – Joshua Evensen Oct 13 '16 at 13:49
  • 1
    `An unhandled exception of type 'System.NullReferenceException' occurred in Microsoft.Diagnostics.Runtime.dll` in `result.Add` – Ruslan K. Oct 19 '16 at 07:41
  • 1
    Be aware: This library causes a memory leak when reading stack traces: https://github.com/Microsoft/clrmd/issues/47. You probably don't want to use it in production. – Zero3 Mar 23 '17 at 20:57
  • I am not sure about this, but it seems the memory leak issue has been fixed. https://github.com/Microsoft/clrmd/commit/8fcbb2a90281a9d3edafc246f62f239c1d0a4eb6 Anyone already tried this and can confirm? – Payam Jul 31 '18 at 08:48
  • This code has a memory leak because ClrInfo implements IDisposable. You need to wrap it in a using statement or otherwise release the memory when done. – Dan Oct 14 '22 at 16:42
  • This code has a memory leak because ClrInfo implements IDisposable. You need to wrap it in a using statement or otherwise release the memory when done. – Dan Oct 14 '22 at 16:42
7

If you want this for debugging purposes alone, the SOS extensions to WinDbg can give you this information.

The command to run is "*~e !clrstack".

Inside of a running C# program, there is no public way to enumerate managed threads or look them up by ID. Even if you could, getting a stack trace on a different thread would likely require it to be suspended, which has some risks of side effects (see why this is obsolete).

The other alternative is to enlist threads as they are known, and scan them at your leisure. This is probably only possible if you're explicitly creating thread objects rather than using the thread pool.

That said, it is also hard for me to see what purpose this approach would serve. If it is for debugging, there are far more powerful techniques that can be done in-memory or on mini-dumps. If it is for logging, then it might make sense to have logging calls contribute their own stacks.

Mason of Words
  • 101
  • 1
  • 2
7

If you want to get stack traces of all the threads within managed code then you could try mdbg. Have a look at Managed Stack Explorer it does use mdbg and gets stacks of all the threads.

Naveen
  • 4,092
  • 29
  • 31
3

Updated code to get a snapshot of all stack traces that uses the answer from @Joshua Evensen as a base. You'll still need to install NuGet package CLR Memory Diagnostics (ClrMD). This snippet also includes extra code to get the thread names, but this isn't required if you just want the stack traces.

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.Diagnostics.Runtime;

namespace CSharpUtils.wrc.utils.debugging
{
    public static class StackTraceAnalysis
    {
        public static string GetAllStackTraces()
        {
            var result = new StringBuilder();
            
            using (var target = DataTarget.CreateSnapshotAndAttach(Process.GetCurrentProcess().Id))
            {
                var runtime = target.ClrVersions.First().CreateRuntime();

                // We can't get the thread name from the ClrThead objects, so we'll look for
                // Thread instances on the heap and get the names from those.    
                var threadNameLookup = new Dictionary<int, string>();
                foreach (var obj in runtime.Heap.EnumerateObjects())
                {
                    if (!(obj.Type is null) && obj.Type.Name == "System.Threading.Thread")
                    {
                        var threadId = obj.ReadField<int>("m_ManagedThreadId");
                        var threadName = obj.ReadStringField("m_Name");
                        threadNameLookup[threadId] = threadName;
                    }
                }

                foreach (var thread in runtime.Threads)
                {
                    threadNameLookup.TryGetValue(thread.ManagedThreadId, out string threadName);
                    result.AppendLine(
                        $"ManagedThreadId: {thread.ManagedThreadId}, Name: {threadName}, OSThreadId: {thread.OSThreadId}, Thread: IsAlive: {thread.IsAlive}, IsBackground: {thread.IsBackground}");
                    foreach (var clrStackFrame in thread.EnumerateStackTrace())
                        result.AppendLine($"{clrStackFrame.Method}");
                }
            }

            return result.ToString();
        }
    }
}
Will Calderwood
  • 4,393
  • 3
  • 39
  • 64
  • It looks like the field names have changed in newer .net versions. For example from m_Name to _name I changed code to iterate over fields and ensure field names end with "name" or "managedthreadid" ignoring case to find the correct field values. – pdross May 18 '22 at 22:28
0

You can use ProcInsp, which has a web API to get threads with their stacks in JSON. The web API is available at /Process/%PID%/Threads (use a GET request).

Disclaimer: I'm the developer of ProcInsp. The tool is under the MIT licence and is free for use.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
-1

As Mason of Words suggests, this doesn't look possible from within the managed code itself. Could you clarify why you need this: there might be a better solution?

For example, if you attach to the process in Visual Studio and press "pause", then the "Threads" window will list all managed threads, and the "Stacktrace" window can show the current stack trace for each thread. Would that suffice?

Rich
  • 15,048
  • 2
  • 66
  • 119
-3

There is a StackTrace class

var trace = new System.Diagnostics.StackTrace(exception);

http://msdn.microsoft.com/en-us/library/system.diagnostics.stacktrace.aspx

Yevhen
  • 1,897
  • 2
  • 14
  • 25
-6

You can loop on System.Diagnostics.Process.GetCurrentProcess().Threads and for each Thread create a StackTrace object with the .ctor that takes a Thread as its param.

Ariel
  • 5,752
  • 5
  • 49
  • 59
  • 4
    As with @MarcosMeli's answer, this is incorrect. As @Hans Loken states, the GetCurrentProcess() method returns a ProcessThreadCollection which contains ProcessThread objects, not Thread objects. As far as I can tell you cannot get a stack trace from a ProcessThread as it represents the Windows native thread. – Jeremy Wiebe Nov 24 '10 at 14:13