如何获取当前C#程序所有线程的调用栈信息?

咨询区

  • Daniel Sperry

请问如何获取 .NET 程序当前所有线程的调用栈信息?我知道在 java 中只需调用 java.lang.Thread.getAllStackTraces() 方法即可。.

回答区

  • Will Calderwood

在 .NET 中并不容易实现,但可以使用诊断库 ClrMD ,可以在 nuget 上下载,它可以获取到当前进程的所有线程栈信息的快照,当然还可以获取 线程名 等各种附加信息,太强大了,参考如下代码:

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();
        }
    }
}

点评区

其实是这样的,如何想自动化获取当前的进程中所有线程的调用栈,用 ClrMD 即可,如果是为了对程序进行分析诊断,可以借助 windbg,再使用 sos 中的 ~*e !clrstack 命令即可,比如下面这样:

0:000> ~*e !clrstack 
OS Thread Id: 0x4110 (0)
Child SP       IP Call Site
0019f3e4 77a2166c [InlinedCallFrame: 0019f3e4] 
0019f3e0 79b49b71 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
0019f3e4 7a27b275 [InlinedCallFrame: 0019f3e4] Microsoft.Win32.Win32Native.ReadFile(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
0019f448 7a27b275 System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean, Boolean, Int32 ByRef)
0019f47c 7a27b17b System.IO.__ConsoleStream.Read(Byte[], Int32, Int32)
0019f49c 79b2e6a3 System.IO.StreamReader.ReadBuffer()
0019f4ac 79b2eb5b System.IO.StreamReader.ReadLine()
0019f4c8 7a3c3786 System.IO.TextReader+SyncTextReader.ReadLine()
0019f4d8 7a221845 System.Console.ReadLine()
0019f4e0 022f0983 *** WARNING: Unable to verify checksum for D:\net5\ConsoleApp1\ConsoleApp1\bin\Debug\ConsoleApp1.exe
ConsoleApp1.Program.Main(System.String[]) [D:\net5\ConsoleApp1\ConsoleApp1\Program.cs @ 25]
0019f67c 78e1f036 [GCFrame: 0019f67c] 
OS Thread Id: 0x11ac (24)
Child SP       IP Call Site
06c4f214 77a21bdc [HelperMethodFrame_1OBJ: 06c4f214] System.Threading.WaitHandle.WaitMultiple(System.Threading.WaitHandle[], Int32, Boolean, Boolean)
06c4f328 79ae8a86 System.Threading.WaitHandle.WaitAny(System.Threading.WaitHandle[], Int32, Boolean)
06c4f34c 7ace3f24 *** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_32\System\258d4259dd4377d917679ad4b058966e\System.ni.dll
System.Net.TimerThread.ThreadProc()
06c4f3a8 79a62e01 System.Threading.ThreadHelper.ThreadStart_Context(System.Object)
06c4f3b4 79a88604 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
06c4f420 79a88537 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
06c4f434 79a884f4 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
06c4f44c 79a62d5b System.Threading.ThreadHelper.ThreadStart()
06c4f630 78e1f036 [GCFrame: 06c4f630] 
06c4f774 78e1f036 [DebuggerU2MCatchHandlerFrame: 06c4f774] 
OS Thread Id: 0x2fdc (25)
Child SP       IP Call Site
0700f114 755be695 [InlinedCallFrame: 0700f114] 
0700f110 7ad6aa01 DomainBoundILStubClass.IL_STUB_PInvoke(System.Net.SSPIHandle ByRef, System.Net.SecurityBufferDescriptor, UInt32, UInt32*)
0700f114 7ad530f4 [InlinedCallFrame: 0700f114] System.Net.UnsafeNclNativeMethods+NativeNTSSPI.DecryptMessage(System.Net.SSPIHandle ByRef, System.Net.SecurityBufferDescriptor, UInt32, UInt32*)
0700f154 7ad530f4 System.Net.SSPISecureChannelType.DecryptMessage(System.Net.SafeDeleteContext, System.Net.SecurityBufferDescriptor, UInt32)
0700f194 7ad51a1a System.Net.SSPIWrapper.EncryptDecryptHelper(OP, System.Net.SSPIInterface, System.Net.SafeDeleteContext, System.Net.SecurityBuffer[], UInt32)
0700f1fc 7ad52fe2 System.Net.Security.SecureChannel.Decrypt(Byte[], Int32 ByRef, Int32 ByRef)
0700f21c 7ad52e07 System.Net.Security._SslStream.ProcessFrameBody(Int32, Byte[], Int32, Int32, System.Net.AsyncProtocolRequest)
0700f248 7ad52d6b System.Net.Security._SslStream.ReadFrameCallback(System.Net.AsyncProtocolRequest)
0700f274 7ad4e576 System.Net.AsyncProtocolRequest.CompleteRequest(Int32)
0700f280 7ad4e537 System.Net.FixedSizeReader.CheckCompletionBeforeNextRead(Int32)
0700f28c 7ad4e4c6 System.Net.FixedSizeReader.ReadCallback(System.IAsyncResult)
0700f2b4 7ad14cf6 System.Net.LazyAsyncResult.Complete(IntPtr)
0700f2e8 7ad49d15 System.Net.ContextAwareResult.CompleteCallback(System.Object)
0700f2ec 79a88604 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
0700f358 79a88537 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
0700f36c 79a884f4 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
0700f384 7ad4856d System.Net.ContextAwareResult.Complete(IntPtr)
0700f39c 7ad14c71 System.Net.LazyAsyncResult.ProtectedInvokeCallback(System.Object, IntPtr)
0700f3c4 7ad48378 System.Net.Sockets.BaseOverlappedAsyncResult.CompletionPortCallback(UInt32, UInt32, System.Threading.NativeOverlapped*)
0700f3f8 79aea3dd System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32, UInt32, System.Threading.NativeOverlapped*)
0700f4f4 78e1f036 [GCFrame: 0700f4f4] 
0700f604 78e1f036 [DebuggerU2MCatchHandlerFrame: 0700f604] 
OS Thread Id: 0x4214 (26)
Child SP       IP Call Site
GetFrameContext failed: 1
00000000 00000000 

0:000> !tp
CPU utilization: 13%
Worker Thread: Total: 13 Running: 0 Idle: 13 MaxLimit: 2047 MinLimit: 12
Work Request in Queue: 0
--------------------------------------
Number of Timers: 1
--------------------------------------
Completion Port Thread:Total: 16 Free: 6 MaxFree: 24 CurrentLimit: 16 MaxLimit: 1000 MinLimit: 12