Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 5개 있습니다.)
windbg - 분석 예: 시작하자마자 비정상 종료하는 프로세스 - NullReferenceException

콘솔 응용 프로그램이었는데, 시작하자마자 비정상 종료하는 프로세스였습니다. 개발자 PC에서 이런 현상이 발생하면 소스 코드 올려놓은 상태에서 'F5' 키를 눌러 바로 디버깅 모드로 넘어가 쉽게 문제 해결을 할 수 있지만, 원격 컴퓨터에서만 재현되는 상황이라면 답답한 심정이 됩니다.

이런 경우에도 역시, "덤프"를 뜨는 것이 적절한 해결책이 될 수 있는데요. 우선, 지난번에 알려드린 "풀 덤프" 방법들 중에서,

풀 덤프 파일을 남기는 방법
; https://www.sysnet.pe.kr/2/0/991

procdump.exe를 이용하여 덤프를 남겨보겠습니다. 예를 들어 실행 파일이 test.exe라고 가정할 때 아래와 같은 옵션으로 프로세스를 시작하면 예외가 발생했을 때 덤프를 남길 수 있습니다.

D:\temp>procdump  -e -ma -x d:\temp\test.exe d:\temp\test.dmp

ProcDump v3.01 - Writes process dump files
Copyright (C) 2009-2010 Mark Russinovich
Sysinternals - www.sysinternals.com

Process:               test.exe (3464)
CPU threshold:         n/a
Performance counter:   n/a
Commit threshold:      n/a
Threshold seconds:     n/a
Number of dumps:       1
Hung window check:     Disabled
Exception monitor:     Unhandled
Terminate monitor:     Disabled
Dump file:             d:\temp\test.dmp

[13:49.54] Unhandled exception.

Writing dump file d:\temp\test_110212_134954.dmp...
Dump written.

The process has exited.

(참고로, "Working Directory"가 procdump.exe 실행 폴더로 바뀌기 때문에 procdump.exe를 이용하여 덤프를 남길 때는 procdump.exe 실행 파일 자체를 대상 EXE 폴더에 복사하고 덤프를 뜨는 것이 좋습니다.)

이 덤프 파일을 windbg에 열면 다음과 같은 화면으로 시작합니다

Microsoft (R) Windows Debugger Version 6.11.0001.404 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.

Loading Dump File [D:\...[생략]...\test_110212_134954.dmp]
User Mini Dump File with Full Memory: Only application data is available

Comment: '
*** procdump   -e -ma -x d:\temp\test.exe d:\temp\test.dmp
*** Unhandled exception'
Symbol search path is: SRV*\\localhost\e$\Symbols*http://msdl.microsoft.com/download/symbols;
Executable search path is: 
Windows 7 Version 7600 MP (4 procs) Free x64
Product: Server, suite: TerminalServer SingleUserTS
Machine Name:
Debug session time: Sat Feb 12 13:49:54.000 2011 (GMT+9)
System Uptime: 1 days 10:22:04.203
Process Uptime: 0 days 0:00:01.000
................................
KERNELBASE!RaiseException+0x39:
000007fe`fd4caa7d 4881c4c8000000  add     rsp,0C8h

...[생략: SOS 로드 단계 설명]...

자, 이제 예외를 검사해 보면, 아래와 같이 Test!Test.MainForm..ctor()에서 NULL 참조 오류가 발생했음을 알 수 있습니다.

0:000> !pe
Exception object: 00000000026e7cc8
Exception type: System.NullReferenceException
Message: Object reference not set to an instance of an object.
InnerException: <none>
StackTrace (generated):
    SP               IP               Function
    00000000001FEC40 000007FF001605B4 Test!Test.MainForm..ctor()+0x434
    00000000001FECC0 000007FF0016014B Test!Test.Program.Main()+0x2b

StackTraceString: <none>
HResult: 80004003

애석하게도 위의 상황에서 sos 수준으로는 더 이상의 오류 추적은 불가능합니다. 왜냐하면 PDB 파일이 없기 때문입니다.




여전히 PDB 파일이 없는 상황에서 SOSEX.dll 확장을 이용하면 조금 더 자세한 정보를 얻을 수 있습니다. 이에 대해서는 다음의 글에서 소개해 주고 있습니다.

Case of NullReferenceException not handled by sos / windbg
; http://naveensrinivasan.com/2010/05/27/case-of-nullreferenceexception-not-handled-by-sos-windbg/

바로, !mk 확장 명령이 그것입니다. 곧바로 ^^ SOSEX.dll 파일을 다운로드해서,

SOSEX v4.0 Now Available 
; http://www.stevestechspot.com/SOSEXV40NowAvailable.aspx

(저는, 64비트 버전을 다운로드 받았습니다.)
DLL 파일을 "C:\Program Files\Debugging Tools for Windows (x64)" 폴더에 복사해줍니다. 그다음 sos.dll을 로드하는 것과 같은 명령어로 다음과 같이 실행하고,

0:000> .load sosex

이전까지 추적했던 덤프 파일에 대해 !mk 명령을 내려주면 다음과 같은 출력을 확인할 수 있습니다.

0:000> !mk
Thread 0:
     ESP              EIP
00:U 00000000001fe5c0 000007fefdbcaa7d KERNELBASE!RaiseException+0x39
01:U 00000000001fe690 000007fef70fdbdc mscorwks!NakedThrowHelper2+0xc
02:U 00000000001fe6c0 000007fef70fdc2a mscorwks!NakedThrowHelper_RspAligned+0x3d
03:U 00000000001fec38 000007fef70fdc35 mscorwks!NakedThrowHelper_FixRsp+0x5
04:M 00000000001fec40 000007ff001605b4 *** ERROR: Module load completed but symbols could not be loaded for test.exe
Test.MainForm..ctor()(+0x1c0 IL)(+0x434 Native)
05:M 00000000001fecc0 000007ff0016014b Test.Program.Main()(+0x10 IL)(+0x2b Native)
06:U 00000000001fecf0 000007fef70fd502 mscorwks!CallDescrWorker+0x82
07:U 00000000001fed30 000007fef6fb9fd3 mscorwks!CallDescrWorkerWithHandler+0xd3
08:U 00000000001fedd0 000007fef6fca3af mscorwks!MethodDesc::CallDescr+0x24f
09:U 00000000001ff020 000007fef6f3dc7f mscorwks!ClassLoader::RunMain+0x22b
0a:U 00000000001ff280 000007fef6f21c74 mscorwks!Assembly::ExecuteMainMethod+0xbc
0b:U 00000000001ff570 000007fef6f59955 mscorwks!SystemDomain::ExecuteMainMethod+0x491
0c:U 00000000001ffb40 000007fef706db07 mscorwks!ExecuteEXE+0x47
0d:U 00000000001ffb90 000007fef6f2855c mscorwks!CorExeMain+0xac
0e:U 00000000001ffbf0 000007fefa2d3309 mscoreei!CorExeMain+0x41
0f:U 00000000001ffc20 000007fefa365b21 mscoree!CorExeMain_Exported+0x57
10:U 00000000001ffc50 00000000777ff56d kernel32!BaseThreadInitThunk+0xd
11:U 00000000001ffc80 0000000077932cc1 ntdll!RtlUserThreadStart+0x1d

즉 IL 코드의 offset 값(== 0x1c0)이 출력되는데요. 이 값을 기반으로 추적하는 방법은 이전에 설명한 Watson Bucket 정보를 추적하는 것과 같습니다.

Watson Bucket 정보를 이용한 CLR 응용 프로그램 예외 분석
; https://www.sysnet.pe.kr/2/0/595

단지, 위의 글에 설명된 것처럼 ildasm.exe를 별도로 구동할 필요는 없습니다. 왜냐하면 windbg 내에서 모두 해결할 수 있기 때문인데요. 우선, Test.Program.Main의 Method Desc를 알아내야 합니다. 이는 !pe 출력에서 나타난 IP 값을 통해서 알 수 있습니다.

0:000> !pe
Exception object: 00000000026e7cc8
Exception type: System.NullReferenceException
Message: Object reference not set to an instance of an object.
InnerException: <none>
StackTrace (generated):
    SP               IP               Function
    00000000001FEC40 000007FF001605B4 Test!Test.MainForm..ctor()+0x434
    00000000001FECC0 000007FF0016014B Test!Test.Program.Main()+0x2b

StackTraceString: <none>
HResult: 80004003

이어서 "!ip2md" 명령을 내리고,

0:000> !ip2md 000007FF001605B4 
MethodDesc: 000007ff00016c58
Method Name: Test.MainForm..ctor()
Class: 000007ff00152578
MethodTable: 000007ff00016e88
mdToken: 0600002f
Module: 000007ff000133d0
IsJitted: yes
CodeAddr: 000007ff00160180

이어서 "!dumpil" 명령을 내리면, ILDASM에서 봤던 출력과 유사한 덤프를 얻을 수 있습니다.

0:000> !dumpil 000007ff00016c58
ilAddr = 0000000000c42d64
...[생략]...
IL_0199: ldarg.0 
IL_019a: call System.Environment::get_MachineName 
IL_019f: stfld Test.MainForm::computerName
IL_01a4: ldstr "CLSID\{{{0}}}\InprocServer32"
IL_01a9: ldstr "FF8C2B6C-DBB5-4AED-9876-2CED6FFAF4C9"
IL_01ae: call System.String::Format 
IL_01b3: stloc.1 
IL_01b4: ldsfld Microsoft.Win32.Registry::ClassesRoot 
IL_01b9: ldloc.1 
IL_01ba: callvirt Microsoft.Win32.RegistryKey::OpenSubKey 
IL_01bf: stloc.2 
IL_01c0: ldloc.2 
IL_01c1: ldsfld System.String::Empty 
IL_01c6: ldsfld System.String::Empty 
IL_01cb: callvirt Microsoft.Win32.RegistryKey::GetValue 
...[생략]...
IL_03d1: ret 

정황을 보아하니, 이렇게 해석이 될 수 있을 것 같습니다.

RegistryKey regKey = RegistryKey.OpenSubKey(...[경로]...);
regKey.GetValue(...); // regKey == NULL 이므로 예외 발생

굳이 위와 같은 정황 분석을 하지 않아도, 개발자 자신이 가지고 있는 .NET 코드와 비교해 보면 충분히 정확한 오류 라인을 찾아낼 수 있습니다.

물론, PDB 파일이 있었다면 이렇게 "!dumpil" 명령을 내리는 수고 없이 곧바로 소스 코드 라인 번호를 얻었을 것입니다.




처음으로 돌아가서, 물론 덤프 파일을 뜨는 것이 도움이 되긴 합니다. 하지만, Watson Bucket 내용을 설명하면서 이벤트 로그에 남는 내용의 의미를 해석해 보았는데요. 어렵게 덤프 분석을 하지 않아도, 이렇게 이벤트 로그를 구하게 되면 좀 더 손쉽게 디버깅이 됩니다. 아래는 위에서 분석한 덤프를 뜬 응용 프로그램이 종료했을 때 이벤트 로그에 남은 내용입니다.

Fault bucket , type 0
Event Name: CLR20r3
Response: Not available
Cab Id: 0

Problem signature:
P1: test.exe
P2: 1.0.0.0
P3: 4d465cd0
P4: test
P5: 1.0.0.0
P6: 4d465cd0
P7: 2f
P8: 1c0
P9: System.NullReferenceException
P10: 

...[생략]...



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

[연관 글]






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

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

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  [8]  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13434정성태11/1/20232687스크립트: 62. 파이썬 - class의 정적 함수를 동적으로 교체
13433정성태11/1/20232399스크립트: 61. 파이썬 - 함수 오버로딩 미지원
13432정성태10/31/20232485오류 유형: 878. 탐색기의 WSL 디렉터리 접근 시 "Attempt to access invalid address." 오류 발생
13431정성태10/31/20232812스크립트: 60. 파이썬 - 비동기 FastAPI 앱을 gunicorn으로 호스팅
13430정성태10/30/20232702닷넷: 2153. C# - 사용자가 빌드한 ICU dll 파일을 사용하는 방법
13429정성태10/27/20232992닷넷: 2152. Win32 Interop - C/C++ DLL로부터 이중 포인터 버퍼를 C#으로 받는 예제파일 다운로드1
13428정성태10/25/20233061닷넷: 2151. C# 12 - ref readonly 매개변수
13427정성태10/18/20233252닷넷: 2150. C# 12 - 정적 문맥에서 인스턴스 멤버에 대한 nameof 접근 허용(Allow nameof to always access instance members from static context)
13426정성태10/13/20233412스크립트: 59. 파이썬 - 비동기 호출 함수(run_until_complete, run_in_executor, create_task, run_in_threadpool)
13425정성태10/11/20233199닷넷: 2149. C# - PLinq의 Partitioner<T>를 이용한 사용자 정의 분할파일 다운로드1
13423정성태10/6/20233178스크립트: 58. 파이썬 - async/await 기본 사용법
13422정성태10/5/20233323닷넷: 2148. C# - async 유무에 따른 awaitable 메서드의 병렬 및 예외 처리
13421정성태10/4/20233401닷넷: 2147. C# - 비동기 메서드의 async 예약어 유무에 따른 차이
13420정성태9/26/20235600스크립트: 57. 파이썬 - UnboundLocalError: cannot access local variable '...' where it is not associated with a value
13419정성태9/25/20233225스크립트: 56. 파이썬 - RuntimeError: dictionary changed size during iteration
13418정성태9/25/20233929닷넷: 2146. C# - ConcurrentDictionary 자료 구조의 동기화 방식
13417정성태9/19/20233462닷넷: 2145. C# - 제네릭의 형식 매개변수에 속한 (매개변수를 가진) 생성자를 호출하는 방법
13416정성태9/19/20233262오류 유형: 877. redis-py - MISCONF Redis is configured to save RDB snapshots, ...
13415정성태9/18/20233760닷넷: 2144. C# 12 - 컬렉션 식(Collection Expressions)
13414정성태9/16/20233521디버깅 기술: 193. Windbg - ThreadStatic 필드 값을 조사하는 방법
13413정성태9/14/20233719닷넷: 2143. C# - 시스템 Time Zone 변경 시 이벤트 알림을 받는 방법
13412정성태9/14/20237003닷넷: 2142. C# 12 - 인라인 배열(Inline Arrays) [1]
13411정성태9/12/20233510Windows: 252. 권한 상승 전/후 따로 관리되는 공유 네트워크 드라이브 정보
13410정성태9/11/20235036닷넷: 2141. C# 12 - Interceptor (컴파일 시에 메서드 호출 재작성) [1]
13409정성태9/8/20233892닷넷: 2140. C# - Win32 API를 이용한 모니터 전원 끄기
13408정성태9/5/20233851Windows: 251. 임의로 만든 EXE 파일을 포함한 ZIP 파일의 압축을 해제할 때 Windows Defender에 의해 삭제되는 경우
1  2  3  4  5  6  7  [8]  9  10  11  12  13  14  15  ...