성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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
사물인터넷
부모글 보이기/감추기
내용
3.5.2 VS.NET 2005를 이용한 미니덤프 파일 분석 (2)<br /> <br /> 이번에는 지난 회에 살펴봤던 <a target="_blank" href="/2/0/318">3.5. VS.NET 2005를 이용한 미니덤프 파일 분석 (1)</a>에 대해서 좀 더 보충 설명을 해보겠습니다.<br /> <br /> 사실, 덤프 파일의 디버깅은 대개의 경우 덤프 파일 만을 고객으로부터 받게 될 뿐 별다른 DLL/EXE는 받지 않는 경우가 많습니다. 소스 파일은 말할 필요도 없겠지요.<br /> 이런 상황은 예전 Win32 VC++ 상황에서는 무척이나 힘든 디버깅을 해야 했었지만, .NET Managed로 오면서는 그러한 부분들이 많이 편해지게 되었습니다. .NET 자체가 Metadata 및 중간 언어로 되어 있으니 그럴 수밖에 없겠지요.<br /> <br /> 본론으로 들어가서, 덤프 파일만으로 해당 DLL들을 뽑아내고 소스 파일 까지 생성해서 디버깅에 도움이 될 수 있도록 해보겠습니다.<br /> <br /> 상황을 바꿔서 여러분들이 특정 프로그램의 문제를 분석하기 위해 덤프 파일을 받은 것으로 가정해 보겠습니다.<br /> 물론 프로그램은 지난번 것을 그대로 이어서 설명합니다.<br /> <br /> 여러분들은 우선, dmp 파일을 더블 클릭해서 VS.NET 2005에서 불러드리게 됩니다.<br /> 평상시와 다름없이 "F5"를 눌러 디버깅을 시작하게 되고, sos.dll을 로드한 다음 어느 스레드에서 어떤 문제가 발생했는지 알아낼 것입니다.<br /> <br /> <pre class="code"> <b>!threads</b> PDB symbol for mscorwks.dll not loaded ThreadCount: 2 UnstartedThread: 0 BackgroundThread: 1 PendingThread: 0 DeadThread: 0 Hosted Runtime: no PreEmptive GC Alloc Lock ID OSID ThreadOBJ State GC Context Domain Count APT Exception 3764 1 eb4 00187e28 86028 Enabled 00000000:00000000 0014ceb0 0 <b>STA System.IO.FileNotFoundException (013175dc)</b> 1876 2 754 00192e60 b228 Enabled 00000000:00000000 0014ceb0 0 <b>MTA (Finalizer)</b> </pre> <br /> Finalizer가 표시된 스레드는 GC에 의해서 사용되는 스레드일 뿐이며, 중요한 것은 위에서 FileNotFoundException 예외가 발생한 사용자 STA 스레드입니다.<br /> 스레드 예외 오른쪽 괄호에는 해당 예외 객체의 주소가 들어가 있는 것인데요. 다음과 같이 해당 객체의 내용을 살펴볼 수가 있습니다.<br /> <br /> <pre class='code'> <b>!dumpobj 013175dc</b> Name: System.IO.FileNotFoundException MethodTable: 79177fc0 EEClass: 79213a28 Size: 84(0x54) bytes (C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll) Fields: MT Field Offset Type VT Attr Value Name 790fa3e0 40000b5 4 System.String 0 instance 00000000 _className 79109208 40000b6 8 ...ection.MethodBase 0 instance 00000000 _exceptionMethod 790fa3e0 40000b7 c System.String 0 instance 00000000 _exceptionMethodString 790fa3e0 40000b8 10 System.String 0 instance 013176dc _message 79113dfc 40000b9 14 ...tions.IDictionary 0 instance 00000000 _data 790fa9e8 40000ba 18 System.Exception 0 instance 00000000 _innerException 790fa3e0 40000bb 1c System.String 0 instance 00000000 _helpURL 790f9c18 40000bc 20 System.Object 0 instance 01317a9c _stackTrace 790fa3e0 40000bd 24 System.String 0 instance 00000000 _stackTraceString 790fa3e0 40000be 28 System.String 0 instance 00000000 _remoteStackTraceString 790fed1c 40000bf 34 System.Int32 0 instance 0 _remoteStackIndex 790f9c18 40000c0 2c System.Object 0 instance 00000000 _dynamicMethods 790fed1c 40000c1 38 System.Int32 0 instance -2147024894 _HResult 790fa3e0 40000c2 30 System.String 0 instance 00000000 _source 790fe160 40000c3 3c System.IntPtr 0 instance 0 _xptrs 790fed1c 40000c4 40 System.Int32 0 instance -532459699 _xcode 790fa3e0 4001b5f 44 System.String 0 instance 00000000 _maybeFullPath 790fa3e0 4001b7b 48 System.String 0 instance 013060bc _fileName 790fa3e0 4001b7c 4c System.String 0 instance 00000000 _fusionLog </pre> <br /> 예외 클래스 내부의 멤버 변수들에 대한 값을 열람할 수 있는데요. Value 형식은 그 값을 그대로 확인할 수 있지만 나머지 참조형의 경우에는 역시 "!dumpobj" 명령어를 통해서 세부적인 값을 확인할 수 있습니다. 사실 대부분의 경우에, 위의 예외 클래스에서 제공하는 정보만으로 예외의 원인을 알 수 있는데, 그렇지 못한 경우에는 더 오류 추적을 해봐야 합니다.<br /> <br /> 우리가 지금 원하는 것은 예외가 발생된 사용자 메서드의 위치를 알고 싶은 것이므로, 다음과 같이 printexception 명령어를 통해서 확인을 합니다.<br /> <br /> <pre class='code'> <b>!printexception 013175dc</b> Exception object: 013175dc Exception type: System.IO.FileNotFoundException Message: Could not find file 'C:\test_nothing.txt'. InnerException: <none> StackTrace (generated): SP IP Function 0012EA20 796538B2 System.IO.__Error.WinIOError(Int32, System.String) 0012EA80 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) 0012EB74 793723BF System.IO.FileStream..ctor(System.String, System.IO.FileMode, System.IO.FileAccess, System.IO.FileShare) 0012EB98 7946D097 System.IO.File.Open(System.String, System.IO.FileMode) <b>0012EBA8 0124030F BaseLibrary.ThrowClass.TestMethod()</b> 0012EBF0 01240270 WinForm.Form1.Form1_Load(System.Object, System.EventArgs) 0012EC40 7B065B27 System.Windows.Forms.Form.OnLoad(System.EventArgs) 0012EC78 7B060BFE System.Windows.Forms.Form.OnCreateControl() ... [중간 생략] ... 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 /> 예외가 발생한 사용자 모듈은 BaseLibrary.ThrowClass.TestMethod임을 알 수 있는데요. <br /> 해당 메서드의 IP(Instruction Pointer) 값을 확인해서 Method Descriptor를 다음과 같이 알아낼 수 있습니다.<br /> <br /> <pre class='code'> <b>!ip2md 0124030F</b> <b>MethodDesc: 00956690</b> Method Name: BaseLibrary.ThrowClass.TestMethod() Class: 012614ec MethodTable: 009566a0 mdToken: 06000001 <b>Module: 009562c8</b> IsJitted: yes m_CodeOrIL: 012402d8 </pre> <br /> 사실 ^^ MethodDesc 보다는 Module 값이 더 궁금한 것이었는데 위와 같이 친절하게 ip2md에서 알려주고 있습니다.<br /> 이제 해당 모듈의 시작 주소를 알아내야 하는데, 다음과 같은 방법으로 알아낼 수 있습니다.<br /> <br /> <pre class='code'> <b>!dumpmodule 009562c8</b> Name: D:\temp2\PdbSample\WinFormApp\WinForm\bin\Debug\BaseLibrary.dll Attributes: PEFile Assembly: 001dad00 LoaderHeap: 00000000 TypeDefToMethodTableMap: 01260330 TypeRefToMethodTableMap: 01260338 MethodDefToDescMap: 0126039c FieldDefToDescMap: 012603a8 MemberRefToDescMap: 012603ac FileReferencesMap: 01260400 AssemblyReferencesMap: 01260404 <b>MetaData start address: 03c7207c (1848 bytes)</b> </pre> <br /> 휴... 시작 주소까지 무사히 알아내었으니, 이젠 해당 주소부터 시작하는 모듈을 추출해서 로컬에 저장해 보겠습니다.<br /> <br /> <pre class='code'> <b>!savemodule 03c7207c C:\mymodule.dll</b> 3 sections in file section 0 - VA=2000, VASize=884, FileAddr=1000, FileSize=1000 section 1 - VA=4000, VASize=360, FileAddr=2000, FileSize=1000 section 2 - VA=6000, VASize=c, FileAddr=3000, FileSize=1000 </pre> <br /> DLL 을 추출했으면 당연히 소스도 얻을 수 있겠지요. ^^ 어렵게 ILDASM을 쓸 필요도 없이 우리에게 널리 알려진 <a target="_blank" href="http://www.aisto.com/roeder/dotnet/">.NET Relector</a>를 통해서 소스를 얻어낼 수 있습니다.<br /> <br /> 여기까지 해보니, 이전에 살펴본 <a target="_blank" href="/2/0/318">"3.5. VS.NET 2005를 이용한 미니덤프 파일 분석 (1)"</a> 때보다도 상황이 훨씬 더 좋아보입니다.<br /> 문제가 발생한 부분의 소스 코드까지 얻었으니 문제 접근이 더욱 용이해졌습니다.<br /> <br /> 이제, 실제 소스와 함께 실질적으로 예외가 발생한 부분을 맞추기 위해서 IP 레지스터가 가리키는 값을 기반으로 역어셈블을 해봐야 합니다.<br /> <br /> <pre class='code'> <b>!u 0124030F</b> Normal JIT generated code BaseLibrary.ThrowClass.TestMethod() Begin 012402d8, size 4c 012402D8 55 push ebp 012402D9 8BEC mov ebp,esp 012402DB 57 push edi 012402DC 56 push esi 012402DD 53 push ebx 012402DE 83EC34 sub esp,34h 012402E1 33C0 xor eax,eax 012402E3 8945F0 mov dword ptr [ebp-10h],eax 012402E6 33C0 xor eax,eax 012402E8 8945E4 mov dword ptr [ebp-1Ch],eax 012402EB 894DC4 mov dword ptr [ebp-3Ch],ecx 012402EE 833D7C64950000 cmp dword ptr ds:[0095647Ch],0 012402F5 7405 je 012402FC 012402F7 E80220E578 call 7A0922FE (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE) 012402FC 33FF xor edi,edi 012402FE 90 nop 012402FF 8B0D9C6B2E02 mov ecx,dword ptr ds:[022E6B9Ch] 01240305 BA03000000 mov edx,3 0124030A E85DCD2278 call 7946D06C (System.IO.File.Open(System.String, System.IO.FileMode), mdToken: 060033db) <b>>>> 0124030F 8BF0 mov esi,eax</b> 01240311 8BFE mov edi,esi 01240313 8BCF mov ecx,edi 01240315 8B01 mov eax,dword ptr [ecx] 01240317 FF5070 call dword ptr [eax+70h] 0124031A 90 nop 0124031B 90 nop 0124031C 8D65F4 lea esp,[ebp-0Ch] 0124031F 5B pop ebx 01240320 5E pop esi 01240321 5F pop edi 01240322 5D pop ebp 01240323 C3 ret </pre> <br /> 위의 굵은 글씨 부분이 눈에 익으실 것입니다. 바로 ">>>" 부분이 오류가 난 다음 라인입니다. 그러니, 그 이전의 call 명령어를 .NET Reflector를 통해 본 소스와 맞춰 보면 실제로 어느 라인에서 오류가 발생했는 지를 알 수 있습니다.<br /> 또한, 이전에 설명한 방법을 사용하시면 인자값까지 확인할 수 있으니... 이렇게 되면 거의 예외 상황의 근거는 확인이 끝난 것이 됩니다.<br /> <br /> 어떠세요? 좀 재미있으셨나요? ^^<br /> <br /> <br /><br /><hr /><span style='color: Maroon'>[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</a>
첨부파일
스팸 방지용 인증 번호
5075
(왼쪽의 숫자를 입력해야 합니다.)