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 process_creator(binfile): 73 Kernel32 = WinDLL("Kernel32") 74 75 # Another flavour of process creation 76 startupinfoa = STARTUPINFOA() 77 startupinfoa.cb = sizeof(STARTUPINFOA) 78 startupinfoa.lpReserved = None 79 startupinfoa.lpDesktop = None 80 startupinfoa.lpTitle = None 81 startupinfoa.dwX = 0 82 startupinfoa.dwY = 0 83 startupinfoa.dwXSize = 0 84 startupinfoa.dwYSize = 0 85 startupinfoa.dwXCountChars = 0 86 startupinfoa.dwYCountChars = 0 87 startupinfoa.dwFillAttribute = 0 88 startupinfoa.dwFlags = 0 89 startupinfoa.wShowWindow = 0 90 startupinfoa.cbReserved2 = 0 91 startupinfoa.lpReserved2 = None 92 startupinfoa.hStdInput = None 93 startupinfoa.hStdOutput = None 94 startupinfoa.hStdError = None 95 processinformation = PROCESS_INFORMATION() 96 97 # 0x4 below specifies CREATE_SUSPENDED. 98 ret = Kernel32.CreateProcessA(binfile.encode("ascii"), None, None, None, False, 0x4, None, None, byref(startupinfoa), byref(processinformation)) 99 if ret == 0: 100 raise Exception('CreateProcess running {}'.format(binfile)) 101 102 return processinformation.dwProcessId, processinformation.dwThreadId, processinformation.hProcess, processinformation.hThread 103 104def thread_resumer(hProcess, hThread): 105 Kernel32 = WinDLL("Kernel32") 106 107 # For reasons unclear to me, other suspend-references seem to be opened on 108 # the opened thread. Clear them all. 109 while True: 110 ret = Kernel32.ResumeThread(hThread) 111 if ret <= 0: 112 break 113 if ret < 0: 114 Kernel32.TerminateProcess(hProcess, 1) 115 raise Exception("Couldn't resume process after startup") 116 117 return 118 119def setup_everything(binfile): 120 from . import client 121 from . import symbols 122 Client = client.Client() 123 124 created_pid, created_tid, hProcess, hThread = process_creator(binfile) 125 126 # Load lines as well as general symbols 127 sym_opts = Client.Symbols.GetSymbolOptions() 128 sym_opts |= symbols.SymbolOptionFlags.SYMOPT_LOAD_LINES 129 Client.Symbols.SetSymbolOptions(sym_opts) 130 131 Client.AttachProcess(created_pid) 132 133 # Need to enter the debugger engine to let it attach properly 134 Client.Control.WaitForEvent(timeout=1) 135 Client.SysObjects.set_current_thread(created_pid, created_tid) 136 Client.Control.Execute("l+t") 137 Client.Control.SetExpressionSyntax(cpp=True) 138 139 module_name = Client.Symbols.get_exefile_module_name() 140 offset = Client.Symbols.GetOffsetByName("{}!main".format(module_name)) 141 breakpoint = Client.Control.AddBreakpoint2(offset=offset, enabled=True) 142 thread_resumer(hProcess, hThread) 143 Client.Control.SetExecutionStatus(control.DebugStatus.DEBUG_STATUS_GO) 144 145 # Problem: there is no guarantee that the client will ever reach main, 146 # something else exciting could happen in that time, the host system may 147 # be very loaded, and similar. Wait for some period, say, five seconds, and 148 # abort afterwards: this is a trade-off between spurious timeouts and 149 # completely hanging in the case of a environmental/programming error. 150 res = Client.Control.WaitForEvent(timeout=5000) 151 if res == S_FALSE: 152 Kernel32.TerminateProcess(hProcess, 1) 153 raise Exception("Debuggee did not reach main function in a timely manner") 154 155 break_on_all_but_main(Client.Control, Client.Symbols, offset) 156 157 # Set the default action on all exceptions to be "quit and detach". If we 158 # don't, dbgeng will merrily spin at the exception site forever. 159 filts = Client.Control.GetNumberEventFilters() 160 for x in range(filts[0], filts[0] + filts[1]): 161 Client.Control.SetExceptionFilterSecondCommand(x, "qd") 162 163 return Client, hProcess 164 165def step_once(client): 166 client.Control.Execute("p") 167 try: 168 client.Control.WaitForEvent() 169 except Exception as e: 170 if client.Control.GetExecutionStatus() == control.DebugStatus.DEBUG_STATUS_NO_DEBUGGEE: 171 return None # Debuggee has gone away, likely due to an exception. 172 raise e 173 # Could assert here that we're in the "break" state 174 client.Control.GetExecutionStatus() 175 return probe_state(client) 176 177def main_loop(client): 178 res = True 179 while res is not None: 180 res = step_once(client) 181 182def cleanup(client, hProcess): 183 res = client.DetachProcesses() 184 Kernel32 = WinDLL("Kernel32") 185 Kernel32.TerminateProcess(hProcess, 1) 186