WinDbg 디버깅2007. 9. 23. 02:16
반응형

오늘은 대드락 디버깅을 해 보겠습니다.

대드락이라함은 두 개의 쓰레드가 두 개의 동기화 객체를 엇갈리게 소유하고 있어서 교착상태에 빠진것을 의미합니다.

한 프로세스에 있는 두 개의 쓰레드가 대드락인 경우는 그 프로세스가 '응답없음' 상태일 꺼구요.
두 프로세스에 각각 존재하는 두 개의 쓰레드가 대드락인 경우는 두 프로세스가 '응답없음' 상태일 겁니다.

더욱 좋지 않는 경우는 대드락인 두 개의 동기화 객체중 하나라도 시스템(OS 또는 파일시스템 등)에서 사용하는 동기화 객체라면 이것이 풀리지 않음으로 인해서 시스템이 멈추는 현상이 발생합니다. (어떤 경우는 마우스 마저 움직이지 않고 화면이 완전히 얼어버립니다. ^^)

일반적으로 대드락의 원인을 찾는 것은 어려운 분석에 속하지만 쉬운 예제를 통해서 가볍게 접근해 보겠습니다. 이번 예제의 시나리오는 MyApp.exe 프로세스가 '응답없음' 상태인 것이 발견되어 원인을 찾아가는 과정입니다.
먼저 MyApp.exe 의 프로세스 정보를 찾아봅니다.

kd> !process 0 0 MyApp.exe
PROCESS 81570b90  SessionId: 0  Cid: 01e4    Peb: 7ffdf000  ParentCid: 01e8
    DirBase: 0bc9c000  ObjectTable: e20a9d60  HandleCount:  44.
    Image: MyApp.exe

프로세스 주소로 프로세스 정보를 확인합니다.

kd> !process 81570b90 
PROCESS 81570b90  SessionId: 0  Cid: 01e4    Peb: 7ffdf000  ParentCid: 01e8
    DirBase: 0bc9c000  ObjectTable: e20a9d60  HandleCount:  44.
    Image: MyApp.exe
    VadRoot 813898a8 Vads 58 Clone 0 Private 175. Modified 81. Locked 0.
    ...
        THREAD 8158c9f8  Cid 1e4.3b0  Teb: 7ffde000 Win32Thread: e1a53660 WAIT: (Executive) KernelMode Non-Alertable
            f9fdf480  Mutant - owning thread 81116030
        IRP List:
            81397b90: (0006,0094) Flags: 00000000  Mdl: 00000000
        Not impersonating
        ...

ChildEBP RetAddr 
f2208bac 804f82e4 nt!KiSwapContext+0x2e (FPO: [EBP 0xf2208be0] [0,0,4])
f2208bb8 804f24b2 nt!KiSwapThread+0x44 (FPO: [0,0,2])
f2208be0 f9fdf9e4 nt!KeWaitForSingleObject+0x1c0 (FPO: [Non-Fpo])
f2208c34 804e8185 MyDrv!MyDrvDeviceControl+0xe4 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\project\test\mydrv\mydrv.c @ 272]
f2208c44 8055887c nt!IopfCallDriver+0x31 (FPO: [0,0,1])
f2208c58 805595a7 nt!IopSynchronousServiceTail+0x5e (FPO: [Non-Fpo])
f2208d00 80552468 nt!IopXxxControlFile+0x5a5
f2208d34 8052a421 nt!NtDeviceIoControlFile+0x28 (FPO: [Non-Fpo])

쓰레드가 하나 있는데 이 녀석이 깨어나지 않는 것이로군요.
콜스택의 마지막 부분에서 KeWaitForSingleObject 를 호출한 것이 보입니다.
KeWaitForSingleObject 를 호출한 MyDrv.sys 의 MyDrvDeviceControl 함수를 확인해 봅니다.

265: case MYDRV_IOCTL_MUTEX1:
266: KeWaitForSingleObject( &MutexA, Executive, KernelMode, FALSE, NULL );
267:
268: interval1.QuadPart = -30000000;
269: KeDelayExecutionThread( KernelMode, FALSE, &interval1 );
270: KeWaitForSingleObject( &MutexB, Executive, KernelMode, FALSE, NULL );
271:
272: KeReleaseMutex( &MutexA, FALSE ); // LINE 272
273: KeReleaseMutex( &MutexB, FALSE );

콜스택에서 표시하는 소스라인 272 는 KeWaitForSingleObject 가 리턴했을 때의 위치입니다. 따라서 현재 상태는 270 라인을 호출하고 있는 상태입니다. MutexB 를 얻어야 하는데 다른 쓰레드가 획득하고 있어서 얻지 못하고 있는 상황입니다.

우리는 어떤 쓰레드가 MutexB 를 획득하고 있는지를 찾아야 합니다.
이번 예제가 간단한 예제가 될 수 있는 이유는 KMUTEX 의 속성을 이용하기 때문입니다.
KMUTEX 는 내부에 자신을 소유한 쓰레드 정보를 포함하고 있습니다. 따라서 MutexB 의 내용을 보면 이 뮤텍스를 소유하고 있는 쓰레드를 알 수 있습니다.

kd> dd mydrv!MutexB
f9fdf480  00080002 00000000 8158ca68 8158ca68
f9fdf490  81116040 81116040 81116030 00000100

MutexB 의 주소는 f9fdf480 이네요.
dt 명령으로 내용을 자세히 봅니다.

kd> dt KMUTEX f9fdf480 
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListEntry  : _LIST_ENTRY [ 0x81116040 - 0x81116040 ]
   +0x018 OwnerThread      : 0x81116030
   +0x01c Abandoned        : 0 ''
   +0x01d ApcDisable       : 0x1 ''

중간에 OwnerThraed 라는 정보가 보입니다.
이것이 바로 이미 이 뮤텍스를 소유하고 있는 쓰레드를 나타냅니다.
쓰레드 정보를 봅니다.

kd> !thread 0x81116030
THREAD 81116030  Cid 428.60c  Teb: 7ffde000 Win32Thread: e20f6cb8 WAIT: (Executive) KernelMode Non-Alertable
    f9fdf4a0  Mutant - owning thread 8158c9f8
IRP List:
    81396c10: (0006,0094) Flags: 00000000  Mdl: 00000000
...
ChildEBP RetAddr  Args to Child             
f26ddbac 804f82e4 811160a0 81116030 804f24b2 nt!KiSwapContext+0x2e
f26ddbb8 804f24b2 81116240 817b6bd8 81396c10 nt!KiSwapThread+0x44
f26ddbe0 f9fdfa59 00000000 00000000 00000000 nt!KeWaitForSingleObject+0x1c0
f26ddc34 804e8185 8120ec98 81396c10 8069f2f0 MyDrv!MyDrvDeviceControl+0x159 (CONV: stdcall) [d:\project\test\mydrv\mydrv.c @ 287]
f26ddc44 8055887c 81412ac0 81396c80 81396c10 nt!IopfCallDriver+0x31
f26ddc58 805595a7 8120ec98 81396c10 81412ac0 nt!IopSynchronousServiceTail+0x5e
f26ddd00 80552468 000000a0 00000000 00000000 nt!IopXxxControlFile+0x5a5
f26ddd34 8052a421 000000a0 00000000 00000000 nt!NtDeviceIoControlFile+0x28

앞서 봤던 콜스택과 비슷한 콜스택이 보입니다.
하지만 소스라인이 다르네요. 소스코드를 확인해 봅니다.

280: case MYDRV_IOCTL_MUTEX2:
281: KeWaitForSingleObject( &MutexB, Executive, KernelMode, FALSE, NULL );
282:
283: interval2.QuadPart = -30000000;
284: KeDelayExecutionThread( KernelMode, FALSE, &interval2 );
285: KeWaitForSingleObject( &MutexA, Executive, KernelMode, FALSE, NULL );
286:
287:  KeReleaseMutex( &MutexB, FALSE ); // LINE 287
288: KeReleaseMutex( &MutexA, FALSE );

소스코드도 비슷해 보이지만 약간 다른 내용을 가지고 있습니다.
281 라인에서 이 쓰레드가 MutexB 를 얻은 것을 확인할 수 있습니다.
285 라인에서 MutexA 를 얻으려고 하지만 획득하지 못하고 대기하는 모습을 볼 수 있습니다.

MutexA 는 이미 앞의 쓰레드 8158c9f8 이 획득한 것을 266 라인에서 보이고 있습니다.
MutexA 의 OwnerThread 에 8158c9f8 이 들어있는지 확인해 봅니다.

kd> dd mydrv!MutexA
f9fdf4a0  00080002 00000000 811160a0 811160a0
f9fdf4b0  8158ca08 8158ca08 8158c9f8 00000100

kd> dt KMUTEX f9fdf4a0 
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListEntry  : _LIST_ENTRY [ 0x8158ca08 - 0x8158ca08 ]
   +0x018 OwnerThread      : 0x8158c9f8
   +0x01c Abandoned        : 0 ''
   +0x01d ApcDisable       : 0x1 ''

정확하게 0x8158c9f8 이 들어있네요.

말 그대로 대드락 이지요.
쓰레드 8158c9f8 는 MutexA 를 획득하고 MutexB 를 요청하고 있고
쓰레드 81116030 은 MutexB 를 획득하고 MutexA 를 요청하고 있습니다.
영원히 풀릴 수가 없겠죠.

대드락 분석은 여기까지 하고 나면 위와 같은 구조가 나오지 않도록 코드를 수정하는 일만 남습니다.
대드락이 발생하지 않도록 코드를 작성하는 한가지 팁은 동기화 객체를 두개 이상 획득하는 코드를 만들지 않는 것입니다.
동기화 객체를 하나 획득했으면 얼른 일을 처리하고 풀어주고 다른 동기화 객체를 요청하는 식으로 처리하는 것이지요.

이번 대드락 분석은 KMUTEX 의 속성을 이용해서 간단하게 해 볼 수 있었습니다.
뮤텍스에 이런 쓰레드 정보가 없었다면 훨씬 복잡한 분석을 해야 하는 상황으로 갔을 것입니다.
모든 쓰레드를 펼쳐놓고 콜스택을 일일히 봐가면서 대드락의 가능성을 점검해야 하는 분석이죠.
동기화 객체로 세마포어, 이벤트, 스핀락 등을 사용했다면 이런 분석을 해야 할 것입니다.
내부에 소유한 쓰레드 정보가 없기 때문입니다.

ERESOURCE 같은 경우도 내부에 쓰레드 정보가 있기 때문에 KMUTEX 와 비슷한 접근방법을 취할 수가 있습니다. !locks 명령어가 일부 분석을 도와 주기도 하구요.
하지만 왠지 기억에 분석이 쉽지 않았던 것 같은 기분이 드는데요. ^^
나중에 적절한 예제가 만들어지면 분석하는 시간을 가지도록 해 보겠습니다.

http://www.driveronline.org/bbs/view.asp?tb=tipbbs&GotoPage=2&s_bulu=&s_key=&no=73

반응형
Posted by GreeMate