| # DExTer : Debugging Experience Tester |
| # ~~~~~~ ~ ~~ ~ ~~ |
| # |
| # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| # See https://llvm.org/LICENSE.txt for license information. |
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| from ctypes import * |
| from functools import partial |
| |
| from .utils import * |
| from .breakpoint import * |
| |
| class DEBUG_STACK_FRAME_EX(Structure): |
| _fields_ = [ |
| ("InstructionOffset", c_ulonglong), |
| ("ReturnOffset", c_ulonglong), |
| ("FrameOffset", c_ulonglong), |
| ("StackOffset", c_ulonglong), |
| ("FuncTableEntry", c_ulonglong), |
| ("Params", c_ulonglong * 4), |
| ("Reserved", c_ulonglong * 6), |
| ("Virtual", c_bool), |
| ("FrameNumber", c_ulong), |
| ("InlineFrameContext", c_ulong), |
| ("Reserved1", c_ulong) |
| ] |
| PDEBUG_STACK_FRAME_EX = POINTER(DEBUG_STACK_FRAME_EX) |
| |
| class DEBUG_VALUE_U(Union): |
| _fields_ = [ |
| ("I8", c_byte), |
| ("I16", c_short), |
| ("I32", c_int), |
| ("I64", c_long), |
| ("F32", c_float), |
| ("F64", c_double), |
| ("RawBytes", c_ubyte * 24) # Force length to 24b. |
| ] |
| |
| class DEBUG_VALUE(Structure): |
| _fields_ = [ |
| ("U", DEBUG_VALUE_U), |
| ("TailOfRawBytes", c_ulong), |
| ("Type", c_ulong) |
| ] |
| PDEBUG_VALUE = POINTER(DEBUG_VALUE) |
| |
| class DebugValueType(IntEnum): |
| DEBUG_VALUE_INVALID = 0 |
| DEBUG_VALUE_INT8 = 1 |
| DEBUG_VALUE_INT16 = 2 |
| DEBUG_VALUE_INT32 = 3 |
| DEBUG_VALUE_INT64 = 4 |
| DEBUG_VALUE_FLOAT32 = 5 |
| DEBUG_VALUE_FLOAT64 = 6 |
| DEBUG_VALUE_FLOAT80 = 7 |
| DEBUG_VALUE_FLOAT82 = 8 |
| DEBUG_VALUE_FLOAT128 = 9 |
| DEBUG_VALUE_VECTOR64 = 10 |
| DEBUG_VALUE_VECTOR128 = 11 |
| DEBUG_VALUE_TYPES = 12 |
| |
| # UUID for DebugControl7 interface. |
| DebugControl7IID = IID(0xb86fb3b1, 0x80d4, 0x475b, IID_Data4_Type(0xae, 0xa3, 0xcf, 0x06, 0x53, 0x9c, 0xf6, 0x3a)) |
| |
| class IDebugControl7(Structure): |
| pass |
| |
| class IDebugControl7Vtbl(Structure): |
| wrp = partial(WINFUNCTYPE, c_long, POINTER(IDebugControl7)) |
| idc_getnumbereventfilters = wrp(c_ulong_p, c_ulong_p, c_ulong_p) |
| idc_setexceptionfiltersecondcommand = wrp(c_ulong, c_char_p) |
| idc_waitforevent = wrp(c_long, c_long) |
| idc_execute = wrp(c_long, c_char_p, c_long) |
| idc_setexpressionsyntax = wrp(c_ulong) |
| idc_addbreakpoint2 = wrp(c_ulong, c_ulong, POINTER(POINTER(DebugBreakpoint2))) |
| idc_setexecutionstatus = wrp(c_ulong) |
| idc_getexecutionstatus = wrp(c_ulong_p) |
| idc_getstacktraceex = wrp(c_ulonglong, c_ulonglong, c_ulonglong, PDEBUG_STACK_FRAME_EX, c_ulong, c_ulong_p) |
| idc_evaluate = wrp(c_char_p, c_ulong, PDEBUG_VALUE, c_ulong_p) |
| idc_setengineoptions = wrp(c_ulong) |
| _fields_ = [ |
| ("QueryInterface", c_void_p), |
| ("AddRef", c_void_p), |
| ("Release", c_void_p), |
| ("GetInterrupt", c_void_p), |
| ("SetInterrupt", c_void_p), |
| ("GetInterruptTimeout", c_void_p), |
| ("SetInterruptTimeout", c_void_p), |
| ("GetLogFile", c_void_p), |
| ("OpenLogFile", c_void_p), |
| ("CloseLogFile", c_void_p), |
| ("GetLogMask", c_void_p), |
| ("SetLogMask", c_void_p), |
| ("Input", c_void_p), |
| ("ReturnInput", c_void_p), |
| ("Output", c_void_p), |
| ("OutputVaList", c_void_p), |
| ("ControlledOutput", c_void_p), |
| ("ControlledOutputVaList", c_void_p), |
| ("OutputPrompt", c_void_p), |
| ("OutputPromptVaList", c_void_p), |
| ("GetPromptText", c_void_p), |
| ("OutputCurrentState", c_void_p), |
| ("OutputVersionInformation", c_void_p), |
| ("GetNotifyEventHandle", c_void_p), |
| ("SetNotifyEventHandle", c_void_p), |
| ("Assemble", c_void_p), |
| ("Disassemble", c_void_p), |
| ("GetDisassembleEffectiveOffset", c_void_p), |
| ("OutputDisassembly", c_void_p), |
| ("OutputDisassemblyLines", c_void_p), |
| ("GetNearInstruction", c_void_p), |
| ("GetStackTrace", c_void_p), |
| ("GetReturnOffset", c_void_p), |
| ("OutputStackTrace", c_void_p), |
| ("GetDebuggeeType", c_void_p), |
| ("GetActualProcessorType", c_void_p), |
| ("GetExecutingProcessorType", c_void_p), |
| ("GetNumberPossibleExecutingProcessorTypes", c_void_p), |
| ("GetPossibleExecutingProcessorTypes", c_void_p), |
| ("GetNumberProcessors", c_void_p), |
| ("GetSystemVersion", c_void_p), |
| ("GetPageSize", c_void_p), |
| ("IsPointer64Bit", c_void_p), |
| ("ReadBugCheckData", c_void_p), |
| ("GetNumberSupportedProcessorTypes", c_void_p), |
| ("GetSupportedProcessorTypes", c_void_p), |
| ("GetProcessorTypeNames", c_void_p), |
| ("GetEffectiveProcessorType", c_void_p), |
| ("SetEffectiveProcessorType", c_void_p), |
| ("GetExecutionStatus", idc_getexecutionstatus), |
| ("SetExecutionStatus", idc_setexecutionstatus), |
| ("GetCodeLevel", c_void_p), |
| ("SetCodeLevel", c_void_p), |
| ("GetEngineOptions", c_void_p), |
| ("AddEngineOptions", c_void_p), |
| ("RemoveEngineOptions", c_void_p), |
| ("SetEngineOptions", idc_setengineoptions), |
| ("GetSystemErrorControl", c_void_p), |
| ("SetSystemErrorControl", c_void_p), |
| ("GetTextMacro", c_void_p), |
| ("SetTextMacro", c_void_p), |
| ("GetRadix", c_void_p), |
| ("SetRadix", c_void_p), |
| ("Evaluate", idc_evaluate), |
| ("CoerceValue", c_void_p), |
| ("CoerceValues", c_void_p), |
| ("Execute", idc_execute), |
| ("ExecuteCommandFile", c_void_p), |
| ("GetNumberBreakpoints", c_void_p), |
| ("GetBreakpointByIndex", c_void_p), |
| ("GetBreakpointById", c_void_p), |
| ("GetBreakpointParameters", c_void_p), |
| ("AddBreakpoint", c_void_p), |
| ("RemoveBreakpoint", c_void_p), |
| ("AddExtension", c_void_p), |
| ("RemoveExtension", c_void_p), |
| ("GetExtensionByPath", c_void_p), |
| ("CallExtension", c_void_p), |
| ("GetExtensionFunction", c_void_p), |
| ("GetWindbgExtensionApis32", c_void_p), |
| ("GetWindbgExtensionApis64", c_void_p), |
| ("GetNumberEventFilters", idc_getnumbereventfilters), |
| ("GetEventFilterText", c_void_p), |
| ("GetEventFilterCommand", c_void_p), |
| ("SetEventFilterCommand", c_void_p), |
| ("GetSpecificFilterParameters", c_void_p), |
| ("SetSpecificFilterParameters", c_void_p), |
| ("GetSpecificFilterArgument", c_void_p), |
| ("SetSpecificFilterArgument", c_void_p), |
| ("GetExceptionFilterParameters", c_void_p), |
| ("SetExceptionFilterParameters", c_void_p), |
| ("GetExceptionFilterSecondCommand", c_void_p), |
| ("SetExceptionFilterSecondCommand", idc_setexceptionfiltersecondcommand), |
| ("WaitForEvent", idc_waitforevent), |
| ("GetLastEventInformation", c_void_p), |
| ("GetCurrentTimeDate", c_void_p), |
| ("GetCurrentSystemUpTime", c_void_p), |
| ("GetDumpFormatFlags", c_void_p), |
| ("GetNumberTextReplacements", c_void_p), |
| ("GetTextReplacement", c_void_p), |
| ("SetTextReplacement", c_void_p), |
| ("RemoveTextReplacements", c_void_p), |
| ("OutputTextReplacements", c_void_p), |
| ("GetAssemblyOptions", c_void_p), |
| ("AddAssemblyOptions", c_void_p), |
| ("RemoveAssemblyOptions", c_void_p), |
| ("SetAssemblyOptions", c_void_p), |
| ("GetExpressionSyntax", c_void_p), |
| ("SetExpressionSyntax", idc_setexpressionsyntax), |
| ("SetExpressionSyntaxByName", c_void_p), |
| ("GetNumberExpressionSyntaxes", c_void_p), |
| ("GetExpressionSyntaxNames", c_void_p), |
| ("GetNumberEvents", c_void_p), |
| ("GetEventIndexDescription", c_void_p), |
| ("GetCurrentEventIndex", c_void_p), |
| ("SetNextEventIndex", c_void_p), |
| ("GetLogFileWide", c_void_p), |
| ("OpenLogFileWide", c_void_p), |
| ("InputWide", c_void_p), |
| ("ReturnInputWide", c_void_p), |
| ("OutputWide", c_void_p), |
| ("OutputVaListWide", c_void_p), |
| ("ControlledOutputWide", c_void_p), |
| ("ControlledOutputVaListWide", c_void_p), |
| ("OutputPromptWide", c_void_p), |
| ("OutputPromptVaListWide", c_void_p), |
| ("GetPromptTextWide", c_void_p), |
| ("AssembleWide", c_void_p), |
| ("DisassembleWide", c_void_p), |
| ("GetProcessrTypeNamesWide", c_void_p), |
| ("GetTextMacroWide", c_void_p), |
| ("SetTextMacroWide", c_void_p), |
| ("EvaluateWide", c_void_p), |
| ("ExecuteWide", c_void_p), |
| ("ExecuteCommandFileWide", c_void_p), |
| ("GetBreakpointByIndex2", c_void_p), |
| ("GetBreakpointById2", c_void_p), |
| ("AddBreakpoint2", idc_addbreakpoint2), |
| ("RemoveBreakpoint2", c_void_p), |
| ("AddExtensionWide", c_void_p), |
| ("GetExtensionByPathWide", c_void_p), |
| ("CallExtensionWide", c_void_p), |
| ("GetExtensionFunctionWide", c_void_p), |
| ("GetEventFilterTextWide", c_void_p), |
| ("GetEventfilterCommandWide", c_void_p), |
| ("SetEventFilterCommandWide", c_void_p), |
| ("GetSpecificFilterArgumentWide", c_void_p), |
| ("SetSpecificFilterArgumentWide", c_void_p), |
| ("GetExceptionFilterSecondCommandWide", c_void_p), |
| ("SetExceptionFilterSecondCommandWider", c_void_p), |
| ("GetLastEventInformationWide", c_void_p), |
| ("GetTextReplacementWide", c_void_p), |
| ("SetTextReplacementWide", c_void_p), |
| ("SetExpressionSyntaxByNameWide", c_void_p), |
| ("GetExpressionSyntaxNamesWide", c_void_p), |
| ("GetEventIndexDescriptionWide", c_void_p), |
| ("GetLogFile2", c_void_p), |
| ("OpenLogFile2", c_void_p), |
| ("GetLogFile2Wide", c_void_p), |
| ("OpenLogFile2Wide", c_void_p), |
| ("GetSystemVersionValues", c_void_p), |
| ("GetSystemVersionString", c_void_p), |
| ("GetSystemVersionStringWide", c_void_p), |
| ("GetContextStackTrace", c_void_p), |
| ("OutputContextStackTrace", c_void_p), |
| ("GetStoredEventInformation", c_void_p), |
| ("GetManagedStatus", c_void_p), |
| ("GetManagedStatusWide", c_void_p), |
| ("ResetManagedStatus", c_void_p), |
| ("GetStackTraceEx", idc_getstacktraceex), |
| ("OutputStackTraceEx", c_void_p), |
| ("GetContextStackTraceEx", c_void_p), |
| ("OutputContextStackTraceEx", c_void_p), |
| ("GetBreakpointByGuid", c_void_p), |
| ("GetExecutionStatusEx", c_void_p), |
| ("GetSynchronizationStatus", c_void_p), |
| ("GetDebuggeeType2", c_void_p) |
| ] |
| |
| IDebugControl7._fields_ = [("lpVtbl", POINTER(IDebugControl7Vtbl))] |
| |
| class DebugStatus(IntEnum): |
| DEBUG_STATUS_NO_CHANGE = 0 |
| DEBUG_STATUS_GO = 1 |
| DEBUG_STATUS_GO_HANDLED = 2 |
| DEBUG_STATUS_GO_NOT_HANDLED = 3 |
| DEBUG_STATUS_STEP_OVER = 4 |
| DEBUG_STATUS_STEP_INTO = 5 |
| DEBUG_STATUS_BREAK = 6 |
| DEBUG_STATUS_NO_DEBUGGEE = 7 |
| DEBUG_STATUS_STEP_BRANCH = 8 |
| DEBUG_STATUS_IGNORE_EVENT = 9 |
| DEBUG_STATUS_RESTART_REQUESTED = 10 |
| DEBUG_STATUS_REVERSE_GO = 11 |
| DEBUG_STATUS_REVERSE_STEP_BRANCH = 12 |
| DEBUG_STATUS_REVERSE_STEP_OVER = 13 |
| DEBUG_STATUS_REVERSE_STEP_INTO = 14 |
| DEBUG_STATUS_OUT_OF_SYNC = 15 |
| DEBUG_STATUS_WAIT_INPUT = 16 |
| DEBUG_STATUS_TIMEOUT = 17 |
| |
| class DebugSyntax(IntEnum): |
| DEBUG_EXPR_MASM = 0 |
| DEBUG_EXPR_CPLUSPLUS = 1 |
| |
| class Control(object): |
| def __init__(self, control): |
| self.ptr = control |
| self.control = control.contents |
| self.vt = self.control.lpVtbl.contents |
| # Keep a handy ulong for passing into C methods. |
| self.ulong = c_ulong() |
| |
| def GetExecutionStatus(self, doprint=False): |
| ret = self.vt.GetExecutionStatus(self.control, byref(self.ulong)) |
| aborter(ret, "GetExecutionStatus") |
| status = DebugStatus(self.ulong.value) |
| if doprint: |
| print("Execution status: {}".format(status)) |
| return status |
| |
| def SetExecutionStatus(self, status): |
| assert isinstance(status, DebugStatus) |
| res = self.vt.SetExecutionStatus(self.control, status.value) |
| aborter(res, "SetExecutionStatus") |
| |
| def WaitForEvent(self, timeout=100): |
| # No flags are taken by WaitForEvent, hence 0 |
| ret = self.vt.WaitForEvent(self.control, 0, timeout) |
| aborter(ret, "WaitforEvent", ignore=[S_FALSE]) |
| return ret |
| |
| def GetNumberEventFilters(self): |
| specific_events = c_ulong() |
| specific_exceptions = c_ulong() |
| arbitrary_exceptions = c_ulong() |
| res = self.vt.GetNumberEventFilters(self.control, byref(specific_events), |
| byref(specific_exceptions), |
| byref(arbitrary_exceptions)) |
| aborter(res, "GetNumberEventFilters") |
| return (specific_events.value, specific_exceptions.value, |
| arbitrary_exceptions.value) |
| |
| def SetExceptionFilterSecondCommand(self, index, command): |
| buf = create_string_buffer(command.encode('ascii')) |
| res = self.vt.SetExceptionFilterSecondCommand(self.control, index, buf) |
| aborter(res, "SetExceptionFilterSecondCommand") |
| return |
| |
| def AddBreakpoint2(self, offset=None, enabled=None): |
| breakpoint = POINTER(DebugBreakpoint2)() |
| res = self.vt.AddBreakpoint2(self.control, BreakpointTypes.DEBUG_BREAKPOINT_CODE, DEBUG_ANY_ID, byref(breakpoint)) |
| aborter(res, "Add breakpoint 2") |
| bp = Breakpoint(breakpoint) |
| |
| if offset is not None: |
| bp.SetOffset(offset) |
| if enabled is not None and enabled: |
| bp.SetFlags(BreakpointFlags.DEBUG_BREAKPOINT_ENABLED) |
| |
| return bp |
| |
| def RemoveBreakpoint(self, bp): |
| res = self.vt.RemoveBreakpoint2(self.control, bp.breakpoint) |
| aborter(res, "RemoveBreakpoint2") |
| bp.die() |
| |
| def GetStackTraceEx(self): |
| # XXX -- I can't find a way to query for how many stack frames there _are_ |
| # in advance. Guess 128 for now. |
| num_frames_buffer = 128 |
| |
| frames = (DEBUG_STACK_FRAME_EX * num_frames_buffer)() |
| numframes = c_ulong() |
| |
| # First three args are frame/stack/IP offsets -- leave them as zero to |
| # default to the current instruction. |
| res = self.vt.GetStackTraceEx(self.control, 0, 0, 0, frames, num_frames_buffer, byref(numframes)) |
| aborter(res, "GetStackTraceEx") |
| return frames, numframes.value |
| |
| def Execute(self, command): |
| # First zero is DEBUG_OUTCTL_*, which we leave as a default, second |
| # zero is DEBUG_EXECUTE_* flags, of which we set none. |
| res = self.vt.Execute(self.control, 0, command.encode('ascii'), 0) |
| aborter(res, "Client execute") |
| |
| def SetExpressionSyntax(self, cpp=True): |
| if cpp: |
| syntax = DebugSyntax.DEBUG_EXPR_CPLUSPLUS |
| else: |
| syntax = DebugSyntax.DEBUG_EXPR_MASM |
| |
| res = self.vt.SetExpressionSyntax(self.control, syntax) |
| aborter(res, "SetExpressionSyntax") |
| |
| def Evaluate(self, expr): |
| ptr = DEBUG_VALUE() |
| res = self.vt.Evaluate(self.control, expr.encode("ascii"), DebugValueType.DEBUG_VALUE_INVALID, byref(ptr), None) |
| aborter(res, "Evaluate", ignore=[E_INTERNALEXCEPTION, E_FAIL]) |
| if res != 0: |
| return None |
| |
| val_type = DebugValueType(ptr.Type) |
| |
| # Here's a map from debug value types to fields. Unclear what happens |
| # with unsigned values, as DbgEng doesn't present any unsigned fields. |
| |
| extract_map = { |
| DebugValueType.DEBUG_VALUE_INT8 : ("I8", "char"), |
| DebugValueType.DEBUG_VALUE_INT16 : ("I16", "short"), |
| DebugValueType.DEBUG_VALUE_INT32 : ("I32", "int"), |
| DebugValueType.DEBUG_VALUE_INT64 : ("I64", "long"), |
| DebugValueType.DEBUG_VALUE_FLOAT32 : ("F32", "float"), |
| DebugValueType.DEBUG_VALUE_FLOAT64 : ("F64", "double") |
| } # And everything else is invalid. |
| |
| if val_type not in extract_map: |
| raise Exception("Unexpected debug value type {} when evalutaing".format(val_type)) |
| |
| # Also produce a type name... |
| |
| return getattr(ptr.U, extract_map[val_type][0]), extract_map[val_type][1] |
| |
| def SetEngineOptions(self, opt): |
| res = self.vt.SetEngineOptions(self.control, opt) |
| aborter(res, "SetEngineOptions") |
| return |