Crash Dump Analysis Patterns (Part 8)
Crash Dump Analysis Patterns (Part 8)
원문 https://www.dumpanalysis.org/blog/index.php/2007/02/02/crash-dump-analysis-patterns-part-8/
번역 김희준(drost@naver.com, https://insidekernel.net, 2007-08-14)
오늘 설명드릴 패턴은 숨겨진 예외(Hidden Exception) 라고 하는 것입니다. !analyze –v 명령어를 실행해봐도 어떠한 예외도 보이지 않거나 브레이크 포인트만 보일 것입니다. 이런 경우에는 수동 분석이 필요합니다. 이런 현상은 때때로 다른 패턴(Multiple Exception) 때문에 발생하기도 합니다. 그런 경우가 아니라면 예외가 발생하고 그 예외가 예외 핸들러에 의해서 처리되어서 없어지고, 계속해서 이어져서 실행되어 천천히 깨져버린 데이터가 누적되어서 새로운 크래쉬나 hang으로 이어지게 됩니다. 여러분들은 가끔 아래처럼 프로세스가 종료될 때에 hang되는 것을 보실 수 있을 것입니다.
프로세스 덤프를 받아보면, 오직 하나의 스레드만 있지요.
0:000> kv
ChildEBP RetAddr
0096fcdc 7c822124 ntdll!KiFastSystemCallRet
0096fce0 77e6baa8 ntdll!NtWaitForSingleObject+0xc
0096fd50 77e6ba12 kernel32!WaitForSingleObjectEx+0xac
0096fd64 67f016ce kernel32!WaitForSingleObject+0x12
0096fd78 7c82257a component!DllInitialize+0xc2
0096fd98 7c8118b0 ntdll!LdrpCallInitRoutine+0x14
0096fe34 77e52fea ntdll!LdrShutdownProcess+0x130
0096ff20 77e5304d kernel32!_ExitProcess+0x43
0096ff34 77bcade4 kernel32!ExitProcess+0x14
0096ff40 77bcaefb msvcrt!__crtExitProcess+0x32
0096ff70 77bcaf6d msvcrt!_cinit+0xd2
0096ff84 77bcb555 msvcrt!_exit+0x11
0096ffb8 77e66063 msvcrt!_endthreadex+0xc8
0096ffec 00000000 kernel32!BaseThreadStart+0x34
스택 덤프를 직접 뒤져서 아래의 주소를 찾도록 해 보세요:
KiUserExceptionDispatcher
이 함수는 RtlDispatchException을 호출합니다.
0:000> !teb
TEB at 7ffdc000
ExceptionList: 0096fd40
StackBase:
00970000
StackLimit:
0096a000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 7ffdc000
EnvironmentPointer: 00000000
ClientId: 00000858 . 000008c0
RpcHandle: 00000000
Tls Storage: 00000000
PEB Address: 7ffdd000
LastErrorValue: 0
LastStatusValue: c0000135
Count Owned Locks: 0
HardErrorMode: 0
0:000>dds 0096a000 00970000
...
...
...
0096c770 7c8140cc ntdll!RtlDispatchException+0x91
0096c774 0096c808
0096c778 0096ffa8
0096c77c 0096c824
0096c780 0096c7e4
0096c784 77bc6c74 msvcrt!_except_handler3
0096c788 00000000
0096c78c 0096c808
0096c790 01030064
0096c794 00000000
0096c798 00000000
0096c79c 00000000
0096c7a0 00000000
0096c7a4 00000000
0096c7a8 00000000
0096c7ac 00000000
0096c7b0 00000000
0096c7b4 00000000
0096c7b8 00000000
0096c7bc 00000000
0096c7c0 00000000
0096c7c4 00000000
0096c7c8 00000000
0096c7cc 00000000
0096c7d0 00000000
0096c7d4 00000000
0096c7d8 00000000
0096c7dc 00000000
0096c7e0 00000000
0096c7e4 00000000
0096c7e8 00970000
0096c7ec 00000000
0096c7f0 0096caf0
0096c7f4 7c82ecc6 ntdll!KiUserExceptionDispatcher+0xe0096c7f8 0096c000
0096c7fc 0096c824 ; a pointer to an exception context
0096c800 0096c808
0096c804 0096c824
0096c808 c0000005
0096c80c 00000000
0096c810 00000000
0096c814 77bd8df3 msvcrt!wcschr+0×15
0096c818 00000002
0096c81c 00000000
0096c820 01031000
0096c824 0001003f
0096c828 00000000
0096c82c 00000000
0096c830 00000000
0096c834 00000000
0096c838 00000000
0096c83c 00000000
두 함수들 모두 두 번째 파라미터로 exception context(예외가 발생했을 때 프로세서 상태)라고 불리는 것에 대한 포인터를 갖습니다. 우리는 .cxr 명령어로 예외 당시의 스레드 예외 컨텍스트로 변경할 수 있습니다.
컨텍스트를 변경한 후에는 예외 발생 전의 스레드 스택을 볼 수 있습니다.
0:000> kL
ChildEBP RetAddr
0096caf0 67b11808 msvcrt!wcschr+0×150096cb10 67b1194d component2!function1+0×50
0096cb24 67b11afb component2!function2+0×1a
0096eb5c 67b11e10 component2!function3+0×39
0096ed94 67b14426 component2!function4+0×155
0096fdc0 67b164b7 component2!function5+0×3b
0096fdcc 00402831 component2!function6+0×5b
0096feec 0096ff14 program!function+0×1d1
0096ffec 00000000 kernel32!BaseThreadStart+0×34
우리는 이 예외가 component2에서 유니코드 문자 하나를 찾을 때(wcschr) 발생한다는 것을 확인할 수 있습니다. 대부분의 경우에 문자열이 0으로 끝나지 않은 경우이지요.
유저 공간에서 자주 발생하는 예외 핸들링 처리 절차를 다시 종합해보기 위해서, 다른 덤프에서가져온 다른 스레드 스택을 살펴보도록 하겠습니다:
ntdll!KiFastSystemCallRet
ntdll!NtWaitForMultipleObjects+0xc
kernel32!UnhandledExceptionFilter+0×746kernel32!_except_handler3+0×61
ntdll!ExecuteHandler2+0×26
ntdll!ExecuteHandler+0×24
ntdll!RtlDispatchException+0×91
ntdll!KiUserExceptionDispatcher+0xe ntdll!RtlpCoalesceFreeBlocks+0×36e ; crash is here
ntdll!RtlFreeHeap+0×38e
msvcrt!free+0xc3
msvcrt!_freefls+0×124
msvcrt!_freeptd+0×27
msvcrt!__CRTDLL_INIT+0×1da
ntdll!LdrpCallInitRoutine+0×14
ntdll!LdrShutdownThread+0xd2
kernel32!ExitThread+0×2f
kernel32!BaseThreadStart+0×39
RtlpCoalesceFreeBlocks(이 함수는 heap을 컴팩트하고 RtlFreeHeap에서 불립니다)가 잘못된 메모리 참조를 하고 그때 이 예외가 커널에서 처음으로 처리되고, 그것이 유저 공간에서 발생했기 때문에 실행이 예외 핸들러를 검색하는 RtlDispatchException으로 이동된다. 그리고 이 경우에 기본 예외 핸들러는 UnhandledExceptionFilter이다.
만약 여러분들이 이 함수를 콜 스택에서 발견한다면 다른 덤프에서 가져온 아래 예제처럼 여러분들은 예외 컨텍스트와 스레드 스택을 수동으로 얻을 수 있을 것입니다.
이 크래쉬는 대부분의 경우 동적 메모리 깨짐 패턴(힙 깨짐) 때문에 발생합니다.
- Dmitry Vostokov -
Comments
- Anonymous
September 17, 2008
PingBack from http://www.easycoded.com/crash-dump-analysis-patterns-part-8/