您的位置:首页 > 运维架构

Writing a Debugger - Part 2: The Debug Loop

2009-12-30 18:49 246 查看

Introduction

Windows NT (and successive operating system versions) expose a set of API functions and structures for debugging a running process. This article shows how these can be accessed from Visual Basic (version 5 or 6). It is recommended that the article Inside the executable: an introduction to the Portable Executable format for VB programmers be read in conjunction with this article, and the source code attached to it has the code for this article included.

Getting a Process to Debug

There are two ways to get a process to debug. Either:

attach a debugger to a process that is already running

or:

start a new process with a debugger attached to it.

Starting a New Process with a Debugger Attached

To start a process, you can use the
CreateProcess
API call:


Collapse

Copy Code
Private Declare Function CreateProcessLib _
"kernel32" Alias "CreateProcessA" (ByVal lpApplicationName As String, _
ByVal lpCommandLine As String, _
ByVal lpProcessAttributes As Long,_
ByVal lpThreadAttributes As Long,_
ByVal bInheritHandles As Long, _
ByVal dwCreationFlags As ProcessCreationFlags, _
ByVal lpEnvironment As Long, _
ByVal lpCurrentDirectory As String, _
lpStartupInfo As STARTUPINFO, _
lpProcessInformation As PROCESS_INFORMATION) _
As Long

In addition to the functionality of the Shell command, this allows you to specify additional flags that affect how the process is created:


Collapse

Copy Code
Public Enum ProcessCreationFlags
DEBUG_PROCESS = &H1
DEBUG_ONLY_THIS_PROCESS = &H2
CREATE_SUSPENDED = &H4
DETACHED_PROCESS = &H8
CREATE_NEW_CONSOLE = &H10
NORMAL_PRIORITY_CLASS = &H20
IDLE_PRIORITY_CLASS = &H40
HIGH_PRIORITY_CLASS = &H80
REALTIME_PRIORITY_CLASS = &H100
CREATE_NEW_PROCESS_GROUP = &H200
CREATE_UNICODE_ENVIRONMENT = &H400
CREATE_SEPARATE_WOW_VDM = &H800
CREATE_SHARED_WOW_VDM = &H1000
CREATE_FORCEDOS = &H2000
CREATE_DEFAULT_ERROR_MODE = &H4000000
CREATE_NO_WINDOW = &H8000000
End Enum

In order to start the process with an attached debugger, you specify the flags DEBUG_PROCESS + DEBUG_ONLY_THIS_PROCESS.

Attaching a Debugger to an Existing Process

To attach a debugger to a process which is already running, you need to obtain a handle to it and then attach a debugger using the
DebugActiveProcess
API call:


Collapse

Copy Code
Private Declare Function DebugActiveProcessLib "kernel32" _
(ByVal dwProcessId As Long) As Long

The Debug Loop

Once you have attached your debugger to the process, you need to go into a debug loop. This consists of waiting for a debug event, processing the event when it comes in and then allowing the debugee to continue.

Waiting for a Debug Event to Occur

To wait for a debug event, you call the
WaitForDebugEvent
API call:


Collapse

Copy Code
Private Declare Function WaitForDebugEventLib "kernel32" _
(lpDebugEvent As DEBUG_EVENT_BUFFER, _
ByVal dwMilliseconds As Long) As Long

This will return
TRUE
when a debug event has occurred and fill out the
DEBUG_EVENT_...
structure, which depends on what event occurred but always starts with a
DEBUG_EVENT_HEADER
:


Collapse

Copy Code
Private Type DEBUG_EVENT_HEADER
dwDebugEventCode As DebugEventTypes
dwProcessId As Long
dwThreadId As Long
End Type

Processing the Debug Event

How you deal with a debug event depends, naturally enough, on what event occurred. The event types are:


Collapse

Copy Code
Public Enum DebugEventTypes
EXCEPTION_DEBUG_EVENT = 1&
CREATE_THREAD_DEBUG_EVENT = 2&
CREATE_PROCESS_DEBUG_EVENT = 3&
EXIT_THREAD_DEBUG_EVENT = 4&
EXIT_PROCESS_DEBUG_EVENT = 5&
LOAD_DLL_DEBUG_EVENT = 6&
UNLOAD_DLL_DEBUG_EVENT = 7&
OUTPUT_DEBUG_STRING_EVENT = 8&
RIP_EVENT = 9&
End Enum

EXCEPTION_DEBUG_EVENT

This debug event is thrown whenever an exception occurs in the application being debugged. For example, if there was code in that application that was attempting to divide by zero then you would get an
EXCEPTION_DEBUG_EVENT
. The buffer that is passed back for this event is:


Collapse

Copy Code
Public Enum ExceptionCodes
EXCEPTION_GUARD_PAGE_VIOLATION = &H80000001
EXCEPTION_DATATYPE_MISALIGNMENT = &H80000002
EXCEPTION_BREAKPOINT = &H80000003
EXCEPTION_SINGLE_STEP = &H80000004
EXCEPTION_ACCESS_VIOLATION = &HC0000005
EXCEPTION_IN_PAGE_ERROR = &HC0000006
EXCEPTION_INVALID_HANDLE = &HC0000008
EXCEPTION_NO_MEMORY = &HC0000017
EXCEPTION_ILLEGAL_INSTRUCTION = &HC000001D
EXCEPTION_NONCONTINUABLE_EXCEPTION = &HC0000025
EXCEPTION_INVALID_DISPOSITION = &HC0000026
EXCEPTION_ARRAY_BOUNDS_EXCEEDED = &HC000008C
EXCEPTION_FLOAT_DENORMAL_OPERAND = &HC000008D
EXCEPTION_FLOAT_DIVIDE_BY_ZERO = &HC000008E
EXCEPTION_FLOAT_INEXACT_RESULT = &HC000008F
EXCEPTION_FLOAT_INVALID_OPERATION = &HC0000090
EXCEPTION_FLOAT_OVERFLOW = &HC0000091
EXCEPTION_FLOAT_STACK_CHECK = &HC0000092
EXCEPTION_FLOAT_UNDERFLOW = &HC0000093
EXCEPTION_INTEGER_DIVIDE_BY_ZERO = &HC0000094
EXCEPTION_INTEGER_OVERFLOW = &HC0000095
EXCEPTION_PRIVILEGED_INSTRUCTION = &HC0000096
EXCEPTION_STACK_OVERFLOW = &HC00000FD
EXCEPTION_CONTROL_C_EXIT = &HC000013A
End Enum

Public Enum ExceptionFlags
EXCEPTION_CONTINUABLE = 0
EXCEPTION_NONCONTINUABLE = 1   '// Noncontinuable exception
End Enum

Private Type DEBUG_EXCEPTION_DEBUG_INFO
Header As DEBUG_EVENT_HEADER
ExceptionCode                                        As ExceptionCodes
ExceptionFlags                                       As ExceptionFlags
pExceptionRecord                                     As Long
ExceptionAddress                                     As Long
NumberParameters                                     As Long
ExceptionInformation(EXCEPTION_MAXIMUM_PARAMETERS)   As Long
dwFirstChance As Long
End Type

The exception flags tell you if it is possible to resume from the exception or not.

CREATE_THREAD_DEBUG_EVENT

This event occurs whenever a new thread is created by the debugee application. The buffer that is passed in is:


Collapse

Copy Code
Private Type DEBUG_CREATE_THREAD_DEBUG_INFO
Header As DEBUG_EVENT_HEADER
hThread As Long
lpThreadLocalBase As Long
lpStartAddress As Long
End Type

This gives you a thread handle (for thread control API calls) and the base address and start address of the thread in the debugee process which is useful for analyzing the memory of that application.

CREATE_PROCESS_DEBUG_EVENT

This event occurs when the process is created. The buffer passed in is:


Collapse

Copy Code
Private Type DEBUG_CREATE_PROCESS_DEBUG_INFO
Header As DEBUG_EVENT_HEADER
hfile As Long
hProcess As Long
hThread As Long
lpBaseOfImage As Long
dwDebugInfoFileOffset As Long
nDebugInfoSize As Long
lpThreadLocalBase As Long
lpStartAddress As Long
lpImageName As Long
fUnicode As Integer
End Type

You can use the file handle passed in as part of this buffer to find the different parts of the process (imports section, exports, debug information, etc.) as per this article.

EXIT_THREAD_DEBUG_EVENT

This event occurs when a thread exits. The buffer passed in is:


Collapse

Copy Code
Private Type DEBUG_EXIT_THREAD_DEBUG_INFO
Header As DEBUG_EVENT_HEADER
dwExitCode As Long
End Type

The exit code is whatever the thread set it to but is usually set to be non zero if an error caused the thread exit.

EXIT_PROCESS_DEBUG_EVENT

This event occurs when the process exits. The buffer passed in is:


Collapse

Copy Code
Private Type DEBUG_EXIT_PROCESS_DEBUG_INFO
Header As DEBUG_EVENT_HEADER
dwExitCode As Long
End Type

The exit code is whatever the process set it to but is usually set to be non zero if an error caused the thread exit. You should stop the debug loop after you receive this event.

LOAD_DLL_DEBUG_EVENT

This event occurs when the application being debugged loads a dynamic link library. The buffer passed in is:


Collapse

Copy Code
Private Type DEBUG_LOAD_DLL_DEBUG_INFO
Header As DEBUG_EVENT_HEADER
hfile As Long
lpBaseOfDllAs Long
dwDebugInfoFileOffset As Long
nDebugInfoSize As Long
lpImageName As Long
fUnicode As Integer
End Type

You can use the file handle passed in as part of this buffer to find the different parts of the DLL (imports section, exports, debug information, etc.) as per this article.

UNLOAD_DLL_DEBUG_EVENT

This event occurs when the process being debugged unloads a DLL it had loaded. The buffer passed in is:


Collapse

Copy Code
Private Type DEBUG_UNLOAD_DLL_DEBUG_INFO
Header As DEBUG_EVENT_HEADER
lpBaseOfDllAs Long
End Type

And you can use the
lpBaseOfDll
value to identify which DLL was unloaded.

OUTPUT_DEBUG_STRING_EVENT

This event occurs when the debugee calls the API call
OutputDebugString
to send debugging information to a debugger (where one is attached). The buffer passed in is:


Collapse

Copy Code
Private Type DEBUG_OUTPUT_DEBUG_STRING_INFO
Header As DEBUG_EVENT_HEADER
lpDebugStringData As Long
fUnicode As Integer
nDebugStringLength As Integer
End Type

And you can read the string from the debugee using the
ReadProcessMemory
API call.

RIP_EVENT

This occurs if your process being debugged dies unexpectedly. The buffer passed in is:


Collapse

Copy Code
Private Type DEBUG_RIP_INFO
Header As DEBUG_EVENT_HEADER
dwError As Long
dwType As Long
End Type

Resuming the Debugee

Once you have extracted the information you need form the debug event, you need to resume the debugee so that it can continue running. To do this, you call the
ContinueDebugEvent
API call:


Collapse

Copy Code
Public Enum DebugStates
DBG_CONTINUE = &H10002
DBG_TERMINATE_THREAD = &H40010003
DBG_TERMINATE_PROCESS = &H40010004
DBG_CONTROL_C = &H40010005
DBG_CONTROL_BREAK = &H40010008
DBG_EXCEPTION_NOT_HANDLED = &H80010001
End Enum

Private Declare Function ContinueDebugEventLib "kernel32" _
(ByVal dwProcessId As Long, _
ByVal dwThreadId As Long, _
ByVal dwContinueStatus As DebugStates) As Long

Further Development

To expand on this framework and create a full debugger requires the ability to walk the memory and stack of the process being debugged and also to set breakpoints. I hope to get to this in the next article.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐