# 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 . import client from . import control from . import symbols from .probe_process import probe_state from .utils import * class STARTUPINFOA(Structure): _fields_ = [ ('cb', c_ulong), ('lpReserved', c_char_p), ('lpDesktop', c_char_p), ('lpTitle', c_char_p), ('dwX', c_ulong), ('dwY', c_ulong), ('dwXSize', c_ulong), ('dwYSize', c_ulong), ('dwXCountChars', c_ulong), ('dwYCountChars', c_ulong), ('dwFillAttribute', c_ulong), ('wShowWindow', c_ushort), ('cbReserved2', c_ushort), ('lpReserved2', c_char_p), ('hStdInput', c_void_p), ('hStdOutput', c_void_p), ('hStdError', c_void_p) ] class PROCESS_INFORMATION(Structure): _fields_ = [ ('hProcess', c_void_p), ('hThread', c_void_p), ('dwProcessId', c_ulong), ('dwThreadId', c_ulong) ] def fetch_local_function_syms(Symbols, prefix): syms = Symbols.get_all_functions() def is_sym_in_src_dir(sym): name, data = sym symdata = Symbols.GetLineByOffset(data.Offset) if symdata is not None: srcfile, line = symdata if prefix in srcfile: return True return False syms = [x for x in syms if is_sym_in_src_dir(x)] return syms def break_on_all_but_main(Control, Symbols, main_offset): mainfile, _ = Symbols.GetLineByOffset(main_offset) prefix = '\\'.join(mainfile.split('\\')[:-1]) for name, rec in fetch_local_function_syms(Symbols, prefix): if name == "main": continue bp = Control.AddBreakpoint2(offset=rec.Offset, enabled=True) # All breakpoints are currently discarded: we just sys.exit for cleanup return def setup_everything(binfile): from . import client from . import symbols Client = client.Client() Client.Control.SetEngineOptions(0x20) # DEBUG_ENGOPT_INITIAL_BREAK Client.CreateProcessAndAttach2(binfile) # Load lines as well as general symbols sym_opts = Client.Symbols.GetSymbolOptions() sym_opts |= symbols.SymbolOptionFlags.SYMOPT_LOAD_LINES Client.Symbols.SetSymbolOptions(sym_opts) # Need to enter the debugger engine to let it attach properly. res = Client.Control.WaitForEvent(timeout=1000) if res == S_FALSE: # The debugee apparently didn't do anything at all. Rather than risk # hanging, bail out at this point. client.TerminateProcesses() raise Exception("Debuggee did not start in a timely manner") # Enable line stepping. Client.Control.Execute("l+t") # Enable C++ expression interpretation. Client.Control.SetExpressionSyntax(cpp=True) # We've requested to break into the process at the earliest opportunity, # and WaitForEvent'ing means we should have reached that break state. # Now set a breakpoint on the main symbol, and "go" until we reach it. module_name = Client.Symbols.get_exefile_module_name() offset = Client.Symbols.GetOffsetByName("{}!main".format(module_name)) breakpoint = Client.Control.AddBreakpoint2(offset=offset, enabled=True) Client.Control.SetExecutionStatus(control.DebugStatus.DEBUG_STATUS_GO) # Problem: there is no guarantee that the client will ever reach main, # something else exciting could happen in that time, the host system may # be very loaded, and similar. Wait for some period, say, five seconds, and # abort afterwards: this is a trade-off between spurious timeouts and # completely hanging in the case of a environmental/programming error. res = Client.Control.WaitForEvent(timeout=5000) if res == S_FALSE: client.TerminateProcesses() raise Exception("Debuggee did not reach main function in a timely manner") break_on_all_but_main(Client.Control, Client.Symbols, offset) # Set the default action on all exceptions to be "quit and detach". If we # don't, dbgeng will merrily spin at the exception site forever. filts = Client.Control.GetNumberEventFilters() for x in range(filts[0], filts[0] + filts[1]): Client.Control.SetExceptionFilterSecondCommand(x, "qd") return Client def step_once(client): client.Control.Execute("p") try: client.Control.WaitForEvent() except Exception as e: if client.Control.GetExecutionStatus() == control.DebugStatus.DEBUG_STATUS_NO_DEBUGGEE: return None # Debuggee has gone away, likely due to an exception. raise e # Could assert here that we're in the "break" state client.Control.GetExecutionStatus() return probe_state(client) def main_loop(client): res = True while res is not None: res = step_once(client) def cleanup(client): client.TerminateProcesses()