성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Roll A Lisp In C - Reading ; https...
[정성태] 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...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
3.5.1 VS.NET 2005를 이용한 미니덤프 파일 분석 (1)<br /> <br /> 지난 회에서는 <a target="_blank" href="/2/0/313">"4. VS.NET 2005 디버그 모드에서의 PDB 파일 사용 차이 (1)</a>, <a target="_blank" href="/2/0/317">(2)</a>"라는 내용을 다뤄봤는데요. 아마도 그 글을 읽으신 분들 중에는 내심으론 현실성이 없다는 생각을 가질 수도 있습니다.<br /> 즉, 그렇게 sos까지 이용하면서 원인 파악을 해줄 라이브러리 사용자가 얼마나 있을 것이냐는 생각인데요. ^^ 맞습니다. 그럴 분들이 과연 얼마나 되겠습니까? (국내 개발자들이 점점 더 그렇게 되었으면 좋겠습니다.)<br /> <br /> 자, 그럼 다소 현실로 돌아와서 이번에는 그렇게 예외가 발생했다고 고객사에서 알려왔을 때, 라이브러리를 실제로 개발한 당사자 측에서는 어떤 대응을 하는 것이 바람직할지 이야기 해보겠습니다. <br /> <br /> 우선 할 일은 고객으로부터 예외가 발생한 시점의 Dump를 받는 것입니다. 아마도 고객은 지난 회 살펴봤던 아래의 화면에서 더 이상 진전없이 헤매고 있을 것입니다.<br /> <br /> <img alt="PDB 파일 없이 F11을 누른 화면" src="/SysWebRes/bbs/pdb_4_1_debug_step_into.png" /><br /> <br /> 이때, 여러분들은 고객에게 예외 창에서 "Break" 버튼을 누르라고 시킨 후, "Debug" 메뉴의 "Save Dump As..."를 선택해서 Mini Dump 파일을 생성하라고 요청합니다. 주의해야 할 것은, 이때 반드시 다음 화면과 같이 Heap 정보를 포함시켜야 합니다.<br /> <br /> <img alt="Heap 정보까지 포함한 덤프 파일 생성" src="/SysWebRes/bbs/pdb_5_1_debug_step_into.png" /><br /> <br /> Heap 정보를 포함시키지 않으면, VS.NET 2005에서 DUMP 파일을 읽어들여 디버깅 상황을 파헤쳐 갈때 SOS.dll의 일부 확장 명령어들이 오동작을 일으키게 됩니다.<br /> 이제, 이렇게 받은 DMP 파일로 어떻게 원인을 파악하는지를 알아보도록 하겠습니다.<br /> <br /> <hr /> <br /> 지금쯤 여러분들의 메일 함에는 고객이 보내 온 HeapDump.dmp 파일이 있을 것입니다. 이것을 자신의 로컬 하드에 저장한 후, dmp 파일을 더블 클릭을 하면 VS.NET 2005가 뜨면서 덤프 파일이 로드됩니다. 그다음, 여느 응용 프로그램 디버깅 하듯이 "F5" 키를 누릅니다. 그럼, 고객이 예외를 만났던 바로 그때의 상황에서 BP(BreakPoint)가 멈춰 있는 것을 볼 수 있습니다.<br /> 이 상태에서는 아무것도 도움이 되는 것이 없습니다. Call Stack 창도, Disassembly 창도 여러분들에게 도움이 되는 정보를 전혀 보여주지 않습니다. 오히려 포기하게 만드는 주요 원인을 제공하지요. ^^; 차라리 고객사에 가서 직접 해당 개발자의 PC 앞에 앉아서 문제 해결을 해주고 오는 편이 더욱 편하다는 생각이 들기 시작할지도 모릅니다. 적어도 여러분들이 이번 토픽을 읽기 전까지는 그랬을 수도 있습니다.<br /> <br /> 하지만, 여러분들은 제가 쓴 토픽을 이미 읽은 상태이고, 모처럼 얻은 실습의 기회를 놓칠 수 없습니다. 심호흡을 한번 하고, 다시 생각을 가다듬습니다. 첫 번째로 할일은? <br /> 우선, "Modules" 창을 통해서 어떤 모듈들이 올라왔는지 확인해 봅니다. 아래는 제 VS.NET 2005에서 확인한 Modules 창입니다.<br /> <br /> <img alt="Modules" src="/SysWebRes/bbs/pdb_5_2_debug_step_into.png" /><br /> <br /> 딱 보니, BaseLibrary.dll은 자신이 만든 DLL임을 알겠고 WinForm.exe는 고객이 만든 것임을 알 수 있습니다. 둘 다 현재 아무런 "Symbol File"이 매칭되어 있지 않음을 확인할 수 있습니다. 그렇군요. 역시 이번에도 중요한 것은 PDB 파일의 유무입니다. <br /> <br /> 그런데, 이걸 어쩝니까? (제가 방법을 모르는 것일 수도 있으나) 현재의 VS.NET 2005에서는 Minidump 파일을 읽어들인 디버깅에서는 .NET 모듈의 PDB 파일을 로드하는 것이 가능하지 않습니다. 기본적으로 VS.NET 2005는 DMP 파일과 동일한 폴더에서 Binary 파일을 찾으려 하고, PDB 파일 역시 동일한 폴더에서 로딩을 합니다. 그런데, .NET 모듈인 경우에는 관련 PDB 파일이 자동으로 매칭이 안될 뿐더러 수동으로 매칭 시키려 해도 다음과 같은 오류를 내고는 더 이상 진행을 하지 않습니다.<br /> (Native Module인 경우에는 정상적으로 PDB 심벌 파일이 로드됩니다.)<br /> <br /> <pre class="code"> "The symbol file WinForm.pdb does not match the module. </pre> <br /> 이야기의 주제가 "PDB"라는 것이 무색해지는 순간입니다. ^^; 어쩔 수 없습니다. PDB 파일을 로드하지 못하는 이상, 덤프 파일이 가진 정보만으로 문제를 파악해야 합니다.<br /> <br /> 역시, 이번에도 이전 토픽에서 살펴본 "Sos.dll"을 사용해서 문제를 해결해 나갈 수 있습니다. 방법은 아래와 같습니다.<br /> <br /> <hr /> <br /> 1. 사용자가 예외 상황 발생 후의 덤프를 보냈으므로, 현재 발생한 예외 정보를 출력시킵니다.<br /> <br /> <pre class="code"> <b>!printexception</b> PDB symbol for mscorwks.dll not loaded Exception object: 012e8878 Exception type: System.IO.FileNotFoundException Message: Could not find file 'C:\test_nothing.txt'. InnerException: <none> StackTrace (generated): SP IP Function 0012EA58 796538B2 System.IO.__Error.WinIOError(Int32, System.String) 0012EAB8 7936F43B System.IO.FileStream.Init(System.String, System.IO.FileMode, System.IO.FileAccess, Int32, Boolean, System.IO.FileShare, Int32, System.IO.FileOptions, SECURITY_ATTRIBUTES, System.String, Boolean) 0012EBAC 793723BF System.IO.FileStream..ctor(System.String, System.IO.FileMode, System.IO.FileAccess, System.IO.FileShare) 0012EBD0 7946D097 System.IO.File.Open(System.String, System.IO.FileMode) <b>0012EBE0 012402E6 BaseLibrary.ThrowClass.TestMethod() 0012EBF0 01240272 WinForm.Form1.Form1_Load(System.Object, System.EventArgs)</b> 0012EC40 7B065B27 System.Windows.Forms.Form.OnLoad(System.EventArgs) 0012EC78 7B060BFE System.Windows.Forms.Form.OnCreateControl() 0012EC80 7B06FB22 System.Windows.Forms.Control.CreateControl(Boolean) 0012ECBC 7B06FB57 System.Windows.Forms.Control.CreateControl() 0012ECCC 7B06BD92 System.Windows.Forms.Control.WmShowWindow(System.Windows.Forms.Message ByRef) 0012ED08 7B072E3B System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef) 0012ED6C 7B07F795 System.Windows.Forms.ScrollableControl.WndProc(System.Windows.Forms.Message ByRef) 0012ED74 7B07F743 System.Windows.Forms.ContainerControl.WndProc(System.Windows.Forms.Message ByRef) 0012ED78 7B067086 System.Windows.Forms.Form.WmShowWindow(System.Windows.Forms.Message ByRef) 0012ED84 7B063F40 System.Windows.Forms.Form.WndProc(System.Windows.Forms.Message ByRef) 0012ED94 7B07A72D System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef) 0012ED98 7B07A706 System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef) 0012EDAC 7B08B845 System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr, Int32, IntPtr, IntPtr) 00000000 00000001 System.Windows.Forms.SafeNativeMethods.ShowWindow(System.Runtime.InteropServices.HandleRef, Int32) 0012F2B0 7B073135 System.Windows.Forms.Control.SetVisibleCore(Boolean) 0012F358 7B06382B System.Windows.Forms.Form.SetVisibleCore(Boolean) 0012F398 7B070336 System.Windows.Forms.Control.set_Visible(Boolean) 0012F39C 7B08429E System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext) 0012F408 7B08416B System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext) 0012F438 7B0C69FE System.Windows.Forms.Application.Run(System.Windows.Forms.Form) 0012F448 012400BA WinForm.Program.Main() StackTraceString: <none> HResult: 80070002 </pre> <br /> PDB 파일이 올라왔다면 위의 부분에서 파일 정보와 소스 코드 라인 정보를 얻을 수 있었겠지만, 지금으로선 그와 같은 정보를 얻을 수 없습니다. <br /> (아쉽게도, 이것은 가정일 뿐 .NET 2.0의 sos.dll은 PDB 파일 유무에 상관없이 소스 및 라인 정보를 보여주지 않습니다.)<br /> 우리가 알 수 있는 정보는 사용자가 작성한 WinForm.Form1.Form1_Load에서 우리가 개발한 BaseLibrary.ThrowClass.TestMethod를 호출해서 예외가 발생했다는 것 정도입니다. <br /> <br /> 2. 이젠 TestMethod의 "IP" 컬럼값인 "012402E6"에 대해서 역어셈블을 합니다. <br /> <br /> <pre class="code"> <b>!u 012402E6</b> Normal JIT generated code BaseLibrary.ThrowClass.TestMethod() Begin 012402c0, size 36 012402C0 57 push edi 012402C1 56 push esi 012402C2 50 push eax 012402C3 890C24 mov dword ptr [esp],ecx 012402C6 833D7C64950000 cmp dword ptr ds:[0095647Ch],0 012402CD 7405 je 012402D4 012402CF E82A20E578 call 7A0922FE (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE) 012402D4 33FF xor edi,edi 012402D6 8B0D9C6B2E02 mov ecx,dword ptr ds:[022E6B9Ch] 012402DC BA03000000 mov edx,3 012402E1 E886CD2278 call 7946D06C (System.IO.File.Open(System.String, System.IO.FileMode), mdToken: 060033db) <b>>>> 012402E6 CC int 3</b> 012402E7 F08BFE lock mov edi,esi 012402EA 8BCF mov ecx,edi 012402EC 8B01 mov eax,dword ptr [ecx] 012402EE FF5070 call dword ptr [eax+70h] 012402F1 90 nop 012402F2 59 pop ecx 012402F3 5E pop esi 012402F4 5F pop edi 012402F5 C3 ret </pre> <br /> 실행이 어느 지점에서 멈췄는 지를 ">>>" 기호를 통해서 알 수 있습니다. 즉 call 명령어에서 예외가 발생했음을 알 수 있고, 이에 대해 File.Open에 들어간 인자값을 알아내야 합니다. 이전 토픽과는 달리, 이때의 ecx 값은 신뢰할 수 없는 상태입니다. 왜냐하면 mov ecx, dword ptr ds:[022E6B9Ch]를 한 이후, 내부의 File.Open 처리 및 예외 발생으로 인해 ecx 레지스터는 달리 쓰여졌을 수 있기 때문입니다. 그러니 애당초 기대를 하지 않는 것이 좋겠고요. 이렇게 되면, ds:[022E6B9Ch] 값을 Memory 창을 이용해서 직접 구해야 합니다.<br /> <br /> 3. 메모리 창을 통해서 ds:[022E6B9Ch] 주소 영역을 확인한 결과는 다음과 같습니다. (대개의 경우, ds 세그먼트 값은 무시하셔도 좋습니다. ds 세그먼트는 0~4GB 영역의 범위를 가지고 있으며 Base Address는 0 이기 때문입니다. 커널 드라이버 개발자가 아니라면 굳이 ds 값을 확인할 필요는 없습니다.)<br /> <br /> <img alt="Memory" src="/SysWebRes/bbs/pdb_5_3_debug_step_into.png" /><br /> <br /> Little Endian 형식으로 저장된 DWORD 값이므로, 실제 값은 "0x012e747c"입니다.<br /> <br /> 4. 지금쯤 다음 단계를 짐작하셔야 될텐데요. ^^ 바로 DumpObj 명령으로 위의 주소값에 해당하는 클래스 인스턴스를 검사해 보는 것입니다.<br /> <br /> <pre class="code"> <b>!DumpObj 012e747c</b> Name: System.String MethodTable: 790fa3e0 EEClass: 790fa340 Size: 58(0x3a) bytes (C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll) <b>String: C:\\test_nothing.txt</b> Fields: MT Field Offset Type VT Attr Value Name 790fed1c 4000096 4 System.Int32 0 instance 21 m_arrayLength 790fed1c 4000097 8 System.Int32 0 instance 20 m_stringLength 790fbefc 4000098 c System.Char 0 instance 43 m_firstChar 790fa3e0 4000099 10 System.String 0 shared static Empty >> Domain:Value 0014ceb0:790d6584 << 79124670 400009a 14 System.Char[] 0 shared static WhitespaceChars >> Domain:Value 0014ceb0:012e13dc << </pre> <br /> 해당 참조형은 System.String 형으로, 그 값은 "C:\\test_nothing.txt"인 것을 확인할 수 있습니다.<br /> <br /> <hr /> <br /> 이렇게 해서 고객이 보내온 덤프 파일을 통해서 우리의 문제를 확인할 수 있었습니다. 이제 여러분은 고객에게 새로 패치된 모듈을 보내주면서, 개발자가 실수로 남겨놓은 하드 코딩된 문자열이 그 원인이었다는 말은 빼고 더 이상 그런 문제가 없을 거라는 말로 마무리 지을 수 있게 됩니다.<br /> 지난 번의 "3.4. VS.NET 2005 디버그 모드에서의 PDB 파일 사용 차이" 토픽에서 살펴보았던 그런 개발자가 고객사에 있다면 여러분의 실수가 드러났을 수도 있겠지만. ^^<br /> <br /> 마지막으로 언급할 것이 있다면 ^^ 물론, 모든 문제들이 이렇게 간단하지는 않습니다. 다양한 문제 상황에 대해서 경험을 쌓아야만 하고 특정 상황에 대해서 어떻게 문제를 풀어나가야 할지 감각을 키우셔야 합니다.<br /> <br /> <br /><br /><hr /><span style='color: Maroon'>[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</a>
첨부파일
스팸 방지용 인증 번호
7374
(왼쪽의 숫자를 입력해야 합니다.)