다른 스레드의 호출 스택 덤프 구하는 방법
처음 이 기능이 필요했을 때 매우 쉬울 것이라고 생각했습니다. 왜냐하면, 이미 System.Diagnostics.StackTrace 타입에서 충분한 기능을 제공하기 때문입니다.
문제는 "다른 스레드"의 콜 스택을 얻을 때 발생했습니다. StackTrace의 생성자 목록에는 이에 대한 배려도 되어 있는데, 실제로 사용해 보면... 결과는 다소 당혹스럽습니다.
static void Main(string[] args)
{
Thread newThread = new Thread(Run);
newThread.Start();
System.Diagnostics.StackTrace trace = new System.Diagnostics.StackTrace(newThread, false);
}
private static void Run()
{
Thread.Sleep(1000 * 10);
}
위의 코드를 실행해 보면, 다음과 같은 예외를 만나게 됩니다.
Unhandled Exception: System.Threading.ThreadStateException: Thread in invalid state.
at System.Diagnostics.StackTrace.GetStackFramesInternal(StackFrameHelper sfh, Int32 iSkip, Exception e)
at System.Diagnostics.StackTrace.CaptureStackTrace(Int32 iSkip, Boolean fNeedFileInfo, Thread targetThread, Exception e)
at System.Diagnostics.StackTrace..ctor(Thread targetThread, Boolean needFileInfo)
at ConsoleApplication1.Program.Main(String[] args) in C:\temp\...\Program.cs:line 21
닷넷 소스 코드 디버깅을 이용해 보면, 다음의 라인에서 오류가 나는 것을 확인할 수 있습니다.
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern void GetStackFramesInternal(StackFrameHelper sfh, int iSkip, Exception e);
private void CaptureStackTrace(int iSkip, bool fNeedFileInfo, Thread targetThread,
Exception e)
{
m_iMethodsToSkip += iSkip;
StackFrameHelper StackF = new StackFrameHelper(fNeedFileInfo, targetThread);
GetStackFramesInternal (StackF, 0, e); // 예외 발생
// ... [생략]...
}
MethodImplOptions.InternalCall로 정의된 메서드라서 더 이상 추적해 볼 여지가 없습니다. 이제 ^^ 구글로 넘어가 봐야죠.
다행히, 이와 관련해서 "Rotor" 소스 코드를 찾을 수가 있었습니다.
Rotor Source code
; http://www.123aspx.com/rotor/RotorSrc.aspx?rot=42047
; https://github.com/SSCLI/sscli20_20060311/blob/master/clr/src/bcl/system/diagnostics/stacktrace.cs
; https://github.com/dotnet/coreclr/blob/release/2.2/src/vm/debugdebugger.cpp#L327-L800
중간에 보면, 다음과 같은 코드가 확인됩니다.
public StackTrace(Thread targetThread, bool needFileInfo)
{
m_iNumOfFrames = 0;
m_iMethodsToSkip = 0;
if (targetThread != null)
{
if (targetThread != Thread.CurrentThread)
{
if (((targetThread.ThreadState & System.Threading.ThreadState.Suspended) != 0)
&& ((targetThread.ThreadState & System.Threading.ThreadState.SuspendRequested) != 0)
&& (targetThread.ThreadState != System.Threading.ThreadState.Stopped)
&& (targetThread.ThreadState != System.Threading.ThreadState.Unstarted))
{
throw new ThreadStateException (
Environment.GetResourceString("ThreadState_NeedSuspended"));
}
}
else
targetThread = null;
}
CaptureStackTrace (METHODS_TO_SKIP, needFileInfo, targetThread, null);
}
오호... 그러니까, "실행 상태"에 있다면 콜 스택을 얻을 수 없다는 것이군요. 그래서, 최초의 StackTrace 코드를 다음과 같이 수정해 보았습니다.
static void Main(string[] args)
{
Thread newThread = new Thread(Run);
newThread.Start();
newThread.Suspend();
System.Diagnostics.StackTrace trace = new System.Diagnostics.StackTrace(newThread, false);
newThread.Resume();
Console.WriteLine(trace.ToString());
}
와~~~ ^^ 이제 예외가 발생하지 않습니다. 하지만! 출력 결과가 다소 실망스럽습니다.
at ConsoleApplication1.Program.Run()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
달랑, Run 메서드 프레임 하나.
이상하다 싶어서 중간에 newThread.Sleep(3000);을 한 번 더 주고 나니 정상적으로 아래와 같이 콜 스택이 출력되었습니다. (즉, 위의 콜 스택 출력 결과는 너무 빨리 시도를 했기 때문에 발생한 것으로 그것 자체도 정상적인 출력이었습니다.)
at System.Threading.Thread.SleepInternal(Int32 millisecondsTimeout)
at System.Threading.Thread.Sleep(Int32 millisecondsTimeout)
at ConsoleApplication1.Program.Run()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
이 외에, StackTrace.ToString의 결과를 사용자 정의하고 싶다면 다음의 코드를 참조하시면 도움이 되겠습니다.
How do I make a thread dump in .NET ? (a la JVM thread dumps)
; http://stackoverflow.com/questions/190236/how-do-i-make-a-thread-dump-in-net-a-la-jvm-thread-dumps
코드가 아닌, 툴을 이용하고 싶다면 다음의 방법이 좋겠고.
.NET production debugging 101
; http://www.tomergabel.com/NETProductionDebugging101.aspx
혹시나, StackTrace를 이용하여 부모 메서드에 따라 동작을 다르게 만들고 싶다면 다음과 같은 함정을 주의하시기 바랍니다.
Caveats about System.Diagnostics.StackTrace
; https://learn.microsoft.com/en-us/archive/blogs/jmstall/caveats-about-system-diagnostics-stacktrace
첨부된 파일은 위의 코드를 포함하고 있는 (VS 2010 beta2)프로젝트입니다.
끝!
[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]