Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

Windbg - SOS 디버깅 사례 System.NullReferenceException 예외 추적

Release 모드로 빌드된 Windows Forms 응용 프로그램 실행 중 별다른 콜스택 정보 없이 예외가 발생했다면서 비정상 종료 메시지만 나오는 상황이었습니다.

메시지 창이 뜬 시점에 프로세스 풀 덤프를 남긴 후 windbg에서 살펴봤습니다.

CLR 2.0이었기 때문에 다음과 같이 sos를 로드하고,

0:000> .loadby sos mscorjit

예외 정보를 확인(!PrintException)하려 했지만,

0:000> !pe
There is no current managed exception on this thread

다른 스레드에서 발생한 듯싶군요. ^^ 그럼, !threads를 이용해 예외가 발생한 스레드를 확인해 보면 됩니다.

0:000> !threads
ThreadCount: 771
UnstartedThread: 0
BackgroundThread: 770
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
                                              PreEmptive                                                Lock
       ID OSID        ThreadOBJ     State   GC     GC Alloc Context                  Domain           Count APT Exception
   0    1 2544 0000000000694bb0      6020 Enabled  0000000003b10b90:0000000003b113c0 000000000068c370     0 STA
   2    2 1cac 00000000006a0630      b220 Enabled  0000000000000000:0000000000000000 000000000068c370     0 MTA (Finalizer)
   5    4  2e8 000000001b3ec740      b220 Enabled  0000000000000000:0000000000000000 000000000068c370     2 MTA System.NullReferenceException (0000000002a87b58)
   6    f 27bc 000000001b41f690   380b220 Enabled  0000000000000000:0000000000000000 000000000068c370     0 MTA (Threadpool Worker)

(Managed 환경 기준으로) 5번 스레드임을 알았으니 문맥 전환을 해주고 다시 예외 정보를 확인합니다.

0:000> ~5s
ntdll!NtWaitForSingleObject+0x14:
00007ff9`82ba6154 c3

0:005> !pe
Exception object: 0000000002a87b58
Exception type: System.NullReferenceException
Message: Object reference not set to an instance of an object.
InnerException: <none>
StackTrace (generated):
    SP               IP               Function
    000000001E2CE820 00007FF9128C862B MyTestEXE!MyTestEXE.SampleType.CallMyMethod(MyTestEXE.SampleType, System.Text.StringBuilder)+0x4cb
    000000001E2CEBB0 00007FF9128C335D MyTestEXE!MyTestEXE.MainForm.SecondThreadFunc(System.Object)+0x8ad
    000000001E2CEF10 00007FF96CEF2C8B mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)+0x9b
    000000001E2CEF60 00007FF96D598C6D mscorlib_ni!System.Threading.ThreadHelper.ThreadStart(System.Object)+0x5d

물론, !clrstack 명령을 이용하셔도 됩니다.

0:005> !clrstack
OS Thread Id: 0x2e8 (5)
Child-SP         RetAddr          Call Site
000000001e2ce820 00007ff9128c335d MyTestEXE.SampleType.CallMyMethod(MyTestEXE.SampleType, System.Text.StringBuilder)
000000001e2cebb0 00007ff96cef2c8b MyTestEXE.MainForm.SecondThreadFunc(System.Object)
000000001e2cef10 00007ff96d598c6d System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
000000001e2cef60 00007ff972148f32 System.Threading.ThreadHelper.ThreadStart(System.Object)

그런데, 아쉽게도 소스 코드 위치를 알 수가 없습니다. 이런 경우 문제 추적을 위해 도움이 되는 방법이 해당 상황을 재현시켜 보는 것입니다. 즉, 메서드가 수행된 시점에 관련 객체들의 상태 정보와 메서드에 들어온 인자 값들의 상태 정보를 알아내면 높은 확률로 재현할 수 있는 환경을 마련할 수 있습니다. ^^

이를 위해 clrstack 명령에 -a 옵션을 주면 다음과 같이 인자 값(이 코드에서는 current와 log)과 객체(this)의 주소를 알아낼 수 있습니다.

0:005> !clrstack -a
OS Thread Id: 0x2e8 (5)
Child-SP         RetAddr          Call Site
000000001e2ce820 00007ff9128c335d MyTestEXE.SampleType.CallMyMethod(MyTestEXE.SampleType, System.Text.StringBuilder)
    PARAMETERS:
        this = 0x00000000029c63f0
        current = 0x00000000029f90a8
        log = 0x0000000002a87250
    LOCALS:
        0x000000001e2ce840 = 0x0000000000000000
        0x000000001e2ce848 = 0x0000000000000000
        0x000000001e2ce850 = 0x0000000000000000
        0x000000001e2ce858 = 0x0000000000000000
        0x000000001e2ce859 = 0x0000000000000001
        0x000000001e2ce860 = 0x0000000000000000
        0x000000001e2ce868 = 0x0000000000000000

...[이하 생략]...

첫 번째 인자인 "MyTestEXE.SampleType current" 값을 다음과 같이 확인할 수 있습니다.

0:005> !dumpobj 0x00000000029f90a8
Name: MyTestEXE.SampleType
MethodTable: 00007ff912768818
EEClass: 00007ff912894250
Size: 224(0xe0) bytes
 (D:\MyTestEXE\bin\debug\MyTestEXE.exe)
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ff96d03f130  4000012       90         System.Int32  1 instance            51964 Position
00007ff96d037ec0  4000013        8        System.String  0 instance 00000000029f9188 RawData
00007ff96d025cb0  4000014       10      System.Object[]  0 instance 00000000029f9ef0 Lines
0000000000000000  4000015       18                       0 instance 00000000029f9fb8 Values
00007ff96d03f130  4000016       94         System.Int32  1 instance               70 Count
00007ff96d03f130  4000017       98         System.Int32  1 instance                0 Limit
00007ff96d037ec0  4000018       20        System.String  0 instance 000000000386aea8 MachineName
...[이하 생략]...

위의 출력은 SampleType 클래스의 멤버들을 "Value" 값과 함께 보여줍니다. 이 중에서 System.Int32와 같이 기본 자료형인 경우 "Value" 칼럼에 나온 값을 그대로 필드 값이라고 보면 됩니다. 반면 System.String, System.Object[]와 같은 참조형들은 Value에 대해 다시 한번 dumpobj를 호출해 그 객체의 내부 값을 확인해 봐야 합니다.

0:005> !dumpobj 000000000386aea8
Name: System.String
MethodTable: 00007ff96d037ec0
EEClass: 00007ff96cc3e560
Size: 46(0x2e) bytes
 (C:\WINDOWS\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: WIN2008X86
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ff96d03f130  4000096        8         System.Int32  1 instance               11 m_arrayLength
00007ff96d03f130  4000097        c         System.Int32  1 instance               10 m_stringLength
00007ff96d039908  4000098       10          System.Char  1 instance               57 m_firstChar
00007ff96d037ec0  4000099       20        System.String  0   shared           static Empty
                                 >> Domain:Value  000000000068c370:0000000002931308 <<
00007ff96d0397b8  400009a       28        System.Char[]  0   shared           static WhitespaceChars
                                 >> Domain:Value  000000000068c370:0000000002931b08 <<

위에서 보는 것처럼, MyTestEXE.SampleType의 MachineName 필드의 값은 "WIN2008X86"이었음을 알 수 있습니다.

운이 좋다면, 문제가 발생한 환경을 재현하는 데 성공할 것입니다. 이 방법은 간혹, CPU 100% 현상을 보일 때에도 유용하게 써먹을 수 있습니다. 같은 구간을 무한 반복해서 실행하는 코드의 경우, 해당 메서드의 수행 환경만 잘 살펴봐도 왜 그런 현상에 빠졌는지를 유추해 볼 수 있기 때문입니다.



이 외에, !pe 출력의 결과로 나온 offset 값을 통해 System.NullReferenceException 예외가 발생한 코드를 대략적으로 찾아낼 수도 있습니다.

0:005> !pe
Exception object: 0000000002a87b58
Exception type: System.NullReferenceException
Message: Object reference not set to an instance of an object.
InnerException: <none>
StackTrace (generated):
    SP               IP               Function
    000000001E2CE820 00007FF9128C862B MyTestEXE!MyTestEXE.SampleType.CallMyMethod(MyTestEXE.SampleType, System.Text.StringBuilder)+0x4cb
    000000001E2CEBB0 00007FF9128C335D MyTestEXE!MyTestEXE.MainForm.SecondThreadFunc(System.Object)+0x8ad
    000000001E2CEF10 00007FF96CEF2C8B mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)+0x9b
    000000001E2CEF60 00007FF96D598C6D mscorlib_ni!System.Threading.ThreadHelper.ThreadStart(System.Object)+0x5d

이에 대해서는 예전 글에서 한번 설명한 적이 있습니다.

Windbg - 비정상 종료된 닷넷 프로그램의 StackTrace에서 보이는 offset 값 의미
; https://www.sysnet.pe.kr/2/0/1095

문제가 발생한 CallMyMethod의 jitted code address를 알아내고,

0:005> !name2ee MyTestEXE!MyTestEXE.SampleType.CallMyMethod
Module: 00007ff912762e38 (MyTestEXE.exe)
Token: 0x0000000006000008
MethodDesc: 00007ff9127687e8
Name: MyTestEXE.SampleType.CallMyMethod(MyTestEXE.SampleType, System.Text.StringBuilder)
JITTED Code Address: 00007ff9128c8160

그 값에 !pe 출력 결과에서 얻은 IP 주소(00007FF9128C862B)와 0x4cb 오프셋 값을 더한 위치(= 7FF9128C8AF6)를 계산하고, 그 위치를 JITTED Code Address 주소를 역어셈블한 코드에서 찾아내면 됩니다.





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







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

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

비밀번호

댓글 작성자
 




... 121  122  123  124  125  126  127  128  129  130  131  132  133  [134]  135  ...
NoWriterDateCnt.TitleFile(s)
1738정성태8/23/201423384.NET Framework: 456. C# - CAS를 이용한 Lock 래퍼 클래스파일 다운로드1
1737정성태8/20/201420839VS.NET IDE: 93. Visual Studio 2013 동기화 문제
1736정성태8/19/201426862VC++: 79. [부연] CAS Lock 알고리즘은 과연 빠른가? [2]파일 다운로드1
1735정성태8/19/201419373.NET Framework: 455. 닷넷 사용자 정의 예외 클래스의 최소 구현 코드 - 두 번째 이야기
1734정성태8/13/201421110오류 유형: 237. Windows Media Player cannot access the file. The file might be in use, you might not have access to the computer where the file is stored, or your proxy settings might not be correct.
1733정성태8/13/201427372.NET Framework: 454. EmptyWorkingSet Win32 API를 사용하는 C# 예제파일 다운로드1
1732정성태8/13/201435736Windows: 99. INetCache 폴더가 다르게 보이는 이유
1731정성태8/11/201428175개발 환경 구성: 235. 점(.)으로 시작하는 파일명을 탐색기에서 만드는 방법
1730정성태8/11/201423380개발 환경 구성: 234. Royal TS의 터미널(Terminal) 연결에서 한글이 깨지는 현상 해결 방법
1729정성태8/11/201419351오류 유형: 236. SqlConnection - The requested Performance Counter is not a custom counter, it has to be initialized as ReadOnly.
1728정성태8/8/201431596.NET Framework: 453. C# - 오피스 파워포인트(Powerpoint) 파일을 WinForm에서 보는 방법파일 다운로드1
1727정성태8/6/201421791오류 유형: 235. SignalR 오류 메시지 - Counter 'Messages Bus Messages Published Total' does not exist in the specified Category. [2]
1726정성태8/6/201420636오류 유형: 234. IIS Express에서 COM+ 사용 시 SecurityException - "Requested registry access is not allowed" 발생
1725정성태8/6/201422587오류 유형: 233. Visual Studio 2013 Update3 적용 후 Microsoft.VisualStudio.Web.PageInspector.Runtime 모듈에 대한 FileNotFoundException 예외 발생
1724정성태8/5/201427425.NET Framework: 452. .NET System.Threading.Thread 개체에서 Native Thread Id를 구하는 방법 - 두 번째 이야기 [1]파일 다운로드1
1723정성태7/29/201459798개발 환경 구성: 233. DirectX 9 예제 프로젝트 빌드하는 방법 [3]파일 다운로드1
1722정성태7/25/201422136오류 유형: 232. IIS 500 Internal Server Error - NTFS 암호화된 폴더에 웹 애플리케이션이 위치한 경우
1721정성태7/24/201425431.NET Framework: 451. 함수형 프로그래밍 개념 - 리스트 해석(List Comprehension)과 순수 함수 [2]
1720정성태7/23/201423401개발 환경 구성: 232. C:\WINDOWS\system32\LogFiles\HTTPERR 폴더에 로그 파일을 남기지 않는 설정
1719정성태7/22/201427239Math: 13. 동전을 여러 더미로 나누는 경우의 수 세기(Partition Number) - 두 번째 이야기파일 다운로드1
1718정성태7/19/201436678Math: 12. HTML에서 수학 관련 기호/수식을 표현하기 위한 방법 - MathJax.js [4]
1716정성태7/17/201436387개발 환경 구성: 231. PC 용 무료 안드로이드 에뮬레이터 - genymotion
1715정성태7/13/201431488기타: 47. 운영체제 종료 후에도 USB 외장 하드의 전원이 꺼지지 않는 경우 [3]
1714정성태7/11/201421552VS.NET IDE: 92. Visual Studio 2013을 지원하는 IL Support 확장 도구
1713정성태7/11/201445318Windows: 98. 윈도우 시스템 디스크 용량 확보를 위한 "Package Cache" 폴더 이동 [1]
1712정성태7/10/201433865.NET Framework: 450. 영문 윈도우에서 C# 콘솔 프로그램의 유니코드 출력 방법 [3]
... 121  122  123  124  125  126  127  128  129  130  131  132  133  [134]  135  ...