오늘은 BugCheck 0xC4-60 을 보도록 하겠습니다.
지금까지와 다르게 버그체크 코드에 60 이 붙었는데요.
60 이 의미하는 것은 Arg1 즉 첫번째 파라미터가 표시하는 값이 되겠습니다.
BugCheck 0xC4 에 대해서 WinDbg Help 를 찾아보면 Arg1 에 따라서 BugCheck 0xC4
는 엄청나게 많은 다른 의미를 가진다는 것을 알 수 있습니다. Arg1 이 중요한 의미
를 가지게 되므로 BugCheck 0xC4 에는 Arg1 을 붙여서 표시하도록 하겠습니다.
BugCheck 0xC4-60 은 드라이버에 메모리 릭이 존재한다는 것을 알려줍니다.
이것은 Verifier 에서 Pool Tracking 옵션을 사용해야 발생합니다.
예제를 보면서 설명합니다.
kd> !analyze -v
****************************************************************************
* *
* Bugcheck Analysis *
* *
****************************************************************************
DRIVER_VERIFIER_DETECTED_VIOLATION (c4)
A device driver attempting to corrupt the system has been caught. This is
because the driver was specified in the registry as being suspect (by the
administrator) and the kernel has enabled substantial checking of this driver.
If the driver attempts to corrupt the system, bugchecks 0xC4, 0xC1 and 0xA will
be among the most commonly seen crashes.
Parameter 1 = 0x1000 .. 0x1020 - deadlock verifier error codes.
Typically the code is 0x1001 (deadlock detected) and you can
issue a '!deadlock' KD command to get more information.
Arguments:
Arg1: 00000060, A driver has forgotten to free its pool allocations prior to unloading.
Arg2: 00000000, paged bytes
Arg3: 00000010, nonpaged bytes,
Arg4: 00000002, total # of (paged+nonpaged) allocations that weren't freed.
To get the name of the driver at fault, type
dp ViBadDriver l1; dS @$p
Then type !verifier 3 drivername.sys for info on the allocations
that were leaked that caused the bugcheck.
Debugging Details:
------------------
BUGCHECK_STR: 0xc4_60
IMAGE_NAME: MyDrv.sys
DEBUG_FLR_IMAGE_TIMESTAMP: 3f963fa3
MODULE_NAME: MyDrv
FAULTING_MODULE: f9d6c000 MyDrv
DEFAULT_BUCKET_ID: DRIVER_FAULT
PROCESS_NAME: services.exe
LAST_CONTROL_TRANSFER: from 80628271 to 804f14c9
STACK_TEXT:
f9cafad8 80628271 000000c4 00000060 00000000 nt!KeBugCheckEx+0x19
f9cafb00 80583885 813f2b98 80f56848 80f56870 nt!MiVerifyingDriverUnloading+0x11f
f9cafb2c 8055c1e0 813f2b98 80f56858 80591517 nt!MmUnloadSystemImage+0x179
f9cafb38 80591517 80f56870 80f56858 00000000 nt!IopDeleteDriver+0x2e
f9cafb54 80513095 80f56870 00000000 f9cafc34 nt!ObpRemoveObjectRoutine+0xdd
f9cafb78 804ed83d f9cafc34 f9cafcb0 8055d706 nt!ObfDereferenceObject+0x5d
f9cafc1c 8055d711 f9cafd1c 00000000 8052a421 nt!IopUnloadDriver+0x28f
f9cafc28 8052a421 f9cafd1c f9cafc4c 805c9c00 nt!NtUnloadDriver+0xb
f9cafc28 804f6c29 f9cafd1c f9cafc4c 805c9c00 nt!KiSystemService+0xc4
f9cafca4 804ed69d f9cafd1c f9cafd64 0076f8d4 nt!ZwUnloadDriver+0x11
f9cafd4c 8055d711 0076f8dc 00000000 8052a421 nt!IopUnloadDriver+0xef
f9cafd58 8052a421 0076f8dc 00000000 00000000 nt!NtUnloadDriver+0xb
f9cafd58 7ffe0304 0076f8dc 00000000 00000000 nt!KiSystemService+0xc4
0076f8e4 00000000 00000000 00000000 00000000 SharedUserData!SystemCallStub+0x4
STACK_COMMAND: kb
FOLLOWUP_NAME: MachineOwner
FAILURE_BUCKET_ID: 0xc4_60_VRF_IMAGE_MyDrv.sys_DATE_2003_10_22
BUCKET_ID: 0xc4_60_VRF_IMAGE_MyDrv.sys_DATE_2003_10_22
Followup: MachineOwner
---------
BugCheck 설명에서 드라이버가 시스템을 망치려고 할 때 잡아준다고 하고 있습니다.
포괄적인 설명이구요. Parameter 1 이 0x1001 일 때는 deadlock 이니 !deadlock
명령어 를 사용하라고 설명해 주기도 하네요.
이번은 Parameter 1 이 0x60 이니 상관없는 사항입니다.
파라미터 메시지를 보면
Arg1: 00000060, A driver has forgotten to free its pool allocations prior to unloading.
드라이버가 언로드되기 전에 할당한 메모리들을 해제하지 않은 경우인 것을 알 수 있습니다.
Arg2: 00000000, paged bytes
Paged Pool 할당한 것은 없네요.
Arg3: 00000010, nonpaged bytes,
NonPaged Pool 할당한 것이 16 바이트 남았다고 합니다.
Arg4: 00000002, total # of (paged+nonpaged) allocations that weren't freed.
할당은 했는데 해제하지 않은 수가 2 개 라고 합니다.
아주 친절한 설명이네요. 문제의 기본적인 상황을 이해하는데 도움이 많이 됩니다.
그 밑에 메시지 보이시죠? 하라는 것도 보이시나요? 그대로 합니다.
기억하시죠? 드래그, 클릭, 클릭, Enter ^^
kd> dp ViBadDriver l1; dS @$p
8054029c 81581f18
81581f68 "MyDrv.sys"
두 명령어가 한번에 실행된 것인데요. ; 를 이용해서 한 것입니다.
첫번째는 dp 명령어를 사용했네요. 지정된 영역을 포인터 값으로 보여줍니다.
dS 는 display string 인데 UNICODE_STRING 을 보이라는 겁니다.
@$p 가 81581f18 을 지시하는 역할을 했네요.
@$p 가 어떤 문법적인 기능을 가지고 있는지 잘 모르겠습니다.
나중에 좀 더 연구해 보기로 하구요. 일단 넘어갑니다.
그 다음에 또 하라는게 있습니다. 또 합니다.
drivername.sys 자리에 이름 바꿔 넣는거 주의하시구요.
kd> !verifier 3 MyDrv.sys
Verify Level 79 ... enabled options are:
Special pool
All pool allocations checked on unload
Io subsystem checking enabled
Deadlock detection enabled
Enhanced Io checking enabled
Summary of All Verifier Statistics
RaiseIrqls 0x0
AcquireSpinLocks 0x0
Synch Executions 0x0
Trims 0x0
Pool Allocations Attempted 0x2
Pool Allocations Succeeded 0x2
Pool Allocations Succeeded SpecialPool 0x2
Pool Allocations With NO TAG 0x0
Pool Allocations Failed 0x0
Resource Allocations Failed Deliberately 0x0
Current paged pool allocations 0x0 for 00000000 bytes
Peak paged pool allocations 0x0 for 00000000 bytes
Current nonpaged pool allocations 0x2 for 00000010 bytes
Peak nonpaged pool allocations 0x2 for 00000010 bytes
Driver Verification List
Entry State NonPagedPool PagedPool Module
81581f08 Loaded 00000010 00000000 MyDrv.sys
Current Pool Allocations 00000000 00000002
Current Pool Bytes 00000000 00000010
Peak Pool Allocations 00000000 00000002
Peak Pool Bytes 00000000 00000010
PoolAddress SizeInBytes Tag CallersAddress
b8104ff8 0x00000008 Ddk f9d6c50b
b86e6ff8 0x00000008 Ddk f9d6c50b
자세한 메시지들이 나왔습니다. 잘 읽어보시면 메모리 사용에 대한 정보를 알수 있구요.
마지막을 보면 해제되지 않은 메모리 주소 2개와 그것을 할당한 함수 주소가 보입니다.
할당한 함수 주소를 알고 있으니 그 함수로 가서 할당하는 부분과 해제하는 부분을
검토해 보면 문제가 바로 풀릴 수 있습니다. 이건 운이 아주 좋은 경우이구요.
드라이버에서 ExAllocatePool 을 호출하는 my_malloc 이라는 Wrapper 함수를 만들어서
사용중이었다면 위의 CallerAddress는 모두 my_malloc 만 보여줄 테니 별로 도움이
되지 않습니다. CallerAddress란 ExAllocaePool 을 호출한 함수의 주소이기 때문입니다.
이럴 땐 메모리의 내용까지 들여다 보고 메모리의 내용을 해석해서 어떤 것을 넣어서
사용하고 있던 메모리인지 확인해야 합니다. 메모리 내용에 문자열이 있었다면 그나마
아주 다행스러운 일입니다. 그 문자열이 사용되는 곳을 찾으면 되니까요.
예제에서의 메모리를 보면
kd> db b8104ff8
b8104ff8 46 69 6e 64 20 4d 65 00-?? ?? ?? ?? ?? ?? ?? ?? Find Me.????????
b8105008 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
kd> db b86e6ff8
b86e6ff8 46 69 6e 64 20 4d 65 00-?? ?? ?? ?? ?? ?? ?? ?? Find Me.????????
b86e7008 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
"Find Me" 라는 아주 귀여운 문자열이 들어있네요.
소스코드에서 Find Me 를 검색하면 다 나올 것 같습니다.
여기에 숫자들만 들어있었다면 메모리 내용을 잘 보면서 분석해야 합니다.
무슨 네오도 아니고... 잘 될까요? 아무래도 쉽지 않겠죠. ^^
이제 함수도 확인해 봅니다.
kd> u f9d6c50b
MyDrv!BugCheckC4+0x13 [d:\project\test\mydrv\mydrv.c @ 315]:
f9d6c50b 8945fc mov dword ptr [ebp-4],eax
f9d6c50e 8b45fc mov eax,dword ptr [ebp-4]
f9d6c511 8b0df0c4d6f9 mov ecx,dword ptr [MyDrv!BugCheckBE+0x10 (f9d6c4f0)]
313: void BugCheckC4(void)
314: {
315: PCHAR p = (PCHAR)ExAllocatePool( NonPagedPool, 8 );
316:
317: strcpy( p, "Find Me" );
메모리를 할당하고 "Find Me" 까지 넣어주는게 바로 보입니다.
할당은 했는데 해제하지를 않아서 발생한 문제이니 해제하는 부분이 어디인지 확인해서
문제를 해결해야 합니다. 하지만 이 소스에서는 해제하는 부분을 찾을 수 없었습니다.
제가 만들지 않았거든요. ^^ 함수 이름도 너무 정직하지 않습니까? BugCheckC4()
이번 예제에서 !analyze -v 메시지에 보이는 콜스택은 분석에 전혀 도움이 되지 않았습니다.
그저 드라이버를 언로드하다가 잡혔다는 내용 정도만 참고하시기 바랍니다.
마무리하기 전에 !verifier 3 의 활용에 대한 이야기를 조금 더 하지요.
이 명령은 메모리 할당 상황에 대한 정보를 제공해 주기 때문에 동작중인 드라이버의 메모리
사용 현황에 대한 분석을 할 때도 사용할 수 있습니다.
개발을 하다 보면 자신이 작성한 드라이버가 메모리를 얼마나 많이 차지하는가, 할당과
해제는 얼마나 많이 하는가를 측정해야 할 일도 있는데요.
드라이버를 동작시키고 WinDbg 로 멈춘 다음 그냥 !verifier 3 MyDrv.sys 를 실행해 줍니다.
그러면 위와 같은 메시지가 발생하는데 다음 항목을 위주로 보시면 됩니다.
Entry State NonPagedPool PagedPool Module
81581f08 Loaded 00000040 00000340 MyDrv.sys
Current Pool Allocations 00000000 00000020 => 현재 할당된 메모리 갯수
Current Pool Bytes 00000000 00000380 => 현재 할당된 총 바이트수
Peak Pool Allocations 00000000 00000142 => 최고로 많이 할당했을 때의 메모리 갯수
Peak Pool Bytes 00000000 00001220 => 최고로 많이 할당했을 때의 총 바이트수
여러분의 드라이버가 메모리를 얼마나 차지하고 있을까요?
한번 확인해 보시죠. ^^
http://www.driveronline.org/bbs/view.asp?tb=tipbbs&GotoPage=6&s_bulu=&s_key=&no=33