Crash Dump Analysis Patterns (Part 9a)
Crash Dump Analysis Patterns (Part 9a)
원문 링크: http://www.dumpanalysis.org/blog/index.php/2007/02/09/crash-dump-analysis-patterns-part-9a/
번역: 김희준( Heejune Kim, drost@naver.com, http://insidekernel.net, 2007-08-27 )
이번 패턴은 데드락입니다. 데드락이 뭔지 모르신다면 초보자편 4번째 글을 읽도록 하세요. 데드락은 mutex나 event같은 primitives 동기화 객체 뿐만 아니라, 보다 복잡한 객체인 critical section이나 executive resources (ERESOURCE)와 같은 primitives를 이용한 상위 객체에서도 발생합니다. 이러한 현상은 높은 레벨인 시스템 관점에서의 프로세스 간, 컴포넌트간 통신에서도 발생할 수 있고, 이러한 예로는 GUI 윈도우 메시지나 ,LPC 메시지, RPC 호출처럼 상호 독립적으로 메시지를 기다릴 때 발생합니다. 이것은 큰 패턴이고, 몇 가지 파트로 이것을 나눠볼 예정입니다.
덤프에서 데드락을 볼 수 있을까요? 우선 유저 덤프와 크리티컬 섹션으로 시작해봅시다.
먼저 저는 CRITICAL_SECTION 구조체의 여러 멤버들을 이해하기 위하여 아래의 MSDN 아티클을 읽어보기를 권합니다:
Break Free of Code Deadlocks in Critical Sections Under Windows
WinDBG의 !locks 명령어는 프로세스의 크리티컬 섹션 리스트를 조사해서 모든 lock된 크리티컬 섹션과, lock count와 현재 크리티컬 섹션을 소유하고 있는 스레드id를 보여줄 것입니다. 아래의 결과는 Windows 프린트 스풀러 프로세스(spoolsv.exe)가 hang 되어 있는 덤프에서 출력 결과를 가져온 것입니다:
0:000> !locks
CritSec NTDLL!LoaderLock+0 at 784B0348
LockCount 4
RecursionCount 1
OwningThread 624
EntryCount 6c3
ContentionCount 6c3
*** Locked
CritSec LOCALSPL!SpoolerSection+0 at 76AB8070
LockCount 3
RecursionCount 1
OwningThread 1c48
EntryCount 646
ContentionCount 646
*** Locked
스레드 #624와 #1c48 살펴본다면 서로 각각을 상호 독립적으로 기다리는 것을 볼 수 있습니다.
- TID#624가 크리티컬 섹션784B0348을 소유하고, 크리티컬 섹션76AB8070를 기다리고 있음
- TID#1c48가 크리티컬 섹션76AB8070를 소유하고, 크리티컬 섹션 784B0348을 기다리고 있음
0:000>~*kv
. 12 Id: bc0.624 Suspend: 1 Teb: 7ffd3000 Unfrozen
0000024c 00000000 00000000 NTDLL!ZwWaitForSingleObject+0xb
76ab8000 76a815ef 76ab8070 NTDLL!RtlpWaitForCriticalSection+0×9e
76ab8070 76a844f8 00cd1f38 NTDLL!RtlEnterCriticalSection+0×46
00cd1f38 76a8a1d7 00000000 LOCALSPL!EnterSplSem+0xb
00000000 00000000 00cd1f38 LOCALSPL!FindSpoolerByNameIncRef+0×1f
00000000 777f19bc 00000001 LOCALSPL!LocalGetPrinterDriverDirectory+0xe
00000000 777f19bc 00000001 spoolss!GetPrinterDriverDirectoryW+0×59
00000000 777f19bc 00000001 spoolsv!YGetPrinterDriverDirectory+0×27
00000000 777f19bc 00000001 WINSPOOL!GetPrinterDriverDirectoryW+0×7b
50000000 00000001 00000000 BRHLUI04+0×14ea
50002ea0 50000000 00000001 BRHLUI04!DllGetClassObject+0×1705
00000000 00000000 000cb570 NTDLL!LdrpRunInitializeRoutines+0×1df
000cc8f8 0288ea30 0288ea38 NTDLL!LdrpLoadDll+0×2e6
000cc8f8 0288ea30 0288ea38 NTDLL!LdrLoadDll+0×17)
000c1258 00000000 00000008 KERNEL32!LoadLibraryExW+0×231
000c150c 0288efd8 00000000 UNIDRVUI!PLoadCommonInfo+0×17e
000c150c 0288efd8 00000007 UNIDRVUI!DwDeviceCapabilities+0×1a
00070000 00071378 00000045 UNIDRVUI!DrvDeviceCapabilities+0×19
. 13 Id: bc0.1c48 Suspend: 1 Teb: 7ffd2000 Unfrozen
0000010c 00000000 00000000 NTDLL!ZwWaitForSingleObject+0xb
784b0301 78468d38 784b0348 NTDLL!RtlpWaitForCriticalSection+0×9e
784b0348 74fb4344 00000000 NTDLL!RtlEnterCriticalSection+0×46
74fb0000 02c0f2a8 00000000 NTDLL!LdrpGetProcedureAddress+0×122
74fb0000 02c0f2a8 00000000 NTDLL!LdrGetProcedureAddress+0×17
74fb0000 74fb4344 02c0f449 KERNEL32!GetProcAddress+0×41
017924b0 00000000 00000001 ws2_32!CheckForHookersOrChainers+0×1f
00000101 02c0f344 017924b0 ws2_32!WSAStartup+0×10f
00cdf79c 02c0f4f4 76a8c9bc LOCALSPL!GetDNSMachineName+0×1e
00000000 76a8c9bc 780276a2 LOCALSPL!GetPrinterUrl+0×2c
0176f570 ffffffff 01000000 LOCALSPL!UpdateDsSpoolerKey+0×322
0176f570 76a8c9bc 01792b90 LOCALSPL!RecreateDsKey+0×50
00000000 00000002 01792b90 LOCALSPL!SplAddPrinter+0×521
01791faa 0176a684 76a5cd34 WIN32SPL!InternalAddPrinterConnection+0×1b4
01791faa 02c0fa00 02c0fabc WIN32SPL!AddPrinterConnectionW+0×15
00076f1c 02c0fabc 01006873 spoolss!AddPrinterConnectionW+0×49
00076f1c 00000001 77107fb0 spoolsv!YAddPrinterConnection+0×17
00076f1c 02020202 00000001 spoolsv!RpcAddPrinterConnection+0xb
01006868 02c0fac0 00000001 rpcrt4!Invoke+0×30
00000000 00000000 000d22c8 rpcrt4!NdrStubCall2+0×655
000d22c8 00076fe0 000d22c8 rpcrt4!NdrServerCall2+0×17
010045fc 000d22c8 02c0fe0c rpcrt4!DispatchToStubInC+0×32
0000002b 00000000 02c0fe0c rpcrt4!RPC_INTERFACE::DispatchToStubWorker+0×100
000d22c8 00000000 02c0fe0c rpcrt4!RPC_INTERFACE::DispatchToStub+0×5e
000d3210 00076608 813b0013 rpcrt4!LRPC_SCALL::DealWithRequestMessage+0×1dd
000d21d0 02c0fe50 000d3210 rpcrt4!LRPC_ADDRESS::DealWithLRPCRequest+0×10c
770c9ad0 00076608 770cb6d8 rpcrt4!LRPC_ADDRESS::ReceiveLotsaCalls+0×229
00076608 770cb6d8 0288f9a8 rpcrt4!RecvLotsaCallsWrapper+0×9
00074a50 02c0ffec 77e7438b rpcrt4!BaseCachedThreadRoutine+0×11f
00076e68 770cb6d8 0288f9a8 rpcrt4!ThreadStartRoutine+0×18
770d1c54 00076e68 00000000 KERNEL32!BaseThreadStart+0×52
이 분석과정이 꽤나 간단하게 보일 것입니다. 그렇다면 커널과 전체 메모리 덤프에선 어떨까요? 물론 커널 메모리 덤프에서 유저 공간의 크리티컬 섹션을 볼 수는 없지만 전체 메모리 덤프에서 !ntsdexts.locks 명령어를 사용하여 적절한 프로세스 컨텍스트로 변경한 후에는 그것들을 볼 수 있습니다. Debugger.chm의 Deadlocks and Critical Sections 에서 따온 간단한 스크립트를 사용해서도 가능합니다.
크리티컬 섹션과 관련된 데드락 보기는 왜 이렇게 간단한 것일까요? 크리티컬 섹션의 구조체가 자신의 소유자를 기록하고 있는 멤버를 가지고 있기 때문입니다. 그래서 그것들을 적절한 스레드로 매핑하기가 무척 쉽습니다. 이것은 커널 ERESOURCE 동기화 객체에서도 동일합니다(다음 회에서 이것을 살펴볼 것입니다). 소유자를 가지고 있는 않은 다른 객체들, 예를 들어서 event와 같은 경우에는 event 객체를 살펴보는 것만으로는 소유자를 찾기가 쉽지 않습니다. 이때에는 스레드 콜 스택을 살펴보거나 다른 구조체 또는 소스 코드를 직접 살펴봐야 합니다.
- Dmitry Vostokov -