diff options
Diffstat (limited to 'debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py')
-rw-r--r-- | debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py new file mode 100644 index 00000000000..30a62f6dd42 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py @@ -0,0 +1,185 @@ +# 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 process_creator(binfile): + Kernel32 = WinDLL("Kernel32") + + # Another flavour of process creation + startupinfoa = STARTUPINFOA() + startupinfoa.cb = sizeof(STARTUPINFOA) + startupinfoa.lpReserved = None + startupinfoa.lpDesktop = None + startupinfoa.lpTitle = None + startupinfoa.dwX = 0 + startupinfoa.dwY = 0 + startupinfoa.dwXSize = 0 + startupinfoa.dwYSize = 0 + startupinfoa.dwXCountChars = 0 + startupinfoa.dwYCountChars = 0 + startupinfoa.dwFillAttribute = 0 + startupinfoa.dwFlags = 0 + startupinfoa.wShowWindow = 0 + startupinfoa.cbReserved2 = 0 + startupinfoa.lpReserved2 = None + startupinfoa.hStdInput = None + startupinfoa.hStdOutput = None + startupinfoa.hStdError = None + processinformation = PROCESS_INFORMATION() + + # 0x4 below specifies CREATE_SUSPENDED. + ret = Kernel32.CreateProcessA(binfile.encode("ascii"), None, None, None, False, 0x4, None, None, byref(startupinfoa), byref(processinformation)) + if ret == 0: + raise Exception('CreateProcess running {}'.format(binfile)) + + return processinformation.dwProcessId, processinformation.dwThreadId, processinformation.hProcess, processinformation.hThread + +def thread_resumer(hProcess, hThread): + Kernel32 = WinDLL("Kernel32") + + # For reasons unclear to me, other suspend-references seem to be opened on + # the opened thread. Clear them all. + while True: + ret = Kernel32.ResumeThread(hThread) + if ret <= 0: + break + if ret < 0: + Kernel32.TerminateProcess(hProcess, 1) + raise Exception("Couldn't resume process after startup") + + return + +def setup_everything(binfile): + from . import client + from . import symbols + Client = client.Client() + + created_pid, created_tid, hProcess, hThread = process_creator(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) + + Client.AttachProcess(created_pid) + + # Need to enter the debugger engine to let it attach properly + Client.Control.WaitForEvent(timeout=1) + Client.SysObjects.set_current_thread(created_pid, created_tid) + Client.Control.Execute("l+t") + Client.Control.SetExpressionSyntax(cpp=True) + + module_name = Client.Symbols.get_exefile_module_name() + offset = Client.Symbols.GetOffsetByName("{}!main".format(module_name)) + breakpoint = Client.Control.AddBreakpoint2(offset=offset, enabled=True) + thread_resumer(hProcess, hThread) + 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: + Kernel32.TerminateProcess(hProcess, 1) + 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, hProcess + +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, hProcess): + res = client.DetachProcesses() + Kernel32 = WinDLL("Kernel32") + Kernel32.TerminateProcess(hProcess, 1) |