Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1865 lines (1539 sloc) 67.3 KB
#!/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2009-2018, Mario Vilas
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice,this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""
Event handling module.
@see: U{http://winappdbg.readthedocs.io/en/latest/Debugging.html}
@group Debugging:
EventHandler, EventSift
@group Debug events:
EventFactory,
EventDispatcher,
Event,
NoEvent,
CreateProcessEvent,
CreateThreadEvent,
ExitProcessEvent,
ExitThreadEvent,
LoadDLLEvent,
UnloadDLLEvent,
OutputDebugStringEvent,
RIPEvent,
ExceptionEvent
@group Warnings:
EventCallbackWarning
"""
__all__ = [
# Factory of Event objects and all of it's subclasses.
# Users should not need to instance Event objects directly.
'EventFactory',
# Event dispatcher used internally by the Debug class.
'EventDispatcher',
# Base classes for user-defined event handlers.
'EventHandler',
'EventSift',
# Warning for uncaught exceptions on event callbacks.
'EventCallbackWarning',
# Dummy event object that can be used as a placeholder.
# It's never returned by the EventFactory.
'NoEvent',
# Base class for event objects.
'Event',
# Event objects.
'CreateProcessEvent',
'CreateThreadEvent',
'ExitProcessEvent',
'ExitThreadEvent',
'LoadDLLEvent',
'UnloadDLLEvent',
'OutputDebugStringEvent',
'RIPEvent',
'ExceptionEvent'
]
import win32
from win32 import FileHandle, ProcessHandle, ThreadHandle
from breakpoint import ApiHook
from module import Module
from thread import Thread
from process import Process
from textio import HexDump
from util import StaticClass, PathOperations
import ctypes
import warnings
import traceback
#==============================================================================
class EventCallbackWarning (RuntimeWarning):
"""
This warning is issued when an uncaught exception was raised by a
user-defined event handler.
"""
#==============================================================================
class Event (object):
"""
Event object.
@type eventMethod: str
@cvar eventMethod:
Method name to call when using L{EventHandler} subclasses.
Used internally.
@type eventName: str
@cvar eventName:
User-friendly name of the event.
@type eventDescription: str
@cvar eventDescription:
User-friendly description of the event.
@type debug: L{Debug}
@ivar debug:
Debug object that received the event.
@type raw: L{DEBUG_EVENT}
@ivar raw:
Raw DEBUG_EVENT structure as used by the Win32 API.
@type continueStatus: int
@ivar continueStatus:
Continue status to pass to L{win32.ContinueDebugEvent}.
"""
eventMethod = 'unknown_event'
eventName = 'Unknown event'
eventDescription = 'A debug event of an unknown type has occured.'
def __init__(self, debug, raw):
"""
@type debug: L{Debug}
@param debug: Debug object that received the event.
@type raw: L{DEBUG_EVENT}
@param raw: Raw DEBUG_EVENT structure as used by the Win32 API.
"""
self.debug = debug
self.raw = raw
self.continueStatus = win32.DBG_EXCEPTION_NOT_HANDLED
## @property
## def debug(self):
## """
## @rtype debug: L{Debug}
## @return debug:
## Debug object that received the event.
## """
## return self.__debug()
def get_event_name(self):
"""
@rtype: str
@return: User-friendly name of the event.
"""
return self.eventName
def get_event_description(self):
"""
@rtype: str
@return: User-friendly description of the event.
"""
return self.eventDescription
def get_event_code(self):
"""
@rtype: int
@return: Debug event code as defined in the Win32 API.
"""
return self.raw.dwDebugEventCode
## # Compatibility with version 1.0
## # XXX to be removed in version 1.4
## def get_code(self):
## """
## Alias of L{get_event_code} for backwards compatibility
## with WinAppDbg version 1.0.
## Will be phased out in the next version.
##
## @rtype: int
## @return: Debug event code as defined in the Win32 API.
## """
## return self.get_event_code()
def get_pid(self):
"""
@see: L{get_process}
@rtype: int
@return: Process global ID where the event occured.
"""
return self.raw.dwProcessId
def get_tid(self):
"""
@see: L{get_thread}
@rtype: int
@return: Thread global ID where the event occured.
"""
return self.raw.dwThreadId
def get_process(self):
"""
@see: L{get_pid}
@rtype: L{Process}
@return: Process where the event occured.
"""
pid = self.get_pid()
system = self.debug.system
if system.has_process(pid):
process = system.get_process(pid)
else:
# XXX HACK
# The process object was missing for some reason, so make a new one.
process = Process(pid)
system._add_process(process)
## process.scan_threads() # not needed
process.scan_modules()
return process
def get_thread(self):
"""
@see: L{get_tid}
@rtype: L{Thread}
@return: Thread where the event occured.
"""
tid = self.get_tid()
process = self.get_process()
if process.has_thread(tid):
thread = process.get_thread(tid)
else:
# XXX HACK
# The thread object was missing for some reason, so make a new one.
thread = Thread(tid)
process._add_thread(thread)
return thread
#==============================================================================
class NoEvent (Event):
"""
No event.
Dummy L{Event} object that can be used as a placeholder when no debug
event has occured yet. It's never returned by the L{EventFactory}.
"""
eventMethod = 'no_event'
eventName = 'No event'
eventDescription = 'No debug event has occured.'
def __init__(self, debug, raw = None):
Event.__init__(self, debug, raw)
def __len__(self):
"""
Always returns C{0}, so when evaluating the object as a boolean it's
always C{False}. This prevents L{Debug.cont} from trying to continue
a dummy event.
"""
return 0
def get_event_code(self):
return -1
def get_pid(self):
return -1
def get_tid(self):
return -1
def get_process(self):
return Process(self.get_pid())
def get_thread(self):
return Thread(self.get_tid())
#==============================================================================
class ExceptionEvent (Event):
"""
Exception event.
@type exceptionName: dict( int S{->} str )
@cvar exceptionName:
Mapping of exception constants to their names.
@type exceptionDescription: dict( int S{->} str )
@cvar exceptionDescription:
Mapping of exception constants to user-friendly strings.
@type breakpoint: L{Breakpoint}
@ivar breakpoint:
If the exception was caused by one of our breakpoints, this member
contains a reference to the breakpoint object. Otherwise it's not
defined. It should only be used from the condition or action callback
routines, instead of the event handler.
@type hook: L{Hook}
@ivar hook:
If the exception was caused by a function hook, this member contains a
reference to the hook object. Otherwise it's not defined. It should
only be used from the hook callback routines, instead of the event
handler.
"""
eventName = 'Exception event'
eventDescription = 'An exception was raised by the debugee.'
__exceptionMethod = {
win32.EXCEPTION_ACCESS_VIOLATION : 'access_violation',
win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED : 'array_bounds_exceeded',
win32.EXCEPTION_BREAKPOINT : 'breakpoint',
win32.EXCEPTION_DATATYPE_MISALIGNMENT : 'datatype_misalignment',
win32.EXCEPTION_FLT_DENORMAL_OPERAND : 'float_denormal_operand',
win32.EXCEPTION_FLT_DIVIDE_BY_ZERO : 'float_divide_by_zero',
win32.EXCEPTION_FLT_INEXACT_RESULT : 'float_inexact_result',
win32.EXCEPTION_FLT_INVALID_OPERATION : 'float_invalid_operation',
win32.EXCEPTION_FLT_OVERFLOW : 'float_overflow',
win32.EXCEPTION_FLT_STACK_CHECK : 'float_stack_check',
win32.EXCEPTION_FLT_UNDERFLOW : 'float_underflow',
win32.EXCEPTION_ILLEGAL_INSTRUCTION : 'illegal_instruction',
win32.EXCEPTION_IN_PAGE_ERROR : 'in_page_error',
win32.EXCEPTION_INT_DIVIDE_BY_ZERO : 'integer_divide_by_zero',
win32.EXCEPTION_INT_OVERFLOW : 'integer_overflow',
win32.EXCEPTION_INVALID_DISPOSITION : 'invalid_disposition',
win32.EXCEPTION_NONCONTINUABLE_EXCEPTION : 'noncontinuable_exception',
win32.EXCEPTION_PRIV_INSTRUCTION : 'privileged_instruction',
win32.EXCEPTION_SINGLE_STEP : 'single_step',
win32.EXCEPTION_STACK_OVERFLOW : 'stack_overflow',
win32.EXCEPTION_GUARD_PAGE : 'guard_page',
win32.EXCEPTION_INVALID_HANDLE : 'invalid_handle',
win32.EXCEPTION_POSSIBLE_DEADLOCK : 'possible_deadlock',
win32.EXCEPTION_WX86_BREAKPOINT : 'wow64_breakpoint',
win32.CONTROL_C_EXIT : 'control_c_exit',
win32.DBG_CONTROL_C : 'debug_control_c',
win32.MS_VC_EXCEPTION : 'ms_vc_exception',
}
__exceptionName = {
win32.EXCEPTION_ACCESS_VIOLATION : 'EXCEPTION_ACCESS_VIOLATION',
win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED : 'EXCEPTION_ARRAY_BOUNDS_EXCEEDED',
win32.EXCEPTION_BREAKPOINT : 'EXCEPTION_BREAKPOINT',
win32.EXCEPTION_DATATYPE_MISALIGNMENT : 'EXCEPTION_DATATYPE_MISALIGNMENT',
win32.EXCEPTION_FLT_DENORMAL_OPERAND : 'EXCEPTION_FLT_DENORMAL_OPERAND',
win32.EXCEPTION_FLT_DIVIDE_BY_ZERO : 'EXCEPTION_FLT_DIVIDE_BY_ZERO',
win32.EXCEPTION_FLT_INEXACT_RESULT : 'EXCEPTION_FLT_INEXACT_RESULT',
win32.EXCEPTION_FLT_INVALID_OPERATION : 'EXCEPTION_FLT_INVALID_OPERATION',
win32.EXCEPTION_FLT_OVERFLOW : 'EXCEPTION_FLT_OVERFLOW',
win32.EXCEPTION_FLT_STACK_CHECK : 'EXCEPTION_FLT_STACK_CHECK',
win32.EXCEPTION_FLT_UNDERFLOW : 'EXCEPTION_FLT_UNDERFLOW',
win32.EXCEPTION_ILLEGAL_INSTRUCTION : 'EXCEPTION_ILLEGAL_INSTRUCTION',
win32.EXCEPTION_IN_PAGE_ERROR : 'EXCEPTION_IN_PAGE_ERROR',
win32.EXCEPTION_INT_DIVIDE_BY_ZERO : 'EXCEPTION_INT_DIVIDE_BY_ZERO',
win32.EXCEPTION_INT_OVERFLOW : 'EXCEPTION_INT_OVERFLOW',
win32.EXCEPTION_INVALID_DISPOSITION : 'EXCEPTION_INVALID_DISPOSITION',
win32.EXCEPTION_NONCONTINUABLE_EXCEPTION : 'EXCEPTION_NONCONTINUABLE_EXCEPTION',
win32.EXCEPTION_PRIV_INSTRUCTION : 'EXCEPTION_PRIV_INSTRUCTION',
win32.EXCEPTION_SINGLE_STEP : 'EXCEPTION_SINGLE_STEP',
win32.EXCEPTION_STACK_OVERFLOW : 'EXCEPTION_STACK_OVERFLOW',
win32.EXCEPTION_GUARD_PAGE : 'EXCEPTION_GUARD_PAGE',
win32.EXCEPTION_INVALID_HANDLE : 'EXCEPTION_INVALID_HANDLE',
win32.EXCEPTION_POSSIBLE_DEADLOCK : 'EXCEPTION_POSSIBLE_DEADLOCK',
win32.EXCEPTION_WX86_BREAKPOINT : 'EXCEPTION_WX86_BREAKPOINT',
win32.CONTROL_C_EXIT : 'CONTROL_C_EXIT',
win32.DBG_CONTROL_C : 'DBG_CONTROL_C',
win32.MS_VC_EXCEPTION : 'MS_VC_EXCEPTION',
}
__exceptionDescription = {
win32.EXCEPTION_ACCESS_VIOLATION : 'Access violation',
win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED : 'Array bounds exceeded',
win32.EXCEPTION_BREAKPOINT : 'Breakpoint',
win32.EXCEPTION_DATATYPE_MISALIGNMENT : 'Datatype misalignment',
win32.EXCEPTION_FLT_DENORMAL_OPERAND : 'Float denormal operand',
win32.EXCEPTION_FLT_DIVIDE_BY_ZERO : 'Float divide by zero',
win32.EXCEPTION_FLT_INEXACT_RESULT : 'Float inexact result',
win32.EXCEPTION_FLT_INVALID_OPERATION : 'Float invalid operation',
win32.EXCEPTION_FLT_OVERFLOW : 'Float overflow',
win32.EXCEPTION_FLT_STACK_CHECK : 'Float stack check',
win32.EXCEPTION_FLT_UNDERFLOW : 'Float underflow',
win32.EXCEPTION_ILLEGAL_INSTRUCTION : 'Illegal instruction',
win32.EXCEPTION_IN_PAGE_ERROR : 'In-page error',
win32.EXCEPTION_INT_DIVIDE_BY_ZERO : 'Integer divide by zero',
win32.EXCEPTION_INT_OVERFLOW : 'Integer overflow',
win32.EXCEPTION_INVALID_DISPOSITION : 'Invalid disposition',
win32.EXCEPTION_NONCONTINUABLE_EXCEPTION : 'Noncontinuable exception',
win32.EXCEPTION_PRIV_INSTRUCTION : 'Privileged instruction',
win32.EXCEPTION_SINGLE_STEP : 'Single step event',
win32.EXCEPTION_STACK_OVERFLOW : 'Stack limits overflow',
win32.EXCEPTION_GUARD_PAGE : 'Guard page hit',
win32.EXCEPTION_INVALID_HANDLE : 'Invalid handle',
win32.EXCEPTION_POSSIBLE_DEADLOCK : 'Possible deadlock',
win32.EXCEPTION_WX86_BREAKPOINT : 'WOW64 breakpoint',
win32.CONTROL_C_EXIT : 'Control-C exit',
win32.DBG_CONTROL_C : 'Debug Control-C',
win32.MS_VC_EXCEPTION : 'Microsoft Visual C++ exception',
}
@property
def eventMethod(self):
return self.__exceptionMethod.get(
self.get_exception_code(), 'unknown_exception')
def get_exception_name(self):
"""
@rtype: str
@return: Name of the exception as defined by the Win32 API.
"""
code = self.get_exception_code()
unk = HexDump.integer(code)
return self.__exceptionName.get(code, unk)
def get_exception_description(self):
"""
@rtype: str
@return: User-friendly name of the exception.
"""
code = self.get_exception_code()
description = self.__exceptionDescription.get(code, None)
if description is None:
try:
description = 'Exception code %s (%s)'
description = description % (HexDump.integer(code),
ctypes.FormatError(code))
except OverflowError:
description = 'Exception code %s' % HexDump.integer(code)
return description
def is_first_chance(self):
"""
@rtype: bool
@return: C{True} for first chance exceptions, C{False} for last chance.
"""
return self.raw.u.Exception.dwFirstChance != 0
def is_last_chance(self):
"""
@rtype: bool
@return: The opposite of L{is_first_chance}.
"""
return not self.is_first_chance()
def is_noncontinuable(self):
"""
@see: U{http://msdn.microsoft.com/en-us/library/aa363082(VS.85).aspx}
@rtype: bool
@return: C{True} if the exception is noncontinuable,
C{False} otherwise.
Attempting to continue a noncontinuable exception results in an
EXCEPTION_NONCONTINUABLE_EXCEPTION exception to be raised.
"""
return bool( self.raw.u.Exception.ExceptionRecord.ExceptionFlags & \
win32.EXCEPTION_NONCONTINUABLE )
def is_continuable(self):
"""
@rtype: bool
@return: The opposite of L{is_noncontinuable}.
"""
return not self.is_noncontinuable()
def is_user_defined_exception(self):
"""
Determines if this is an user-defined exception. User-defined
exceptions may contain any exception code that is not system reserved.
Often the exception code is also a valid Win32 error code, but that's
up to the debugged application.
@rtype: bool
@return: C{True} if the exception is user-defined, C{False} otherwise.
"""
return self.get_exception_code() & 0x10000000 == 0
def is_system_defined_exception(self):
"""
@rtype: bool
@return: The opposite of L{is_user_defined_exception}.
"""
return not self.is_user_defined_exception()
def get_exception_code(self):
"""
@rtype: int
@return: Exception code as defined by the Win32 API.
"""
return self.raw.u.Exception.ExceptionRecord.ExceptionCode
def get_exception_address(self):
"""
@rtype: int
@return: Memory address where the exception occured.
"""
address = self.raw.u.Exception.ExceptionRecord.ExceptionAddress
if address is None:
address = 0
return address
def get_exception_information(self, index):
"""
@type index: int
@param index: Index into the exception information block.
@rtype: int
@return: Exception information DWORD.
"""
if index < 0 or index > win32.EXCEPTION_MAXIMUM_PARAMETERS:
raise IndexError("Array index out of range: %s" % repr(index))
info = self.raw.u.Exception.ExceptionRecord.ExceptionInformation
value = info[index]
if value is None:
value = 0
return value
def get_exception_information_as_list(self):
"""
@rtype: list( int )
@return: Exception information block.
"""
info = self.raw.u.Exception.ExceptionRecord.ExceptionInformation
data = list()
for index in xrange(0, win32.EXCEPTION_MAXIMUM_PARAMETERS):
value = info[index]
if value is None:
value = 0
data.append(value)
return data
def get_fault_type(self):
"""
@rtype: int
@return: Access violation type.
Should be one of the following constants:
- L{win32.EXCEPTION_READ_FAULT}
- L{win32.EXCEPTION_WRITE_FAULT}
- L{win32.EXCEPTION_EXECUTE_FAULT}
@note: This method is only meaningful for access violation exceptions,
in-page memory error exceptions and guard page exceptions.
@raise NotImplementedError: Wrong kind of exception.
"""
if self.get_exception_code() not in (win32.EXCEPTION_ACCESS_VIOLATION,
win32.EXCEPTION_IN_PAGE_ERROR, win32.EXCEPTION_GUARD_PAGE):
msg = "This method is not meaningful for %s."
raise NotImplementedError(msg % self.get_exception_name())
return self.get_exception_information(0)
def get_fault_address(self):
"""
@rtype: int
@return: Access violation memory address.
@note: This method is only meaningful for access violation exceptions,
in-page memory error exceptions and guard page exceptions.
@raise NotImplementedError: Wrong kind of exception.
"""
if self.get_exception_code() not in (win32.EXCEPTION_ACCESS_VIOLATION,
win32.EXCEPTION_IN_PAGE_ERROR, win32.EXCEPTION_GUARD_PAGE):
msg = "This method is not meaningful for %s."
raise NotImplementedError(msg % self.get_exception_name())
return self.get_exception_information(1)
def get_ntstatus_code(self):
"""
@rtype: int
@return: NTSTATUS status code that caused the exception.
@note: This method is only meaningful for in-page memory error
exceptions.
@raise NotImplementedError: Not an in-page memory error.
"""
if self.get_exception_code() != win32.EXCEPTION_IN_PAGE_ERROR:
msg = "This method is only meaningful "\
"for in-page memory error exceptions."
raise NotImplementedError(msg)
return self.get_exception_information(2)
def is_nested(self):
"""
@rtype: bool
@return: Returns C{True} if there are additional exception records
associated with this exception. This would mean the exception
is nested, that is, it was triggered while trying to handle
at least one previous exception.
"""
return bool(self.raw.u.Exception.ExceptionRecord.ExceptionRecord)
def get_raw_exception_record_list(self):
"""
Traverses the exception record linked list and builds a Python list.
Nested exception records are received for nested exceptions. This
happens when an exception is raised in the debugee while trying to
handle a previous exception.
@rtype: list( L{win32.EXCEPTION_RECORD} )
@return:
List of raw exception record structures as used by the Win32 API.
There is always at least one exception record, so the list is
never empty. All other methods of this class read from the first
exception record only, that is, the most recent exception.
"""
# The first EXCEPTION_RECORD is contained in EXCEPTION_DEBUG_INFO.
# The remaining EXCEPTION_RECORD structures are linked by pointers.
nested = list()
record = self.raw.u.Exception
while True:
record = record.ExceptionRecord
if not record:
break
nested.append(record)
return nested
def get_nested_exceptions(self):
"""
Traverses the exception record linked list and builds a Python list.
Nested exception records are received for nested exceptions. This
happens when an exception is raised in the debugee while trying to
handle a previous exception.
@rtype: list( L{ExceptionEvent} )
@return:
List of ExceptionEvent objects representing each exception record
found in this event.
There is always at least one exception record, so the list is
never empty. All other methods of this class read from the first
exception record only, that is, the most recent exception.
"""
# The list always begins with ourselves.
# Just put a reference to "self" as the first element,
# and start looping from the second exception record.
nested = [ self ]
raw = self.raw
dwDebugEventCode = raw.dwDebugEventCode
dwProcessId = raw.dwProcessId
dwThreadId = raw.dwThreadId
dwFirstChance = raw.u.Exception.dwFirstChance
record = raw.u.Exception.ExceptionRecord
while True:
record = record.ExceptionRecord
if not record:
break
raw = win32.DEBUG_EVENT()
raw.dwDebugEventCode = dwDebugEventCode
raw.dwProcessId = dwProcessId
raw.dwThreadId = dwThreadId
raw.u.Exception.ExceptionRecord = record
raw.u.Exception.dwFirstChance = dwFirstChance
event = EventFactory.get(self.debug, raw)
nested.append(event)
return nested
#==============================================================================
class CreateThreadEvent (Event):
"""
Thread creation event.
"""
eventMethod = 'create_thread'
eventName = 'Thread creation event'
eventDescription = 'A new thread has started.'
def get_thread_handle(self):
"""
@rtype: L{ThreadHandle}
@return: Thread handle received from the system.
Returns C{None} if the handle is not available.
"""
# The handle doesn't need to be closed.
# See http://msdn.microsoft.com/en-us/library/ms681423(VS.85).aspx
hThread = self.raw.u.CreateThread.hThread
if hThread in (0, win32.NULL, win32.INVALID_HANDLE_VALUE):
hThread = None
else:
hThread = ThreadHandle(hThread, False, win32.THREAD_ALL_ACCESS)
return hThread
def get_teb(self):
"""
@rtype: int
@return: Pointer to the TEB.
"""
return self.raw.u.CreateThread.lpThreadLocalBase
def get_start_address(self):
"""
@rtype: int
@return: Pointer to the first instruction to execute in this thread.
Returns C{NULL} when the debugger attached to a process
and the thread already existed.
See U{http://msdn.microsoft.com/en-us/library/ms679295(VS.85).aspx}
"""
return self.raw.u.CreateThread.lpStartAddress
#==============================================================================
class CreateProcessEvent (Event):
"""
Process creation event.
"""
eventMethod = 'create_process'
eventName = 'Process creation event'
eventDescription = 'A new process has started.'
def get_file_handle(self):
"""
@rtype: L{FileHandle} or None
@return: File handle to the main module, received from the system.
Returns C{None} if the handle is not available.
"""
# This handle DOES need to be closed.
# Therefore we must cache it so it doesn't
# get closed after the first call.
try:
hFile = self.__hFile
except AttributeError:
hFile = self.raw.u.CreateProcessInfo.hFile
if hFile in (0, win32.NULL, win32.INVALID_HANDLE_VALUE):
hFile = None
else:
hFile = FileHandle(hFile, True)
self.__hFile = hFile
return hFile
def get_process_handle(self):
"""
@rtype: L{ProcessHandle}
@return: Process handle received from the system.
Returns C{None} if the handle is not available.
"""
# The handle doesn't need to be closed.
# See http://msdn.microsoft.com/en-us/library/ms681423(VS.85).aspx
hProcess = self.raw.u.CreateProcessInfo.hProcess
if hProcess in (0, win32.NULL, win32.INVALID_HANDLE_VALUE):
hProcess = None
else:
hProcess = ProcessHandle(hProcess, False, win32.PROCESS_ALL_ACCESS)
return hProcess
def get_thread_handle(self):
"""
@rtype: L{ThreadHandle}
@return: Thread handle received from the system.
Returns C{None} if the handle is not available.
"""
# The handle doesn't need to be closed.
# See http://msdn.microsoft.com/en-us/library/ms681423(VS.85).aspx
hThread = self.raw.u.CreateProcessInfo.hThread
if hThread in (0, win32.NULL, win32.INVALID_HANDLE_VALUE):
hThread = None
else:
hThread = ThreadHandle(hThread, False, win32.THREAD_ALL_ACCESS)
return hThread
def get_start_address(self):
"""
@rtype: int
@return: Pointer to the first instruction to execute in this process.
Returns C{NULL} when the debugger attaches to a process.
See U{http://msdn.microsoft.com/en-us/library/ms679295(VS.85).aspx}
"""
return self.raw.u.CreateProcessInfo.lpStartAddress
def get_image_base(self):
"""
@rtype: int
@return: Base address of the main module.
@warn: This value is taken from the PE file
and may be incorrect because of ASLR!
"""
# TODO try to calculate the real value when ASLR is active.
return self.raw.u.CreateProcessInfo.lpBaseOfImage
def get_teb(self):
"""
@rtype: int
@return: Pointer to the TEB.
"""
return self.raw.u.CreateProcessInfo.lpThreadLocalBase
def get_debug_info(self):
"""
@rtype: str
@return: Debugging information.
"""
raw = self.raw.u.CreateProcessInfo
ptr = raw.lpBaseOfImage + raw.dwDebugInfoFileOffset
size = raw.nDebugInfoSize
data = self.get_process().peek(ptr, size)
if len(data) == size:
return data
return None
def get_filename(self):
"""
@rtype: str, None
@return: This method does it's best to retrieve the filename to
the main module of the process. However, sometimes that's not
possible, and C{None} is returned instead.
"""
# Try to get the filename from the file handle.
szFilename = None
hFile = self.get_file_handle()
if hFile:
szFilename = hFile.get_filename()
if not szFilename:
# Try to get it from CREATE_PROCESS_DEBUG_INFO.lpImageName
# It's NULL or *NULL most of the times, see MSDN:
# http://msdn.microsoft.com/en-us/library/ms679286(VS.85).aspx
aProcess = self.get_process()
lpRemoteFilenamePtr = self.raw.u.CreateProcessInfo.lpImageName
if lpRemoteFilenamePtr:
lpFilename = aProcess.peek_uint(lpRemoteFilenamePtr)
fUnicode = bool( self.raw.u.CreateProcessInfo.fUnicode )
szFilename = aProcess.peek_string(lpFilename, fUnicode)
# XXX TODO
# Sometimes the filename is relative (ntdll.dll, kernel32.dll).
# It could be converted to an absolute pathname (SearchPath).
# Try to get it from Process.get_image_name().
if not szFilename:
szFilename = aProcess.get_image_name()
# Return the filename, or None on error.
return szFilename
def get_module_base(self):
"""
@rtype: int
@return: Base address of the main module.
"""
return self.get_image_base()
def get_module(self):
"""
@rtype: L{Module}
@return: Main module of the process.
"""
return self.get_process().get_module( self.get_module_base() )
#==============================================================================
class ExitThreadEvent (Event):
"""
Thread termination event.
"""
eventMethod = 'exit_thread'
eventName = 'Thread termination event'
eventDescription = 'A thread has finished executing.'
def get_exit_code(self):
"""
@rtype: int
@return: Exit code of the thread.
"""
return self.raw.u.ExitThread.dwExitCode
#==============================================================================
class ExitProcessEvent (Event):
"""
Process termination event.
"""
eventMethod = 'exit_process'
eventName = 'Process termination event'
eventDescription = 'A process has finished executing.'
def get_exit_code(self):
"""
@rtype: int
@return: Exit code of the process.
"""
return self.raw.u.ExitProcess.dwExitCode
def get_filename(self):
"""
@rtype: None or str
@return: Filename of the main module.
C{None} if the filename is unknown.
"""
return self.get_module().get_filename()
def get_image_base(self):
"""
@rtype: int
@return: Base address of the main module.
"""
return self.get_module_base()
def get_module_base(self):
"""
@rtype: int
@return: Base address of the main module.
"""
return self.get_module().get_base()
def get_module(self):
"""
@rtype: L{Module}
@return: Main module of the process.
"""
return self.get_process().get_main_module()
#==============================================================================
class LoadDLLEvent (Event):
"""
Module load event.
"""
eventMethod = 'load_dll'
eventName = 'Module load event'
eventDescription = 'A new DLL library was loaded by the debugee.'
def get_module_base(self):
"""
@rtype: int
@return: Base address for the newly loaded DLL.
"""
return self.raw.u.LoadDll.lpBaseOfDll
def get_module(self):
"""
@rtype: L{Module}
@return: Module object for the newly loaded DLL.
"""
lpBaseOfDll = self.get_module_base()
aProcess = self.get_process()
if aProcess.has_module(lpBaseOfDll):
aModule = aProcess.get_module(lpBaseOfDll)
else:
# XXX HACK
# For some reason the module object is missing, so make a new one.
aModule = Module(lpBaseOfDll,
hFile = self.get_file_handle(),
fileName = self.get_filename(),
process = aProcess)
aProcess._add_module(aModule)
return aModule
def get_file_handle(self):
"""
@rtype: L{FileHandle} or None
@return: File handle to the newly loaded DLL received from the system.
Returns C{None} if the handle is not available.
"""
# This handle DOES need to be closed.
# Therefore we must cache it so it doesn't
# get closed after the first call.
try:
hFile = self.__hFile
except AttributeError:
hFile = self.raw.u.LoadDll.hFile
if hFile in (0, win32.NULL, win32.INVALID_HANDLE_VALUE):
hFile = None
else:
hFile = FileHandle(hFile, True)
self.__hFile = hFile
return hFile
def get_filename(self):
"""
@rtype: str, None
@return: This method does it's best to retrieve the filename to
the newly loaded module. However, sometimes that's not
possible, and C{None} is returned instead.
"""
szFilename = None
# Try to get it from LOAD_DLL_DEBUG_INFO.lpImageName
# It's NULL or *NULL most of the times, see MSDN:
# http://msdn.microsoft.com/en-us/library/ms679286(VS.85).aspx
aProcess = self.get_process()
lpRemoteFilenamePtr = self.raw.u.LoadDll.lpImageName
if lpRemoteFilenamePtr:
lpFilename = aProcess.peek_uint(lpRemoteFilenamePtr)
fUnicode = bool( self.raw.u.LoadDll.fUnicode )
szFilename = aProcess.peek_string(lpFilename, fUnicode)
if not szFilename:
szFilename = None
# Try to get the filename from the file handle.
if not szFilename:
hFile = self.get_file_handle()
if hFile:
szFilename = hFile.get_filename()
# Return the filename, or None on error.
return szFilename
#==============================================================================
class UnloadDLLEvent (Event):
"""
Module unload event.
"""
eventMethod = 'unload_dll'
eventName = 'Module unload event'
eventDescription = 'A DLL library was unloaded by the debugee.'
def get_module_base(self):
"""
@rtype: int
@return: Base address for the recently unloaded DLL.
"""
return self.raw.u.UnloadDll.lpBaseOfDll
def get_module(self):
"""
@rtype: L{Module}
@return: Module object for the recently unloaded DLL.
"""
lpBaseOfDll = self.get_module_base()
aProcess = self.get_process()
if aProcess.has_module(lpBaseOfDll):
aModule = aProcess.get_module(lpBaseOfDll)
else:
aModule = Module(lpBaseOfDll, process = aProcess)
aProcess._add_module(aModule)
return aModule
def get_file_handle(self):
"""
@rtype: None or L{FileHandle}
@return: File handle to the recently unloaded DLL.
Returns C{None} if the handle is not available.
"""
hFile = self.get_module().hFile
if hFile in (0, win32.NULL, win32.INVALID_HANDLE_VALUE):
hFile = None
return hFile
def get_filename(self):
"""
@rtype: None or str
@return: Filename of the recently unloaded DLL.
C{None} if the filename is unknown.
"""
return self.get_module().get_filename()
#==============================================================================
class OutputDebugStringEvent (Event):
"""
Debug string output event.
"""
eventMethod = 'output_string'
eventName = 'Debug string output event'
eventDescription = 'The debugee sent a message to the debugger.'
def get_debug_string(self):
"""
@rtype: str, unicode
@return: String sent by the debugee.
It may be ANSI or Unicode and may end with a null character.
"""
return self.get_process().peek_string(
self.raw.u.DebugString.lpDebugStringData,
bool( self.raw.u.DebugString.fUnicode ),
self.raw.u.DebugString.nDebugStringLength)
#==============================================================================
class RIPEvent (Event):
"""
RIP event.
"""
eventMethod = 'rip'
eventName = 'RIP event'
eventDescription = 'An error has occured and the process ' \
'can no longer be debugged.'
def get_rip_error(self):
"""
@rtype: int
@return: RIP error code as defined by the Win32 API.
"""
return self.raw.u.RipInfo.dwError
def get_rip_type(self):
"""
@rtype: int
@return: RIP type code as defined by the Win32 API.
May be C{0} or one of the following:
- L{win32.SLE_ERROR}
- L{win32.SLE_MINORERROR}
- L{win32.SLE_WARNING}
"""
return self.raw.u.RipInfo.dwType
#==============================================================================
class EventFactory (StaticClass):
"""
Factory of L{Event} objects.
@type baseEvent: L{Event}
@cvar baseEvent:
Base class for Event objects.
It's used for unknown event codes.
@type eventClasses: dict( int S{->} L{Event} )
@cvar eventClasses:
Dictionary that maps event codes to L{Event} subclasses.
"""
baseEvent = Event
eventClasses = {
win32.EXCEPTION_DEBUG_EVENT : ExceptionEvent, # 1
win32.CREATE_THREAD_DEBUG_EVENT : CreateThreadEvent, # 2
win32.CREATE_PROCESS_DEBUG_EVENT : CreateProcessEvent, # 3
win32.EXIT_THREAD_DEBUG_EVENT : ExitThreadEvent, # 4
win32.EXIT_PROCESS_DEBUG_EVENT : ExitProcessEvent, # 5
win32.LOAD_DLL_DEBUG_EVENT : LoadDLLEvent, # 6
win32.UNLOAD_DLL_DEBUG_EVENT : UnloadDLLEvent, # 7
win32.OUTPUT_DEBUG_STRING_EVENT : OutputDebugStringEvent, # 8
win32.RIP_EVENT : RIPEvent, # 9
}
@classmethod
def get(cls, debug, raw):
"""
@type debug: L{Debug}
@param debug: Debug object that received the event.
@type raw: L{DEBUG_EVENT}
@param raw: Raw DEBUG_EVENT structure as used by the Win32 API.
@rtype: L{Event}
@returns: An Event object or one of it's subclasses,
depending on the event type.
"""
eventClass = cls.eventClasses.get(raw.dwDebugEventCode, cls.baseEvent)
return eventClass(debug, raw)
#==============================================================================
class EventHandler (object):
"""
Base class for debug event handlers.
Your program should subclass it to implement it's own event handling.
The constructor can be overriden as long as you call the superclass
constructor. The special method L{__call__} B{MUST NOT} be overriden.
The signature for event handlers is the following::
def event_handler(self, event):
Where B{event} is an L{Event} object.
Each event handler is named after the event they handle.
This is the list of all valid event handler names:
- I{event}
Receives an L{Event} object or an object of any of it's subclasses,
and handles any event for which no handler was defined.
- I{unknown_event}
Receives an L{Event} object or an object of any of it's subclasses,
and handles any event unknown to the debugging engine. (This is not
likely to happen unless the Win32 debugging API is changed in future
versions of Windows).
- I{exception}
Receives an L{ExceptionEvent} object and handles any exception for
which no handler was defined. See above for exception handlers.
- I{unknown_exception}
Receives an L{ExceptionEvent} object and handles any exception unknown
to the debugging engine. This usually happens for C++ exceptions, which
are not standardized and may change from one compiler to the next.
Currently we have partial support for C++ exceptions thrown by Microsoft
compilers.
Also see: U{RaiseException()
<http://msdn.microsoft.com/en-us/library/ms680552(VS.85).aspx>}
- I{create_thread}
Receives a L{CreateThreadEvent} object.
- I{create_process}
Receives a L{CreateProcessEvent} object.
- I{exit_thread}
Receives a L{ExitThreadEvent} object.
- I{exit_process}
Receives a L{ExitProcessEvent} object.
- I{load_dll}
Receives a L{LoadDLLEvent} object.
- I{unload_dll}
Receives an L{UnloadDLLEvent} object.
- I{output_string}
Receives an L{OutputDebugStringEvent} object.
- I{rip}
Receives a L{RIPEvent} object.
This is the list of all valid exception handler names
(they all receive an L{ExceptionEvent} object):
- I{access_violation}
- I{array_bounds_exceeded}
- I{breakpoint}
- I{control_c_exit}
- I{datatype_misalignment}
- I{debug_control_c}
- I{float_denormal_operand}
- I{float_divide_by_zero}
- I{float_inexact_result}
- I{float_invalid_operation}
- I{float_overflow}
- I{float_stack_check}
- I{float_underflow}
- I{guard_page}
- I{illegal_instruction}
- I{in_page_error}
- I{integer_divide_by_zero}
- I{integer_overflow}
- I{invalid_disposition}
- I{invalid_handle}
- I{ms_vc_exception}
- I{noncontinuable_exception}
- I{possible_deadlock}
- I{privileged_instruction}
- I{single_step}
- I{stack_overflow}
- I{wow64_breakpoint}
@type apiHooks: dict( str S{->} list( tuple( str, int ) ) )
@cvar apiHooks:
Dictionary that maps module names to lists of
tuples of ( procedure name, parameter count ).
All procedures listed here will be hooked for calls from the debugee.
When this happens, the corresponding event handler can be notified both
when the procedure is entered and when it's left by the debugee.
For example, let's hook the LoadLibraryEx() API call.
This would be the declaration of apiHooks::
from winappdbg import EventHandler
from winappdbg.win32 import *
# (...)
class MyEventHandler (EventHandler):
apiHook = {
"kernel32.dll" : (
# Procedure name Signature
( "LoadLibraryEx", (PVOID, HANDLE, DWORD) ),
# (more procedures can go here...)
),
# (more libraries can go here...)
}
# (your method definitions go here...)
Note that all pointer types are treated like void pointers, so your
callback won't get the string or structure pointed to by it, but the
remote memory address instead. This is so to prevent the ctypes library
from being "too helpful" and trying to dereference the pointer. To get
the actual data being pointed to, use one of the L{Process.read}
methods.
Now, to intercept calls to LoadLibraryEx define a method like this in
your event handler class::
def pre_LoadLibraryEx(self, event, ra, lpFilename, hFile, dwFlags):
szFilename = event.get_process().peek_string(lpFilename)
# (...)
Note that the first parameter is always the L{Event} object, and the
second parameter is the return address. The third parameter and above
are the values passed to the hooked function.
Finally, to intercept returns from calls to LoadLibraryEx define a
method like this::
def post_LoadLibraryEx(self, event, retval):
# (...)
The first parameter is the L{Event} object and the second is the
return value from the hooked function.
"""
#------------------------------------------------------------------------------
# Default (empty) API hooks dictionary.
apiHooks = {}
def __init__(self):
"""
Class constructor. Don't forget to call it when subclassing!
Forgetting to call the superclass constructor is a common mistake when
you're new to Python. :)
Example::
class MyEventHandler (EventHandler):
# Override the constructor to use an extra argument.
def __init__(self, myArgument):
# Do something with the argument, like keeping it
# as an instance variable.
self.myVariable = myArgument
# Call the superclass constructor.
super(MyEventHandler, self).__init__()
# The rest of your code below...
"""
# TODO
# All this does is set up the hooks.
# This code should be moved to the EventDispatcher class.
# Then the hooks can be set up at set_event_handler() instead, making
# this class even simpler. The downside here is deciding where to store
# the ApiHook objects.
# Convert the tuples into instances of the ApiHook class.
# A new dictionary must be instanced, otherwise we could also be
# affecting all other instances of the EventHandler.
apiHooks = dict()
for lib, hooks in self.apiHooks.iteritems():
hook_objs = []
for proc, args in hooks:
if type(args) in (int, long):
h = ApiHook(self, lib, proc, paramCount = args)
else:
h = ApiHook(self, lib, proc, signature = args)
hook_objs.append(h)
apiHooks[lib] = hook_objs
self.__apiHooks = apiHooks
def __get_hooks_for_dll(self, event):
"""
Get the requested API hooks for the current DLL.
Used by L{__hook_dll} and L{__unhook_dll}.
"""
result = []
if self.__apiHooks:
path = event.get_module().get_filename()
if path:
lib_name = PathOperations.pathname_to_filename(path).lower()
for hook_lib, hook_api_list in self.__apiHooks.iteritems():
if hook_lib == lib_name:
result.extend(hook_api_list)
return result
def __hook_dll(self, event):
"""
Hook the requested API calls (in self.apiHooks).
This method is called automatically whenever a DLL is loaded.
"""
debug = event.debug
pid = event.get_pid()
for hook_api_stub in self.__get_hooks_for_dll(event):
hook_api_stub.hook(debug, pid)
def __unhook_dll(self, event):
"""
Unhook the requested API calls (in self.apiHooks).
This method is called automatically whenever a DLL is unloaded.
"""
debug = event.debug
pid = event.get_pid()
for hook_api_stub in self.__get_hooks_for_dll(event):
hook_api_stub.unhook(debug, pid)
def __call__(self, event):
"""
Dispatch debug events.
@warn: B{Don't override this method!}
@type event: L{Event}
@param event: Event object.
"""
try:
code = event.get_event_code()
if code == win32.LOAD_DLL_DEBUG_EVENT:
self.__hook_dll(event)
elif code == win32.UNLOAD_DLL_DEBUG_EVENT:
self.__unhook_dll(event)
finally:
method = EventDispatcher.get_handler_method(self, event)
if method is not None:
return method(event)
#==============================================================================
# TODO
# * Make it more generic by adding a few more callbacks.
# That way it will be possible to make a thread sifter too.
# * This interface feels too much like an antipattern.
# When apiHooks is deprecated this will have to be reviewed.
class EventSift(EventHandler):
"""
Event handler that allows you to use customized event handlers for each
process you're attached to.
This makes coding the event handlers much easier, because each instance
will only "know" about one process. So you can code your event handler as
if only one process was being debugged, but your debugger can attach to
multiple processes.
Example::
from winappdbg import Debug, EventHandler, EventSift
# This class was written assuming only one process is attached.
# If you used it directly it would break when attaching to another
# process, or when a child process is spawned.
class MyEventHandler (EventHandler):
def create_process(self, event):
self.first = True
self.name = event.get_process().get_filename()
print "Attached to %s" % self.name
def breakpoint(self, event):
if self.first:
self.first = False
print "First breakpoint reached at %s" % self.name
def exit_process(self, event):
print "Detached from %s" % self.name
# Now when debugging we use the EventSift to be able to work with
# multiple processes while keeping our code simple. :)
if __name__ == "__main__":
handler = EventSift(MyEventHandler)
#handler = MyEventHandler() # try uncommenting this line...
with Debug(handler) as debug:
debug.execl("calc.exe")
debug.execl("notepad.exe")
debug.execl("charmap.exe")
debug.loop()
Subclasses of C{EventSift} can prevent specific event types from
being forwarded by simply defining a method for it. That means your
subclass can handle some event types globally while letting other types
be handled on per-process basis. To forward events manually you can
call C{self.event(event)}.
Example::
class MySift (EventSift):
# Don't forward this event.
def debug_control_c(self, event):
pass
# Handle this event globally without forwarding it.
def output_string(self, event):
print "Debug string: %s" % event.get_debug_string()
# Handle this event globally and then forward it.
def create_process(self, event):
print "New process created, PID: %d" % event.get_pid()
return self.event(event)
# All other events will be forwarded.
Note that overriding the C{event} method would cause no events to be
forwarded at all. To prevent this, call the superclass implementation.
Example::
def we_want_to_forward_this_event(event):
"Use whatever logic you want here..."
# (...return True or False...)
class MySift (EventSift):
def event(self, event):
# If the event matches some custom criteria...
if we_want_to_forward_this_event(event):
# Forward it.
return super(MySift, self).event(event)
# Otherwise, don't.
@type cls: class
@ivar cls:
Event handler class. There will be one instance of this class
per debugged process in the L{forward} dictionary.
@type argv: list
@ivar argv:
Positional arguments to pass to the constructor of L{cls}.
@type argd: list
@ivar argd:
Keyword arguments to pass to the constructor of L{cls}.
@type forward: dict
@ivar forward:
Dictionary that maps each debugged process ID to an instance of L{cls}.
"""
def __init__(self, cls, *argv, **argd):
"""
Maintains an instance of your event handler for each process being
debugged, and forwards the events of each process to each corresponding
instance.
@warn: If you subclass L{EventSift} and reimplement this method,
don't forget to call the superclass constructor!
@see: L{event}
@type cls: class
@param cls: Event handler class. This must be the class itself, not an
instance! All additional arguments passed to the constructor of
the event forwarder will be passed on to the constructor of this
class as well.
"""
self.cls = cls
self.argv = argv
self.argd = argd
self.forward = dict()
super(EventSift, self).__init__()
# XXX HORRIBLE HACK
# This makes apiHooks work in the inner handlers.
def __call__(self, event):
try:
eventCode = event.get_event_code()
if eventCode in (win32.LOAD_DLL_DEBUG_EVENT,
win32.LOAD_DLL_DEBUG_EVENT):
pid = event.get_pid()
handler = self.forward.get(pid, None)
if handler is None:
handler = self.cls(*self.argv, **self.argd)
self.forward[pid] = handler
if isinstance(handler, EventHandler):
if eventCode == win32.LOAD_DLL_DEBUG_EVENT:
handler.__EventHandler_hook_dll(event)
else:
handler.__EventHandler_unhook_dll(event)
finally:
return super(EventSift, self).__call__(event)
def event(self, event):
"""
Forwards events to the corresponding instance of your event handler
for this process.
If you subclass L{EventSift} and reimplement this method, no event
will be forwarded at all unless you call the superclass implementation.
If your filtering is based on the event type, there's a much easier way
to do it: just implement a handler for it.
"""
eventCode = event.get_event_code()
pid = event.get_pid()
handler = self.forward.get(pid, None)
if handler is None:
handler = self.cls(*self.argv, **self.argd)
if eventCode != win32.EXIT_PROCESS_DEBUG_EVENT:
self.forward[pid] = handler
elif eventCode == win32.EXIT_PROCESS_DEBUG_EVENT:
del self.forward[pid]
return handler(event)
#==============================================================================
class EventDispatcher (object):
"""
Implements debug event dispatching capabilities.
@group Debugging events:
get_event_handler, set_event_handler, get_handler_method
"""
# Maps event code constants to the names of the pre-notify routines.
# These routines are called BEFORE the user-defined handlers.
# Unknown codes are ignored.
__preEventNotifyCallbackName = {
win32.CREATE_THREAD_DEBUG_EVENT : '_notify_create_thread',
win32.CREATE_PROCESS_DEBUG_EVENT : '_notify_create_process',
win32.LOAD_DLL_DEBUG_EVENT : '_notify_load_dll',
}
# Maps event code constants to the names of the post-notify routines.
# These routines are called AFTER the user-defined handlers.
# Unknown codes are ignored.
__postEventNotifyCallbackName = {
win32.EXIT_THREAD_DEBUG_EVENT : '_notify_exit_thread',
win32.EXIT_PROCESS_DEBUG_EVENT : '_notify_exit_process',
win32.UNLOAD_DLL_DEBUG_EVENT : '_notify_unload_dll',
win32.RIP_EVENT : '_notify_rip',
}
# Maps exception code constants to the names of the pre-notify routines.
# These routines are called BEFORE the user-defined handlers.
# Unknown codes are ignored.
__preExceptionNotifyCallbackName = {
win32.EXCEPTION_BREAKPOINT : '_notify_breakpoint',
win32.EXCEPTION_WX86_BREAKPOINT : '_notify_breakpoint',
win32.EXCEPTION_SINGLE_STEP : '_notify_single_step',
win32.EXCEPTION_GUARD_PAGE : '_notify_guard_page',
win32.DBG_CONTROL_C : '_notify_debug_control_c',
win32.MS_VC_EXCEPTION : '_notify_ms_vc_exception',
}
# Maps exception code constants to the names of the post-notify routines.
# These routines are called AFTER the user-defined handlers.
# Unknown codes are ignored.
__postExceptionNotifyCallbackName = {
}
def __init__(self, eventHandler = None):
"""
Event dispatcher.
@type eventHandler: L{EventHandler}
@param eventHandler: (Optional) User-defined event handler.
@raise TypeError: The event handler is of an incorrect type.
@note: The L{eventHandler} parameter may be any callable Python object
(for example a function, or an instance method).
However you'll probably find it more convenient to use an instance
of a subclass of L{EventHandler} here.
"""
self.set_event_handler(eventHandler)
def get_event_handler(self):
"""
Get the event handler.
@see: L{set_event_handler}
@rtype: L{EventHandler}
@return: Current event handler object, or C{None}.
"""
return self.__eventHandler
def set_event_handler(self, eventHandler):
"""
Set the event handler.
@warn: This is normally not needed. Use with care!
@type eventHandler: L{EventHandler}
@param eventHandler: New event handler object, or C{None}.
@rtype: L{EventHandler}
@return: Previous event handler object, or C{None}.
@raise TypeError: The event handler is of an incorrect type.
@note: The L{eventHandler} parameter may be any callable Python object
(for example a function, or an instance method).
However you'll probably find it more convenient to use an instance
of a subclass of L{EventHandler} here.
"""
if eventHandler is not None and not callable(eventHandler):
raise TypeError("Event handler must be a callable object")
try:
wrong_type = issubclass(eventHandler, EventHandler)
except TypeError:
wrong_type = False
if wrong_type:
classname = str(eventHandler)
msg = "Event handler must be an instance of class %s"
msg += "rather than the %s class itself. (Missing parens?)"
msg = msg % (classname, classname)
raise TypeError(msg)
try:
previous = self.__eventHandler
except AttributeError:
previous = None
self.__eventHandler = eventHandler
return previous
@staticmethod
def get_handler_method(eventHandler, event, fallback=None):
"""
Retrieves the appropriate callback method from an L{EventHandler}
instance for the given L{Event} object.
@type eventHandler: L{EventHandler}
@param eventHandler:
Event handler object whose methods we are examining.
@type event: L{Event}
@param event: Debugging event to be handled.
@type fallback: callable
@param fallback: (Optional) If no suitable method is found in the
L{EventHandler} instance, return this value.
@rtype: callable
@return: Bound method that will handle the debugging event.
Returns C{None} if no such method is defined.
"""
eventCode = event.get_event_code()
method = getattr(eventHandler, 'event', fallback)
if eventCode == win32.EXCEPTION_DEBUG_EVENT:
method = getattr(eventHandler, 'exception', method)
method = getattr(eventHandler, event.eventMethod, method)
return method
def dispatch(self, event):
"""
Sends event notifications to the L{Debug} object and
the L{EventHandler} object provided by the user.
The L{Debug} object will forward the notifications to it's contained
snapshot objects (L{System}, L{Process}, L{Thread} and L{Module}) when
appropriate.
@warning: This method is called automatically from L{Debug.dispatch}.
@see: L{Debug.cont}, L{Debug.loop}, L{Debug.wait}
@type event: L{Event}
@param event: Event object passed to L{Debug.dispatch}.
@raise WindowsError: Raises an exception on error.
"""
returnValue = None
bCallHandler = True
pre_handler = None
post_handler = None
eventCode = event.get_event_code()
# Get the pre and post notification methods for exceptions.
# If not found, the following steps take care of that.
if eventCode == win32.EXCEPTION_DEBUG_EVENT:
exceptionCode = event.get_exception_code()
pre_name = self.__preExceptionNotifyCallbackName.get(
exceptionCode, None)
post_name = self.__postExceptionNotifyCallbackName.get(
exceptionCode, None)
if pre_name is not None:
pre_handler = getattr(self, pre_name, None)
if post_name is not None:
post_handler = getattr(self, post_name, None)
# Get the pre notification method for all other events.
# This includes the exception event if no notify method was found
# for this exception code.
if pre_handler is None:
pre_name = self.__preEventNotifyCallbackName.get(eventCode, None)
if pre_name is not None:
pre_handler = getattr(self, pre_name, pre_handler)
# Get the post notification method for all other events.
# This includes the exception event if no notify method was found
# for this exception code.
if post_handler is None:
post_name = self.__postEventNotifyCallbackName.get(eventCode, None)
if post_name is not None:
post_handler = getattr(self, post_name, post_handler)
# Call the pre-notify method only if it was defined.
# If an exception is raised don't call the other methods.
if pre_handler is not None:
bCallHandler = pre_handler(event)
# Call the user-defined event handler only if the pre-notify
# method was not defined, or was and it returned True.
try:
if bCallHandler and self.__eventHandler is not None:
try:
returnValue = self.__eventHandler(event)
except Exception, e:
msg = ("Event handler pre-callback %r"
" raised an exception: %s")
msg = msg % (self.__eventHandler, traceback.format_exc(e))
warnings.warn(msg, EventCallbackWarning)
returnValue = None
# Call the post-notify method if defined, even if an exception is
# raised by the user-defined event handler.
finally:
if post_handler is not None:
post_handler(event)
# Return the value from the call to the user-defined event handler.
# If not defined return None.
return returnValue