mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-04 00:20:55 +07:00
Manually picked from PyDev revision `26864816cbfcf002a99913bcc31ebef48042a4ac`. Notable changes: * Inject DLL to Python processfa163ccc2d* Support for Python 3.10bdec612183* Support for Python 3.118ab57600deGitOrigin-RevId: 720414ca39573b3f8b4b9e1b5b72f33eb024ccc1
2128 lines
74 KiB
Python
2128 lines
74 KiB
Python
#!~/.wine/drive_c/Python25/python.exe
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (c) 2009-2014, 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.
|
|
|
|
"""
|
|
Thread instrumentation.
|
|
|
|
@group Instrumentation:
|
|
Thread
|
|
"""
|
|
|
|
from __future__ import with_statement
|
|
|
|
__revision__ = "$Id$"
|
|
|
|
__all__ = ['Thread']
|
|
|
|
from winappdbg import win32
|
|
from winappdbg import compat
|
|
from winappdbg.textio import HexDump
|
|
from winappdbg.util import DebugRegister
|
|
from winappdbg.window import Window
|
|
|
|
import sys
|
|
import struct
|
|
import warnings
|
|
|
|
# delayed imports
|
|
Process = None
|
|
|
|
#==============================================================================
|
|
|
|
# TODO
|
|
# + fetch special registers (MMX, XMM, 3DNow!, etc)
|
|
|
|
class Thread (object):
|
|
"""
|
|
Interface to a thread in another process.
|
|
|
|
@group Properties:
|
|
get_tid, get_pid, get_process, set_process, get_exit_code, is_alive,
|
|
get_name, set_name, get_windows, get_teb, get_teb_address, is_wow64,
|
|
get_arch, get_bits, get_handle, open_handle, close_handle
|
|
|
|
@group Instrumentation:
|
|
suspend, resume, kill, wait
|
|
|
|
@group Debugging:
|
|
get_seh_chain_pointer, set_seh_chain_pointer,
|
|
get_seh_chain, get_wait_chain, is_hidden
|
|
|
|
@group Disassembly:
|
|
disassemble, disassemble_around, disassemble_around_pc,
|
|
disassemble_string, disassemble_instruction, disassemble_current
|
|
|
|
@group Stack:
|
|
get_stack_frame, get_stack_frame_range, get_stack_range,
|
|
get_stack_trace, get_stack_trace_with_labels,
|
|
read_stack_data, read_stack_dwords, read_stack_qwords,
|
|
peek_stack_data, peek_stack_dwords, peek_stack_qwords,
|
|
read_stack_structure, read_stack_frame
|
|
|
|
@group Registers:
|
|
get_context,
|
|
get_register,
|
|
get_flags, get_flag_value,
|
|
get_pc, get_sp, get_fp,
|
|
get_cf, get_df, get_sf, get_tf, get_zf,
|
|
set_context,
|
|
set_register,
|
|
set_flags, set_flag_value,
|
|
set_pc, set_sp, set_fp,
|
|
set_cf, set_df, set_sf, set_tf, set_zf,
|
|
clear_cf, clear_df, clear_sf, clear_tf, clear_zf,
|
|
Flags
|
|
|
|
@group Threads snapshot:
|
|
clear
|
|
|
|
@group Miscellaneous:
|
|
read_code_bytes, peek_code_bytes,
|
|
peek_pointers_in_data, peek_pointers_in_registers,
|
|
get_linear_address, get_label_at_pc
|
|
|
|
@type dwThreadId: int
|
|
@ivar dwThreadId: Global thread ID. Use L{get_tid} instead.
|
|
|
|
@type hThread: L{ThreadHandle}
|
|
@ivar hThread: Handle to the thread. Use L{get_handle} instead.
|
|
|
|
@type process: L{Process}
|
|
@ivar process: Parent process object. Use L{get_process} instead.
|
|
|
|
@type pInjectedMemory: int
|
|
@ivar pInjectedMemory: If the thread was created by L{Process.inject_code},
|
|
this member contains a pointer to the memory buffer for the injected
|
|
code. Otherwise it's C{None}.
|
|
|
|
The L{kill} method uses this member to free the buffer
|
|
when the injected thread is killed.
|
|
"""
|
|
|
|
def __init__(self, dwThreadId, hThread = None, process = None):
|
|
"""
|
|
@type dwThreadId: int
|
|
@param dwThreadId: Global thread ID.
|
|
|
|
@type hThread: L{ThreadHandle}
|
|
@param hThread: (Optional) Handle to the thread.
|
|
|
|
@type process: L{Process}
|
|
@param process: (Optional) Parent Process object.
|
|
"""
|
|
self.dwProcessId = None
|
|
self.dwThreadId = dwThreadId
|
|
self.hThread = hThread
|
|
self.pInjectedMemory = None
|
|
self.set_name(None)
|
|
self.set_process(process)
|
|
|
|
# Not really sure if it's a good idea...
|
|
## def __eq__(self, aThread):
|
|
## """
|
|
## Compare two Thread objects. The comparison is made using the IDs.
|
|
##
|
|
## @warning:
|
|
## If you have two Thread instances with different handles the
|
|
## equality operator still returns C{True}, so be careful!
|
|
##
|
|
## @type aThread: L{Thread}
|
|
## @param aThread: Another Thread object.
|
|
##
|
|
## @rtype: bool
|
|
## @return: C{True} if the two thread IDs are equal,
|
|
## C{False} otherwise.
|
|
## """
|
|
## return isinstance(aThread, Thread) and \
|
|
## self.get_tid() == aThread.get_tid()
|
|
|
|
def __load_Process_class(self):
|
|
global Process # delayed import
|
|
if Process is None:
|
|
from winappdbg.process import Process
|
|
|
|
def get_process(self):
|
|
"""
|
|
@rtype: L{Process}
|
|
@return: Parent Process object.
|
|
Returns C{None} if unknown.
|
|
"""
|
|
if self.__process is not None:
|
|
return self.__process
|
|
self.__load_Process_class()
|
|
self.__process = Process(self.get_pid())
|
|
return self.__process
|
|
|
|
def set_process(self, process = None):
|
|
"""
|
|
Manually set the parent Process object. Use with care!
|
|
|
|
@type process: L{Process}
|
|
@param process: (Optional) Process object. Use C{None} for no process.
|
|
"""
|
|
if process is None:
|
|
self.dwProcessId = None
|
|
self.__process = None
|
|
else:
|
|
self.__load_Process_class()
|
|
if not isinstance(process, Process):
|
|
msg = "Parent process must be a Process instance, "
|
|
msg += "got %s instead" % type(process)
|
|
raise TypeError(msg)
|
|
self.dwProcessId = process.get_pid()
|
|
self.__process = process
|
|
|
|
process = property(get_process, set_process, doc="")
|
|
|
|
def get_pid(self):
|
|
"""
|
|
@rtype: int
|
|
@return: Parent process global ID.
|
|
|
|
@raise WindowsError: An error occured when calling a Win32 API function.
|
|
@raise RuntimeError: The parent process ID can't be found.
|
|
"""
|
|
if self.dwProcessId is None:
|
|
if self.__process is not None:
|
|
# Infinite loop if self.__process is None
|
|
self.dwProcessId = self.get_process().get_pid()
|
|
else:
|
|
try:
|
|
# I wish this had been implemented before Vista...
|
|
# XXX TODO find the real ntdll call under this api
|
|
hThread = self.get_handle(
|
|
win32.THREAD_QUERY_LIMITED_INFORMATION)
|
|
self.dwProcessId = win32.GetProcessIdOfThread(hThread)
|
|
except AttributeError:
|
|
# This method is really bad :P
|
|
self.dwProcessId = self.__get_pid_by_scanning()
|
|
return self.dwProcessId
|
|
|
|
def __get_pid_by_scanning(self):
|
|
'Internally used by get_pid().'
|
|
dwProcessId = None
|
|
dwThreadId = self.get_tid()
|
|
with win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPTHREAD) as hSnapshot:
|
|
te = win32.Thread32First(hSnapshot)
|
|
while te is not None:
|
|
if te.th32ThreadID == dwThreadId:
|
|
dwProcessId = te.th32OwnerProcessID
|
|
break
|
|
te = win32.Thread32Next(hSnapshot)
|
|
if dwProcessId is None:
|
|
msg = "Cannot find thread ID %d in any process" % dwThreadId
|
|
raise RuntimeError(msg)
|
|
return dwProcessId
|
|
|
|
def get_tid(self):
|
|
"""
|
|
@rtype: int
|
|
@return: Thread global ID.
|
|
"""
|
|
return self.dwThreadId
|
|
|
|
def get_name(self):
|
|
"""
|
|
@rtype: str
|
|
@return: Thread name, or C{None} if the thread is nameless.
|
|
"""
|
|
return self.name
|
|
|
|
def set_name(self, name = None):
|
|
"""
|
|
Sets the thread's name.
|
|
|
|
@type name: str
|
|
@param name: Thread name, or C{None} if the thread is nameless.
|
|
"""
|
|
self.name = name
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
def open_handle(self, dwDesiredAccess = win32.THREAD_ALL_ACCESS):
|
|
"""
|
|
Opens a new handle to the thread, closing the previous one.
|
|
|
|
The new handle is stored in the L{hThread} property.
|
|
|
|
@warn: Normally you should call L{get_handle} instead, since it's much
|
|
"smarter" and tries to reuse handles and merge access rights.
|
|
|
|
@type dwDesiredAccess: int
|
|
@param dwDesiredAccess: Desired access rights.
|
|
Defaults to L{win32.THREAD_ALL_ACCESS}.
|
|
See: U{http://msdn.microsoft.com/en-us/library/windows/desktop/ms686769(v=vs.85).aspx}
|
|
|
|
@raise WindowsError: It's not possible to open a handle to the thread
|
|
with the requested access rights. This tipically happens because
|
|
the target thread belongs to system process and the debugger is not
|
|
runnning with administrative rights.
|
|
"""
|
|
hThread = win32.OpenThread(dwDesiredAccess, win32.FALSE, self.dwThreadId)
|
|
|
|
# In case hThread was set to an actual handle value instead of a Handle
|
|
# object. This shouldn't happen unless the user tinkered with it.
|
|
if not hasattr(self.hThread, '__del__'):
|
|
self.close_handle()
|
|
|
|
self.hThread = hThread
|
|
|
|
def close_handle(self):
|
|
"""
|
|
Closes the handle to the thread.
|
|
|
|
@note: Normally you don't need to call this method. All handles
|
|
created by I{WinAppDbg} are automatically closed when the garbage
|
|
collector claims them.
|
|
"""
|
|
try:
|
|
if hasattr(self.hThread, 'close'):
|
|
self.hThread.close()
|
|
elif self.hThread not in (None, win32.INVALID_HANDLE_VALUE):
|
|
win32.CloseHandle(self.hThread)
|
|
finally:
|
|
self.hThread = None
|
|
|
|
def get_handle(self, dwDesiredAccess = win32.THREAD_ALL_ACCESS):
|
|
"""
|
|
Returns a handle to the thread with I{at least} the access rights
|
|
requested.
|
|
|
|
@note:
|
|
If a handle was previously opened and has the required access
|
|
rights, it's reused. If not, a new handle is opened with the
|
|
combination of the old and new access rights.
|
|
|
|
@type dwDesiredAccess: int
|
|
@param dwDesiredAccess: Desired access rights.
|
|
See: U{http://msdn.microsoft.com/en-us/library/windows/desktop/ms686769(v=vs.85).aspx}
|
|
|
|
@rtype: ThreadHandle
|
|
@return: Handle to the thread.
|
|
|
|
@raise WindowsError: It's not possible to open a handle to the thread
|
|
with the requested access rights. This tipically happens because
|
|
the target thread belongs to system process and the debugger is not
|
|
runnning with administrative rights.
|
|
"""
|
|
if self.hThread in (None, win32.INVALID_HANDLE_VALUE):
|
|
self.open_handle(dwDesiredAccess)
|
|
else:
|
|
dwAccess = self.hThread.dwAccess
|
|
if (dwAccess | dwDesiredAccess) != dwAccess:
|
|
self.open_handle(dwAccess | dwDesiredAccess)
|
|
return self.hThread
|
|
|
|
def clear(self):
|
|
"""
|
|
Clears the resources held by this object.
|
|
"""
|
|
try:
|
|
self.set_process(None)
|
|
finally:
|
|
self.close_handle()
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
def wait(self, dwTimeout = None):
|
|
"""
|
|
Waits for the thread to finish executing.
|
|
|
|
@type dwTimeout: int
|
|
@param dwTimeout: (Optional) Timeout value in milliseconds.
|
|
Use C{INFINITE} or C{None} for no timeout.
|
|
"""
|
|
self.get_handle(win32.SYNCHRONIZE).wait(dwTimeout)
|
|
|
|
def kill(self, dwExitCode = 0):
|
|
"""
|
|
Terminates the thread execution.
|
|
|
|
@note: If the C{lpInjectedMemory} member contains a valid pointer,
|
|
the memory is freed.
|
|
|
|
@type dwExitCode: int
|
|
@param dwExitCode: (Optional) Thread exit code.
|
|
"""
|
|
hThread = self.get_handle(win32.THREAD_TERMINATE)
|
|
win32.TerminateThread(hThread, dwExitCode)
|
|
|
|
# Ugliest hack ever, won't work if many pieces of code are injected.
|
|
# Seriously, what was I thinking? :(
|
|
if self.pInjectedMemory is not None:
|
|
try:
|
|
self.get_process().free(self.pInjectedMemory)
|
|
self.pInjectedMemory = None
|
|
except Exception:
|
|
## raise # XXX DEBUG
|
|
pass
|
|
|
|
# XXX TODO
|
|
# suspend() and resume() should have a counter of how many times a thread
|
|
# was suspended, so on debugger exit they could (optionally!) be restored
|
|
|
|
def suspend(self):
|
|
"""
|
|
Suspends the thread execution.
|
|
|
|
@rtype: int
|
|
@return: Suspend count. If zero, the thread is running.
|
|
"""
|
|
hThread = self.get_handle(win32.THREAD_SUSPEND_RESUME)
|
|
if self.is_wow64():
|
|
# FIXME this will be horribly slow on XP 64
|
|
# since it'll try to resolve a missing API every time
|
|
try:
|
|
return win32.Wow64SuspendThread(hThread)
|
|
except AttributeError:
|
|
pass
|
|
return win32.SuspendThread(hThread)
|
|
|
|
def resume(self):
|
|
"""
|
|
Resumes the thread execution.
|
|
|
|
@rtype: int
|
|
@return: Suspend count. If zero, the thread is running.
|
|
"""
|
|
hThread = self.get_handle(win32.THREAD_SUSPEND_RESUME)
|
|
return win32.ResumeThread(hThread)
|
|
|
|
def is_alive(self):
|
|
"""
|
|
@rtype: bool
|
|
@return: C{True} if the thread if currently running.
|
|
@raise WindowsError:
|
|
The debugger doesn't have enough privileges to perform this action.
|
|
"""
|
|
try:
|
|
self.wait(0)
|
|
except WindowsError:
|
|
e = sys.exc_info()[1]
|
|
error = e.winerror
|
|
if error == win32.ERROR_ACCESS_DENIED:
|
|
raise
|
|
return error == win32.WAIT_TIMEOUT
|
|
return True
|
|
|
|
def get_exit_code(self):
|
|
"""
|
|
@rtype: int
|
|
@return: Thread exit code, or C{STILL_ACTIVE} if it's still alive.
|
|
"""
|
|
if win32.THREAD_ALL_ACCESS == win32.THREAD_ALL_ACCESS_VISTA:
|
|
dwAccess = win32.THREAD_QUERY_LIMITED_INFORMATION
|
|
else:
|
|
dwAccess = win32.THREAD_QUERY_INFORMATION
|
|
return win32.GetExitCodeThread( self.get_handle(dwAccess) )
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
# XXX TODO
|
|
# Support for string searches on the window captions.
|
|
|
|
def get_windows(self):
|
|
"""
|
|
@rtype: list of L{Window}
|
|
@return: Returns a list of windows handled by this thread.
|
|
"""
|
|
try:
|
|
process = self.get_process()
|
|
except Exception:
|
|
process = None
|
|
return [
|
|
Window( hWnd, process, self ) \
|
|
for hWnd in win32.EnumThreadWindows( self.get_tid() )
|
|
]
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
# TODO
|
|
# A registers cache could be implemented here.
|
|
def get_context(self, ContextFlags = None, bSuspend = False):
|
|
"""
|
|
Retrieves the execution context (i.e. the registers values) for this
|
|
thread.
|
|
|
|
@type ContextFlags: int
|
|
@param ContextFlags: Optional, specify which registers to retrieve.
|
|
Defaults to C{win32.CONTEXT_ALL} which retrieves all registes
|
|
for the current platform.
|
|
|
|
@type bSuspend: bool
|
|
@param bSuspend: C{True} to automatically suspend the thread before
|
|
getting its context, C{False} otherwise.
|
|
|
|
Defaults to C{False} because suspending the thread during some
|
|
debug events (like thread creation or destruction) may lead to
|
|
strange errors.
|
|
|
|
Note that WinAppDbg 1.4 used to suspend the thread automatically
|
|
always. This behavior was changed in version 1.5.
|
|
|
|
@rtype: dict( str S{->} int )
|
|
@return: Dictionary mapping register names to their values.
|
|
|
|
@see: L{set_context}
|
|
"""
|
|
|
|
# Some words on the "strange errors" that lead to the bSuspend
|
|
# parameter. Peter Van Eeckhoutte and I were working on a fix
|
|
# for some bugs he found in the 1.5 betas when we stumbled upon
|
|
# what seemed to be a deadlock in the debug API that caused the
|
|
# GetThreadContext() call never to return. Since removing the
|
|
# call to SuspendThread() solved the problem, and a few Google
|
|
# searches showed a handful of problems related to these two
|
|
# APIs and Wow64 environments, I decided to break compatibility.
|
|
#
|
|
# Here are some pages about the weird behavior of SuspendThread:
|
|
# http://zachsaw.blogspot.com.es/2010/11/wow64-bug-getthreadcontext-may-return.html
|
|
# http://stackoverflow.com/questions/3444190/windows-suspendthread-doesnt-getthreadcontext-fails
|
|
|
|
# Get the thread handle.
|
|
dwAccess = win32.THREAD_GET_CONTEXT
|
|
if bSuspend:
|
|
dwAccess = dwAccess | win32.THREAD_SUSPEND_RESUME
|
|
hThread = self.get_handle(dwAccess)
|
|
|
|
# Suspend the thread if requested.
|
|
if bSuspend:
|
|
try:
|
|
self.suspend()
|
|
except WindowsError:
|
|
# Threads can't be suspended when the exit process event
|
|
# arrives, but you can still get the context.
|
|
bSuspend = False
|
|
|
|
# If an exception is raised, make sure the thread execution is resumed.
|
|
try:
|
|
|
|
if win32.bits == self.get_bits():
|
|
|
|
# 64 bit debugger attached to 64 bit process, or
|
|
# 32 bit debugger attached to 32 bit process.
|
|
ctx = win32.GetThreadContext(hThread,
|
|
ContextFlags = ContextFlags)
|
|
|
|
else:
|
|
if self.is_wow64():
|
|
|
|
# 64 bit debugger attached to 32 bit process.
|
|
if ContextFlags is not None:
|
|
ContextFlags &= ~win32.ContextArchMask
|
|
ContextFlags |= win32.WOW64_CONTEXT_i386
|
|
ctx = win32.Wow64GetThreadContext(hThread, ContextFlags)
|
|
|
|
else:
|
|
|
|
# 32 bit debugger attached to 64 bit process.
|
|
# XXX only i386/AMD64 is supported in this particular case
|
|
if win32.arch not in (win32.ARCH_I386, win32.ARCH_AMD64):
|
|
raise NotImplementedError()
|
|
if ContextFlags is not None:
|
|
ContextFlags &= ~win32.ContextArchMask
|
|
ContextFlags |= win32.context_amd64.CONTEXT_AMD64
|
|
ctx = win32.context_amd64.GetThreadContext(hThread,
|
|
ContextFlags = ContextFlags)
|
|
|
|
finally:
|
|
|
|
# Resume the thread if we suspended it.
|
|
if bSuspend:
|
|
self.resume()
|
|
|
|
# Return the context.
|
|
return ctx
|
|
|
|
def set_context(self, context, bSuspend = False):
|
|
"""
|
|
Sets the values of the registers.
|
|
|
|
@see: L{get_context}
|
|
|
|
@type context: dict( str S{->} int )
|
|
@param context: Dictionary mapping register names to their values.
|
|
|
|
@type bSuspend: bool
|
|
@param bSuspend: C{True} to automatically suspend the thread before
|
|
setting its context, C{False} otherwise.
|
|
|
|
Defaults to C{False} because suspending the thread during some
|
|
debug events (like thread creation or destruction) may lead to
|
|
strange errors.
|
|
|
|
Note that WinAppDbg 1.4 used to suspend the thread automatically
|
|
always. This behavior was changed in version 1.5.
|
|
"""
|
|
|
|
# Get the thread handle.
|
|
dwAccess = win32.THREAD_SET_CONTEXT
|
|
if bSuspend:
|
|
dwAccess = dwAccess | win32.THREAD_SUSPEND_RESUME
|
|
hThread = self.get_handle(dwAccess)
|
|
|
|
# Suspend the thread if requested.
|
|
if bSuspend:
|
|
self.suspend()
|
|
# No fix for the exit process event bug.
|
|
# Setting the context of a dead thread is pointless anyway.
|
|
|
|
# Set the thread context.
|
|
try:
|
|
if win32.bits == 64 and self.is_wow64():
|
|
win32.Wow64SetThreadContext(hThread, context)
|
|
else:
|
|
win32.SetThreadContext(hThread, context)
|
|
|
|
# Resume the thread if we suspended it.
|
|
finally:
|
|
if bSuspend:
|
|
self.resume()
|
|
|
|
def get_register(self, register):
|
|
"""
|
|
@type register: str
|
|
@param register: Register name.
|
|
|
|
@rtype: int
|
|
@return: Value of the requested register.
|
|
"""
|
|
'Returns the value of a specific register.'
|
|
context = self.get_context()
|
|
return context[register]
|
|
|
|
def set_register(self, register, value):
|
|
"""
|
|
Sets the value of a specific register.
|
|
|
|
@type register: str
|
|
@param register: Register name.
|
|
|
|
@rtype: int
|
|
@return: Register value.
|
|
"""
|
|
context = self.get_context()
|
|
context[register] = value
|
|
self.set_context(context)
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
# TODO: a metaclass would do a better job instead of checking the platform
|
|
# during module import, also would support mixing 32 and 64 bits
|
|
|
|
if win32.arch in (win32.ARCH_I386, win32.ARCH_AMD64):
|
|
|
|
def get_pc(self):
|
|
"""
|
|
@rtype: int
|
|
@return: Value of the program counter register.
|
|
"""
|
|
context = self.get_context(win32.CONTEXT_CONTROL)
|
|
return context.pc
|
|
|
|
def set_pc(self, pc):
|
|
"""
|
|
Sets the value of the program counter register.
|
|
|
|
@type pc: int
|
|
@param pc: Value of the program counter register.
|
|
"""
|
|
context = self.get_context(win32.CONTEXT_CONTROL)
|
|
context.pc = pc
|
|
self.set_context(context)
|
|
|
|
def get_sp(self):
|
|
"""
|
|
@rtype: int
|
|
@return: Value of the stack pointer register.
|
|
"""
|
|
context = self.get_context(win32.CONTEXT_CONTROL)
|
|
return context.sp
|
|
|
|
def set_sp(self, sp):
|
|
"""
|
|
Sets the value of the stack pointer register.
|
|
|
|
@type sp: int
|
|
@param sp: Value of the stack pointer register.
|
|
"""
|
|
context = self.get_context(win32.CONTEXT_CONTROL)
|
|
context.sp = sp
|
|
self.set_context(context)
|
|
|
|
def get_fp(self):
|
|
"""
|
|
@rtype: int
|
|
@return: Value of the frame pointer register.
|
|
"""
|
|
flags = win32.CONTEXT_CONTROL | win32.CONTEXT_INTEGER
|
|
context = self.get_context(flags)
|
|
return context.fp
|
|
|
|
def set_fp(self, fp):
|
|
"""
|
|
Sets the value of the frame pointer register.
|
|
|
|
@type fp: int
|
|
@param fp: Value of the frame pointer register.
|
|
"""
|
|
flags = win32.CONTEXT_CONTROL | win32.CONTEXT_INTEGER
|
|
context = self.get_context(flags)
|
|
context.fp = fp
|
|
self.set_context(context)
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
if win32.arch in (win32.ARCH_I386, win32.ARCH_AMD64):
|
|
|
|
class Flags (object):
|
|
'Commonly used processor flags'
|
|
Overflow = 0x800
|
|
Direction = 0x400
|
|
Interrupts = 0x200
|
|
Trap = 0x100
|
|
Sign = 0x80
|
|
Zero = 0x40
|
|
# 0x20 ???
|
|
Auxiliary = 0x10
|
|
# 0x8 ???
|
|
Parity = 0x4
|
|
# 0x2 ???
|
|
Carry = 0x1
|
|
|
|
def get_flags(self, FlagMask = 0xFFFFFFFF):
|
|
"""
|
|
@type FlagMask: int
|
|
@param FlagMask: (Optional) Bitwise-AND mask.
|
|
|
|
@rtype: int
|
|
@return: Flags register contents, optionally masking out some bits.
|
|
"""
|
|
context = self.get_context(win32.CONTEXT_CONTROL)
|
|
return context['EFlags'] & FlagMask
|
|
|
|
def set_flags(self, eflags, FlagMask = 0xFFFFFFFF):
|
|
"""
|
|
Sets the flags register, optionally masking some bits.
|
|
|
|
@type eflags: int
|
|
@param eflags: Flags register contents.
|
|
|
|
@type FlagMask: int
|
|
@param FlagMask: (Optional) Bitwise-AND mask.
|
|
"""
|
|
context = self.get_context(win32.CONTEXT_CONTROL)
|
|
context['EFlags'] = (context['EFlags'] & FlagMask) | eflags
|
|
self.set_context(context)
|
|
|
|
def get_flag_value(self, FlagBit):
|
|
"""
|
|
@type FlagBit: int
|
|
@param FlagBit: One of the L{Flags}.
|
|
|
|
@rtype: bool
|
|
@return: Boolean value of the requested flag.
|
|
"""
|
|
return bool( self.get_flags(FlagBit) )
|
|
|
|
def set_flag_value(self, FlagBit, FlagValue):
|
|
"""
|
|
Sets a single flag, leaving the others intact.
|
|
|
|
@type FlagBit: int
|
|
@param FlagBit: One of the L{Flags}.
|
|
|
|
@type FlagValue: bool
|
|
@param FlagValue: Boolean value of the flag.
|
|
"""
|
|
if FlagValue:
|
|
eflags = FlagBit
|
|
else:
|
|
eflags = 0
|
|
FlagMask = 0xFFFFFFFF ^ FlagBit
|
|
self.set_flags(eflags, FlagMask)
|
|
|
|
def get_zf(self):
|
|
"""
|
|
@rtype: bool
|
|
@return: Boolean value of the Zero flag.
|
|
"""
|
|
return self.get_flag_value(self.Flags.Zero)
|
|
|
|
def get_cf(self):
|
|
"""
|
|
@rtype: bool
|
|
@return: Boolean value of the Carry flag.
|
|
"""
|
|
return self.get_flag_value(self.Flags.Carry)
|
|
|
|
def get_sf(self):
|
|
"""
|
|
@rtype: bool
|
|
@return: Boolean value of the Sign flag.
|
|
"""
|
|
return self.get_flag_value(self.Flags.Sign)
|
|
|
|
def get_df(self):
|
|
"""
|
|
@rtype: bool
|
|
@return: Boolean value of the Direction flag.
|
|
"""
|
|
return self.get_flag_value(self.Flags.Direction)
|
|
|
|
def get_tf(self):
|
|
"""
|
|
@rtype: bool
|
|
@return: Boolean value of the Trap flag.
|
|
"""
|
|
return self.get_flag_value(self.Flags.Trap)
|
|
|
|
def clear_zf(self):
|
|
'Clears the Zero flag.'
|
|
self.set_flag_value(self.Flags.Zero, False)
|
|
|
|
def clear_cf(self):
|
|
'Clears the Carry flag.'
|
|
self.set_flag_value(self.Flags.Carry, False)
|
|
|
|
def clear_sf(self):
|
|
'Clears the Sign flag.'
|
|
self.set_flag_value(self.Flags.Sign, False)
|
|
|
|
def clear_df(self):
|
|
'Clears the Direction flag.'
|
|
self.set_flag_value(self.Flags.Direction, False)
|
|
|
|
def clear_tf(self):
|
|
'Clears the Trap flag.'
|
|
self.set_flag_value(self.Flags.Trap, False)
|
|
|
|
def set_zf(self):
|
|
'Sets the Zero flag.'
|
|
self.set_flag_value(self.Flags.Zero, True)
|
|
|
|
def set_cf(self):
|
|
'Sets the Carry flag.'
|
|
self.set_flag_value(self.Flags.Carry, True)
|
|
|
|
def set_sf(self):
|
|
'Sets the Sign flag.'
|
|
self.set_flag_value(self.Flags.Sign, True)
|
|
|
|
def set_df(self):
|
|
'Sets the Direction flag.'
|
|
self.set_flag_value(self.Flags.Direction, True)
|
|
|
|
def set_tf(self):
|
|
'Sets the Trap flag.'
|
|
self.set_flag_value(self.Flags.Trap, True)
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
def is_wow64(self):
|
|
"""
|
|
Determines if the thread is running under WOW64.
|
|
|
|
@rtype: bool
|
|
@return:
|
|
C{True} if the thread is running under WOW64. That is, it belongs
|
|
to a 32-bit application running in a 64-bit Windows.
|
|
|
|
C{False} if the thread belongs to either a 32-bit application
|
|
running in a 32-bit Windows, or a 64-bit application running in a
|
|
64-bit Windows.
|
|
|
|
@raise WindowsError: On error an exception is raised.
|
|
|
|
@see: U{http://msdn.microsoft.com/en-us/library/aa384249(VS.85).aspx}
|
|
"""
|
|
try:
|
|
wow64 = self.__wow64
|
|
except AttributeError:
|
|
if (win32.bits == 32 and not win32.wow64):
|
|
wow64 = False
|
|
else:
|
|
wow64 = self.get_process().is_wow64()
|
|
self.__wow64 = wow64
|
|
return wow64
|
|
|
|
def get_arch(self):
|
|
"""
|
|
@rtype: str
|
|
@return: The architecture in which this thread believes to be running.
|
|
For example, if running a 32 bit binary in a 64 bit machine, the
|
|
architecture returned by this method will be L{win32.ARCH_I386},
|
|
but the value of L{System.arch} will be L{win32.ARCH_AMD64}.
|
|
"""
|
|
if win32.bits == 32 and not win32.wow64:
|
|
return win32.arch
|
|
return self.get_process().get_arch()
|
|
|
|
def get_bits(self):
|
|
"""
|
|
@rtype: str
|
|
@return: The number of bits in which this thread believes to be
|
|
running. For example, if running a 32 bit binary in a 64 bit
|
|
machine, the number of bits returned by this method will be C{32},
|
|
but the value of L{System.arch} will be C{64}.
|
|
"""
|
|
if win32.bits == 32 and not win32.wow64:
|
|
return 32
|
|
return self.get_process().get_bits()
|
|
|
|
def is_hidden(self):
|
|
"""
|
|
Determines if the thread has been hidden from debuggers.
|
|
|
|
Some binary packers hide their own threads to thwart debugging.
|
|
|
|
@rtype: bool
|
|
@return: C{True} if the thread is hidden from debuggers.
|
|
This means the thread's execution won't be stopped for debug
|
|
events, and thus said events won't be sent to the debugger.
|
|
"""
|
|
return win32.NtQueryInformationThread(
|
|
self.get_handle(), # XXX what permissions do I need?
|
|
win32.ThreadHideFromDebugger)
|
|
|
|
def get_teb(self):
|
|
"""
|
|
Returns a copy of the TEB.
|
|
To dereference pointers in it call L{Process.read_structure}.
|
|
|
|
@rtype: L{TEB}
|
|
@return: TEB structure.
|
|
@raise WindowsError: An exception is raised on error.
|
|
"""
|
|
return self.get_process().read_structure( self.get_teb_address(),
|
|
win32.TEB )
|
|
|
|
def get_teb_address(self):
|
|
"""
|
|
Returns a remote pointer to the TEB.
|
|
|
|
@rtype: int
|
|
@return: Remote pointer to the L{TEB} structure.
|
|
@raise WindowsError: An exception is raised on error.
|
|
"""
|
|
try:
|
|
return self._teb_ptr
|
|
except AttributeError:
|
|
try:
|
|
hThread = self.get_handle(win32.THREAD_QUERY_INFORMATION)
|
|
tbi = win32.NtQueryInformationThread( hThread,
|
|
win32.ThreadBasicInformation)
|
|
address = tbi.TebBaseAddress
|
|
except WindowsError:
|
|
address = self.get_linear_address('SegFs', 0) # fs:[0]
|
|
if not address:
|
|
raise
|
|
self._teb_ptr = address
|
|
return address
|
|
|
|
def get_linear_address(self, segment, address):
|
|
"""
|
|
Translates segment-relative addresses to linear addresses.
|
|
|
|
Linear addresses can be used to access a process memory,
|
|
calling L{Process.read} and L{Process.write}.
|
|
|
|
@type segment: str
|
|
@param segment: Segment register name.
|
|
|
|
@type address: int
|
|
@param address: Segment relative memory address.
|
|
|
|
@rtype: int
|
|
@return: Linear memory address.
|
|
|
|
@raise ValueError: Address is too large for selector.
|
|
|
|
@raise WindowsError:
|
|
The current architecture does not support selectors.
|
|
Selectors only exist in x86-based systems.
|
|
"""
|
|
hThread = self.get_handle(win32.THREAD_QUERY_INFORMATION)
|
|
selector = self.get_register(segment)
|
|
ldt = win32.GetThreadSelectorEntry(hThread, selector)
|
|
BaseLow = ldt.BaseLow
|
|
BaseMid = ldt.HighWord.Bytes.BaseMid << 16
|
|
BaseHi = ldt.HighWord.Bytes.BaseHi << 24
|
|
Base = BaseLow | BaseMid | BaseHi
|
|
LimitLow = ldt.LimitLow
|
|
LimitHi = ldt.HighWord.Bits.LimitHi << 16
|
|
Limit = LimitLow | LimitHi
|
|
if address > Limit:
|
|
msg = "Address %s too large for segment %s (selector %d)"
|
|
msg = msg % (HexDump.address(address, self.get_bits()),
|
|
segment, selector)
|
|
raise ValueError(msg)
|
|
return Base + address
|
|
|
|
def get_label_at_pc(self):
|
|
"""
|
|
@rtype: str
|
|
@return: Label that points to the instruction currently being executed.
|
|
"""
|
|
return self.get_process().get_label_at_address( self.get_pc() )
|
|
|
|
def get_seh_chain_pointer(self):
|
|
"""
|
|
Get the pointer to the first structured exception handler block.
|
|
|
|
@rtype: int
|
|
@return: Remote pointer to the first block of the structured exception
|
|
handlers linked list. If the list is empty, the returned value is
|
|
C{0xFFFFFFFF}.
|
|
|
|
@raise NotImplementedError:
|
|
This method is only supported in 32 bits versions of Windows.
|
|
"""
|
|
if win32.arch != win32.ARCH_I386:
|
|
raise NotImplementedError(
|
|
"SEH chain parsing is only supported in 32-bit Windows.")
|
|
|
|
process = self.get_process()
|
|
address = self.get_linear_address( 'SegFs', 0 )
|
|
return process.read_pointer( address )
|
|
|
|
def set_seh_chain_pointer(self, value):
|
|
"""
|
|
Change the pointer to the first structured exception handler block.
|
|
|
|
@type value: int
|
|
@param value: Value of the remote pointer to the first block of the
|
|
structured exception handlers linked list. To disable SEH set the
|
|
value C{0xFFFFFFFF}.
|
|
|
|
@raise NotImplementedError:
|
|
This method is only supported in 32 bits versions of Windows.
|
|
"""
|
|
if win32.arch != win32.ARCH_I386:
|
|
raise NotImplementedError(
|
|
"SEH chain parsing is only supported in 32-bit Windows.")
|
|
|
|
process = self.get_process()
|
|
address = self.get_linear_address( 'SegFs', 0 )
|
|
process.write_pointer( address, value )
|
|
|
|
def get_seh_chain(self):
|
|
"""
|
|
@rtype: list of tuple( int, int )
|
|
@return: List of structured exception handlers.
|
|
Each SEH is represented as a tuple of two addresses:
|
|
- Address of this SEH block
|
|
- Address of the SEH callback function
|
|
Do not confuse this with the contents of the SEH block itself,
|
|
where the first member is a pointer to the B{next} block instead.
|
|
|
|
@raise NotImplementedError:
|
|
This method is only supported in 32 bits versions of Windows.
|
|
"""
|
|
seh_chain = list()
|
|
try:
|
|
process = self.get_process()
|
|
seh = self.get_seh_chain_pointer()
|
|
while seh != 0xFFFFFFFF:
|
|
seh_func = process.read_pointer( seh + 4 )
|
|
seh_chain.append( (seh, seh_func) )
|
|
seh = process.read_pointer( seh )
|
|
except WindowsError:
|
|
seh_chain.append( (seh, None) )
|
|
return seh_chain
|
|
|
|
def get_wait_chain(self):
|
|
"""
|
|
@rtype:
|
|
tuple of (
|
|
list of L{win32.WaitChainNodeInfo} structures,
|
|
bool)
|
|
@return:
|
|
Wait chain for the thread.
|
|
The boolean indicates if there's a cycle in the chain (a deadlock).
|
|
@raise AttributeError:
|
|
This method is only suppported in Windows Vista and above.
|
|
@see:
|
|
U{http://msdn.microsoft.com/en-us/library/ms681622%28VS.85%29.aspx}
|
|
"""
|
|
with win32.OpenThreadWaitChainSession() as hWct:
|
|
return win32.GetThreadWaitChain(hWct, ThreadId = self.get_tid())
|
|
|
|
def get_stack_range(self):
|
|
"""
|
|
@rtype: tuple( int, int )
|
|
@return: Stack beginning and end pointers, in memory addresses order.
|
|
That is, the first pointer is the stack top, and the second pointer
|
|
is the stack bottom, since the stack grows towards lower memory
|
|
addresses.
|
|
@raise WindowsError: Raises an exception on error.
|
|
"""
|
|
# TODO use teb.DeallocationStack too (max. possible stack size)
|
|
teb = self.get_teb()
|
|
tib = teb.NtTib
|
|
return ( tib.StackLimit, tib.StackBase ) # top, bottom
|
|
|
|
def __get_stack_trace(self, depth = 16, bUseLabels = True,
|
|
bMakePretty = True):
|
|
"""
|
|
Tries to get a stack trace for the current function using the debug
|
|
helper API (dbghelp.dll).
|
|
|
|
@type depth: int
|
|
@param depth: Maximum depth of stack trace.
|
|
|
|
@type bUseLabels: bool
|
|
@param bUseLabels: C{True} to use labels, C{False} to use addresses.
|
|
|
|
@type bMakePretty: bool
|
|
@param bMakePretty:
|
|
C{True} for user readable labels,
|
|
C{False} for labels that can be passed to L{Process.resolve_label}.
|
|
|
|
"Pretty" labels look better when producing output for the user to
|
|
read, while pure labels are more useful programatically.
|
|
|
|
@rtype: tuple of tuple( int, int, str )
|
|
@return: Stack trace of the thread as a tuple of
|
|
( return address, frame pointer address, module filename )
|
|
when C{bUseLabels} is C{True}, or a tuple of
|
|
( return address, frame pointer label )
|
|
when C{bUseLabels} is C{False}.
|
|
|
|
@raise WindowsError: Raises an exception on error.
|
|
"""
|
|
|
|
aProcess = self.get_process()
|
|
arch = aProcess.get_arch()
|
|
bits = aProcess.get_bits()
|
|
|
|
if arch == win32.ARCH_I386:
|
|
MachineType = win32.IMAGE_FILE_MACHINE_I386
|
|
elif arch == win32.ARCH_AMD64:
|
|
MachineType = win32.IMAGE_FILE_MACHINE_AMD64
|
|
elif arch == win32.ARCH_IA64:
|
|
MachineType = win32.IMAGE_FILE_MACHINE_IA64
|
|
else:
|
|
msg = "Stack walking is not available for this architecture: %s"
|
|
raise NotImplementedError(msg % arch)
|
|
|
|
hProcess = aProcess.get_handle( win32.PROCESS_VM_READ |
|
|
win32.PROCESS_QUERY_INFORMATION )
|
|
hThread = self.get_handle( win32.THREAD_GET_CONTEXT |
|
|
win32.THREAD_QUERY_INFORMATION )
|
|
|
|
StackFrame = win32.STACKFRAME64()
|
|
StackFrame.AddrPC = win32.ADDRESS64( self.get_pc() )
|
|
StackFrame.AddrFrame = win32.ADDRESS64( self.get_fp() )
|
|
StackFrame.AddrStack = win32.ADDRESS64( self.get_sp() )
|
|
|
|
trace = list()
|
|
while win32.StackWalk64(MachineType, hProcess, hThread, StackFrame):
|
|
if depth <= 0:
|
|
break
|
|
fp = StackFrame.AddrFrame.Offset
|
|
ra = aProcess.peek_pointer(fp + 4)
|
|
if ra == 0:
|
|
break
|
|
lib = aProcess.get_module_at_address(ra)
|
|
if lib is None:
|
|
lib = ""
|
|
else:
|
|
if lib.fileName:
|
|
lib = lib.fileName
|
|
else:
|
|
lib = "%s" % HexDump.address(lib.lpBaseOfDll, bits)
|
|
if bUseLabels:
|
|
label = aProcess.get_label_at_address(ra)
|
|
if bMakePretty:
|
|
label = '%s (%s)' % (HexDump.address(ra, bits), label)
|
|
trace.append( (fp, label) )
|
|
else:
|
|
trace.append( (fp, ra, lib) )
|
|
fp = aProcess.peek_pointer(fp)
|
|
return tuple(trace)
|
|
|
|
def __get_stack_trace_manually(self, depth = 16, bUseLabels = True,
|
|
bMakePretty = True):
|
|
"""
|
|
Tries to get a stack trace for the current function.
|
|
Only works for functions with standard prologue and epilogue.
|
|
|
|
@type depth: int
|
|
@param depth: Maximum depth of stack trace.
|
|
|
|
@type bUseLabels: bool
|
|
@param bUseLabels: C{True} to use labels, C{False} to use addresses.
|
|
|
|
@type bMakePretty: bool
|
|
@param bMakePretty:
|
|
C{True} for user readable labels,
|
|
C{False} for labels that can be passed to L{Process.resolve_label}.
|
|
|
|
"Pretty" labels look better when producing output for the user to
|
|
read, while pure labels are more useful programatically.
|
|
|
|
@rtype: tuple of tuple( int, int, str )
|
|
@return: Stack trace of the thread as a tuple of
|
|
( return address, frame pointer address, module filename )
|
|
when C{bUseLabels} is C{True}, or a tuple of
|
|
( return address, frame pointer label )
|
|
when C{bUseLabels} is C{False}.
|
|
|
|
@raise WindowsError: Raises an exception on error.
|
|
"""
|
|
aProcess = self.get_process()
|
|
st, sb = self.get_stack_range() # top, bottom
|
|
fp = self.get_fp()
|
|
trace = list()
|
|
if aProcess.get_module_count() == 0:
|
|
aProcess.scan_modules()
|
|
bits = aProcess.get_bits()
|
|
while depth > 0:
|
|
if fp == 0:
|
|
break
|
|
if not st <= fp < sb:
|
|
break
|
|
ra = aProcess.peek_pointer(fp + 4)
|
|
if ra == 0:
|
|
break
|
|
lib = aProcess.get_module_at_address(ra)
|
|
if lib is None:
|
|
lib = ""
|
|
else:
|
|
if lib.fileName:
|
|
lib = lib.fileName
|
|
else:
|
|
lib = "%s" % HexDump.address(lib.lpBaseOfDll, bits)
|
|
if bUseLabels:
|
|
label = aProcess.get_label_at_address(ra)
|
|
if bMakePretty:
|
|
label = '%s (%s)' % (HexDump.address(ra, bits), label)
|
|
trace.append( (fp, label) )
|
|
else:
|
|
trace.append( (fp, ra, lib) )
|
|
fp = aProcess.peek_pointer(fp)
|
|
return tuple(trace)
|
|
|
|
def get_stack_trace(self, depth = 16):
|
|
"""
|
|
Tries to get a stack trace for the current function.
|
|
Only works for functions with standard prologue and epilogue.
|
|
|
|
@type depth: int
|
|
@param depth: Maximum depth of stack trace.
|
|
|
|
@rtype: tuple of tuple( int, int, str )
|
|
@return: Stack trace of the thread as a tuple of
|
|
( return address, frame pointer address, module filename ).
|
|
|
|
@raise WindowsError: Raises an exception on error.
|
|
"""
|
|
try:
|
|
trace = self.__get_stack_trace(depth, False)
|
|
except Exception:
|
|
import traceback
|
|
traceback.print_exc()
|
|
trace = ()
|
|
if not trace:
|
|
trace = self.__get_stack_trace_manually(depth, False)
|
|
return trace
|
|
|
|
def get_stack_trace_with_labels(self, depth = 16, bMakePretty = True):
|
|
"""
|
|
Tries to get a stack trace for the current function.
|
|
Only works for functions with standard prologue and epilogue.
|
|
|
|
@type depth: int
|
|
@param depth: Maximum depth of stack trace.
|
|
|
|
@type bMakePretty: bool
|
|
@param bMakePretty:
|
|
C{True} for user readable labels,
|
|
C{False} for labels that can be passed to L{Process.resolve_label}.
|
|
|
|
"Pretty" labels look better when producing output for the user to
|
|
read, while pure labels are more useful programatically.
|
|
|
|
@rtype: tuple of tuple( int, int, str )
|
|
@return: Stack trace of the thread as a tuple of
|
|
( return address, frame pointer label ).
|
|
|
|
@raise WindowsError: Raises an exception on error.
|
|
"""
|
|
try:
|
|
trace = self.__get_stack_trace(depth, True, bMakePretty)
|
|
except Exception:
|
|
trace = ()
|
|
if not trace:
|
|
trace = self.__get_stack_trace_manually(depth, True, bMakePretty)
|
|
return trace
|
|
|
|
def get_stack_frame_range(self):
|
|
"""
|
|
Returns the starting and ending addresses of the stack frame.
|
|
Only works for functions with standard prologue and epilogue.
|
|
|
|
@rtype: tuple( int, int )
|
|
@return: Stack frame range.
|
|
May not be accurate, depending on the compiler used.
|
|
|
|
@raise RuntimeError: The stack frame is invalid,
|
|
or the function doesn't have a standard prologue
|
|
and epilogue.
|
|
|
|
@raise WindowsError: An error occured when getting the thread context.
|
|
"""
|
|
st, sb = self.get_stack_range() # top, bottom
|
|
sp = self.get_sp()
|
|
fp = self.get_fp()
|
|
size = fp - sp
|
|
if not st <= sp < sb:
|
|
raise RuntimeError('Stack pointer lies outside the stack')
|
|
if not st <= fp < sb:
|
|
raise RuntimeError('Frame pointer lies outside the stack')
|
|
if sp > fp:
|
|
raise RuntimeError('No valid stack frame found')
|
|
return (sp, fp)
|
|
|
|
def get_stack_frame(self, max_size = None):
|
|
"""
|
|
Reads the contents of the current stack frame.
|
|
Only works for functions with standard prologue and epilogue.
|
|
|
|
@type max_size: int
|
|
@param max_size: (Optional) Maximum amount of bytes to read.
|
|
|
|
@rtype: str
|
|
@return: Stack frame data.
|
|
May not be accurate, depending on the compiler used.
|
|
May return an empty string.
|
|
|
|
@raise RuntimeError: The stack frame is invalid,
|
|
or the function doesn't have a standard prologue
|
|
and epilogue.
|
|
|
|
@raise WindowsError: An error occured when getting the thread context
|
|
or reading data from the process memory.
|
|
"""
|
|
sp, fp = self.get_stack_frame_range()
|
|
size = fp - sp
|
|
if max_size and size > max_size:
|
|
size = max_size
|
|
return self.get_process().peek(sp, size)
|
|
|
|
def read_stack_data(self, size = 128, offset = 0):
|
|
"""
|
|
Reads the contents of the top of the stack.
|
|
|
|
@type size: int
|
|
@param size: Number of bytes to read.
|
|
|
|
@type offset: int
|
|
@param offset: Offset from the stack pointer to begin reading.
|
|
|
|
@rtype: str
|
|
@return: Stack data.
|
|
|
|
@raise WindowsError: Could not read the requested data.
|
|
"""
|
|
aProcess = self.get_process()
|
|
return aProcess.read(self.get_sp() + offset, size)
|
|
|
|
def peek_stack_data(self, size = 128, offset = 0):
|
|
"""
|
|
Tries to read the contents of the top of the stack.
|
|
|
|
@type size: int
|
|
@param size: Number of bytes to read.
|
|
|
|
@type offset: int
|
|
@param offset: Offset from the stack pointer to begin reading.
|
|
|
|
@rtype: str
|
|
@return: Stack data.
|
|
Returned data may be less than the requested size.
|
|
"""
|
|
aProcess = self.get_process()
|
|
return aProcess.peek(self.get_sp() + offset, size)
|
|
|
|
def read_stack_dwords(self, count, offset = 0):
|
|
"""
|
|
Reads DWORDs from the top of the stack.
|
|
|
|
@type count: int
|
|
@param count: Number of DWORDs to read.
|
|
|
|
@type offset: int
|
|
@param offset: Offset from the stack pointer to begin reading.
|
|
|
|
@rtype: tuple( int... )
|
|
@return: Tuple of integers read from the stack.
|
|
|
|
@raise WindowsError: Could not read the requested data.
|
|
"""
|
|
if count > 0:
|
|
stackData = self.read_stack_data(count * 4, offset)
|
|
return struct.unpack('<'+('L'*count), stackData)
|
|
return ()
|
|
|
|
def peek_stack_dwords(self, count, offset = 0):
|
|
"""
|
|
Tries to read DWORDs from the top of the stack.
|
|
|
|
@type count: int
|
|
@param count: Number of DWORDs to read.
|
|
|
|
@type offset: int
|
|
@param offset: Offset from the stack pointer to begin reading.
|
|
|
|
@rtype: tuple( int... )
|
|
@return: Tuple of integers read from the stack.
|
|
May be less than the requested number of DWORDs.
|
|
"""
|
|
stackData = self.peek_stack_data(count * 4, offset)
|
|
if len(stackData) & 3:
|
|
stackData = stackData[:-len(stackData) & 3]
|
|
if not stackData:
|
|
return ()
|
|
return struct.unpack('<'+('L'*count), stackData)
|
|
|
|
def read_stack_qwords(self, count, offset = 0):
|
|
"""
|
|
Reads QWORDs from the top of the stack.
|
|
|
|
@type count: int
|
|
@param count: Number of QWORDs to read.
|
|
|
|
@type offset: int
|
|
@param offset: Offset from the stack pointer to begin reading.
|
|
|
|
@rtype: tuple( int... )
|
|
@return: Tuple of integers read from the stack.
|
|
|
|
@raise WindowsError: Could not read the requested data.
|
|
"""
|
|
stackData = self.read_stack_data(count * 8, offset)
|
|
return struct.unpack('<'+('Q'*count), stackData)
|
|
|
|
def peek_stack_qwords(self, count, offset = 0):
|
|
"""
|
|
Tries to read QWORDs from the top of the stack.
|
|
|
|
@type count: int
|
|
@param count: Number of QWORDs to read.
|
|
|
|
@type offset: int
|
|
@param offset: Offset from the stack pointer to begin reading.
|
|
|
|
@rtype: tuple( int... )
|
|
@return: Tuple of integers read from the stack.
|
|
May be less than the requested number of QWORDs.
|
|
"""
|
|
stackData = self.peek_stack_data(count * 8, offset)
|
|
if len(stackData) & 7:
|
|
stackData = stackData[:-len(stackData) & 7]
|
|
if not stackData:
|
|
return ()
|
|
return struct.unpack('<'+('Q'*count), stackData)
|
|
|
|
def read_stack_structure(self, structure, offset = 0):
|
|
"""
|
|
Reads the given structure at the top of the stack.
|
|
|
|
@type structure: ctypes.Structure
|
|
@param structure: Structure of the data to read from the stack.
|
|
|
|
@type offset: int
|
|
@param offset: Offset from the stack pointer to begin reading.
|
|
The stack pointer is the same returned by the L{get_sp} method.
|
|
|
|
@rtype: tuple
|
|
@return: Tuple of elements read from the stack. The type of each
|
|
element matches the types in the stack frame structure.
|
|
"""
|
|
aProcess = self.get_process()
|
|
stackData = aProcess.read_structure(self.get_sp() + offset, structure)
|
|
return tuple([ stackData.__getattribute__(name)
|
|
for (name, type) in stackData._fields_ ])
|
|
|
|
def read_stack_frame(self, structure, offset = 0):
|
|
"""
|
|
Reads the stack frame of the thread.
|
|
|
|
@type structure: ctypes.Structure
|
|
@param structure: Structure of the stack frame.
|
|
|
|
@type offset: int
|
|
@param offset: Offset from the frame pointer to begin reading.
|
|
The frame pointer is the same returned by the L{get_fp} method.
|
|
|
|
@rtype: tuple
|
|
@return: Tuple of elements read from the stack frame. The type of each
|
|
element matches the types in the stack frame structure.
|
|
"""
|
|
aProcess = self.get_process()
|
|
stackData = aProcess.read_structure(self.get_fp() + offset, structure)
|
|
return tuple([ stackData.__getattribute__(name)
|
|
for (name, type) in stackData._fields_ ])
|
|
|
|
def read_code_bytes(self, size = 128, offset = 0):
|
|
"""
|
|
Tries to read some bytes of the code currently being executed.
|
|
|
|
@type size: int
|
|
@param size: Number of bytes to read.
|
|
|
|
@type offset: int
|
|
@param offset: Offset from the program counter to begin reading.
|
|
|
|
@rtype: str
|
|
@return: Bytes read from the process memory.
|
|
|
|
@raise WindowsError: Could not read the requested data.
|
|
"""
|
|
return self.get_process().read(self.get_pc() + offset, size)
|
|
|
|
def peek_code_bytes(self, size = 128, offset = 0):
|
|
"""
|
|
Tries to read some bytes of the code currently being executed.
|
|
|
|
@type size: int
|
|
@param size: Number of bytes to read.
|
|
|
|
@type offset: int
|
|
@param offset: Offset from the program counter to begin reading.
|
|
|
|
@rtype: str
|
|
@return: Bytes read from the process memory.
|
|
May be less than the requested number of bytes.
|
|
"""
|
|
return self.get_process().peek(self.get_pc() + offset, size)
|
|
|
|
def peek_pointers_in_registers(self, peekSize = 16, context = None):
|
|
"""
|
|
Tries to guess which values in the registers are valid pointers,
|
|
and reads some data from them.
|
|
|
|
@type peekSize: int
|
|
@param peekSize: Number of bytes to read from each pointer found.
|
|
|
|
@type context: dict( str S{->} int )
|
|
@param context: (Optional)
|
|
Dictionary mapping register names to their values.
|
|
If not given, the current thread context will be used.
|
|
|
|
@rtype: dict( str S{->} str )
|
|
@return: Dictionary mapping register names to the data they point to.
|
|
"""
|
|
peekable_registers = (
|
|
'Eax', 'Ebx', 'Ecx', 'Edx', 'Esi', 'Edi', 'Ebp'
|
|
)
|
|
if not context:
|
|
context = self.get_context(win32.CONTEXT_CONTROL | \
|
|
win32.CONTEXT_INTEGER)
|
|
aProcess = self.get_process()
|
|
data = dict()
|
|
for (reg_name, reg_value) in compat.iteritems(context):
|
|
if reg_name not in peekable_registers:
|
|
continue
|
|
## if reg_name == 'Ebp':
|
|
## stack_begin, stack_end = self.get_stack_range()
|
|
## print hex(stack_end), hex(reg_value), hex(stack_begin)
|
|
## if stack_begin and stack_end and stack_end < stack_begin and \
|
|
## stack_begin <= reg_value <= stack_end:
|
|
## continue
|
|
reg_data = aProcess.peek(reg_value, peekSize)
|
|
if reg_data:
|
|
data[reg_name] = reg_data
|
|
return data
|
|
|
|
# TODO
|
|
# try to avoid reading the same page twice by caching it
|
|
def peek_pointers_in_data(self, data, peekSize = 16, peekStep = 1):
|
|
"""
|
|
Tries to guess which values in the given data are valid pointers,
|
|
and reads some data from them.
|
|
|
|
@type data: str
|
|
@param data: Binary data to find pointers in.
|
|
|
|
@type peekSize: int
|
|
@param peekSize: Number of bytes to read from each pointer found.
|
|
|
|
@type peekStep: int
|
|
@param peekStep: Expected data alignment.
|
|
Tipically you specify 1 when data alignment is unknown,
|
|
or 4 when you expect data to be DWORD aligned.
|
|
Any other value may be specified.
|
|
|
|
@rtype: dict( str S{->} str )
|
|
@return: Dictionary mapping stack offsets to the data they point to.
|
|
"""
|
|
aProcess = self.get_process()
|
|
return aProcess.peek_pointers_in_data(data, peekSize, peekStep)
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
# TODO
|
|
# The disassemble_around and disassemble_around_pc methods
|
|
# should take as parameter instruction counts rather than sizes
|
|
|
|
def disassemble_string(self, lpAddress, code):
|
|
"""
|
|
Disassemble instructions from a block of binary code.
|
|
|
|
@type lpAddress: int
|
|
@param lpAddress: Memory address where the code was read from.
|
|
|
|
@type code: str
|
|
@param code: Binary code to disassemble.
|
|
|
|
@rtype: list of tuple( long, int, str, str )
|
|
@return: List of tuples. Each tuple represents an assembly instruction
|
|
and contains:
|
|
- Memory address of instruction.
|
|
- Size of instruction in bytes.
|
|
- Disassembly line of instruction.
|
|
- Hexadecimal dump of instruction.
|
|
"""
|
|
aProcess = self.get_process()
|
|
return aProcess.disassemble_string(lpAddress, code)
|
|
|
|
def disassemble(self, lpAddress, dwSize):
|
|
"""
|
|
Disassemble instructions from the address space of the process.
|
|
|
|
@type lpAddress: int
|
|
@param lpAddress: Memory address where to read the code from.
|
|
|
|
@type dwSize: int
|
|
@param dwSize: Size of binary code to disassemble.
|
|
|
|
@rtype: list of tuple( long, int, str, str )
|
|
@return: List of tuples. Each tuple represents an assembly instruction
|
|
and contains:
|
|
- Memory address of instruction.
|
|
- Size of instruction in bytes.
|
|
- Disassembly line of instruction.
|
|
- Hexadecimal dump of instruction.
|
|
"""
|
|
aProcess = self.get_process()
|
|
return aProcess.disassemble(lpAddress, dwSize)
|
|
|
|
def disassemble_around(self, lpAddress, dwSize = 64):
|
|
"""
|
|
Disassemble around the given address.
|
|
|
|
@type lpAddress: int
|
|
@param lpAddress: Memory address where to read the code from.
|
|
|
|
@type dwSize: int
|
|
@param dwSize: Delta offset.
|
|
Code will be read from lpAddress - dwSize to lpAddress + dwSize.
|
|
|
|
@rtype: list of tuple( long, int, str, str )
|
|
@return: List of tuples. Each tuple represents an assembly instruction
|
|
and contains:
|
|
- Memory address of instruction.
|
|
- Size of instruction in bytes.
|
|
- Disassembly line of instruction.
|
|
- Hexadecimal dump of instruction.
|
|
"""
|
|
aProcess = self.get_process()
|
|
return aProcess.disassemble_around(lpAddress, dwSize)
|
|
|
|
def disassemble_around_pc(self, dwSize = 64):
|
|
"""
|
|
Disassemble around the program counter of the given thread.
|
|
|
|
@type dwSize: int
|
|
@param dwSize: Delta offset.
|
|
Code will be read from pc - dwSize to pc + dwSize.
|
|
|
|
@rtype: list of tuple( long, int, str, str )
|
|
@return: List of tuples. Each tuple represents an assembly instruction
|
|
and contains:
|
|
- Memory address of instruction.
|
|
- Size of instruction in bytes.
|
|
- Disassembly line of instruction.
|
|
- Hexadecimal dump of instruction.
|
|
"""
|
|
aProcess = self.get_process()
|
|
return aProcess.disassemble_around(self.get_pc(), dwSize)
|
|
|
|
def disassemble_instruction(self, lpAddress):
|
|
"""
|
|
Disassemble the instruction at the given memory address.
|
|
|
|
@type lpAddress: int
|
|
@param lpAddress: Memory address where to read the code from.
|
|
|
|
@rtype: tuple( long, int, str, str )
|
|
@return: The tuple represents an assembly instruction
|
|
and contains:
|
|
- Memory address of instruction.
|
|
- Size of instruction in bytes.
|
|
- Disassembly line of instruction.
|
|
- Hexadecimal dump of instruction.
|
|
"""
|
|
aProcess = self.get_process()
|
|
return aProcess.disassemble(lpAddress, 15)[0]
|
|
|
|
def disassemble_current(self):
|
|
"""
|
|
Disassemble the instruction at the program counter of the given thread.
|
|
|
|
@rtype: tuple( long, int, str, str )
|
|
@return: The tuple represents an assembly instruction
|
|
and contains:
|
|
- Memory address of instruction.
|
|
- Size of instruction in bytes.
|
|
- Disassembly line of instruction.
|
|
- Hexadecimal dump of instruction.
|
|
"""
|
|
return self.disassemble_instruction( self.get_pc() )
|
|
|
|
#==============================================================================
|
|
|
|
class _ThreadContainer (object):
|
|
"""
|
|
Encapsulates the capability to contain Thread objects.
|
|
|
|
@group Instrumentation:
|
|
start_thread
|
|
|
|
@group Threads snapshot:
|
|
scan_threads,
|
|
get_thread, get_thread_count, get_thread_ids,
|
|
has_thread, iter_threads, iter_thread_ids,
|
|
find_threads_by_name, get_windows,
|
|
clear_threads, clear_dead_threads, close_thread_handles
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.__threadDict = dict()
|
|
|
|
def __initialize_snapshot(self):
|
|
"""
|
|
Private method to automatically initialize the snapshot
|
|
when you try to use it without calling any of the scan_*
|
|
methods first. You don't need to call this yourself.
|
|
"""
|
|
if not self.__threadDict:
|
|
self.scan_threads()
|
|
|
|
def __contains__(self, anObject):
|
|
"""
|
|
@type anObject: L{Thread}, int
|
|
@param anObject:
|
|
- C{int}: Global ID of the thread to look for.
|
|
- C{Thread}: Thread object to look for.
|
|
|
|
@rtype: bool
|
|
@return: C{True} if the snapshot contains
|
|
a L{Thread} object with the same ID.
|
|
"""
|
|
if isinstance(anObject, Thread):
|
|
anObject = anObject.dwThreadId
|
|
return self.has_thread(anObject)
|
|
|
|
def __iter__(self):
|
|
"""
|
|
@see: L{iter_threads}
|
|
@rtype: dictionary-valueiterator
|
|
@return: Iterator of L{Thread} objects in this snapshot.
|
|
"""
|
|
return self.iter_threads()
|
|
|
|
def __len__(self):
|
|
"""
|
|
@see: L{get_thread_count}
|
|
@rtype: int
|
|
@return: Count of L{Thread} objects in this snapshot.
|
|
"""
|
|
return self.get_thread_count()
|
|
|
|
def has_thread(self, dwThreadId):
|
|
"""
|
|
@type dwThreadId: int
|
|
@param dwThreadId: Global ID of the thread to look for.
|
|
|
|
@rtype: bool
|
|
@return: C{True} if the snapshot contains a
|
|
L{Thread} object with the given global ID.
|
|
"""
|
|
self.__initialize_snapshot()
|
|
return dwThreadId in self.__threadDict
|
|
|
|
def get_thread(self, dwThreadId):
|
|
"""
|
|
@type dwThreadId: int
|
|
@param dwThreadId: Global ID of the thread to look for.
|
|
|
|
@rtype: L{Thread}
|
|
@return: Thread object with the given global ID.
|
|
"""
|
|
self.__initialize_snapshot()
|
|
if dwThreadId not in self.__threadDict:
|
|
msg = "Unknown thread ID: %d" % dwThreadId
|
|
raise KeyError(msg)
|
|
return self.__threadDict[dwThreadId]
|
|
|
|
def iter_thread_ids(self):
|
|
"""
|
|
@see: L{iter_threads}
|
|
@rtype: dictionary-keyiterator
|
|
@return: Iterator of global thread IDs in this snapshot.
|
|
"""
|
|
self.__initialize_snapshot()
|
|
return compat.iterkeys(self.__threadDict)
|
|
|
|
def iter_threads(self):
|
|
"""
|
|
@see: L{iter_thread_ids}
|
|
@rtype: dictionary-valueiterator
|
|
@return: Iterator of L{Thread} objects in this snapshot.
|
|
"""
|
|
self.__initialize_snapshot()
|
|
return compat.itervalues(self.__threadDict)
|
|
|
|
def get_thread_ids(self):
|
|
"""
|
|
@rtype: list( int )
|
|
@return: List of global thread IDs in this snapshot.
|
|
"""
|
|
self.__initialize_snapshot()
|
|
return compat.keys(self.__threadDict)
|
|
|
|
def get_thread_count(self):
|
|
"""
|
|
@rtype: int
|
|
@return: Count of L{Thread} objects in this snapshot.
|
|
"""
|
|
self.__initialize_snapshot()
|
|
return len(self.__threadDict)
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
def find_threads_by_name(self, name, bExactMatch = True):
|
|
"""
|
|
Find threads by name, using different search methods.
|
|
|
|
@type name: str, None
|
|
@param name: Name to look for. Use C{None} to find nameless threads.
|
|
|
|
@type bExactMatch: bool
|
|
@param bExactMatch: C{True} if the name must be
|
|
B{exactly} as given, C{False} if the name can be
|
|
loosely matched.
|
|
|
|
This parameter is ignored when C{name} is C{None}.
|
|
|
|
@rtype: list( L{Thread} )
|
|
@return: All threads matching the given name.
|
|
"""
|
|
found_threads = list()
|
|
|
|
# Find threads with no name.
|
|
if name is None:
|
|
for aThread in self.iter_threads():
|
|
if aThread.get_name() is None:
|
|
found_threads.append(aThread)
|
|
|
|
# Find threads matching the given name exactly.
|
|
elif bExactMatch:
|
|
for aThread in self.iter_threads():
|
|
if aThread.get_name() == name:
|
|
found_threads.append(aThread)
|
|
|
|
# Find threads whose names match the given substring.
|
|
else:
|
|
for aThread in self.iter_threads():
|
|
t_name = aThread.get_name()
|
|
if t_name is not None and name in t_name:
|
|
found_threads.append(aThread)
|
|
|
|
return found_threads
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
# XXX TODO
|
|
# Support for string searches on the window captions.
|
|
|
|
def get_windows(self):
|
|
"""
|
|
@rtype: list of L{Window}
|
|
@return: Returns a list of windows handled by this process.
|
|
"""
|
|
window_list = list()
|
|
for thread in self.iter_threads():
|
|
window_list.extend( thread.get_windows() )
|
|
return window_list
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
def start_thread(self, lpStartAddress, lpParameter=0, bSuspended = False):
|
|
"""
|
|
Remotely creates a new thread in the process.
|
|
|
|
@type lpStartAddress: int
|
|
@param lpStartAddress: Start address for the new thread.
|
|
|
|
@type lpParameter: int
|
|
@param lpParameter: Optional argument for the new thread.
|
|
|
|
@type bSuspended: bool
|
|
@param bSuspended: C{True} if the new thread should be suspended.
|
|
In that case use L{Thread.resume} to start execution.
|
|
"""
|
|
if bSuspended:
|
|
dwCreationFlags = win32.CREATE_SUSPENDED
|
|
else:
|
|
dwCreationFlags = 0
|
|
hProcess = self.get_handle( win32.PROCESS_CREATE_THREAD |
|
|
win32.PROCESS_QUERY_INFORMATION |
|
|
win32.PROCESS_VM_OPERATION |
|
|
win32.PROCESS_VM_WRITE |
|
|
win32.PROCESS_VM_READ )
|
|
hThread, dwThreadId = win32.CreateRemoteThread(
|
|
hProcess, 0, 0, lpStartAddress, lpParameter, dwCreationFlags)
|
|
aThread = Thread(dwThreadId, hThread, self)
|
|
self._add_thread(aThread)
|
|
return aThread
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
# TODO
|
|
# maybe put all the toolhelp code into their own set of classes?
|
|
#
|
|
# XXX this method musn't end up calling __initialize_snapshot by accident!
|
|
def scan_threads(self):
|
|
"""
|
|
Populates the snapshot with running threads.
|
|
"""
|
|
|
|
# Ignore special process IDs.
|
|
# PID 0: System Idle Process. Also has a special meaning to the
|
|
# toolhelp APIs (current process).
|
|
# PID 4: System Integrity Group. See this forum post for more info:
|
|
# http://tinyurl.com/ycza8jo
|
|
# (points to social.technet.microsoft.com)
|
|
# Only on XP and above
|
|
# PID 8: System (?) only in Windows 2000 and below AFAIK.
|
|
# It's probably the same as PID 4 in XP and above.
|
|
dwProcessId = self.get_pid()
|
|
if dwProcessId in (0, 4, 8):
|
|
return
|
|
|
|
## dead_tids = set( self.get_thread_ids() ) # XXX triggers a scan
|
|
dead_tids = self._get_thread_ids()
|
|
dwProcessId = self.get_pid()
|
|
hSnapshot = win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPTHREAD,
|
|
dwProcessId)
|
|
try:
|
|
te = win32.Thread32First(hSnapshot)
|
|
while te is not None:
|
|
if te.th32OwnerProcessID == dwProcessId:
|
|
dwThreadId = te.th32ThreadID
|
|
if dwThreadId in dead_tids:
|
|
dead_tids.remove(dwThreadId)
|
|
## if not self.has_thread(dwThreadId): # XXX triggers a scan
|
|
if not self._has_thread_id(dwThreadId):
|
|
aThread = Thread(dwThreadId, process = self)
|
|
self._add_thread(aThread)
|
|
te = win32.Thread32Next(hSnapshot)
|
|
finally:
|
|
win32.CloseHandle(hSnapshot)
|
|
for tid in dead_tids:
|
|
self._del_thread(tid)
|
|
|
|
def clear_dead_threads(self):
|
|
"""
|
|
Remove Thread objects from the snapshot
|
|
referring to threads no longer running.
|
|
"""
|
|
for tid in self.get_thread_ids():
|
|
aThread = self.get_thread(tid)
|
|
if not aThread.is_alive():
|
|
self._del_thread(aThread)
|
|
|
|
def clear_threads(self):
|
|
"""
|
|
Clears the threads snapshot.
|
|
"""
|
|
for aThread in compat.itervalues(self.__threadDict):
|
|
aThread.clear()
|
|
self.__threadDict = dict()
|
|
|
|
def close_thread_handles(self):
|
|
"""
|
|
Closes all open handles to threads in the snapshot.
|
|
"""
|
|
for aThread in self.iter_threads():
|
|
try:
|
|
aThread.close_handle()
|
|
except Exception:
|
|
try:
|
|
e = sys.exc_info()[1]
|
|
msg = "Cannot close thread handle %s, reason: %s"
|
|
msg %= (aThread.hThread.value, str(e))
|
|
warnings.warn(msg)
|
|
except Exception:
|
|
pass
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
# XXX _notify_* methods should not trigger a scan
|
|
|
|
def _add_thread(self, aThread):
|
|
"""
|
|
Private method to add a thread object to the snapshot.
|
|
|
|
@type aThread: L{Thread}
|
|
@param aThread: Thread object.
|
|
"""
|
|
## if not isinstance(aThread, Thread):
|
|
## if hasattr(aThread, '__class__'):
|
|
## typename = aThread.__class__.__name__
|
|
## else:
|
|
## typename = str(type(aThread))
|
|
## msg = "Expected Thread, got %s instead" % typename
|
|
## raise TypeError(msg)
|
|
dwThreadId = aThread.dwThreadId
|
|
## if dwThreadId in self.__threadDict:
|
|
## msg = "Already have a Thread object with ID %d" % dwThreadId
|
|
## raise KeyError(msg)
|
|
aThread.set_process(self)
|
|
self.__threadDict[dwThreadId] = aThread
|
|
|
|
def _del_thread(self, dwThreadId):
|
|
"""
|
|
Private method to remove a thread object from the snapshot.
|
|
|
|
@type dwThreadId: int
|
|
@param dwThreadId: Global thread ID.
|
|
"""
|
|
try:
|
|
aThread = self.__threadDict[dwThreadId]
|
|
del self.__threadDict[dwThreadId]
|
|
except KeyError:
|
|
aThread = None
|
|
msg = "Unknown thread ID %d" % dwThreadId
|
|
warnings.warn(msg, RuntimeWarning)
|
|
if aThread:
|
|
aThread.clear() # remove circular references
|
|
|
|
def _has_thread_id(self, dwThreadId):
|
|
"""
|
|
Private method to test for a thread in the snapshot without triggering
|
|
an automatic scan.
|
|
"""
|
|
return dwThreadId in self.__threadDict
|
|
|
|
def _get_thread_ids(self):
|
|
"""
|
|
Private method to get the list of thread IDs currently in the snapshot
|
|
without triggering an automatic scan.
|
|
"""
|
|
return compat.keys(self.__threadDict)
|
|
|
|
def __add_created_thread(self, event):
|
|
"""
|
|
Private method to automatically add new thread objects from debug events.
|
|
|
|
@type event: L{Event}
|
|
@param event: Event object.
|
|
"""
|
|
dwThreadId = event.get_tid()
|
|
hThread = event.get_thread_handle()
|
|
## if not self.has_thread(dwThreadId): # XXX this would trigger a scan
|
|
if not self._has_thread_id(dwThreadId):
|
|
aThread = Thread(dwThreadId, hThread, self)
|
|
teb_ptr = event.get_teb() # remember the TEB pointer
|
|
if teb_ptr:
|
|
aThread._teb_ptr = teb_ptr
|
|
self._add_thread(aThread)
|
|
#else:
|
|
# aThread = self.get_thread(dwThreadId)
|
|
# if hThread != win32.INVALID_HANDLE_VALUE:
|
|
# aThread.hThread = hThread # may have more privileges
|
|
|
|
def _notify_create_process(self, event):
|
|
"""
|
|
Notify the creation of the main thread of this process.
|
|
|
|
This is done automatically by the L{Debug} class, you shouldn't need
|
|
to call it yourself.
|
|
|
|
@type event: L{CreateProcessEvent}
|
|
@param event: Create process event.
|
|
|
|
@rtype: bool
|
|
@return: C{True} to call the user-defined handle, C{False} otherwise.
|
|
"""
|
|
self.__add_created_thread(event)
|
|
return True
|
|
|
|
def _notify_create_thread(self, event):
|
|
"""
|
|
Notify the creation of a new thread in this process.
|
|
|
|
This is done automatically by the L{Debug} class, you shouldn't need
|
|
to call it yourself.
|
|
|
|
@type event: L{CreateThreadEvent}
|
|
@param event: Create thread event.
|
|
|
|
@rtype: bool
|
|
@return: C{True} to call the user-defined handle, C{False} otherwise.
|
|
"""
|
|
self.__add_created_thread(event)
|
|
return True
|
|
|
|
def _notify_exit_thread(self, event):
|
|
"""
|
|
Notify the termination of a thread.
|
|
|
|
This is done automatically by the L{Debug} class, you shouldn't need
|
|
to call it yourself.
|
|
|
|
@type event: L{ExitThreadEvent}
|
|
@param event: Exit thread event.
|
|
|
|
@rtype: bool
|
|
@return: C{True} to call the user-defined handle, C{False} otherwise.
|
|
"""
|
|
dwThreadId = event.get_tid()
|
|
## if self.has_thread(dwThreadId): # XXX this would trigger a scan
|
|
if self._has_thread_id(dwThreadId):
|
|
self._del_thread(dwThreadId)
|
|
return True
|