본문 바로가기

카테고리 없음

커널모드 덤프 디버깅

윈도우 10에서 생성된 커널모드 덤프 파일을 수집하는 방법과 분석하는 방법을 알아보자.

덤프 수집은 유저모드와 방법이 조금 다르지만 분석 방법은 크게 다르지 않다.

분석 방법은 유저모드 예제에서 진행했던 것과 같은 과정으로 진행하겠지만 복습으로 생각하자.

또한, 이 것이 커널모드 환경에서 진행되고 있음을 항상 상기하고 어떤 차이점이 존재하는지 눈 여겨 보자.

 

덤프 파일 수집

커널모드에서 문제가 발생해 BSOD가 나타나면 크래시 덤프 파일이 생성된다.

크래시 덤프 파일은 설정된 유형에 따라 생성되는 위치가 각기 다르다.

 

미니 덤프로 설정되어있으면 다음 위치에 생성된다.

C:\Windows\Minidump\031020-10078-01.dmp

실제로 아무 설정도 하지 않은 기본 설정의 나의 개인 PC에는 아래와 같이 미니 덤프가 생성되어 있었다.

(실제로 예전에 갑자기 BSOD가 발생한 적 있었음..)

 

C:\Windows는 윈도우 설치 경로이므로, PC마다 윈도우 설치 경로가 다르면 해당 설치 경로를 기준으로 보면 된다.

덤프 파일 이름은 날짜를 기준으로 생성되므로, 2020년 03월 10일에 발생한 첫 번째 덤프파일인 것을 알 수 있다.

10078은 시간이 지남에 따라 증가하는 숫자로, 같은 날 같은 시간에 다시 문제가 발생해 미니 덤프가 생성된다면 세 번째 숫자인 01만 02로 변경된 이름이 생성된다.

하지만 동일 시간에 연속으로 BSOD가 생성될 확률은 적으니 일반적으로는 시간 값이 증가하고 01이 붙은 이름으로 생성된다.

 

 

커널 덤프나 전체 덤프로 설정되어있다면 다음 위치에 덤프 파일이 생성된다.

C:\Windows\MEMORY.dmp

실제로 나의 개인 PC를 보니 아래와 같이 덤프 파일이 생성되어 있었다.

커널 덤프나 전체 덤프는 'MEMORY.DMP'라는 이름으로만 생성되므로, BSOD가 다시 발생하면 같은 이름으로 덮어써진다.

따라서 해당 경로의 'MEMORY.DMP' 파일은 가장 최근에 발생한 문제에 대한 덤프 파일이며, 장기간 분석해야하는 덤프파일이라면 다른 위치에 복사해 보관하는 것이 안전하다.

 

 

덤프 파일 열기

유저모드 덤프 파일과 동일하게 'Open Crash Dump..." 메뉴 또는 드래그 앤 드롭으로 열 수 있다.

실습 파일로 제공된 'MEMORY.dmp'를 열어보면 아래와 같은 메시지가 나타난다.

 

더보기

Microsoft (R) Windows Debugger Version 10.0.18362.1 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.


Loading Dump File [C:\Users\Jaehoon\Desktop\안랩\WinDbg\windbgwindbg2nd-master\windbgwindbg2nd-master\Ch3\BugCheck 0x3B\MEMORY\MEMORY.DMP]
Kernel Bitmap Dump File: Kernel address space is available, User address space may not be available.

Symbol search path is: srv*
Executable search path is: 
Windows 10 Kernel Version 16299 UP Free x64
Product: WinNt, suite: TerminalServer SingleUserTS Personal
Built by: 16299.431.amd64fre.rs3_release_svc_escrow.180502-1908
Machine Name:
Kernel base = 0xfffff802`70811000 PsLoadedModuleList = 0xfffff802`70b77030
Debug session time: Sat May 26 23:38:48.990 2018 (UTC + 9:00)
System Uptime: 0 days 1:26:35.530
Loading Kernel Symbols
...............................................................
................Page 3416 not present in the dump file. Type ".hh dbgerr004" for details
................................................
...............................................................
Loading User Symbols
PEB is paged out (Peb.Ldr = 000000cd`6ff71018).  Type ".hh dbgerr001" for details
Loading unloaded module list
...............
For analysis of this file, run !analyze -v

 

유저모드 덤프와 마찬가지로 조금 읽어보다가 닫기 딱 좋은 메시지이다.

주의 깊게 봐야하는 부분을 굵게 표시했다.

 

첫 번째 부분은 덤프 파일 경로와 어떤 유형의 덤프인지 표시하는 부분이다.

예제 덤프에서는 "Kernel Bitmap Dump File: Kernel address space is available, User address space may not be available." 라고 적혀있는데, 이는 '커널 덤프' 파일이라는 의미다.

 

덤프 종류에 따른 메시지는 다음과 같다.

전체 덤프 Kernel Bitmap Dump File: Full address space is available
활성 덤프 Kernel Bitmap Dump File: Active memory is available
커널 덤프 Kernel Bitmap Dump File: Kernel address space is available, User address space may not be available
자동 덤프 Kernel Bitmap Dump File: Kernel address space is available, User address space may not be available
미니 덤프 Kernel Bitmap Dump File: Only registers and stack trace are available

 

그 다음은 덤프가 발생한 운영체제를 표시한다.

 

그 다음은 덤프가 발생한 시간을 표시하며, 커널 덤프에서도 유저 덤프와 같이 시간은 매우 중요한 단서가 된다.

 

그 다음에 나오는 'System Uptime'은 부팅 후 얼마나 오랫동안 동작하고 있었는지 알 수 있는 정보다.

이를 통해 부팅 직후 문제가 발생했는지, 아니면 몇 달 동안 운영되던 서버 환경에서 문제가 발생한 것인지 등을 확인할 수 있다.

예제 덤프에서는 1시간 26분 35초 후에 문제가 발생했다.

 

run !analyze -v 문구는 매우 명쾌하다. 그대로 해당 명령어를 수행하면 WinDbg가 자동 분석을 먼저 실시해주므로 많은 도움을 얻을 수 있다.

 

자동 분석은 WinDbg가 업그레이드될 때마다 함께 업그레이드되어 더욱 자세한 분석이 추가되므로 항상 이 명령으로부터 분석을 시작하는 것이 정석이다.

유저모드 덤프에서는 run !analyze -v 메시지가 명시적으로 나오지는 않지만 유저모드 덤프에서도 이 명령을 수행하면 분석에 필요한 정보를 조금이라도 얻을 수 있다.

 

 

!analyze -v 메시지 보기

"!analyze -v" 메시지를 잘 보면 하늘색으로 표시되어있고 밑줄이 쳐져있다. 마우스를 여기에 대고 클릭하면 명령 창에 해당 명령어가 자동으로 입력되고 실행된다.

 

명령 실행 후 나오는 메시지는 다음과 같다.

더보기

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

SYSTEM_SERVICE_EXCEPTION (3b)
An exception happened while executing a system service routine.
Arguments:
Arg1: 00000000c0000005, Exception code that caused the bugcheck
Arg2: fffff802732f1231, Address of the instruction which caused the bugcheck
Arg3: ffffed09c50a7040, Address of the context record for the exception that caused the bugcheck
Arg4: 0000000000000000, zero.

...(생략)

끊어서 앞 부분 부터 보자.

 

매우 중요한 정보인 버그체크 번호(BugCheck Code)가 나온다.

덤프 파일을 분석하는데 있어서 버그체크 번호는 아주 기본적인 정보일 뿐만 아니라 분석을 풀어나가는 실마리가 된다.

따라서 누군가 BSOD가 떴다고 문제 해결을 요청한다면 가장 먼저 버그체크 번호를 물어보게 되는 것이다.

 

예제 덤프에서는 Bug Check 0x3b로 BSOD가 발생한 것을 알 수 있다.

 

또한 아래 문구를 통해 어떤 경우에 문제가 발생하며 원인은 어떤 것일 수 있고 해결은 어떤 식으로 할 수 있는지에 대한 힌트를 제공한다. 즉, 버그체크 번호마다 각각에 해당하는 적절한 메시지를 보여준다.

 

이런 분석 아이디어는 WinDbg 도움말을 통해서도 얻을 수 있다.

사실 메시지를 읽어보는 것과 함께 F1 키를 눌러 WinDbg 도움말에서 Bug Check 0x3b를 검색하여 설명을 찾아봐야한다.

 

이번 예제 덤프는 Bug Check 0x3B로 SYSTEM_SERVICE_EXCEPTION 이라는 이름이다.

특권없는 코드에서 특권있는 코드로 진입해 실행되던 루틴에서 예외가 발생했음을 설명한다.

 

프로그램에서 메모리 주소를 잘못 참조하는 경우에 예외가 많이 발생하므로 이는 일반적인 버그체크로 볼 수 있다.

 

이런 경우 메모리를 잘못 참조한 메모리일 가능성이 많으므로, 예외가 발생한 주소를 잘 확인해야한다.

 

뒤에 나오는 파라미터들(Arg1~4)도 중요한 정보다.

파라미터 1에 따라서 약간씩 다른 문제로 분류되는 버그체크들이 많으므로 눈여겨봐야한다.

 

예제 덤프에서 발생한 문제는 c0000005이고 이 것은 'STATUS_ACCESS_VIOLATION'을 의미한다.

메모리를 잘 못 참조한 경우에 나타나는 예외 코드다.

 

파라미터 2는 예외가 발생한 주소다. 디스어셈블리 창에 해당 주소를 입력하여 어셈블리 코드를 보면서 분석할 수도 있찌만, 심볼만 맞추면 소스 코드가 바로 나타나므로 대부분 소스 코드를 보면서 문제를 확인한다.

 

파라미터 3은 컨텍스트 레코드(context record) 주소이다.

 

※ 컨텍스트 레코드(Context Record) : 예외가 발생했을 때 예외가 발생한 주소와 레지스터 값들을 저장하는 영역

 

예외가 발생하면 제어권은 커널로 넘어오고 커널이 블루스크린을 발생시키므로, 블루스크린이 발생하는 시점은 문제가 발생하고 한 참 후의 상황이다.

따라서 이 때의 실행 주소나 레지스터 값들은 블루스크린을 띄우려고 커널에 의해 사용된 것들이므로, 문제의 원인을 찾기 위해서는 컨텍스트 레코드를 활용해야한다.

 

컨텍스트 레코드에 문제가 발생할 당시의 상황이 그대로 들어있으므로 덤프를 분석할 때 이번 예제 처럼 컨텍스트 레코드가 제공된다면 .cxr 명령으로 문제가 발생한 당시 상황으로 되돌려 놓을 수 있다.

 

 

이어서 !analyze -v 분석 내용을 살펴보자.

더보기

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


KEY_VALUES_STRING: 1


PROCESSES_ANALYSIS: 1

SERVICE_ANALYSIS: 1

STACKHASH_ANALYSIS: 1

TIMELINE_ANALYSIS: 1


DUMP_CLASS: 1

DUMP_QUALIFIER: 401

BUILD_VERSION_STRING:  16299.431.amd64fre.rs3_release_svc_escrow.180502-1908

SYSTEM_MANUFACTURER:  VMware, Inc.

VIRTUAL_MACHINE:  VMware

SYSTEM_PRODUCT_NAME:  VMware Virtual Platform

SYSTEM_VERSION:  None

BIOS_VENDOR:  Phoenix Technologies LTD

BIOS_VERSION:  6.00

BIOS_DATE:  07/02/2015

BASEBOARD_MANUFACTURER:  Intel Corporation

BASEBOARD_PRODUCT:  440BX Desktop Reference Platform

BASEBOARD_VERSION:  None

DUMP_TYPE:  1

BUGCHECK_P1: c0000005

BUGCHECK_P2: fffff802732f1231

BUGCHECK_P3: ffffed09c50a7040

BUGCHECK_P4: 0

EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - 

FAULTING_IP: 
MyDrv!MyStrCpy+51 [c:\github\windbgwindbg2nd\ch2\src\mydrv\mydrv.c @ 314]
fffff802`732f1231 88040a          mov     byte ptr [rdx+rcx],al

CONTEXT:  ffffed09c50a7040 -- (.cxr 0xffffed09c50a7040)
rax=0000000000000042 rbx=ffffdb8d93136d30 rcx=0000000000000000
rdx=0000000000000000 rsi=0000000000000001 rdi=ffffdb8d95591080
rip=fffff802732f1231 rsp=ffffed09c50a7a30 rbp=0000000000000002
 r8=fffff802732f1430  r9=4bcaff4132772f1f r10=0000000000000204
r11=8101010101010100 r12=0000000000000000 r13=ffffdb8d94791060
r14=ffffdb8d95591080 r15=0000000000000000
iopl=0         nv up ei ng nz ac po cy
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00010297
MyDrv!MyStrCpy+0x51:
fffff802`732f1231 88040a          mov     byte ptr [rdx+rcx],al ds:002b:00000000`00000000=??
Resetting default scope

BUGCHECK_STR:  0x3B_c0000005

CPU_COUNT: 1

CPU_MHZ: 960

CPU_VENDOR:  GenuineIntel

CPU_FAMILY: 6

CPU_MODEL: 8e

CPU_STEPPING: 9

CPU_MICROCODE: 6,8e,9,0 (F,M,S,R)  SIG: 62'00000000 (cache) 62'00000000 (init)

BLACKBOXBSD: 1 (!blackboxbsd)


BLACKBOXPNP: 1 (!blackboxpnp)


DEFAULT_BUCKET_ID:  WIN8_DRIVER_FAULT

PROCESS_NAME:  MyApp.exe

CURRENT_IRQL:  0

ANALYSIS_SESSION_HOST:  DESKTOP-E0TEGSR

ANALYSIS_SESSION_TIME:  04-19-2020 13:59:16.0162

ANALYSIS_VERSION: 10.0.18362.1 amd64fre

LAST_CONTROL_TRANSFER:  from fffff802732f104a to fffff802732f1231

STACK_TEXT:  
ffffed09`c50a7a30 fffff802`732f104a : 00000000`00000000 fffff802`732f1430 ffffb308`64004ce0 ffffb308`64021ea0 : MyDrv!MyStrCpy+0x51 [c:\github\windbgwindbg2nd\ch2\src\mydrv\mydrv.c @ 314] 
ffffed09`c50a7a70 fffff802`732f54cd : ffffdb8d`93136d30 00000000`00000001 00000000`00000001 fffff802`70914bea : MyDrv!BugCheck3B+0x4a [c:\github\windbgwindbg2nd\ch2\src\mydrv\mydrv.c @ 328] 
ffffed09`c50a7ac0 fffff802`708bb219 : ffffdb8d`94791060 ffffdb8d`93136d30 ffffdb8d`92f4f3d0 ffffed09`c50a7be8 : MyDrv!MyDrvDeviceControl+0x43d [c:\github\windbgwindbg2nd\ch2\src\mydrv\mydrv.c @ 540] 
ffffed09`c50a7b40 fffff802`70cdc5be : ffffdb8d`93136d30 ffffed09`c50a7ec0 00000000`00000001 00000000`00000001 : nt!IofCallDriver+0x59
ffffed09`c50a7b80 fffff802`70cdbdfc : ffffdb8d`00000000 ffffdb8d`955910d0 00000000`00000000 ffffed09`c50a7ec0 : nt!IopSynchronousServiceTail+0x19e
ffffed09`c50a7c30 fffff802`70cdb776 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!IopXxxControlFile+0x66c
ffffed09`c50a7d60 fffff802`70998363 : 00000000`00000000 ffffdb8d`92a74170 ffff84aa`fcfe55a8 ffff84bc`c01c7a50 : nt!NtDeviceIoControlFile+0x56
ffffed09`c50a7dd0 00007fff`422503a4 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13
000000cd`700fe6f8 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x00007fff`422503a4

(생략)

 

첫 번째로 굵게 표시한 라인은 문제를 일으킨 모듈 이름인 MyDrv를 나타낸다.

하지만 이 것은 WinDbg 자동 분석에 의한 추정일 뿐이므로 항상 정확한 것은 아니다.

 

두 번째로 굵게 표시한 라인은 컨텍스트 레코드 주소이며, .cxr 명령의 사용 형식을 보여준다.

WinDbg에서 보면 .cxr 0xffffed09c50a7040 부분이 하늘색에 밑줄이 쳐져있으므로 클릭하면 실행된다.

kd> .cxr 0xffffed09c50a7040
rax=0000000000000042 rbx=ffffdb8d93136d30 rcx=0000000000000000 rdx=0000000000000000 rsi=0000000000000001 rdi=ffffdb8d95591080 rip=fffff802732f1231 rsp=ffffed09c50a7a30 rbp=0000000000000002 r8=fffff802732f1430 r9=4bcaff4132772f1f r10=0000000000000204 r11=8101010101010100 r12=0000000000000000 r13=ffffdb8d94791060 r14=ffffdb8d95591080 r15=0000000000000000 iopl=0 nv up ei ng nz ac po cy cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00010297 MyDrv!MyStrCpy+0x51: fffff802`732f1231 88040a mov byte ptr [rdx+rcx],al ds:002b:00000000`00000000=??

문제가 발생했던 당시의 레지스터들을 보여주며, WinDbg는 해당 레지스터들을 로드한다.

 

세 번째로 굵게 표시한 라인은 콜 스택에서 MyDrv에 문제가 발생했음을 보여준다.

현 상태에서 k 를 눌러 콜 스택을 확인해보면 위와 동일한 형태의 콜스택이 보일 것이다.

kd> k *** Stack trace for last set context - .thread/.cxr resets it
# Child-SP RetAddr Call Site
00 ffffed09`c50a7a30 fffff802`732f104a MyDrv!MyStrCpy+0x51 [c:\github\windbgwindbg2nd\ch2\src\mydrv\mydrv.c @ 314]
01 ffffed09`c50a7a70 fffff802`732f54cd MyDrv!BugCheck3B+0x4a [c:\github\windbgwindbg2nd\ch2\src\mydrv\mydrv.c @ 328]
02 ffffed09`c50a7ac0 fffff802`708bb219 MyDrv!MyDrvDeviceControl+0x43d [c:\github\windbgwindbg2nd\ch2\src\mydrv\mydrv.c @ 540]
03 ffffed09`c50a7b40 fffff802`70cdc5be nt!IofCallDriver+0x59
04 ffffed09`c50a7b80 fffff802`70cdbdfc nt!IopSynchronousServiceTail+0x19e
05 ffffed09`c50a7c30 fffff802`70cdb776 nt!IopXxxControlFile+0x66c
06 ffffed09`c50a7d60 fffff802`70998363 nt!NtDeviceIoControlFile+0x56
07 ffffed09`c50a7dd0 00007fff`422503a4 nt!KiSystemServiceCopyEnd+0x13
08 000000cd`700fe6f8 00000000`00000000 0x00007fff`422503a4

 

윗 부분이 마지막 호출이므로, MyDrv 내부에서 문제가 발생했음을 보여준다.

 

참고로 현재 미리 심볼과 소스 코드를 맞춰놨기 때문에 문제가 발생한 코드의 소스 라인과, 해당 소스 코드를 WinDbg에서 확인할 수 있다. (심볼과 소스 코드 설정 방법은 유저모드 덤프 디버깅 포스팅을 참고하자)

 

모듈 정보 보기

MyApp 때와 마찬가지로 문제 모듈의 정보를 정확히 확인해야한다.

파일 이름, 파일 버전, 파일 날짜 등을 확인하고 모듈의 정확한 소스 코드와 심볼 파일을 찾아야 한다.

lm 명령의 vm 옵션을 사용해 MyDrv의 정보를 확인한다.

kd> lmvm MyDrv
Browse full module list
start end module name
fffff802`732f0000 fffff802`732f8000 MyDrv (private pdb symbols) c:\users\jaehoon\desktop\안랩\windbg\windbgwindbg2nd-master\windbgwindbg2nd-master\ch2\build\x64\release\MyDrv.pdb
Loaded symbol image file: MyDrv.sys
Image path: \??\C:\GitHub\windbgwindbg2nd\Ch2\Build\x64\Release\MyDrv.sys
Image name: MyDrv.sys
Browse all global symbols functions data
Timestamp: Sat May 26 07:06:32 2018 (5B0969E8)
CheckSum: 00009A52
ImageSize: 00008000
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
Information from resource tables:

굵게 표시한 부분인 모듈의 시작 주소와 끝 주소, 실행 위치, 파일 이름, 만들어진 날짜/시간은 자주 참고하는 내용이므로 눈여겨봐둔다.

 

심볼 맞추기

유저모드 덤프 디버깅과 동일하므로 해당 포스팅을 참고하자

 

기록할만한 내용으로는 만약 MyDrv 영역이 아직 참조되지 않았다면 심볼이 로드되지 않을 수도 있다는 점이다.

하지만 이 예제 덤프에서는 콜 스택에 MyDrv가 걸려있으므로 바로 로드되어 심볼이 잘 잡힌다.

 

 

콜 스택 보기

다시 콜 스택을 살펴보자

kd> k *** Stack trace for last set context - .thread/.cxr resets it
# Child-SP RetAddr Call Site
00 ffffed09`c50a7a30 fffff802`732f104a MyDrv!MyStrCpy+0x51 [c:\github\windbgwindbg2nd\ch2\src\mydrv\mydrv.c @ 314]
01 ffffed09`c50a7a70 fffff802`732f54cd MyDrv!BugCheck3B+0x4a [c:\github\windbgwindbg2nd\ch2\src\mydrv\mydrv.c @ 328]
02 ffffed09`c50a7ac0 fffff802`708bb219 MyDrv!MyDrvDeviceControl+0x43d [c:\github\windbgwindbg2nd\ch2\src\mydrv\mydrv.c @ 540]
03 ffffed09`c50a7b40 fffff802`70cdc5be nt!IofCallDriver+0x59
04 ffffed09`c50a7b80 fffff802`70cdbdfc nt!IopSynchronousServiceTail+0x19e
05 ffffed09`c50a7c30 fffff802`70cdb776 nt!IopXxxControlFile+0x66c
06 ffffed09`c50a7d60 fffff802`70998363 nt!NtDeviceIoControlFile+0x56
07 ffffed09`c50a7dd0 00007fff`422503a4 nt!KiSystemServiceCopyEnd+0x13
08 000000cd`700fe6f8 00000000`00000000 0x00007fff`422503a4 

콜 스택의 맨 아랫 부분을 보면, nt!NtDeviceIoControlFile 함수가 보인다.

이는 WIN32 API 중 DeviceIoControl()을 호출했을 때 커널모드에서 호출되는 함수이므로 응용프로그램에서 DeviceIoControl() 함수를 사용했음을 알 수 있다.

 

콜 스택을 따라가보면 MyDrv 모듈이 나타난다.

자신이 작성한 모듈이므로 주의 깊게 살펴보자.

(보통 자신의 모듈이 콜 스택의 마지막에 걸려있다면 자신의 모듈에서 발생한 문제라고 인정하면 된다.)

 

콜 스택 분석의 첫 번째 단계는 항상 호출 흐름의 마지막 함수를 살펴보는 것이다.

콜 스택창에서 해당 함수의 프레임 번호를 클릭하면 소스 코드가 나타난다.

 

이제 함수를 보면서 왜 문제가 발생했는지 생각해보자.

자세히 살펴보니 MyApp의 문제를 분석할 때 봤던 소스 코드와 동일하다.

 

여기서 같은 원인이라고 추측하고 분석을 멈춘 후 코드를 이렇게 저렇게 바꿔보는 수정을 하는 것이 가장 많이 하는 실수이다.

 

현재 예제에서는 간단한 함수를 사용했으므로 추측이 맞을 가능성이 높지만, 조금이라도 복잡해보이는 코드에서 문제가 발생했다는 표시가 나타나면 수많은 사람들이 추측에 의한 수정을 감행한다.

 

조금만 더 분석하면 정확한 원인을 찾아 정확한 수정을 할 수 있는데, 조급한 마음으로 추측에 의한 수정을 하기 시작하면 문제는 또 다시 발생할 것이고 더 먼 길로 돌아가는 역효과가 발생한다.

 

정확한 원인을 찾기 위해 로컬 변수를 확인해보는 다음 단계로 넘어가보자.

 

pDest, pSrc 포인터와 i, dwSrcLen 값을 알아내야한다.

모두 입력 파라미터 또는 지역변수이므로 Locals 창에서 확인할 수 있다.

 

MyApp에서 본 것과 비슷한 값들이 나왔다. 다른 점은 MyDrv는 C++이 아닌 C 언어로 작성되었으므로, this 포인터가 나오지 않은 것과 pSrc 포인터 값이 0xfffff802`732f1430 으로 커널 영역의 주소라는 점이다.

하지만 이는 분석에 영향을 줄만한 정보는 아니므로 참고만 하고 넘어가자.

 

※ 아래 표 참고

  32비트 윈도우 시스템 64비트 윈도우 시스템
유저모드영역 0 ~ 0x7fffffff 0 ~ 0x0000xxxx`ffffffff
커널모드 영역 0x80000000 ~ 0xffffffff 0xffffxxxx`00000000 ~ 0xffffffff`ffffffff

 

pSrc는 "BugCheck 0x3B"라는 문자열이 들어있는 0xfffff802`732f1430 주소 값이 전달됐고, pDest는 0x00000000`00000000 주소 값이 전달됐다.

그리고 i는 0이므로 문제가 된 코드라인을 해석해보면 다음과 같다.

pDest[0] = pSrc[0] // *(0x00000000`00000000 + 0) = *(0xfffff802`732f1430 + 0)

pSrc[0]에 접근할 때는 해당 주소의 메모리를 정상적으로 읽었지만, pDest[0]에 쓰려고 할 때 0x00000000`00000000 주소에 쓰려고 하니 문제가 발생한 것으로 보인다.

 

이번 예제는 커널모드 드라이버이므로 그 결과로 블루스크린과 함께 재부팅이 되어버린다.

 

pDest에 0x00000000`00000000 값이 전달된 이유를 더 확인해보자.

 

MyDrv!MyStrCpy를 호출한 이전 함수인 MyDrv!BugCheck3B 확인을 위해 1번 프레임을 클릭해 컨텍스트를 맞춰보자.

 

kd> .frame 0n1;dv /t /v
01 ffffed09`c50a7a70 fffff802`732f54cd MyDrv!BugCheck3B+0x4a [c:\github\windbgwindbg2nd\ch2\src\mydrv\mydrv.c @ 328]
ffffed09`c50a7a98 char *[2] pBuffer = char *[2]
ffffed09`c50a7a90 int i = 0n1

자동으로 지역 변수까지 조회하여 값을 보여주며, 소스 코드도 나타난다.

 

보기 쉽게 로컬 창으로 확인해보면, 현재 i 값은 1이며 따라서 pBuffer[1]의 값인 0x00000000`00000000이 MyStrCpy에 전달됐음을 확인할 수 있다.

 

pBuffer[1]에 왜 0x00000000`00000000이 들어가있는지 확인해야 수정 방향을 찾을 수 있다.

 

BugCheck3B() 함수의 소스 코드를 살펴보면 아래와 같은 코드에서 문제가 발생했음을 알 수 있다.

char *pBuffer[2] = { g_szBuffer, NULL };

*pBuffer[2]를 함수 진입부에서 초기화하는데, 두 번째 값을 NULL로 채웠으므로 pBuffer[1]에 0x00000000`00000000 값이 들어있었던 것이다.

이제 수정 방법을 생각해보자.

 

MyApp과 마찬가지로 두 번째 값을 NULL로 채우는 것이 의도된 것인지 아닌지 확인해야한다.

 

  1. NULL이 아니라 어떤 메모리 주소가 들어가야 하는 것이었다면 이 자리에 NULL을 넣은 것이 원인이고, 이를 수정하면 문제는 해결된다.
  2. for 문에서 2번 루프를 도는 것이 의도된 것인지 확인한다. 의도된 것이 아니라면 for 문을 1번만 돌도록 수정하면 문제는 해결된다.
  3. for 문이 2번 이상 도는 것이었다면 for문의 원래 의도가 pBuffer[i]의 유효성을 검증하는 것이었는지 확인하고 NULL 체크 예외처리 코드를 추가하면 문제가 해결될 것이다.
for(i = 0; i < 2; i ++)
{
	if(NULL == pBuffer[i])
    		break;
    
    MyStrCpy(pBuffer[i], "BugCheck 0x3B");
}

 

와치 창으로 메모리 보기

유저모드 덤프 디버깅과 동일하게, 전역변수를 확인할 필요가 있는 경우 와치(Watch) 창을 활용한다.

 

메모리 창으로 메모리 보기

유저모드 덤프 디버깅과 동일하게 메모리 창을 통해 메모리를 확인할 수 있다.

 

메모리(Memory)창을 띄운 후에 Virtual 란에 MyDrv!g_szBuffer라고 입력해보자.

 

전역변수 g_szBuffer의 시작 주소와 바이트 단위로 나열된 데이터, ASCII 문자로까지 조회가 가능하다.

 

"db MyDrv!g_szBuffer" 명령어를 통해서도 메모리를 확인할 수 있으니 참고하자.

 

정리

유저모드 덤프와 커널모드 덤프의 분석 방법은 근본적으로 동일하다.

어떤 덤프이건 언제나 콜 스택을 추적하는 것이 분석의 기본이므로, 콜 스택을 보면서 분석을 시작하는 방법은 동일하다.

물론 단순한 문제가 아니라면 콜 스택에서 문제의 원인이 드러나지 않아 메모리의 이곳 저곳을 살펴봐야 하고 운영체제의 내부 구조까지 살펴봐야 하는 일이 생길 수 있지만, 확률상 단순 문제를 만날 일이 훨씬 많을 것이다.

 

어려운 문제들의 해법은 나머지 장에서 디버깅 케이스 별 분석을 통해 익혀보자