Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 5.6. VS.NET 2005를 이용한 미니덤프 파일 분석 (1) [링크 복사], [링크+제목 복사],
조회: 32948
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
부모글 보이기/감추기
(연관된 글이 6개 있습니다.)
3.5.1 VS.NET 2005를 이용한 미니덤프 파일 분석 (1)

지난 회에서는 "4. VS.NET 2005 디버그 모드에서의 PDB 파일 사용 차이 (1), (2)"라는 내용을 다뤄봤는데요. 아마도 그 글을 읽으신 분들 중에는 내심으론 현실성이 없다는 생각을 가질 수도 있습니다.
즉, 그렇게 sos까지 이용하면서 원인 파악을 해줄 라이브러리 사용자가 얼마나 있을 것이냐는 생각인데요. ^^ 맞습니다. 그럴 분들이 과연 얼마나 되겠습니까? (국내 개발자들이 점점 더 그렇게 되었으면 좋겠습니다.)

자, 그럼 다소 현실로 돌아와서 이번에는 그렇게 예외가 발생했다고 고객사에서 알려왔을 때, 라이브러리를 실제로 개발한 당사자 측에서는 어떤 대응을 하는 것이 바람직할지 이야기 해보겠습니다.

우선 할 일은 고객으로부터 예외가 발생한 시점의 Dump를 받는 것입니다. 아마도 고객은 지난 회 살펴봤던 아래의 화면에서 더 이상 진전없이 헤매고 있을 것입니다.

PDB 파일 없이 F11을 누른 화면

이때, 여러분들은 고객에게 예외 창에서 "Break" 버튼을 누르라고 시킨 후, "Debug" 메뉴의 "Save Dump As..."를 선택해서 Mini Dump 파일을 생성하라고 요청합니다. 주의해야 할 것은, 이때 반드시 다음 화면과 같이 Heap 정보를 포함시켜야 합니다.

Heap 정보까지 포함한 덤프 파일 생성

Heap 정보를 포함시키지 않으면, VS.NET 2005에서 DUMP 파일을 읽어들여 디버깅 상황을 파헤쳐 갈때 SOS.dll의 일부 확장 명령어들이 오동작을 일으키게 됩니다.
이제, 이렇게 받은 DMP 파일로 어떻게 원인을 파악하는지를 알아보도록 하겠습니다.



지금쯤 여러분들의 메일 함에는 고객이 보내 온 HeapDump.dmp 파일이 있을 것입니다. 이것을 자신의 로컬 하드에 저장한 후, dmp 파일을 더블 클릭을 하면 VS.NET 2005가 뜨면서 덤프 파일이 로드됩니다. 그다음, 여느 응용 프로그램 디버깅 하듯이 "F5" 키를 누릅니다. 그럼, 고객이 예외를 만났던 바로 그때의 상황에서 BP(BreakPoint)가 멈춰 있는 것을 볼 수 있습니다.
이 상태에서는 아무것도 도움이 되는 것이 없습니다. Call Stack 창도, Disassembly 창도 여러분들에게 도움이 되는 정보를 전혀 보여주지 않습니다. 오히려 포기하게 만드는 주요 원인을 제공하지요. ^^; 차라리 고객사에 가서 직접 해당 개발자의 PC 앞에 앉아서 문제 해결을 해주고 오는 편이 더욱 편하다는 생각이 들기 시작할지도 모릅니다. 적어도 여러분들이 이번 토픽을 읽기 전까지는 그랬을 수도 있습니다.

하지만, 여러분들은 제가 쓴 토픽을 이미 읽은 상태이고, 모처럼 얻은 실습의 기회를 놓칠 수 없습니다. 심호흡을 한번 하고, 다시 생각을 가다듬습니다. 첫 번째로 할일은?
우선, "Modules" 창을 통해서 어떤 모듈들이 올라왔는지 확인해 봅니다. 아래는 제 VS.NET 2005에서 확인한 Modules 창입니다.

Modules

딱 보니, BaseLibrary.dll은 자신이 만든 DLL임을 알겠고 WinForm.exe는 고객이 만든 것임을 알 수 있습니다. 둘 다 현재 아무런 "Symbol File"이 매칭되어 있지 않음을 확인할 수 있습니다. 그렇군요. 역시 이번에도 중요한 것은 PDB 파일의 유무입니다.

그런데, 이걸 어쩝니까? (제가 방법을 모르는 것일 수도 있으나) 현재의 VS.NET 2005에서는 Minidump 파일을 읽어들인 디버깅에서는 .NET 모듈의 PDB 파일을 로드하는 것이 가능하지 않습니다. 기본적으로 VS.NET 2005는 DMP 파일과 동일한 폴더에서 Binary 파일을 찾으려 하고, PDB 파일 역시 동일한 폴더에서 로딩을 합니다. 그런데, .NET 모듈인 경우에는 관련 PDB 파일이 자동으로 매칭이 안될 뿐더러 수동으로 매칭 시키려 해도 다음과 같은 오류를 내고는 더 이상 진행을 하지 않습니다.
(Native Module인 경우에는 정상적으로 PDB 심벌 파일이 로드됩니다.)

"The symbol file WinForm.pdb does not match the module.

이야기의 주제가 "PDB"라는 것이 무색해지는 순간입니다. ^^; 어쩔 수 없습니다. PDB 파일을 로드하지 못하는 이상, 덤프 파일이 가진 정보만으로 문제를 파악해야 합니다.

역시, 이번에도 이전 토픽에서 살펴본 "Sos.dll"을 사용해서 문제를 해결해 나갈 수 있습니다. 방법은 아래와 같습니다.



1. 사용자가 예외 상황 발생 후의 덤프를 보냈으므로, 현재 발생한 예외 정보를 출력시킵니다.

!printexception
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: 
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)
    0012EBE0 012402E6 BaseLibrary.ThrowClass.TestMethod()
    0012EBF0 01240272 WinForm.Form1.Form1_Load(System.Object, System.EventArgs)
    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: 
HResult: 80070002

PDB 파일이 올라왔다면 위의 부분에서 파일 정보와 소스 코드 라인 정보를 얻을 수 있었겠지만, 지금으로선 그와 같은 정보를 얻을 수 없습니다.
(아쉽게도, 이것은 가정일 뿐 .NET 2.0의 sos.dll은 PDB 파일 유무에 상관없이 소스 및 라인 정보를 보여주지 않습니다.)
우리가 알 수 있는 정보는 사용자가 작성한 WinForm.Form1.Form1_Load에서 우리가 개발한 BaseLibrary.ThrowClass.TestMethod를 호출해서 예외가 발생했다는 것 정도입니다.

2. 이젠 TestMethod의 "IP" 컬럼값인 "012402E6"에 대해서 역어셈블을 합니다.

!u 012402E6
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)
>>> 012402E6 CC               int         3
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

실행이 어느 지점에서 멈췄는 지를 ">>>" 기호를 통해서 알 수 있습니다. 즉 call 명령어에서 예외가 발생했음을 알 수 있고, 이에 대해 File.Open에 들어간 인자값을 알아내야 합니다. 이전 토픽과는 달리, 이때의 ecx 값은 신뢰할 수 없는 상태입니다. 왜냐하면 mov ecx, dword ptr ds:[022E6B9Ch]를 한 이후, 내부의 File.Open 처리 및 예외 발생으로 인해 ecx 레지스터는 달리 쓰여졌을 수 있기 때문입니다. 그러니 애당초 기대를 하지 않는 것이 좋겠고요. 이렇게 되면, ds:[022E6B9Ch] 값을 Memory 창을 이용해서 직접 구해야 합니다.

3. 메모리 창을 통해서 ds:[022E6B9Ch] 주소 영역을 확인한 결과는 다음과 같습니다. (대개의 경우, ds 세그먼트 값은 무시하셔도 좋습니다. ds 세그먼트는 0~4GB 영역의 범위를 가지고 있으며 Base Address는 0 이기 때문입니다. 커널 드라이버 개발자가 아니라면 굳이 ds 값을 확인할 필요는 없습니다.)

Memory

Little Endian 형식으로 저장된 DWORD 값이므로, 실제 값은 "0x012e747c"입니다.

4. 지금쯤 다음 단계를 짐작하셔야 될텐데요. ^^ 바로 DumpObj 명령으로 위의 주소값에 해당하는 클래스 인스턴스를 검사해 보는 것입니다.

!DumpObj 012e747c
Name: System.String
MethodTable: 790fa3e0
EEClass: 790fa340
Size: 58(0x3a) bytes
 (C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: C:\\test_nothing.txt
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 <<

해당 참조형은 System.String 형으로, 그 값은 "C:\\test_nothing.txt"인 것을 확인할 수 있습니다.



이렇게 해서 고객이 보내온 덤프 파일을 통해서 우리의 문제를 확인할 수 있었습니다. 이제 여러분은 고객에게 새로 패치된 모듈을 보내주면서, 개발자가 실수로 남겨놓은 하드 코딩된 문자열이 그 원인이었다는 말은 빼고 더 이상 그런 문제가 없을 거라는 말로 마무리 지을 수 있게 됩니다.
지난 번의 "3.4. VS.NET 2005 디버그 모드에서의 PDB 파일 사용 차이" 토픽에서 살펴보았던 그런 개발자가 고객사에 있다면 여러분의 실수가 드러났을 수도 있겠지만. ^^

마지막으로 언급할 것이 있다면 ^^ 물론, 모든 문제들이 이렇게 간단하지는 않습니다. 다양한 문제 상황에 대해서 경험을 쌓아야만 하고 특정 상황에 대해서 어떻게 문제를 풀어나가야 할지 감각을 키우셔야 합니다.




[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]
[연관 글]






[최초 등록일: ]
[최종 수정일: 7/1/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 46  47  48  [49]  50  51  52  53  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12715정성태7/17/202122508오류 유형: 736. Windows - MySQL zip 파일 버전의 "mysqld --skip-grant-tables" 실행 시 비정상 종료 [1]
12714정성태7/16/202115920오류 유형: 735. VCRUNTIME140.dll, MSVCP140.dll, VCRUNTIME140.dll, VCRUNTIME140_1.dll이 없어 exe 실행이 안 되는 경우
12713정성태7/16/202117308.NET Framework: 1077. C# - 동기 방식이면서 비동기 규약을 따르게 만드는 Task.FromResult파일 다운로드1
12712정성태7/15/202116250개발 환경 구성: 579. Azure - 리눅스 호스팅의 Site Extension 제작 방법
12711정성태7/15/202116220개발 환경 구성: 578. Azure - Java Web App Service를 위한 Site Extension 제작 방법
12710정성태7/15/202118873개발 환경 구성: 577. MQTT - emqx.io 서비스 소개
12709정성태7/14/202114411Linux: 42. 실행 중인 docker 컨테이너에 대한 구동 시점의 docker run 명령어를 확인하는 방법
12708정성태7/14/202118664Linux: 41. 리눅스 환경에서 디스크 용량 부족 시 원인 분석 방법
12707정성태7/14/202185820오류 유형: 734. MySQL - Authentication method 'caching_sha2_password' not supported by any of the available plugins.
12706정성태7/14/202117011.NET Framework: 1076. C# - AsyncLocal 기능을 CallContext만으로 구현하는 방법 [2]파일 다운로드1
12705정성태7/13/202117463VS.NET IDE: 168. x64 DLL 프로젝트의 컨트롤이 Visual Studio의 Designer에서 보이지 않는 문제 - 두 번째 이야기
12704정성태7/12/202116203개발 환경 구성: 576. Azure VM의 서비스를 Azure Web App Service에서만 접근하도록 NSG 설정을 제한하는 방법
12703정성태7/11/202121562개발 환경 구성: 575. Azure VM에 (ICMP) ping을 허용하는 방법
12702정성태7/11/202117334오류 유형: 733. TaskScheduler에 등록된 wacs.exe의 Let's Encrypt 인증서 업데이트 문제
12701정성태7/9/202116838.NET Framework: 1075. C# - ThreadPool의 스레드는 반환 시 ThreadStatic과 AsyncLocal 값이 초기화 될까요?파일 다운로드1
12700정성태7/8/202117321.NET Framework: 1074. RuntimeType의 메모리 누수? [1]
12699정성태7/8/202115872VS.NET IDE: 167. Visual Studio 디버깅 중 GC Heap 상태를 보여주는 "Show Diagnostic Tools" 메뉴 사용법
12698정성태7/7/202120090오류 유형: 732. Windows 11 업데이트 시 3% 또는 0%에서 다운로드가 멈춘 경우
12697정성태7/7/202115157개발 환경 구성: 574. Windows 11 (Insider Preview) 설치하는 방법
12696정성태7/6/202116101VC++: 146. 운영체제의 스레드 문맥 교환(Context Switch)을 유사하게 구현하는 방법파일 다운로드2
12695정성태7/3/202116157VC++: 145. C 언어의 setjmp/longjmp 기능을 Thread Context를 이용해 유사하게 구현하는 방법파일 다운로드1
12694정성태7/2/202118113Java: 24. Azure - Spring Boot 앱을 Java SE(Embedded Web Server)로 호스팅 시 로그 파일 남기는 방법 [1]
12693정성태6/30/202115039오류 유형: 731. Azure Web App Site Extension - Failed to install web app extension [...]. {1}
12692정성태6/30/202115526디버깅 기술: 180. Azure - Web App의 비정상 종료 시 남겨지는 로그 확인
12691정성태6/30/202115713개발 환경 구성: 573. 테스트 용도이지만 테스트에 적합하지 않은 Azure D1 공유(shared) 요금제
12690정성태6/28/202116739Java: 23. Azure - 자바(Java)로 만드는 Web App Service - Tomcat 호스팅
... 46  47  48  [49]  50  51  52  53  54  55  56  57  58  59  60  ...