성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>.NET 스레드 콜 스택 덤프 (6) - MDbg를 이용한 방법</h1> <p> ".NET 스레드 콜 스택 덤프" 관련 시리즈의 마지막이 될 것 같군요. ^^<br /> <br /> 4편에 보면 MDbg를 이용한 방법을 설명드렸는데요.<br /> <br /> 오늘은 문득, MDbg가 GUI에서 Attach했을 때 .NET 4.0 응용 프로그램이 지원되지 않았던 것일 뿐 명령행 프롬프트 창에서는 가능하지 않을까...라는 생각이 들었습니다. ^^; 세상에나 그런 간단한 생각이 이제서야 떠오르다니.<br /> <br /> 그래서 실제로 한번 해봤습니다. 다음과 같이 예제로 Sleep 프로그램을 실행하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { Console.WriteLine(Process.GetCurrentProcess().Id); Thread.Sleep(new TimeSpan(60, 0, 0)); } </pre> <br /> MDbg를 이용해 콜스택을 뜨면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CLR Managed Debugger (mdbg) Sample ; http://www.microsoft.com/en-us/download/details.aspx?id=19621 ; <a target='tab' href='https://github.com/ichengzi/MDbg-Sample'>https://github.com/ichengzi/MDbg-Sample</a> </pre> <br /> MDbg 명령행 모드에서 .NET 4.0 응용 프로그램을 attach시켜 콜 스택을 뜰 수 있습니다.<br /> <br /> <img alt='managed_thread_call_stack_dump_0.png' src='/SysWebRes/bbs/managed_thread_call_stack_dump_0.png' /> <br /><br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > MDbg (Managed debugger) v2.1.0.0 started. Copyright (C) Microsoft Corporation. All rights reserved. For information about commands type "help"; to exit program type "quit". mdbg> <span style='color: blue; font-weight: bold'>a 11336</span> [p#:0, t#:1] mdbg> <span style='color: blue; font-weight: bold'>t 0</span> Current thread is #0 [waiting]. STOP AttachComplete IP: 0 @ System.Threading.Thread.Sleep - MAPPING_APPROXIMATE [p#:0, t#:0] mdbg> <span style='color: blue; font-weight: bold'>w</span> Thread [#:0] *0. System.Threading.Thread.Sleep (source line information unavailable) 1. System.Threading.Thread.Sleep (source line information unavailable) 2. ConsoleApplication1.Program.Main (d:\...\ConsoleApplication1\Program.cs:37) [p#:0, t#:0] mdbg> <span style='color: blue; font-weight: bold'>de</span> mdbg> </pre> <br /> 간단하게 명령어 설명을 해보면 "a 11336"은 PID가 11336인 프로세스에 디버거를 attach시키고, "t 0"을 통해 0번 Managed Thread로 문맥 전환을 한 후 "w(here)" 명령어를 이용해 최종적으로 콜스택을 뜨게 됩니다. 마지막으로 "de(ttach)" 명령으로 디버거 연결을 끊게 되는데, 만약 명시적인 de 명령없이 mdbg를 종료시키면 디버거로 연결된 프로세스까지 강제종료되므로 주의해야 합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 자, 그럼 이 명령어를 코드에서 그대로 수행해 주면 .NET 2.0/4.0 프로세스의 콜스택을 얻는 것이 가능하겠지요. ^^<br /> <br /> MDbg는 소스코드가 공개되어 있으므로 Visual Studio에서 열어보면 다음과 같은데,<br /> <br /> <img alt='managed_thread_call_stack_dump_1.png' src='/SysWebRes/bbs/managed_thread_call_stack_dump_1.png' /> <br /><br /> 이 중에서 라이브러리처럼 사용하는 경우 실제 필요한 것은 corapi, mdbgeng, NativeDebugWrappers, raw 프로젝트입니다. 이들을 참조하는 간단한 콘솔 프로젝트를 만들고 다음과 같이 코딩을 해주면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Diagnostics; using System.Globalization; using System.Text; using Microsoft.Samples.Debugging.CorDebug; using Microsoft.Samples.Debugging.MdbgEngine; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { int pid = 4768; ThreadDump(pid); } private static void ThreadDump(int pid) { MDbgEngine debugger = new MDbgEngine(); if (Process.GetCurrentProcess().Id == pid) { Console.WriteLine("Cannot attach to myself!"); return; } // Can't attach to a process that we're already debugging. foreach (MDbgProcess procOther in debugger.Processes) { if (pid == procOther.CorProcess.Id) { Console.WriteLine("Can't attach to process " + pid + " because it's already being debugged"); return; } } string debuggerVersion = CorDebugger.GetDefaultDebuggerVersion(); // "attach" 명령어 단계 string version = debuggerVersion; if (string.IsNullOrEmpty(version) == true) { Console.WriteLine("Can't determine .NET Version"); return; } <span style='color: blue; font-weight: bold'> MDbgProcess p = null; try { p = debugger.Attach(pid, version); } catch (Exception ex) { Console.WriteLine("Attach failed: " + ex.ToString()); return; } p.Go().WaitOne(); // "t 0" 명령어 단계 MDbgThread targetThread = p.Threads[0]; p.Threads.Active = targetThread; // "where" 명령어 단계 StringBuilder sb = new StringBuilder(); foreach (MDbgThread t in p.Threads) { InternalWhereCommand(sb, t, 0, false); } // InternalWhereCommand(sb, targetThread, 0, false); Console.WriteLine(sb.ToString()); // "dettach" 명령어 단계 p.Detach(); if (p.CanExecute() == true) { p.StopEvent.WaitOne(); }</span> } private static void InternalWhereCommand(StringBuilder sb, MDbgThread thread, int depth, bool verboseOutput) { Debug.Assert(thread != null); bool ShowInternalFrames = true; sb.AppendLine("Thread [#:" + thread.Id + "]"); MDbgFrame af = thread.HaveCurrentFrame ? thread.CurrentFrame : null; MDbgFrame f = thread.BottomFrame; int i = 0; while (f != null && (depth == 0 || i < depth)) { string line; if (f.IsInfoOnly) { if (!ShowInternalFrames) { // in cases when we don't want to show internal frames, we'll skip them f = f.NextUp; continue; } line = string.Format(CultureInfo.InvariantCulture, " {0}", f.ToString()); } else { string frameDescription = f.ToString(verboseOutput ? "v" : null); line = string.Format(CultureInfo.InvariantCulture, "{0}{1}. {2}", f.Equals(af) ? "*" : " ", i, frameDescription); ++i; } sb.AppendLine(line); f = f.NextUp; } if (f != null && depth != 0) // means we still have some frames to show.... { sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "displayed only first {0} frames. For more frames use -c switch", depth)); } } } } </pre> <br /> 끝입니다. 더 설명할 것이 없군요. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로 다음과 같은 오류가 발생한다면?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Attach failed: System.Runtime.InteropServices.COMException (0x80131C30): The operation failed because debuggee and debugger are on incompatible platforms. (Exception from HRESULT: 0x80131C30) at Microsoft.Samples.Debugging.CorDebug.NativeApi.ICorDebug.DebugActiveProcess(UInt32 id, Int32 win32Attach, ICorDebugProcess& ppProcess) at Microsoft.Samples.Debugging.CorDebug.CorDebugger.DebugActiveProcess(Int32processId, Boolean win32Attach, CorRemoteTarget target) in d:\...\debugger\corapi\Debugger.cs:line 340 at Microsoft.Samples.Debugging.MdbgEngine.MDbgProcess.Attach(Int32 processId, SafeWin32Handle attachContinuationEvent, CorRemoteTarget target) in d:\...\debugger\mdbgeng\Process.cs:line 1225 at Microsoft.Samples.Debugging.MdbgEngine.MDbgProcess.Attach(Int32 processId, SafeWin32Handle attachContinuationEvent) in d:\...\debugger\mdbgeng\Process.cs:line 1183 at Microsoft.Samples.Debugging.MdbgEngine.MDbgEngine.Attach(Int32 processId, SafeWin32Handle attachContinuationEvent, String version) in d:\...\debugger\mdbgeng\Engine.cs:line 571 at Microsoft.Samples.Debugging.MdbgEngine.MDbgEngine.Attach(Int32 processId, String version) in d:\...\debugger\mdbgeng\Engine.cs:line 553 at ConsoleApplication1.Program.ThreadDump(Int32 pid) in d:\...\ConsoleApplication1\Program.cs:line 53 </pre> <br /> 대상 응용 프로그램과 그것에 attach하려는 프로그램의 플랫폼 빌드가 맞지 않아서입니다. 가령 대상 응용 프로그램은 32비트인데, 덤프 뜨는 측은 64비트인 경우 저런 오류가 발생합니다.<br /> <br /> 재미있는 점이 하나 있다면 대상 응용 프로그램이 64비트이고, 덤프 뜨는 측이 32비트인 경우에는 오류 메시지가 정확하지 않아서 좀 당혹스럽습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > System.Runtime.InteropServices.COMException (0x80070032): The request is not supported. (Exception from HRESULT: 0x80070032) </pre> <br /> 또는 이렇게도 오류가 발생할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Unhandled Exception: System.Runtime.InteropServices.COMException: Only part of a ReadProcessMemory or WriteProcessMemory request was completed. (Exception from HRESULT: 0x8007012B) at Microsoft.Samples.Debugging.CorDebug.ICLRMetaHost.EnumerateLoadedRuntimes(ProcessSafeHandle hndProcess) at Microsoft.Samples.Debugging.CorDebug.CLRMetaHost.EnumerateLoadedRuntimes(Int32 processId) in d:\...\corapi\Debugger.cs:line 747 at Microsoft.Samples.Debugging.MdbgEngine.MdbgVersionPolicy.GetDefaultAttachVersion(Int32 processId) in d:\...\mdbgeng\Engine.cs:line 370 at ConsoleApplication1.Program.ThreadDump(Int32 pid) in d:\...\Program.cs:line 44 at ConsoleApplication1.Program.Main(String[] args) in d:\...\Program.cs:line 20 </pre> <br /> 이어서 다른 예외를 소개하자면, MDbgEngine.Attach시에 "Failed to load the runtime" 오류가 발생할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > string debuggerVersion = "<span style='color: blue; font-weight: bold'>v4.0.30319.34003</span>"; p = debugger.Attach(pid, debuggerVersion); // "Failed to load the runtime" </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > System.Runtime.InteropServices.COMException was caught HResult=-2146232576 Message=Failed to load the runtime. (Exception from HRESULT: 0x80131700) Source=corapi ErrorCode=-2146232576 StackTrace: at Microsoft.Samples.Debugging.CorDebug.ICLRMetaHost.GetRuntime(String pwzVersion, Guid& riid) at Microsoft.Samples.Debugging.CorDebug.CLRMetaHost.GetRuntime(String version) in d:\...\corapi\Debugger.cs:line 763 at Microsoft.Samples.Debugging.CorDebug.CorDebugger.InitFromVersion(String debuggerVersion) in d:\...\corapi\Debugger.cs:line 405 at Microsoft.Samples.Debugging.CorDebug.CorDebugger..ctor(String debuggerVersion) in d:\...\corapi\Debugger.cs:line 95 at Microsoft.Samples.Debugging.MdbgEngine.MDbgEngine.Attach(Int32 processId, SafeWin32Handle attachContinuationEvent, String version) in d:\...\mdbgeng\Engine.cs:line 570 at Microsoft.Samples.Debugging.MdbgEngine.MDbgEngine.Attach(Int32 processId, String version) in d:\...\mdbgeng\Engine.cs:line 553 at ConsoleApplication1.Program.ThreadDump(Int32 pid) in d:\...\Program.cs:line 61 InnerException: </pre> <br /> 원인이 좀 황당합니다. ^^ 디버거 버전으로 전달되는 문자열은 [major].[minor].[build].[revision]가 있는데요. 여기서 허용되는 버전 길이는 build까지입니다. 따라서 반드시 다음과 같이 전달해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > string debuggerVersion = "<span style='color: blue; font-weight: bold'>v4.0.30319</span>"; p = debugger.Attach(pid, debuggerVersion); </pre> <br /> 이것이 왜 문제가 되냐면 테스트하다 보니 가끔씩 MdbgVersionPolicy.GetDefaultAttachVersion의 결과로 revision 번호가 포함될 때가 있기 때문입니다.<br /> <br /> 그것 외에, 대상 프로세스의 CLR 버전을 구하는 방법으로 GetDebuggerVersionFromPid 메서드가 제공되는데요. 대상 응용 프로그램이 CLR 2인 경우에는 잘 수행되지만 CLR 4인 경우에는 다음과 같은 오류가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > string debuggerVersion = CorDebugger.GetDebuggerVersionFromPid(pid); Unhandled Exception: System.ArgumentException: Value does not fall within the expected range. at Microsoft.Samples.Debugging.CorDebug.NativeMethods.GetVersionFromProcess(ProcessSafeHandle hProcess, StringBuilder versionString, Int32 bufferSize, Int32& dwLength) at Microsoft.Samples.Debugging.CorDebug.CorDebugger.GetDebuggerVersionFromPid(Int32 pid) in d:\...\debugger\corapi\Debugger.cs:line 59 at ConsoleApplication1.Program.ThreadDump(Int32 pid) in d:\...\Program.cs:line 44 at ConsoleApplication1.Program.Main(String[] args) in d:\...\Program.cs:line 20 </pre> <br /> 왜냐하면, 이것의 근간이 되는 GetVersionFromProcess Win32 API가 .NET 4.0의 mscoree.dll에서는 deprecated 상태이기 때문입니다. 설령 Win32 API로 직접 호출해도 빈 문자열만 나옵니다.<br /> <br /> 마지막으로 대상 응용 프로그램이 CLR 2인데, 덤프뜨려는 측에서 v4.0 문자열로 Attach 시키면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > string debuggerVersion = "v4.0.30319"; p = debugger.Attach(pid, debuggerVersion); p.Go().WaitOne(); // hang! </pre> <br /> Go().WaitOne() 단계에서 블록이 걸립니다. 스레드가 멈추는데요, 따라서 반드시 대상 응용 프로그램의 CLR과 동일한 버전으로 Attach해야 합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 정리를 해보면, 결국 덤프를 뜨는 기능을 안정적으로 구현하고 싶다면 반드시 x86/x64에 따른 플랫폼과 .NET 버전을 세심하게 신경쓰셔야 합니다. 그렇지 않으면 각종 오류에 빠져 헤어나지 못할 수 있습니다. ^^<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=800&boardid=331301885'>첨부된 프로젝트는 위의 예제를 실습한 프로젝트</a>입니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1122
(왼쪽의 숫자를 입력해야 합니다.)