Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get non-current thread's stacktrace?

It is possible to get stacktrace using System.Diagnostics.StackTrace, but thread has to be suspended. Suspend and Resume function are obsolete, so I expect that better way exists.

like image 449
bh213 Avatar asked Nov 12 '08 19:11

bh213


People also ask

How do you get a StackTrace?

You can obtain a stack trace from a thread – by calling the getStackTrace method on that Thread instance. This invocation returns an array of StackTraceElement, from which details about stack frames of the thread can be extracted.

How do you analyze a StackTrace?

Analyze external stack tracesFrom the main menu, select Code | Analyze Stack Trace or Thread Dump. In the Analyze Stack Trace dialog that opens, paste the external stack trace or thread dump into the Put a stacktrace here: text area. Click OK. The stack trace is displayed in the Run tool window.

What is error StackTrace?

Stack trace error is a generic term frequently associated with long error messages. The stack trace information identifies where in the program the error occurs and is helpful to programmers. For users, the long stack track information may not be very useful for troubleshooting web errors.

What is StackTrace C#?

The StackTrace property returns the frames of the call stack that originate at the location where the exception was thrown. You can obtain information about additional frames in the call stack by creating a new instance of the System. Diagnostics. StackTrace class and using its StackTrace. ToString method.


2 Answers

NB: Skip to the bottom of this answer for an update.

Here's what's worked for me so far:

StackTrace GetStackTrace (Thread targetThread) {     StackTrace stackTrace = null;     var ready = new ManualResetEventSlim();      new Thread (() =>     {         // Backstop to release thread in case of deadlock:         ready.Set();         Thread.Sleep (200);         try { targetThread.Resume(); } catch { }     }).Start();      ready.Wait();     targetThread.Suspend();     try { stackTrace = new StackTrace (targetThread, true); }     catch { /* Deadlock */ }     finally     {         try { targetThread.Resume(); }         catch { stackTrace = null;  /* Deadlock */  }     }      return stackTrace; } 

If it deadlocks, the deadlock is automatically freed and you get back a null trace. (You can then call it again.)

I should add that after a few days of testing, I've only once been able to create a deadlock on my Core i7 machine. Deadlocks are common, though, on single-core VM when the CPU runs at 100%.

Update: This approach works only for .NET Framework. In .NET Core and .NET 5+, Suspend and Resume cannot be called, so you must use an alternative approach such as Microsoft's ClrMD library. Add a NuGet reference to the Microsoft.Diagnostics.Runtime package; then you can call DataTarget.AttachToProcess to obtain information about threads and stacks. Note that you cannot sample your own process, so you must start another process, but that is not difficult. Here is a basic Console demo that illustrates the process, using a redirected stdout to send the stack traces back to the host:

using Microsoft.Diagnostics.Runtime; using System.Diagnostics; using System.Reflection;  if (args.Length == 3 &&     int.TryParse (args [0], out int pid) &&     int.TryParse (args [1], out int threadID) &&     int.TryParse (args [2], out int sampleInterval)) {     // We're being called from the Process.Start call below.     ThreadSampler.Start (pid, threadID, sampleInterval); } else {     // Start ThreadSampler in another process, with 100ms sampling interval     var startInfo = new ProcessStartInfo (         Path.ChangeExtension (Assembly.GetExecutingAssembly().Location, ".exe"),         Process.GetCurrentProcess().Id + " " + Thread.CurrentThread.ManagedThreadId + " 100")     {         RedirectStandardOutput = true,         CreateNoWindow = true     };      var proc = Process.Start (startInfo);      proc.OutputDataReceived += (sender, args) =>         Console.WriteLine (args.Data != "" ? "  " + args.Data : "New stack trace:");      proc.BeginOutputReadLine();      // Do some work to test the stack trace sampling     Demo.DemoStackTrace();      // Kill the worker process when we're done.     proc.Kill(); }  class Demo {     public static void DemoStackTrace()     {         for (int i = 0; i < 10; i++)         {             Method1();             Method2();             Method3();         }     }      static void Method1()     {         Foo();     }      static void Method2()     {         Foo();     }      static void Method3()     {         Foo();     }      static void Foo() => Thread.Sleep (100); }  static class ThreadSampler {     public static void Start (int pid, int threadID, int sampleInterval)     {         DataTarget target = DataTarget.AttachToProcess (pid, false);         ClrRuntime runtime = target.ClrVersions [0].CreateRuntime();          while (true)         {             // Flush cached data, otherwise we'll get old execution info.             runtime.FlushCachedData();              foreach (ClrThread thread in runtime.Threads)                 if (thread.ManagedThreadId == threadID)                 {                     Console.WriteLine();   // Signal new stack trace                      foreach (var frame in thread.EnumerateStackTrace().Take (100))                         if (frame.Kind == ClrStackFrameKind.ManagedMethod)                             Console.WriteLine ("    " + frame.ToString());                      break;                 }              Thread.Sleep (sampleInterval);         }     } } 

This is the mechanism that LINQPad 6+ uses to show live execution tracking in queries (with additional checks, metadata probing and a more elaborate IPC).

like image 69
Joe Albahari Avatar answered Sep 16 '22 14:09

Joe Albahari


This is an old Thread, but just wanted to warn about the proposed solution: The Suspend and Resume solution does not work - I just experienced a deadlock in my code trying the sequence Suspend/StackTrace/Resume.

The Problem is that StackTrace constructor does RuntimeMethodHandle -> MethodBase conversions, and this changes a internal MethodInfoCache, which takes a lock. The deadlock occurred because the thread I was examining also was doing reflection, and was holding that lock.

It is a pity that the suspend/resume stuff is not done inside the StackTrace constructor -then this problem could easily have been circumvented.

like image 45
Dirk Bonné Avatar answered Sep 16 '22 14:09

Dirk Bonné