WinDbg 디버깅2007. 6. 29. 00:48
반응형

Verifier의 Special Pool 을 이용할 때 발견할 수 있는 오류를 하나 더 보겠습니다.
BugCheck 0xC1 입니다. 이번엔 바로 !analyze -v 를 해 봅니다.

kd> !analyze -v
****************************************************************************
*                                                                          *
*                     Bugcheck Analysis                                    *
*                                                                          *
****************************************************************************

SPECIAL_POOL_DETECTED_MEMORY_CORRUPTION (c1)
Special pool has detected memory corruption.  Typically the current thread's
stack backtrace will reveal the guilty party.
Arguments:
Arg1: 83d76ff8, address trying to free
Arg2: 83d76ffc, address where bits are corrupted
Arg3: 0081c004, (reserved)
Arg4: 00000024, caller is freeing an address where bytes after the end of the allocation have been overwritten

Debugging Details:
------------------


BUGCHECK_STR:  0xC1_24

SPECIAL_POOL_CORRUPTION_TYPE:  24

DEFAULT_BUCKET_ID:  DRIVER_FAULT

PROCESS_NAME:  System

LAST_CONTROL_TRANSFER:  from 8053425b to 804e5592

STACK_TEXT: 
f88ee700 8053425b 00000003 f88eea5c 00000000 nt!RtlpBreakWithStatusInstruction
f88ee74c 80534d2e 00000003 83d76000 00000004 nt!KiBugCheckDebugBreak+0x19
f88eeb2c 8053531e 000000c1 83d76ff8 83d76ffc nt!KeBugCheck2+0x574
f88eeb4c 806811ae 000000c1 83d76ff8 83d76ffc nt!KeBugCheckEx+0x1b
f88eeb8c 8054d5aa 83d76ff8 81ac7550 e14374d2 nt!MmFreeSpecialPool+0x29b
f88eebcc 8066da60 83d76ff8 00000000 f88eebe8 nt!ExFreePoolWithTag+0x4a
f88eebdc f38e3da3 83d76ff8 f88eebf8 f38e3f23 nt!VerifierFreePool+0x1f
f88eebe8 f38e3f23 83d76ff8 01d76ff8 f88eec20 MyDrv!my_free+0x13 [d:\mytest\mydrv\my_mem.c @ 38]
f88eebf8 f38e1ce0 83d76ff8 83d76ff8 0013a8d9 MyDrv!my_close+0x43 [d:\mytest\mydrv\my_file.c @ 59]
f88eec20 f38e1047 e30c2970 e30c0000 f88eec48 MyDrv!Test_Open+0x130 [d:\mytest\mydrv\my_test.c @ 455]
...

STACK_COMMAND:  kb

FOLLOWUP_IP:
MyDrv!my_free+13 [d:\mytest\mydrv\my_mem.c @ 38]
f38e3da3 5d              pop     ebp

FAULTING_SOURCE_CODE: 
    34:  
    35:  if (pv)
    36:   ExFreePool(pv);
    37:
>   38: }
    39:
    40: void my_copy(LPVOID pDest, LPCVOID pSrc, size_t nLength)
    41: {


SYMBOL_STACK_INDEX:  7

SYMBOL_NAME:  MyDrv!my_free+13

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: MyDrv

IMAGE_NAME:  MyDrv.sys

DEBUG_FLR_IMAGE_TIMESTAMP:  45127eee

FAILURE_BUCKET_ID:  0xC1_24_VRF_MyDrv!my_free+13

BUCKET_ID:  0xC1_24_VRF_MyDrv!my_free+13

Followup: MachineOwner
---------

메시지를 읽어보면 이번에는 특별히 하라는 것은 없습니다.
그래도 메시지는 꼼꼼히 읽어봅시다. 이 BugCheck 의 원인을 정확히 설명해 주고 있습니다.

일단 메모리를 깨뜨려서 발생한 것이라고 설명하고 있습니다. 콜스택을 보면 문제 해결에
도움이 될 거라고 하네요. 콜스택은 조금 있다가 보기로 하구요.

아주 유용한 정보가 보입니다.

Arg4: 00000024, caller is freeing an address where bytes after the end of the allocation have been overwritten

문제가 발생한 원인을 말해주고 있습니다.
할당한 메모리를 넘어서는 부분을 깨뜨리고 메모리를 해제하려고 했다고 하네요.

Special Pool 을 이용하면 이런 문제를 잡을 수 있습니다. 메모리를 깨뜨리는 문제는
매우 까다로운 문제에 속합니다. 원인을 찾지 못하는 경우도 많습니다.
이럴 때 Verifier의 도움을 받으면 효과를 볼 수 있다는 것을 기억하시기 바랍니다.
아니 Verifier 는 개발중에 무조건 켜놓고 사용하는 것이라고 보면 되기 때문에
특별히 기억할 필요도 없겠네요. 그냥 습관적으로 사용하시기 바랍니다.

본론으로 돌아가서 Arg4 는 BugCheck 0xC1 을 발생시키는 원인을 지시하는 값이 들어갑니다.
0x24 는 위와 같은 뜻이구요. 다른 코드들과 그 의미는 WinDbg Help 를 참고하시기 바랍니다.
메모리 사용에 있어서 여러가지 오류 상황들을 잡아낼 수 있다는 것을 아시게 될 겁니다.

Arg1: 83d76ff8, address trying to free
Arg2: 83d76ffc, address where bits are corrupted

Arg1 은 메모리 주소네요. Arg2 는 깨진 곳의 주소입니다.

Verifier 는 어떻게 깨진 곳의 주소를 알고 있는 것일까요?
이제 콜스택을 봅니다.

f88ee700 8053425b 00000003 f88eea5c 00000000 nt!RtlpBreakWithStatusInstruction
f88ee74c 80534d2e 00000003 83d76000 00000004 nt!KiBugCheckDebugBreak+0x19
f88eeb2c 8053531e 000000c1 83d76ff8 83d76ffc nt!KeBugCheck2+0x574
f88eeb4c 806811ae 000000c1 83d76ff8 83d76ffc nt!KeBugCheckEx+0x1b
f88eeb8c 8054d5aa 83d76ff8 81ac7550 e14374d2 nt!MmFreeSpecialPool+0x29b
f88eebcc 8066da60 83d76ff8 00000000 f88eebe8 nt!ExFreePoolWithTag+0x4a
f88eebdc f38e3da3 83d76ff8 f88eebf8 f38e3f23 nt!VerifierFreePool+0x1f
f88eebe8 f38e3f23 83d76ff8 01d76ff8 f88eec20 MyDrv!my_free+0x13 [d:\mytest\mydrv\my_mem.c @ 38]
f88eebf8 f38e1ce0 83d76ff8 83d76ff8 0013a8d9 MyDrv!my_close+0x43 [d:\mytest\mydrv\my_file.c @ 59]
f88eec20 f38e1047 e30c2970 e30c0000 f88eec48 MyDrv!Test_Open+0x130 [d:\mytest\mydrv\my_test.c @ 455]

my_free() 함수에서 호출한 ExFreePool()이 결국 MmFreeSpecialPool() 을 호출하고 여기서
KeBugCheckEx() 를 호출하는 것을 볼 수 있습니다.

즉, Verifier는 메모리를 해제할 때 메모리의 내용을 체크해서 문제가 있는지 확인합니다.
메모리의 내용을 보면

kd> !pool 83d76ff8
Pool page 83d76ff8 region is Special pool
*83d76ff8 size:    4 pagable special pool, Tag is MDRV
  Owning component : Unknown (update pooltag.txt)

kd> db 83d76ff8
83d76ff8  d8 03 00 80 10 d2 8e f3-?? ?? ?? ?? ?? ?? ?? ??  ........????????
83d77008  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
83d77018  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
83d77028  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

size 4 의 메모리였네요. d8 03 00 80 가 할당해서 사용했던 데이터구요.
깨진 주소가 83d76ffc 이므로 10 d2 8e f3 에서 10 에 해당합니다.

깨진것을 판단하는 방법은 지나번에 설명한 Special Pool 의 구조로 설명됩니다.
할당된 메모리 조금 앞을 보면

kd> db 83d76ff8 - 10
83d76fe8  81 81 81 81 81 81 81 81-81 81 81 81 81 81 81 81  ................
83d76ff8  d8 03 00 80 10 d2 8e f3-?? ?? ?? ?? ?? ?? ?? ??  ........????????

81 이라는 값이 차 있는 것을 볼 수 있습니다.

이 페이지 맨 앞을 보면

kd> db 83d76000
83d76000  04 c0 81 00 4d 44 53 56-00 00 00 00 08 4f f1 81  ....MDRV.....O..
83d76010  81 81 81 81 81 81 81 81-81 81 81 81 81 81 81 81  ................
83d76020  81 81 81 81 81 81 81 81-81 81 81 81 81 81 81 81  ................
83d76030  81 81 81 81 81 81 81 81-81 81 81 81 81 81 81 81  ................

맨 앞에 size 4 가 보이고 그 다음 다음에 81 이 보이고 뒤부분이 모두 81 로 채워지
있는 것이 보입니다. 맨 뒤도 사실은 다음과 같이 채워져 있었습니다.

83d76ff8  d8 03 00 80 81 81 81 81-?? ?? ?? ?? ?? ?? ?? ??  ........????????

MmFreeSpecialPool()에서는 81 로 채워진 부분이 온전한지를 검사합니다.
할당해준 메모리 영역만 정확히 사용했는지 검사하는 겁니다.
할당된 영역을 넘어선 뒤부분 검사를 시작하자마자 딱 걸렸네요.
할당된 크기보다 큰 영역을 건드렸으므로 오류라는 뜻입니다.

메시지에서 보여주는 오류 소스코드를 보면

FAULTING_SOURCE_CODE: 
    34:  
    35:  if (pv)
    36:   ExFreePool(pv);
    37:

my_free() 내부의 소스코드인데 메모리 해제 밖에 없으니 원인을 알 수 없습니다.
상위 함수의 내용을 보면

BOOLEAN my_close(PMYHANDLE pMyHandle)
{
 ...

 ZwClose(pMyHandle->Handle);
 my_free(pMyHandle);

 return bRet;
}

pMyHandle 을 해제하라고 넘겨준 것이므로 pMyHandle을 사용하는 곳들을 살펴보면
원인을 찾을 수 있습니다.

생성부터 살펴보니

PMYHANDLE my_open(LPCTSTR pszFile)
{
 PMYHANDLE pMyHandle = my_malloc(sizeof(PMYHANDLE));
 if (!pMyHandle) return 0;

 ...
}

바로 문제가 발견되었네요. 한 눈에 보이시나요?
흔히 하는 실수 중에 하나인데 sizeof() 에 구조체 타입이 아닌 구조체 포인터 타입을
주는 것이 문제였습니다. 포인터 타입이니 4바이트만 할당되겠죠. 구조체는 4바이트
보다 클테니 구조체에 어떤 값을 쓰다가 어디선가 깨뜨리게 됩니다.
이 문제는 다음과 같이 코드를 수정하면 해결됩니다.

 PMYHANDLE pMyHandle = my_malloc(sizeof(MYHANDLE));

메모리를 깨뜨리는 문제에서 이런 경우는 아주 운이 좋은 케이스입니다.
문제의 메모리를 할당하는 곳부터 살펴보기 시작했는데 거기서 바로 문제가 발견된
경우이니까요. 저 부분이 정상이었다면 pMyHandle 을 사용하는 모든 곳을 살펴봐야
하는 상황이 됩니다. 복잡한 코드가 많이 있다면 더더욱 갑갑해 지겠죠.

이것이 BugCheck 0xC1 이 가지는 한계이기도 합니다.
메모리가 깨졌을 때 바로 BugCheck 를 발생시키는 것이 아니라 깨진 메모리가 해제될
때 체크하는 방식이기 때문입니다. 깨뜨린 곳이 어디인지를 찾는 고통의 시간이 항상
필요합니다. 그렇다고 하더라도 이렇게라도 잡아주는 것에 감사해야 합니다. ^^

좀 더 손쉽게 문제의 정확한 위치를 찾는 방법이 있기는 합니다.
덤프파일을 분석하는 상황이었다면 위에서 처럼 소스를 뒤져서 원인을 찾는 것 밖에
방법이 없습니다. 하지만 live debugging 중이었다면 메모리 브레이크 포인트를 활용
할 수 있습니다.

처음에 죽었을 때 깨지는 위치를 확인한 후에 리부팅하고 다시 죽기 전에 깨지는 위치에
메모리 브레이크 포인트를 걸면 되겠지요.

kd> ba w1 83d76ffc
kd> bl
 0 e 83d76ffc w 1 0001 (0001)

이렇게 해 놓으면 누군가가 메모리를 꺠뜨리는 순간... 범인은 바로 잡힙니다.
생각만 해도 통쾌하네요. ^^

이번에는 메모리 깨뜨리는 문제에 대해서 살펴봤습니다.
골치아픈 문제지만 손쉽게 분석해서 해결하시기 바라겠습니다.

http://www.driveronline.org/bbs/view.asp?tb=tipbbs&GotoPage=6&s_bulu=&s_key=&no=27
반응형
Posted by GreeMate