1# DExTer : Debugging Experience Tester 2# ~~~~~~ ~ ~~ ~ ~~ 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-exception 7 8from ctypes import * 9 10from . import client 11from . import control 12from . import symbols 13from .probe_process import probe_state 14from .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 = sym 53 symdata = Symbols.GetLineByOffset(data.Offset) 54 if symdata is not None: 55 srcfile, line = symdata 56 if prefix in srcfile: 57 return True 58 return False 59 60 syms = [x for x in syms if is_sym_in_src_dir(x)] 61 return syms 62 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 continue 71 bp = Control.AddBreakpoint2(offset=rec.Offset, enabled=True) 72 73 # All breakpoints are currently discarded: we just sys.exit for cleanup 74 return 75 76 77def setup_everything(binfile): 78 from . import client 79 from . import symbols 80 81 Client = client.Client() 82 83 Client.Control.SetEngineOptions(0x20) # DEBUG_ENGOPT_INITIAL_BREAK 84 85 Client.CreateProcessAndAttach2(binfile) 86 87 # Load lines as well as general symbols 88 sym_opts = Client.Symbols.GetSymbolOptions() 89 sym_opts |= symbols.SymbolOptionFlags.SYMOPT_LOAD_LINES 90 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 risk 96 # 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 may 115 # be very loaded, and similar. Wait for some period, say, five seconds, and 116 # abort afterwards: this is a trade-off between spurious timeouts and 117 # 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 we 126 # 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 Client 132 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_DEBUGGEE 142 ): 143 return None # Debuggee has gone away, likely due to an exception. 144 raise e 145 # Could assert here that we're in the "break" state 146 client.Control.GetExecutionStatus() 147 return probe_state(client) 148 149 150def main_loop(client): 151 res = True 152 while res is not None: 153 res = step_once(client) 154 155 156def cleanup(client): 157 client.TerminateProcesses() 158