C# - 닷넷 코어에서 다른 스레드의 callstack을 구하는 방법
.NET Framework와는 다르게,
다른 스레드의 호출 스택 덤프 구하는 방법
; https://www.sysnet.pe.kr/2/0/802
.NET Core에서는 기본적으로 System.Diagnostics.StackTrace 타입이 없습니다. 그것을 사용하려면 명시적으로 Nuget으로부터 System.Diagnostics.StackTrace를 참조 추가해야 합니다.
Install-Package System.Diagnostics.StackTrace
그런데, nuget의 StackTrace 타입에는 Thread 타입을 받는 생성자가 없습니다.
Thread newThread = new Thread(Run);
newThread.Start();
newThread.Suspend();
System.Diagnostics.StackTrace trace = new System.Diagnostics.StackTrace(/* newThread, */ false);
newThread.Resume();
그래서 다른 스레드의 호출 스택을 받을 방법이 없습니다.
이를 우회할 수 있는 현실적인 방법으로는 그나마 ClrMD가 적당해 보입니다.
.NET 스레드 콜 스택 덤프 (7) - ClrMD(Microsoft.Diagnostics.Runtime)를 이용한 방법
; https://www.sysnet.pe.kr/2/0/11043
Install-Package Microsoft.Diagnostics.Runtime
다음은 .NET Core에서의 사용 예제입니다.
using Microsoft.Diagnostics.Runtime;
using System;
using System.Diagnostics;
using System.Threading;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Main: " + Thread.CurrentThread.ManagedThreadId);
Thread t = new Thread(CallMethod);
t.Start();
DoLoop();
}
private static void DoLoop()
{
int i = 0;
while (true)
{
Console.WriteLine("==================================================" + (i++).ToString());
Thread.Sleep(1000);
}
}
private static void CallMethod(object obj)
{
while (true)
{
var pid = Process.GetCurrentProcess().Id;
using (var dataTarget = DataTarget.AttachToProcess(pid, false))
{
ClrInfo runtimeInfo = dataTarget.ClrVersions[0];
using (ClrRuntime runtime = runtimeInfo.CreateRuntime())
{
foreach (var t in runtime.Threads)
{
foreach (ClrStackFrame st in t.EnumerateStackTrace())
{
// Console.WriteLine($"{t.ManagedThreadId} {st.Method} {st.Kind} at 0x{st.InstructionPointer.ToString("x")}");
Console.WriteLine(st);
}
Console.WriteLine();
}
}
}
}
}
}
}
/* 출력 결과 Console.WriteLine($"{t.ManagedThreadId} {st.Method} {st.Kind} at 0x{st.InstructionPointer.ToString("x")}");
1 Runtime at 0x0
1 System.Threading.Thread.Sleep(Int32) ManagedMethod at 0x7ff83de0b92b
1 ConsoleApp1.Program.DoLoop() ManagedMethod at 0x7ff7e1e46347
1 ConsoleApp1.Program.Main(System.String[]) ManagedMethod at 0x7ff7e1e41046
1 Runtime at 0x7ff841953903
1 Runtime at 0x7ff841953903
2 Runtime at 0x0
3 ManagedMethod at 0x0
*/
/* 출력 결과 Console.WriteLine(st);
[HelperMethodFrame]
System.Threading.Thread.Sleep(Int32)
ConsoleApp1.Program.DoLoop()
ConsoleApp1.Program.Main(System.String[])
[GCFrame]
[GCFrame]
[DebuggerU2MCatchHandlerFrame]
*/
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
보는 바와 같이 순수 Managed 영역의 호출 스택만 정상적으로 값을 받아올 수 있고 그 외 Native/Runtime 등의 호출 스택 정보는 알 수 없습니다. 또한, (아마도 향후 지원할지는 모르겠지만) 소스 코드 라인 정보도 구할 방법이 없습니다.
게다가, 이전 버전(1.1.142101)과는 달리 현재 2.0.161401에서는 저렇게 무한 루프를 돌며 호출 스택을 구하면 어느 순간 화면에 "[Unknown Frame]"이 반복되며 더 이상 정상적인 동작을 하지 않는 문제가 있습니다. (그러니까, "그나마" 나은 방법입니다. ^^;)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]