기술자료

DBMS, DB 구축 절차, 빅데이터 기술 칼럼, 사례연구 및 세미나 자료를 소개합니다.

응용프로그램을 위한 서브시스템과 DLL

기술자료
DBMS별 분류
Etc
작성자
dataonair
작성일
2014-04-07 00:00
조회
5782



개발자를 위한 윈도우 다시보기

응용프로그램을 위한 서브시스템과 DLL



윈도우에 대한 이야기를 시작하면서 일하며 경험했던 잔잔한 얘기도 해보고 싶었고, 기술적으로도 유용한 정보를 나누고도 싶었다. 단순히 윈도우를 사용하는 것만이 아니라 윈도우 프로그램들의 세부적인 작동 원리부터 이해함으로써, 이를 잘 다뤄야 하는 IT 엔지니어가 되는 초석을 마련하는 데 도움이 됐으면 한다. 이번 시간에는 응용프로그램을 위한 서브시스템에서부터 DLL까지 다룬다.



윈도우 응용프로그램은 운영체제의 보안과 호환성을 위해 직접 커널의 시스템 자원을 호출하지 않고, 서브시스템을 통해 호출된다. 그리고 프로그램의 실행 환경을 지원하는 환경 서브시스템과 시스템 자원 호출을 처리하는 서브시스템 DLL로 나눌 수 있다.



서브시스템

먼저 환경 서브시스템부터 알아보자. 환경 서브시스템은 응용프로그램의 구동 환경을 제공하고 관리하는 서브시스템이다. 여기에는 윈도우 환경을 지원하는 윈도우 서브시스템인 Csrss.exe와 UNIX용 프로그램의 실행 환경을 지원하는 POSIX 서브시스템(Portable operating system interface base on UNIX)이 있다. POSIX 서브시스템은 Psxss.exe를 통해 UNIX 용도로 개발된 응용프로그램을 윈도우에서 구동할 수 있도록 호환성을 제공하는 서브시스템이다.

응용프로그램의 커널 호출을 도와주는 Kernel32.dll, Advapi 32.dll, User32.dll, Gdi32.dll을 서브시스템 DLL(시스템 DLL이라고도 함)이라고 부른다. 응용프로그램 역분석을 진행한다면 아마 이 서브시스템 DLL을 자주 접하게 될 것이다. 그럼 이 두 가지 서브시스템들을 보다 자세히 알아보도록 하자.



tech_img1190.jpg

환경 서브시스템

환경 서브시스템은 응용프로그램 환경 부분과 프로세스 관리만 담당한다. 실제적인 응용프로그램의 커널 요청은 서브시스템 DLL에서 사용할 수 있는 인터페이스(API)를 노출해 커널의 시스템 자원을 호출하므로, 환경 서브시스템은 서브시스템 DLL과는 별도로 생각해야 한다. 윈도우에서 제공하는 환경 서브시스템은 크게 2개로 구분할 수 있다. Win32 응용프로그램을 지원하는 Csrss.exe와 POSIX를 지원하는 Posix.exe가 그것이다. 이 환경 서브시스템의 이용 여부는 레지스트리에 등록돼 있으며, 다음 주소를 통해 확인할 수 있다.

HKLM\System\CCS\Control\Session Manager\Subsystems

Regedit.exe를 이용해 레지스트리를 확인해보면 Windows와 POSIX 이외에 몇 개의 모드가 더 들어가 있는 것을 알 수 있다.

tech_img1191.jpg

이 중 Kmode는 Win32k.sys로, 이 커널 드라이버는 창 관리자, 사용자 입력 수집, 화면 출력 기능 등을 제공하는 환경 서브시스템이다. Gdi32.dll 등이 Win32k.sys를 사용하며, 게임에서 3D 표현에 사용하는 DirectX 역시 Win32k.sys가 지원된다. 이외의 디버깅 모드 등은 커널 디버깅과 관련있으며, 중요하게 확인해야 할 부분은 Windows로 우리가 윈도우에서 사용하는 대부분의 응용프로그램들이 여기에 영향을 받는다. 그럼 응용프로그램들이 어떻게 환경 서브시스템으로 정해지는지 알아보기 위해 좀더 실행 구조로 들어가 보자. 이를 위해 실행 파일인 PE Header 구조의 Option Header를 알 필요가 있다. 윈도우는 이를 통해 이용하는 서브시스템의 유형을 구분하기 때문이다. 이번 시간에는 실행 파일의 서브시스템 정보를 확인할 수 있는 Dependency Walker를 이용해 실습해 본다.



프로세스의 환경 서브시스템 확인

프로세스별 서브시스템 타입을 확인하기 위해 사전에 준비한 Dependency Walker를 실행하고, C:\Windows\Notepad.exe를 Dependency Walker로 열어보자. Dependency Walker를 실행하면 현재 Notepad.exe가 사용하는 DLL리스트가 왼쪽 위단에 표시된다.

tech_img1192.jpg

현재 Notepad.exe 프로세스를 확인하기 위해 Notepad.exe에서 Highlist Matchig Module In List 메뉴를 선택한다.

tech_img1193.jpg

모듈창 오른쪽으로 이동하면 Subsystem 컬럼을 확인할 수 있다. 현재 Notepad.exe는 Win32 응용프로그램으로서 GUI (Graghic User Interface)를 이용하고 있음을 알 수 있다.

tech_img1194.jpg

Win32 응용프로그램이라고 단순히 Csrss.exe만을 이용하는 것이 아니다. Win32에서도 서브시스템은 세분화돼 있다. PE파일의 구분에 의해 <그림 6>과 같이 동작하는 실행자가 변경되지만, 도스 응용프로그램을 제외하면 대부분 Use.exe를 이용한다.

tech_img1195.jpg

사용자가 파일을 실행하면 윈도우는 프로세스를 실행하기 위해 CreateProcess 함수를 호출해 파일 이미지의 로드를 진행한다. 동시에 실행 파일 구조를 확인해 어떤 형식의 파일인지 확인하는 절차가 진행된다. 윈도우에서는 실행 가능한 프로그램에 대해 실행 파일 구조라는 PE Header를 제공한다. 윈도우는 PE Header의 서브시스템 코드를 확인하고, 여러 실행자 중 적절한 실행자를 선택해 실행되도록 설계돼 있다. 서브시스템 코드에서 선택해 메모리에 실행 프로세스 개체(스레드를 담아 작업을 수행할 수 있는 개체들의 집합 등으로, 이는 Csrss의 레지스트리와 관계가 있다)를 만들게 된다.

윈도우에서 흔히 보는 .bat, .cmd와 같은 실행 파일은 확장자만으로 구분해 실행되지만, 그 외 파일의 경우 PE Header 구조의 Option Header 정보를 참조해 실행자를 선택한다. 이후 일련의 프로세스 생성 작업이 이뤄진다. 프로세스와 스레드의 커널 개체를 생성한 이후에는 Csrss.exe가 관리를 담당하게 된다. 그럼 PE Header에서 제공하는 서브시스템 코드는 어떻게 될까 해당 코드는 Winnt.h에 정의돼 있다.


<리스트 1> PE Header에 등록된 서브시스템 타입
#define IMAGE_SUBSYSTEM_UNKNOWN 0
← 서브시스템을 알 수 없는 경우
#define IMAGE_SUBSYSTEM_NATIVE 1
← Native API 프로그램으로서 서브시스템을 이용하지 않음
#define IMAGE_SUBSYSTEM_WINDOWS_GUI 2
← GUI로 표시되는 Win32 프로그램으로 Csrss를 이용함
#define IMAGE_SUBSYSTEM_WINDOWS_CUI 3
← Console로 표시되는 Win32 프로그램으로 Csrss를 이용함
#define IMAGE_SUBSYSTEM_OS2_CUI 5
← OS2로 개발된 프로그램으로 OS2를 이용함
#define IMAGE_SUBSYSTEM_POSIX_CUI 7
← POSIX로 개발된 프로그램으로 Posix를 이용함
#define IMAGE_SUBSYSTEM_NATIVE_WINDOWS 8
← Windows 9x Native 드라이버로 개발
#define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI 9
← Windows CE로 개발된 프로그램

서브시스템 타입 변경

파일 변경을 위해, 탐색기를 이용해 C:\Windows\Notepad .exe를 C:\에 복사한 후 PEview를 통해서 C:\Notepad.exe를 열어보자. 그리고 IMAGE_OPTIONAL_HEADER 하위의 서브시스템 코드의 위치를 확인해 보면, 현재 Notepad가 사용하는 환경 서브시스템을 알 수 있다.

tech_img1196.jpg

환경 서브시스템을 변경하기 위해 서브시스템의 위치인 0x0000013C 위치를 메모한 후, PEview를 종료한다. 그리고 C:\Notepad.exe에서 HxD를 이용해 데이터 위치인 0x000 0013C를 확인한다. 여기서 서브시스템과 동일한 코드가 등록돼 있는 것을 볼 수 있다(데이터는 리틀 인디안 방식으로 등록돼 있다). 이 코드를 0003, 즉 CUI 서브시스템으로 변경해 보자(<그림 8> 참조).

코드를 0003으로 변경하고 PEview로 확인해 보면 CUI형 프로그램으로 변경돼 있는 것을 알 수 있다(<그림 9> 참조). 동일한 환경 서브시스템을 사용하기 때문에 프로그램을 실행해 보면 정상적으로 실행된다. 이를 다시 0001과 같은 전혀 다른 서브시스템 유형으로 변경하면 <그림 10>과 같이 실행이 불가능하게 된다.

tech_img1197.jpg

tech_img1198.jpg

tech_img1199.jpg

여기까지의 실습을 통해 환경 서브시스템의 용도가 어떤 것인지 대략적으로 이해할 수 있을 것이다. 이제 서브시스템 DLL에 대해 알아보자.



서브시스템 DLL

앞서 Dependency Walker를 이용해 Notepad.exe를 확인해 봤다. 이때 Notepad.exe로 연결된 DLL 리스트를 볼 수 있었는데, 바로 이것이 서브시스템 DLL이다. 서브시스템 DLL은 호출 함수의 인자 값들에 대한 검증을 진행하고, Ntdll.dll로 전달하는 역할을 한다. 프로그램이 사용할 서브시스템 DLL 역시 PE Header에 정의돼 있으며, 프로세스 생성 시 PE Header를 참조해 이용할 DLL을 로드하게 된다. 이는 IAT와도 관련이 있다. 하나의 프로그램은 여러 개의 서브시스템 DLL을 이용하는 구조로 돼 있다. 이를 통해 프로그램은 자신이 원하는 기능을 호출해 사용하게 된다.

대표적인 윈도우 프로그램인 Win32 프로그램을 실행하기 위해 제공되는 서브시스템 DLL로는 Kernel32.dll, User32.dll, Gdi32.dll, Advapi32.dll 등이 있다. 이 DLL들은 Win32 프로그램에 반드시 필요한 프로그램들이다. 그럼 실습을 통해서 서브시스템 DLL을 확인해 보자.



tech_img1200.jpg

Notepad에서 이용하는 서브시스템 DLL 확인

다시 Dependency Walker를 이용해 C:\Windows\Notepad. exe를 실행하자. 가장 왼쪽 위의 창에 현재 Notepad.exe에서 사용되는 DLL들을 확인할 수 있다. 이 중 가장 먼저 윈도우 프로그램 실행에 기본적으로 필요한 4개의 서브시스템 DLL들이 보일 것이다. 그리고 Kernel32.dll을 선택해 보자. 오른쪽 창에 현재 Kernel32.dll에서 사용 가능한 리스트와 현재 Notepad.exe에서 사용하는 리스트가 녹색으로 표시된다. tech_img1201.jpg

Notepad가 사용하는 API 중 CreateFile이 아닌 CreateFileW API를 사용하는 이유는 앞서 배운 유니코드와 관계가 있다. 이제 조금은 다른 유형인 CUI형 프로그램인 C:\Windows\ System32\Cmd.exe를 Dependency Walker로 열어보자. 앞서 확인한 Notepad.exe와 다르게 Gdi32.dll을 이용하지 않는 것을 확인할 수 있다.

tech_img1202.jpg

이제 서브시스템 DLL들이 이용하는 DLL에 대해 살펴보자. Kernel32.dll, User32.dll, Gdi32.dll, Advapi32.dll 파일 역시 C:\Windows\System32에 위치하고 있으므로, 각각 Depen dency Walker로 열어본다. 각 서브시스템 DLL 파일을 확인해보면, 공통적으로 Ntdll.dll들이 이용되고 있는 것을 알 수 있다. 그리고 Ntdll.dll에서 제공하는 함수 중 자신에게 필요한 각각의 함수들을 가지고 있는 것을 확인할 수 있다.

tech_img1203.jpg

즉 프로그램 필요에 따라서 윈도우 서브시스템 DLL을 이용하게 되며, 필요하지 않을 경우에는 사용하지 않는다. 그리고 서브시스템을 사용하지 않는 호출도 존재하는데 이를 Native API라 한다. Natvie API로 개발된 것이 Ntdll.dll이며, Ntdll.dll은 유저 영역에서의 가장 마지막 통과 경로라 할 수 있다. Native API는 Ntdll.dll과 Smss.exe와 같이 대표적인 시스템 서비스 프로세스다.

tech_img1204.jpg

NTDLL의 주요 역할에 대해서 알아보고, 이를 통해 전체적인 호출 흐름을 이해해보자.



NTDLL

Ntdll.dll 파일을 의미하는 NTDLL은 유저 모드에서 동작 중이던 프로그램들을 위해 커널 모드의 요청을 대신 처리한 후 결과 값을 반환해 주는 역할을 한다. 커널 모드 이용 권한이 필요한 시스템 명령들을 System Service API(Native API라고도 한다)라 부르는데, 서브시스템 DLL들은 사용자의 응용프로그램 혹은 서비스 실행을 돕고, 실질적인 커널 요청은 Ntdll.dll을 통해 진행한다. 즉 Ntdll.dll은 서브시스템과 커널 모드를 연결하는 중간 다리 역할을 해준다. 운영체제는 Ntdll.dll과 같은 별도의 통일화된 인터페이스를 제공해 시스템 자원의 관리와 호환성을 높일 수 있기 때문이다.

그럼 Ntdll.dll은 어떻게 커널 모드로 진입하는 것일까 Ntdll.dll은 커널 모드 진입을 위해 명령 int 0x2e(혹은 Sysenter)를 통해 커널에 필요한 요청을 하게 된다. 이렇게 커널에 진입해 처리를 요청할 때 디스패치(DPC) 과정을 거치게 된다. 만약 응용프로그램이 파일 읽기를 요청했다면, 요청을 처리하기 위한 호출 흐름은 다음과 같다. 진하게 표시된 부분이 Ntdll.dll 이후 커널에서 처리되는 부분이다.

BOOL WINAPI ReadFile() → Kernel32.ReadFile() → Kernelbase.ReadFile() → Ntdll.NtReadFile() → SYSENTER → nt!NtReadFile() → 처리 → 처리 결과 리턴

이 요청 처리 흐름을 그림으로 표현하면 <그림 16>과 같다. tech_img1205.jpg

파일 읽기의 요청 순서 중 Ntdll.dll부터 NtReadFile이라고 한다. ReadFile이라는 동일한 작업을 하는 API 앞에 짧은 문자들이 추가돼 있는 것을 확인할 수 있다. 앞에 붙은 Zw, Ki, NT는 윈도우 시스템에서 함수의 의미별로 나누어 놓은 접두어다. 각 접두어가 나타내는 의미는 <표 1>과 같다. 이 중 자주 보는 접두어는 당연히 Nt, Zw이다. Nt의 미러 포인트(같은 기능을 하는 함수)로 사용되는 Zw는 유저 모드에서 요청한 경우, 처리 전 입력한 인자에 대한 검증(int 2e, Sysenter)을 진행한다. 이를 통해 권한이나 인자 검증과 같은 처리를 진행하게 된다. 커널 내에서 사용되는 Nt로 시작하는 API는 이와 같은 검증을 진행하지 않는다. 하지만 Zw를 호출하는 경우 유저 모드에서 호출한 것 같이 Nt 관련 API로 포워드하며 검증을 진행한다. 단 이전 액세스 모드(Previous mode)를 커널 모드로 설정해 인자 검증을 하지는 않도록 한다. 요약하면, 유저 모드에서는 Nt, Zw 모두 검증을 진행하고, 커널 모드에서는 Zw만이 일부 검증을 진행한다. 불필요한 검증을 생략해 처리 성능을 개선한 것이다. 그럼 Windbg를 이용해 처리 흐름을 확인해 보자.



함수 호출 흐름 확인

여기서는 커널까지의 호출 흐름을 확인해 본다. 커널 디버깅 상태에서는 개별 응응프로그램들에 대한 제어가 불가능하다. 따라서 nt.NtReadFile의 호출까지의 유저 모드 동작을 확인하기 위해 가상머신 안에서 Windbg를 실행하고 C:\Windows\ Notepad.exe를 연결하도록 하자. 연결하면 바로 프로그램이 실행됨과 동시에 일시정시 상태로 메모장을 실행하기 전 단계에서 잠시 대기 상태가 된다.

tech_img1206.jpg

다음으로 앞서 배운 Kernel32와 Ntdll.dll의 호출 부분을 확인하기 위해, 각 함수에 브레이크 포인트를 설정하자.

<리스트 2> 유저 모드 호출 흐름// 아직 메모장이 실행되기 이전 단계인 프로세스를 초기화하고 바로 일시정지 된다.
0:000> k
ChildEBP RetAddr
0024fa74 77ac0e00 ntdll!LdrpDoDebuggerBreak+0x2c
← Windbg에 의한 프로세스 일시중지
0024fbd4 77aa60a7 ntdll!LdrpInitializeProcess+0x11a9
← 프로세스 초기화
0024fc24 77aa3659 ntdll!_LdrpInitialize+0x78
0024fc34 00000000 ntdll!LdrInitializeThunk+0x10
// 서브시스템 DLL과 Ntdll.dll의 파일 생성 함수에 브레이크 포인트를 설정한다.
0:000> bp kernel32!CreateFileW
0:000> bp ntdll!NtCreateFile
0:000> bl
0 e 7657e8e8 0001 (0001) 0:**** kernel32!CreateFileW
1 e 77a85608 0001 (0001) 0:**** ntdll!ZwCreateFile
// 이후 메모장을 실행하면 초기에 바로 다시 브레이크 포인트가 설정된다.
0:000> g
ModLoad: 760b0000 760cf000 C:\Windows\system32\IMM32.DLL
ModLoad: 76810000 768dc000 C:\Windows\system32\MSCTF.dll
ModLoad: 75a60000 75a6c000 C:\Windows\system32\CRYPTBASE.dll
ModLoad: 747b0000 747f0000 C:\Windows\system32\uxtheme.dll
ModLoad: 74540000 74553000 C:\Windows\system32\dwmapi.dll
Breakpoint 0 hit
eax=00000000 ebx=77be5fe0 ecx=7657e9a9 edx=7657e9bc esi=00000000 edi=77be5fb0
eip=7657e8e8 esp=0024e174 ebp=0024e19c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
kernel32!CreateFileW:
7657e8e8 ff25e0195376 jmp dword ptr [kernel32!_imp__CreateFileW (765319e0)]
ds:0023:765319e0={KERNELBASE!CreateFileW (75c6a808)}
// 호출 스택을 확인해 보면, 프로그램의 화면 구성 등을 진행하다가 멈춘 것을
알 수 있다. 호출 스택은 너무 많은 관계로 중략한다.
0:000> k
ChildEBP RetAddr
0024e19c 77c03679 kernel32!CreateFileW ← 현재 처리 중인 스택
0024e21c 77be6276 USP10!UniStorInit+0xa9
중략...
0024fde0 009c1455 notepad!NPInit+0x11d
0024fe1c 009c16ec notepad!WinMain+0x50 ← 메모장 프로그램 메인 함수
0024feac 7657ed5c notepad!_initterm_e+0x1a1
0024feb8 77aa37eb kernel32!BaseThreadInitThunk+0xe
0024fef8 77aa37be ntdll!__RtlUserThreadStart+0x70
0024ff10 00000000 ntdll!_RtlUserThreadStart+0x1b
// 한 번 더 g 명령을 이용해 실행하면 Ntdll.dll에서 프로그램이 일시정지하는 것을 알 수 있다.
0:000> g
Breakpoint 1 hit
eax=0021ddd0 ebx=00000000 ecx=0021dd74 edx=00000000 esi=77a85608 edi=00000000
eip=77a85608 esp=0021dd38 ebp=0021ddd8 iopl=0 nv up ei ng nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000282
ntdll!ZwCreateFile:
77a85608 b842000000 mov eax,42h
// 현재 호출 스택을 확인해 보면, Kernel32.dll에서 KERNERBASE.dll을 거쳐서
Ntdll.dll에 도착한 것을 알 수 있다.
0:000> k
ChildEBP RetAddr
0021dd34 75c6a9d9 ntdll!ZwCreateFile
← Zw는 유저모드 인자 검증을 위해 사용된다.
0021ddd8 7657e8df KERNELBASE!CreateFileW+0x35e
← 윈도우7에 추가된 DLL로 Ntdll을 넘긴다.
0021de04 77c03679 kernel32!CreateFileWImplementation+0x69 ← KERNELBASE.dll로 넘긴다.
중략…

Kernelbase.dll은 윈도우7에서 추가됐다. Kernle32.dll이 처리하던 일부 함수들이 Kernelbase.dll로 옮겨졌으며, Kernel32.dll에서 Kernelbase.dll로 포워딩하게 된다. Advapi32.dll도 일부 Kernelbase.dll로 옮겨 갔는데, 이는 윈도우7의 구조적 차이 때문이라고 할 수 있다. 조심스레 예측해 보자면 보안을 위해 추가한 DLL이지 않을까 생각된다. 이제 다시 실습으로 돌아와 현재 처리 중인 어셈블리를 확인해 보자.

<리스트 3> Ntdll.dll은 호출할 시스템 서비스 함수 번호를 입력한다.// 어셈블리 코드를 확인해 보면, 시스템 서비스 번호를 EAX 레지스터에
입력한 후 시스템 호출을 진행하는 것을 알 수 있다.
0:000> u
ntdll!ZwCreateFile:
77a85608 b842000000 mov eax,42h
← 시스템 서비스 번호로서 NtCreateFile의 위치를 의미함
77a8560d ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
77a85612 ff12 call dword ptr [edx]
77a85614 c22c00 ret 2Ch
77a85617 90 nop
ntdll!NtCreateIoCompletion:
← ZwCreateFile 다음에 NtCreateIoCompletion이 위치함
77a85618 b843000000 mov eax,43h
77a8561d ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
77a85622 ff12 call dword ptr [edx]



<리스트 4> 브레이크 포인트 설정// 브레이크 포인트를 설정한 후, 커널 디버깅을 실행하면 곧바로 일시정지 된다.
1: kd> bp nt!NtCreateFile
1: kd> bl
0 e 82ea4460 0001 (0001) nt!NtCreateFile
1: kd> g
Breakpoint 0 hit
nt!NtCreateFile:
82ea4460 8bff mov edi,edi
// NtCreateFile 상태에서의 호출 스택을 확인해 보면 지금껏 살펴봤던
내용과 같이 호출 흐름이 나열돼 있는 것을 확인할 수 있다.
0: kd> k
ChildEBP RetAddr
8b06fc00 82c918c6 nt!NtCreateFile ← 커널에서 실제로 처리되는 함수
8b06fc00 77a870f4 nt!KiSystemServicePostCall
← 시스템 서비스 호출
0024f420 77a85614 ntdll!KiFastSystemCallRet ← 커널 진입
0024f424 75c6a9d9 ntdll!ZwCreateFile+0xc
← Ntdll.dll, 커널 진입을 위한 인자 값 검증
0024f4c8 7657e8df KernelBase!CreateFileW+0x35e
← 윈도우7에서 추가된 Kernelbase.dll
0024f4f4 7170102b kernel32!CreateFileWImplementation+0x69 ← Kernel32.dll
WARNING: Frame IP not in any known module. Following frames may be wrong.
0024f5d4 767d31ff 0x7170102b
0024f5e4 0024f5f4 ws2_32!WSAEnumNetworkEvents+0x50
0024f64c 71656815 0x24f5f4
0024f650 000690ba 0x71656815
0024f654 00000000 0x690ba


이제 좀더 커널 영역으로 들어가 보자. 커널 쪽을 확인하기 위해서는 커널 디버깅 상태에서 진행해야 한다. 따라서 커널 디버깅을 진행하는 Windbg로 넘어와 가상머신을 일시 중지한 후에 브레이크 포인트를 설정하면 된다. <리스트 4>처럼 함수 호출 흐름을 한 눈에 확인할 수 있다.