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 16class STARTUPINFOA(Structure): 17 _fields_ = [ 18 ('cb', c_ulong), 19 ('lpReserved', c_char_p), 20 ('lpDesktop', c_char_p), 21 ('lpTitle', c_char_p), 22 ('dwX', c_ulong), 23 ('dwY', c_ulong), 24 ('dwXSize', c_ulong), 25 ('dwYSize', c_ulong), 26 ('dwXCountChars', c_ulong), 27 ('dwYCountChars', c_ulong), 28 ('dwFillAttribute', c_ulong), 29 ('wShowWindow', c_ushort), 30 ('cbReserved2', c_ushort), 31 ('lpReserved2', c_char_p), 32 ('hStdInput', c_void_p), 33 ('hStdOutput', c_void_p), 34 ('hStdError', c_void_p) 35 ] 36 37class PROCESS_INFORMATION(Structure): 38 _fields_ = [ 39 ('hProcess', c_void_p), 40 ('hThread', c_void_p), 41 ('dwProcessId', c_ulong), 42 ('dwThreadId', c_ulong) 43 ] 44 45def fetch_local_function_syms(Symbols, prefix): 46 syms = Symbols.get_all_functions() 47 48 def is_sym_in_src_dir(sym): 49 name, data = sym 50 symdata = Symbols.GetLineByOffset(data.Offset) 51 if symdata is not None: 52 srcfile, line = symdata 53 if prefix in srcfile: 54 return True 55 return False 56 57 syms = [x for x in syms if is_sym_in_src_dir(x)] 58 return syms 59 60def break_on_all_but_main(Control, Symbols, main_offset): 61 mainfile, _ = Symbols.GetLineByOffset(main_offset) 62 prefix = '\\'.join(mainfile.split('\\')[:-1]) 63 64 for name, rec in fetch_local_function_syms(Symbols, prefix): 65 if name == "main": 66 continue 67 bp = Control.AddBreakpoint2(offset=rec.Offset, enabled=True) 68 69 # All breakpoints are currently discarded: we just sys.exit for cleanup 70 return 71 72def setup_everything(binfile): 73 from . import client 74 from . import symbols 75 Client = client.Client() 76 77 Client.Control.SetEngineOptions(0x20) # DEBUG_ENGOPT_INITIAL_BREAK 78 79 Client.CreateProcessAndAttach2(binfile) 80 81 # Load lines as well as general symbols 82 sym_opts = Client.Symbols.GetSymbolOptions() 83 sym_opts |= symbols.SymbolOptionFlags.SYMOPT_LOAD_LINES 84 Client.Symbols.SetSymbolOptions(sym_opts) 85 86 # Need to enter the debugger engine to let it attach properly. 87 res = Client.Control.WaitForEvent(timeout=1000) 88 if res == S_FALSE: 89 # The debugee apparently didn't do anything at all. Rather than risk 90 # hanging, bail out at this point. 91 client.TerminateProcesses() 92 raise Exception("Debuggee did not start in a timely manner") 93 94 # Enable line stepping. 95 Client.Control.Execute("l+t") 96 # Enable C++ expression interpretation. 97 Client.Control.SetExpressionSyntax(cpp=True) 98 99 # We've requested to break into the process at the earliest opportunity, 100 # and WaitForEvent'ing means we should have reached that break state. 101 # Now set a breakpoint on the main symbol, and "go" until we reach it. 102 module_name = Client.Symbols.get_exefile_module_name() 103 offset = Client.Symbols.GetOffsetByName("{}!main".format(module_name)) 104 breakpoint = Client.Control.AddBreakpoint2(offset=offset, enabled=True) 105 Client.Control.SetExecutionStatus(control.DebugStatus.DEBUG_STATUS_GO) 106 107 # Problem: there is no guarantee that the client will ever reach main, 108 # something else exciting could happen in that time, the host system may 109 # be very loaded, and similar. Wait for some period, say, five seconds, and 110 # abort afterwards: this is a trade-off between spurious timeouts and 111 # completely hanging in the case of a environmental/programming error. 112 res = Client.Control.WaitForEvent(timeout=5000) 113 if res == S_FALSE: 114 client.TerminateProcesses() 115 raise Exception("Debuggee did not reach main function in a timely manner") 116 117 break_on_all_but_main(Client.Control, Client.Symbols, offset) 118 119 # Set the default action on all exceptions to be "quit and detach". If we 120 # don't, dbgeng will merrily spin at the exception site forever. 121 filts = Client.Control.GetNumberEventFilters() 122 for x in range(filts[0], filts[0] + filts[1]): 123 Client.Control.SetExceptionFilterSecondCommand(x, "qd") 124 125 return Client 126 127def step_once(client): 128 client.Control.Execute("p") 129 try: 130 client.Control.WaitForEvent() 131 except Exception as e: 132 if client.Control.GetExecutionStatus() == control.DebugStatus.DEBUG_STATUS_NO_DEBUGGEE: 133 return None # Debuggee has gone away, likely due to an exception. 134 raise e 135 # Could assert here that we're in the "break" state 136 client.Control.GetExecutionStatus() 137 return probe_state(client) 138 139def main_loop(client): 140 res = True 141 while res is not None: 142 res = step_once(client) 143 144def cleanup(client): 145 client.TerminateProcesses() 146