성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
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'>windbg 분석 사례 - WPF 응용 프로그램의 UI가 반응하지 않는 문제</h1> <p> 예전에 닷넷 엑스퍼트 시절에 ^^ 끝마쳤던 프로젝트였는데... 거의 5년 정도가 지나서 실제 구축된 이후 발생하는 문제에 대해서 문의가 왔습니다. 대략 문제는 이랬습니다. 응용 프로그램의 특성상 한번 부팅되면 1년이고 2년이고 계속 쓰는 시스템이었는데 30일 정도만 지나면 시스템에 마비가 온다는 것이었습니다. 고객사 개발팀은 나름 로그도 남겨보고 했지만 원인을 알 수 없어 고심을 하다가 결국 제게 도움을 요청한 것입니다. .NET WPF로 구축된 사례였는데 그당시 제가 개발 팀장을 맡고 있었기에 사실 저도 이 문제가 그리 좋은 소식은 아니었습니다. 자신이 기여한 프로젝트가 문제를 겪고 있다는 사실을 반길 개발자는 없겠지요. ^^<br /> <br /> 그렇다고 해서 제가 그 정도의 증상만 듣고 해결할 수는 없기 때문에, 문제가 발생한 시점의 덤프를 남겨서 보내달라고 했습니다. (다행히, 시스템 마비로 보이지만 프로그램은 돌고 있어서 덤프를 생성할 수 있었습니다.)<br /> <br /> 자, 그럼 기지개 한번 쭉 켜고 덤프 분석을 시작해 볼까요? ^^ 우선 SOS를 올리고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>.load "C:\\Windows\\Microsoft.NET\\Framework\\v2.0.50727\\SOS.dll"</span> </pre> <br /> 아울러 sosex도 함께 올리는 것이 좋습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > SOSEX v4.0 Now Available ; <a target='tab' href='http://www.stevestechspot.com/SOSEXV40NowAvailable.aspx'>http://www.stevestechspot.com/SOSEXV40NowAvailable.aspx</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>.load "C:\\temp\\sosex.dll"</span> </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;' > 0:000> <span style='color: blue; font-weight: bold'>!clrstack</span> Failed to load data access DLL, 0x80004005 Verify that 1) you have a recent build of the debugger (6.2.14 or newer) 2) the file mscordacwks.dll that matches your version of mscorwks.dll is in the version directory 3) or, if you are debugging a dump file, verify that the file mscordacwks_<arch>_<arch>_<version>.dll is on your symbol path. 4) you are debugging on the same architecture as the dump file. For example, an IA64 dump file must be debugged on an IA64 machine. You can also run the debugger command .cordll to control the debugger's load of mscordacwks.dll. .cordll -ve -u -l will do a verbose reload. If that succeeds, the SOS command should work on retry. If you are debugging a minidump, you need to make sure that your executable path is pointing to mscorwks.dll as well. </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;' > windbg의 mscordacwks DLL 로드 문제 - 두 번째 이야기 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1375'>http://www.sysnet.pe.kr/2/0/1375</a> windbg의 mscordacwks DLL 로드 문제 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1181'>http://www.sysnet.pe.kr/2/0/1181</a> Windbg - ERROR: Unable to load DLL mscordacwks_x86_x86_2.0.50727.4200.dll, Win32 error 0n2 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/994'>http://www.sysnet.pe.kr/2/0/994</a> </pre> <br /> 고객사에 다시 요청해서 문제가 발생한 PC에 있는 "c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscordacwks.dll" 파일을 보내달라고 했습니다. 그런 다음 .cordll 명령어로 파일명을 얻어내고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>.cordll -ve -u -l</span> CLRDLL: c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscordacwks.dll:2.0.50727.7905 f:0 doesn't match desired version 2.0.50727.3625 f:0 CLRDLL: Unable to find <span style='color: blue; font-weight: bold'>mscordacwks_x86_x86_2.0.50727.3625.dll</span> by mscorwks search CLRDLL: Unable to find 'mscordacwks_x86_x86_2.0.50727.3625.dll' on the path CLRDLL: Unable to get version info for 'e:\symbols\mscorwks.dll\4E154C985a9000\mscordacwks_x86_x86_2.0.50727.3625.dll', Win32 error 0n87 CLRDLL: ERROR: Unable to load DLL mscordacwks_x86_x86_2.0.50727.3625.dll, Win32 error 0n87 CLR DLL status: ERROR: Unable to load DLL mscordacwks_x86_x86_2.0.50727.3625.dll, Win32 error 0n87 </pre> <br /> 고객사로부터 전달받은 mscordacwks.dll 파일을 mscordacwks_x86_x86_2.0.50727.3625.dll 파일로 이름을 바꾸고 심벌 폴더에 저장해 두었습니다.<br /> <br /> 처음 덤프를 받았을 때는 해당 덤프가 crash 덤프인 줄 알았습니다. 그래서 일단 "!analyze -v"를 실행해 보았는데요.<br /> <br /> <pre style='height: 400px; margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!analyze -v</span> ******************************************************************************* * * * Exception Analysis * * * ******************************************************************************* *** WARNING: Unable to verify checksum for WindowsBase.ni.dll ...[생략]... *** WARNING: Unable to verify checksum for System.Data.ni.dll FAULTING_IP: +0 00000000 ?? ??? EXCEPTION_RECORD: ffffffff -- (.exr 0xffffffffffffffff) ExceptionAddress: 00000000 ExceptionCode: 80000003 (Break instruction exception) ExceptionFlags: 00000000 NumberParameters: 0 FAULTING_THREAD: 0000052c DEFAULT_BUCKET_ID: WRONG_SYMBOLS PROCESS_NAME: TestShell.exe <span style='color: blue; font-weight: bold'>ERROR_CODE: (NTSTATUS) 0x80000003 - {EXCEPTION} Breakpoint A breakpoint has been reached. EXCEPTION_CODE: (HRESULT) 0x80000003 (2147483651) - One or more arguments are invalid</span> NTGLOBALFLAG: 0 APPLICATION_VERIFIER_FLAGS: 0 APP: TestShell.exe MANAGED_STACK: (TransitionMU) 0012CA70 1F68A89F Test_Support!Test.Support.CTestManager.SetMethod(Test.DTO.ITestMachine)+0x2067 0012D6A0 1F680A3A Test_Support!Test.Support.CPosition.ModifyPos(Double, Double)+0x6aa 0012D8D8 1F68034F Test_Support!Test.Support.CDailyMapper.ModifyPos(Double, Double)+0x2df ...[생략]... 0012F400 578440CC WindowsBase_ni!System.Windows.Threading.Dispatcher.Run()+0x4c ...[생략]...0012F458 03B600BC TestShell!Test.Shell.App.Main()+0x4c (TransitionUM) MANAGED_STACK_COMMAND: _EFN_StackTrace PRIMARY_PROBLEM_CLASS: WRONG_SYMBOLS BUGCHECK_STR: APPLICATION_FAULT_WRONG_SYMBOLS LAST_CONTROL_TRANSFER: from 16ea93b4 to 16ea399d STACK_TEXT: WARNING: Stack unwind information not available. Following frames may be wrong. 0012b328 16ea93b4 00000001 fffffde7 0012bba4 AxObj+0x399d 0012bbf8 16ea9e3e 0012be38 0012bdc4 16e93d5c AxObj+0x93b4 0012bc1c 16ea9d30 16e93d5c 0012bc4c 0012be10 AxObj+0x9e3e 0012bc98 16ea9f07 16e93d5c 0012be38 0012be10 AxObj+0x9d30 ...[생략]... 0012ffb8 79004de3 00360034 7c7e7077 00370035 mscoree!ShellShim__CorExeMain+0x29 0012ffc0 7c7e7077 00370035 00360034 7ffd3000 mscoree!_CorExeMain_Exported+0x8 0012fff0 00000000 79004ddb 00000000 78746341 kernel32!BaseProcessStart+0x23 STACK_COMMAND: ~0s; .ecxr ; kb FOLLOWUP_IP: AxObj+399d 16ea399d 8955fc mov dword ptr [ebp-4],edx SYMBOL_STACK_INDEX: 0 SYMBOL_NAME: axobj+399d FOLLOWUP_NAME: MachineOwner MODULE_NAME: AxObj IMAGE_NAME: AxObj.dll DEBUG_FLR_IMAGE_TIMESTAMP: 521259cc FAILURE_BUCKET_ID: WRONG_SYMBOLS_80000003_AxObj.dll!Unknown BUCKET_ID: APPLICATION_FAULT_WRONG_SYMBOLS_axobj+399d WATSON_STAGEONE_URL: ... Followup: MachineOwner --------- </pre> <br /> 보시는 바와 같이 딱히 ^^; 문제가 없습니다. ERROR_CODE가 0x80000003은 도대체 뭘까요? 이에 대해서는 다음의 글에 나와 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Dump Analysis: Debugging a Multi-Process Hang ; <a target='tab' href='http://www.codemachine.com/article_rpcchain.html'>http://www.codemachine.com/article_rpcchain.html</a> </pre> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> If the exception code is either 0x80000007 or 0x80000003, it is an indication of a manually generated application dump as opposed to an application crash dump. <br /> </div><br /> <br /> 즉, 시스템에 의해 남겨진 crash 덤프는 아니고 사용자가 일부러 남긴 덤프였던 것입니다. (나중에 고객사에 알아봤는데, 문제가 발생한 시점에 외부에서 해당 프로세스의 덤프를 남긴 것이라고 합니다.)<br /> <br /> 또한, !pe를 실행해 봐도 역시 아무런 예외 기록이 없습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!printexception</span> There is no current managed exception on this thread </pre> <br /> 순간 땀이 나기 시작합니다. ^^ 예외도 없이 시스템이 얼어버리다니... 왠지 내 실력으로 분석할만한 수준을 넘어선 것 같았습니다. 혹시, 디바이스 간의 문제가 아닐까... 하는 상상까지 하게 되었는데요.<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;' > 0:000> <span style='color: blue; font-weight: bold'>!threads</span> <span style='color: blue; font-weight: bold'>ThreadCount: 529</span> UnstartedThread: 0 BackgroundThread: 507 PendingThread: 0 DeadThread: 21 Hosted Runtime: no PreEmptive GC Alloc Lock ID OSID ThreadOBJ State GC Context Domain Count APT Exception 0 1 52c 001601b8 6020 Enabled 00000000:00000000 00164fc0 0 STA 2 2 db8 0015c138 b220 Enabled 00000000:00000000 00164fc0 0 MTA (Finalizer) XXXX 3 0 001bcef8 5820 Enabled 00000000:00000000 00164fc0 0 MTA 4 4 cdc 05e2e0b8 220 Enabled 00000000:00000000 00164fc0 0 Ukn 5 5 8a0 001fa7c8 80a220 Enabled 00000000:00000000 00164fc0 0 MTA (Threadpool Completion Port) 8 6 ec 05e5b598 380b220 Enabled 00000000:00000000 00164fc0 0 MTA (Threadpool Worker) ... [생략] ... 529 210 134c 214648a8 380b220 Enabled 0c6f2c18:0c6f46c8 00164fc0 0 MTA (Threadpool Worker) 530 211 136c 21464c90 380b220 Enabled 0c6f490c:0c6f6340 00164fc0 0 MTA (Threadpool Worker) </pre> <br /> 오호~~~ 스레드 수가 500개가 넘는다니, 참으로 반가운 상황이었습니다. ^^ 그래요, 이렇게 문제 상황이 드러나야 분석을 할 수 있으니까요.<br /> <br /> 그런데, 도대체 스레드들에 어떤 문제가 발생한 것일까요? 500개의 콜스택을 남기는 것은 너무 많으므로 다음과 같이 일단 전체 콜스택을 로그 파일로 남기고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .logopen d:\eestack_log.txt ~*e!clrstack .logclose </pre> <br /> 해당 로그 파일을 읽어 동일한 콜스택의 종류를 세는 프로그램을 만들어 돌려보았습니다.<br /> <br /> <pre style='height: 400px; margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Collections.Generic; using System.IO; using System.Text; namespace CallStackCounter { class Program { static void Main(string[] args) { string filePath = @"d:\eestack_log.txt"; int threadCount = 0; StringBuilder sb = new StringBuilder(); Dictionary<string, int> perThread = new Dictionary<string, int>(); foreach (var item in File.ReadAllLines(filePath)) { string txt = item; if (txt.IndexOf("OS Thread Id:") != -1) { string callStack = sb.ToString(); sb = new StringBuilder(); if (perThread.ContainsKey(callStack) == true) { perThread[callStack]++; } else { perThread.Add(callStack, 1); } threadCount++; continue; } if (txt != null) { txt = txt.Trim(); } if (string.IsNullOrEmpty(txt) == true) { continue; } string stackFrame = TrimCallStackFrame(txt); if (string.IsNullOrEmpty(stackFrame) == false) { sb.AppendLine(stackFrame); } } Console.WriteLine("Total # of threds: " + threadCount); Console.WriteLine(); foreach (var thread in perThread) { Console.WriteLine("# of threads: " + thread.Value); Console.WriteLine(thread.Key); Console.WriteLine(); } } private static string TrimCallStackFrame(string txt) { string[] infos = txt.Split(' '); if (infos.Length < 3) { return string.Empty; } int value; if (Int32.TryParse(infos[0], System.Globalization.NumberStyles.AllowHexSpecifier, null, out value) == false) { return string.Empty; } if (Int32.TryParse(infos[1], System.Globalization.NumberStyles.AllowHexSpecifier, null, out value) == false) { return string.Empty; } int pos = txt.IndexOf(' '); pos = txt.IndexOf(' ', pos + 1); txt = txt.Substring(pos + 1); if (txt.IndexOf("[HelperMethodFrame") != -1) { pos = txt.IndexOf("] "); txt = txt.Substring(pos + 2); } if (txt.IndexOf("GCFrame: ") != -1) { pos = txt.IndexOf("]"); txt = txt.Substring(pos + 1); } if (txt.IndexOf("[ComPlusMethodFrameGeneric: ") != -1) { pos = txt.IndexOf("]"); txt = txt.Substring(pos + 1); } return txt; } } } </pre> <br /> 그랬더니, 452개의 스레드가 Wait 상태에 있고, Main UI를 실행중인 스레드는 SetPosition이라는 메서드를 실행 중에 있었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ...[생략]... # of threads: 1 <span style='color: blue; font-weight: bold'>[ComPlusMethodFrameGeneric: 0012ca10] TestCOMLib.TestClass.SetPosition(Int32, Double, Double, Int32, Double, Double, Int32, Double, Double, System.String, System.Object, Int32, Int32, Int32, Int32)</span> Test.Support.CTestManager.SetMethod(Test.DTO.ITestMachine) Test.Support.CPosition.ModifyPos(Double, Double) Test.Support.CDailyMapper.ModifyPos(Double, Double) ...[생략]... System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame) System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame) System.Windows.Threading.Dispatcher.Run() System.Windows.Application.RunDispatcher(System.Object) System.Windows.Application.RunInternal(System.Windows.Window) System.Windows.Application.Run(System.Windows.Window) System.Windows.Application.Run() Test.Shell.App.Main() ...[생략]... # of threads: 452 <span style='color: blue; font-weight: bold'>System.Threading.WaitHandle.WaitOneNative(Microsoft.Win32.SafeHandles.SafeWaitHandle, UInt32, Boolean, Boolean) System.Threading.WaitHandle.WaitOne(Int64, Boolean) System.Threading.WaitHandle.WaitOne(System.TimeSpan, Boolean) System.Windows.Threading.DispatcherOperation+DispatcherOperationEvent.WaitOne() System.Windows.Threading.DispatcherOperation.Wait(System.TimeSpan) System.Windows.Threading.Dispatcher.InvokeImpl(System.Windows.Threading.DispatcherPriority, System.TimeSpan, System.Delegate, System.Object, Boolean) System.Windows.Threading.Dispatcher.Invoke(System.Delegate, System.Object[]) Test.Shell.App.tickTimer_Elapsed(System.Object, System.Timers.ElapsedEventArgs) System.Timers.Timer.MyTimerCallback(System.Object)</span> System.Threading._TimerCallback.TimerCallback_Context(System.Object) System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) System.Threading._TimerCallback.PerformTimerCallback(System.Object) ...[생략]... </pre> <br /> <br /> 452개의 스레드 콜스택을 보면 중간에 tickTimer_Elapsed가 있고, 그것을 호출한 메서드가 System.Timers.Timer 객체로 나옵니다. 그러니까, 상황 정리를 해 보면 대충 이런 식입니다.<br /> <br /> <ol> <li>Main UI 스레드가 현재 SetPosition을 실행하면서 block 된 상태임.</li> <li>System.Timers.Timer 객체는 1초마다 스레드 풀로부터 스레드 하나를 가져와서 Elapsed 이벤트를 발생</li> <li>Elapsed 이벤트를 수신하는 사용자 코드에서 Main UI 스레드를 통해 UI 요소를 접근해야 하므로 Dispatcher.Invoke를 호출</li> <li>하지만, Main UI 스레드가 block 된 상태이므로 다른 스레드로부터 들어오는 Dispatcher.Invoke의 델리게이트를 실행할 수 없음. 따라서, Elapsed 이벤트의 Invoke 호출은 대기하게 되고 해당 스레드는 종료하지 않음.</li> <li>Elapsed 이벤트가 1초마다 발생하므로 지속적으로 스레드가 누적됨.</li> </ol> <br /> 스레드가 계속 누적되는 것은 사실 현상에 불과한 것입니다. 결정적인 원인은 Main UI 스레드가 블록되었기 때문에 WPF의 전체 UI 렌더링이 멈춰버린 것입니다.<br /> <br /> 이런 경우, 어쩌면 System.Timers.Timer의 스레드 풀을 사용하는 동작이 도움이 되었다고도 볼 수 있습니다. 만약, 1개의 스레드로 Elapsed를 발생시키는 것이었다면 그 스레드 하나만 멈췄을 것이고, 프로세스 덤프를 떴을 때 일반적인 콜 스택만 나와서 원인 규명이 더 힘들었을 수 있습니다. 즉, 단순히 SetPosition 메서드가 호출되는 시점에 프로세스 덤프를 뜬 것과 별반 다르지 않은 상황이기 때문입니다. (그렇다고 System.Timers.Timer 사용을 독려하는 것이 아닙니다. 단지 긍정적인 우연이 겹쳤다는 것을 이야기하고 싶은 것입니다.)<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;' > 0:008> <span style='color: blue; font-weight: bold'>!EEHeap</span> ... [생략]... ------------------------------ GC Heap Size 0x2079394(34,050,964) </pre> <br /> 24일 동안 켜놓은 WPF 프로그램인데도 GC Heap의 총 크기가 34MB면 상당히 양호한 수준입니다. 힙에 누적되어 있는 객체는 볼 것도 없지만, 그래도 확인해 보는 습관을 들이는 것이 좋겠지요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:008> <span style='color: blue; font-weight: bold'>!dumpheap -stat</span> total 729019 objects Statistics: MT Count TotalSize Class Name 7b22eaec 1 12 System.Windows.Forms.FlowLayoutSettings 7b22ea88 1 12 System.Windows.Forms.Layout.FlowLayout ...[생략]... 79333594 3820 2248156 System.Byte[] 793042f4 76166 2513388 System.Object[] 79330b24 63074 3664460 System.String Total 729019 objects </pre> <br /> 역시, 딱히 문제되는 수준은 아닙니다. 콜스택으로 다시 돌아가서, 그럼 구체적으로 어떤 코드를 실행하다 멈췄는지 알 수 있을까요? Main UI 스레드의 가장 상단에 있는 메서드는 COM 개체 호출로 보이므로 바로 아래의 Test.Support.CTestManager.SetMethod를 살펴봐야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> !clrstack -p OS Thread Id: 0x52c (0) ESP EIP 0012ca10 16ea399d [ComPlusMethodFrameGeneric: 0012ca10] TestCOMLib.TestClass.SetPosition(Int32, Double, Double, Int32, Double, Double, Int32, Double, Double, System.String, System.Object, Int32, Int32, Int32, Int32) 0012ca70 1f68a89f <span style='color: blue; font-weight: bold'>Test.Support.CTestManager.SetMethod</span>(Test.DTO.ITestMachine) PARAMETERS: this = 0x0ba5b180 vessel = 0x022c8544 ...[생략]... </pre> <br /> 우선, 이 메서드를 가진 DLL 파일을 알아내야 합니다. (만약 해당 응용 프로그램의 PDB 파일과 DLL 파일이 있다면 이 작업은 생략할 수 있습니다.) 다음과 같이 명령을 내리면 Test.Support.CTestManager.SetMethod 메서드가 정의된 모듈을 찾아낼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!name2ee *!Test.Support.CTestManager.SetMethod</span> Module: 790c1000 (mscorlib.dll) -------------------------------------- ...[생략]... -------------------------------------- <span style='color: blue; font-weight: bold'>Module: 03c0bbc0 (Test.Support.dll)</span> Token: 0x0600062e MethodDesc: 1715fd28 Name: Test.Support.CTestManager.SetMethod(Test.DTO.ITestMachine) JITTED Code Address: 1f688838 -------------------------------------- ...[생략]... </pre> <br /> 정의된 모듈명이 Test.Support.dll이라고 나왔으니 lm 명령을 통해 해당 모듈의 시작 주소를 알아냅니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>lm</span> start end module name ...[생략]... <span style='color: blue; font-weight: bold'>06510000</span> 068cc000 <span style='color: blue; font-weight: bold'>Test_Support</span> (deferred) ...[생략]... 1b570000 1b5c4000 msjetoledb40 (deferred) 1b5d0000 1b665000 mswstr10 (deferred) 1f370000 1f375000 msadcer (deferred) 7d5a0000 7dd9d000 shell32 (deferred) 7e5a0000 7e65a000 unidrvui (deferred) 7e8c0000 7e971000 userenv (deferred) Unloaded modules: 60340000 60348000 culture.dll 1b670000 1b73b000 MSWDAT10.DLL 0d940000 0d974000 mxdwdui.DLL 69780000 69797000 FontSub.dll 6eb40000 6eb7b000 compstui.dll </pre> <br /> 그럼, savemodule 명령어를 이용해 이 어셈블리를 파일로 저장할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > !savemodule 06510000 C:\temp\Test_Support.dll </pre> <br /> 거의 다 왔습니다. ^^ 이제 실제로 콜 스택이 실행된 옵셋값을 구해야 하는데요. 아쉽게도 sos.dll은 이 값을 출력해 주지 않습니다. 대신 sosex.dll의 mk 명령어는 이를 출력해 주기 때문에 다음과 같이 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!mk</span> Thread 0: SP IP 00:U 0012b30c 16ea399d AxObj+0x399d 01:U 0012b330 16ea93b4 AxObj+0x93b4 ...[생략]... 0e:U 0012c78c 79f18cb1 mscorwks!CLRToCOMWorker+0x14f 0f:U 0012c9c4 0435141a CLRStub[StubLinkStub]@435141a 10:M 0012ca00 1f68a89f <span style='color: blue; font-weight: bold'>Test.Support.CTestManager.SetMethod</span>(Test.DTO.ITestMachine)(<span style='color: blue; font-weight: bold'>+0x978 IL</span>,+0x2067 Native) 11:M 0012d6a0 1f680a3a Test.Support.CPosition.ModifyPos(Double, Double)(+0x231 IL,+0x6aa Native) 12:U 0012d718 7c94005d ntdll!RtlFreeHeap+0x647 13:M 0012d8c8 1f68034f Test.Support.CDailyMapper.ModifyPos(Double, Double)(+0x123 IL,+0x2df Native) ...[생략]... 5e:U 0012fa3c 79f20cf1 mscorwks!SystemDomain::ExecuteMainMethod+0x456 5f:U 0012ff0c 79f20edb mscorwks!ExecuteEXE+0x59 60:U 0012ff5c 79f20e0b mscorwks!_CorExeMain+0x15c 61:U 0012ffa4 7900b77b mscoree!_CorExeMain+0x2e 62:U 0012ffb4 7900b73d mscoree!ShellShim__CorExeMain+0x29 63:U 0012ffc0 79004de3 mscoree!_CorExeMain_Exported+0x8 64:U 0012ffc8 7c7e7077 kernel32!BaseProcessStart+0x23 </pre> <br /> 보시는 바와 같이 IL 옵셋값이 0x978로 나옵니다. 그럼, 저장된 Test.Support.dll 파일을 .NET Reflector로 불러온 다음 IL 보기 모드에서 0x978의 위치에 어떤 코드가 있는지 짐작할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ...[생략]... L_0962: ldc.i4.0 L_0963: box int32 L_0968: ldc.i4.0 L_0969: ldc.i4.0 L_096a: ldc.i4.1 L_096b: ldloc.s flag2 L_096d: brtrue.s L_0972 L_096f: ldc.i4.0 L_0970: br.s L_0973 L_0972: ldc.i4.1 <span style='color: blue; font-weight: bold'> L_0973: callvirt instance void [Interop.TestCOMLib]TestCOMLib.TestClass::SetPosition(int32, float64, float64, int32, float64, float64, int32, float64, float64, string, object, int32, int32, int32, int32)</span> L_0978: nop L_0979: ldarg.0 L_097a: ldfld class [Interop.TestCOMLib]TestCOMLib.TestClass Test.Support.CTargetMgr::m_pTargets L_097f: ldc.i4.s 10 L_0981: ldloc.s classf ...[생략]... </pre> <br /> IL 코드를 조금 볼 줄 안다면 C# 코드의 어느 위치에 해당하는 지 금방 알 수 있습니다. 따라서 문제가 발생한 소스코드의 라인정보까지 얻은 것이나 다름없습니다. 일단, 이걸로 분석은 끝!<br /> <br /> <hr style='width: 50%' /><br /> <br /> 저야 SetPosition에서 왜 block 되었는지 알 수 있는 방법이 없습니다. 그래서 고객사에 이 사실을 알렸고 분석 끝에 SetPosition의 인자로 double 값이 전달되는데 이 중에 NaN이 입력되면 COM 객체 내부에서 block 상태에 빠지는 확률이 높아진다는 것을 알게 되었습니다.<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;' > C# - double 값에 대한 windbg 확인 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1526'>http://www.sysnet.pe.kr/2/0/1526</a> </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1975
(왼쪽의 숫자를 입력해야 합니다.)