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 -