158 lines · python
1# DExTer : Debugging Experience Tester2# ~~~~~~ ~ ~~ ~ ~~3#4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.5# See https://llvm.org/LICENSE.txt for license information.6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception7 8from ctypes import *9 10from . import client11from . import control12from . import symbols13from .probe_process import probe_state14from .utils import *15 16 17class STARTUPINFOA(Structure):18 _fields_ = [19 ("cb", c_ulong),20 ("lpReserved", c_char_p),21 ("lpDesktop", c_char_p),22 ("lpTitle", c_char_p),23 ("dwX", c_ulong),24 ("dwY", c_ulong),25 ("dwXSize", c_ulong),26 ("dwYSize", c_ulong),27 ("dwXCountChars", c_ulong),28 ("dwYCountChars", c_ulong),29 ("dwFillAttribute", c_ulong),30 ("wShowWindow", c_ushort),31 ("cbReserved2", c_ushort),32 ("lpReserved2", c_char_p),33 ("hStdInput", c_void_p),34 ("hStdOutput", c_void_p),35 ("hStdError", c_void_p),36 ]37 38 39class PROCESS_INFORMATION(Structure):40 _fields_ = [41 ("hProcess", c_void_p),42 ("hThread", c_void_p),43 ("dwProcessId", c_ulong),44 ("dwThreadId", c_ulong),45 ]46 47 48def fetch_local_function_syms(Symbols, prefix):49 syms = Symbols.get_all_functions()50 51 def is_sym_in_src_dir(sym):52 name, data = sym53 symdata = Symbols.GetLineByOffset(data.Offset)54 if symdata is not None:55 srcfile, line = symdata56 if prefix in srcfile:57 return True58 return False59 60 syms = [x for x in syms if is_sym_in_src_dir(x)]61 return syms62 63 64def break_on_all_but_main(Control, Symbols, main_offset):65 mainfile, _ = Symbols.GetLineByOffset(main_offset)66 prefix = "\\".join(mainfile.split("\\")[:-1])67 68 for name, rec in fetch_local_function_syms(Symbols, prefix):69 if name == "main":70 continue71 bp = Control.AddBreakpoint2(offset=rec.Offset, enabled=True)72 73 # All breakpoints are currently discarded: we just sys.exit for cleanup74 return75 76 77def setup_everything(binfile):78 from . import client79 from . import symbols80 81 Client = client.Client()82 83 Client.Control.SetEngineOptions(0x20) # DEBUG_ENGOPT_INITIAL_BREAK84 85 Client.CreateProcessAndAttach2(binfile)86 87 # Load lines as well as general symbols88 sym_opts = Client.Symbols.GetSymbolOptions()89 sym_opts |= symbols.SymbolOptionFlags.SYMOPT_LOAD_LINES90 Client.Symbols.SetSymbolOptions(sym_opts)91 92 # Need to enter the debugger engine to let it attach properly.93 res = Client.Control.WaitForEvent(timeout=1000)94 if res == S_FALSE:95 # The debugee apparently didn't do anything at all. Rather than risk96 # hanging, bail out at this point.97 client.TerminateProcesses()98 raise Exception("Debuggee did not start in a timely manner")99 100 # Enable line stepping.101 Client.Control.Execute("l+t")102 # Enable C++ expression interpretation.103 Client.Control.SetExpressionSyntax(cpp=True)104 105 # We've requested to break into the process at the earliest opportunity,106 # and WaitForEvent'ing means we should have reached that break state.107 # Now set a breakpoint on the main symbol, and "go" until we reach it.108 module_name = Client.Symbols.get_exefile_module_name()109 offset = Client.Symbols.GetOffsetByName("{}!main".format(module_name))110 breakpoint = Client.Control.AddBreakpoint2(offset=offset, enabled=True)111 Client.Control.SetExecutionStatus(control.DebugStatus.DEBUG_STATUS_GO)112 113 # Problem: there is no guarantee that the client will ever reach main,114 # something else exciting could happen in that time, the host system may115 # be very loaded, and similar. Wait for some period, say, five seconds, and116 # abort afterwards: this is a trade-off between spurious timeouts and117 # completely hanging in the case of a environmental/programming error.118 res = Client.Control.WaitForEvent(timeout=5000)119 if res == S_FALSE:120 client.TerminateProcesses()121 raise Exception("Debuggee did not reach main function in a timely manner")122 123 break_on_all_but_main(Client.Control, Client.Symbols, offset)124 125 # Set the default action on all exceptions to be "quit and detach". If we126 # don't, dbgeng will merrily spin at the exception site forever.127 filts = Client.Control.GetNumberEventFilters()128 for x in range(filts[0], filts[0] + filts[1]):129 Client.Control.SetExceptionFilterSecondCommand(x, "qd")130 131 return Client132 133 134def step_once(client):135 client.Control.Execute("p")136 try:137 client.Control.WaitForEvent()138 except Exception as e:139 if (140 client.Control.GetExecutionStatus()141 == control.DebugStatus.DEBUG_STATUS_NO_DEBUGGEE142 ):143 return None # Debuggee has gone away, likely due to an exception.144 raise e145 # Could assert here that we're in the "break" state146 client.Control.GetExecutionStatus()147 return probe_state(client)148 149 150def main_loop(client):151 res = True152 while res is not None:153 res = step_once(client)154 155 156def cleanup(client):157 client.TerminateProcesses()158