diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/README.md b/debuginfo-tests/dexter/dex/debugger/dbgeng/README.md --- a/debuginfo-tests/dexter/dex/debugger/dbgeng/README.md +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/README.md @@ -40,10 +40,6 @@ ## Sharp edges -For reasons still unclear, using CreateProcessAndAttach never appears to -allow the debuggee to resume, hence this implementation creates the -debuggee process manually, attaches, and resumes. - On process startup, we set a breakpoint on main and then continue running to it. This has the potential to never complete -- although of course, there's no guarantee that the debuggee will ever do anything anyway. diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/client.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/client.py --- a/debuginfo-tests/dexter/dex/debugger/dbgeng/client.py +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/client.py @@ -26,6 +26,14 @@ # UUID for DebugClient7 interface. DebugClient7IID = IID(0x13586be3, 0x542e, 0x481e, IID_Data4_Type(0xb1, 0xf2, 0x84, 0x97, 0xba, 0x74, 0xf9, 0xa9 )) +class DEBUG_CREATE_PROCESS_OPTIONS(Structure): + _fields_ = [ + ("CreateFlags", c_ulong), + ("EngCreateFlags", c_ulong), + ("VerifierFlags", c_ulong), + ("Reserved", c_ulong) + ] + class IDebugClient7(Structure): pass @@ -34,6 +42,8 @@ idc_queryinterface = wrp(POINTER(IID), POINTER(c_void_p)) idc_attachprocess = wrp(c_longlong, c_long, c_long) idc_detachprocesses = wrp() + idc_terminateprocesses = wrp() + idc_createprocessandattach2 = wrp(c_ulonglong, c_char_p, c_void_p, c_ulong, c_char_p, c_char_p, c_ulong, c_ulong) _fields_ = [ ("QueryInterface", idc_queryinterface), ("AddRef", c_void_p), @@ -59,7 +69,7 @@ ("ConnectSession", c_void_p), ("StartServer", c_void_p), ("OutputServers", c_void_p), - ("TerminateProcesses", c_void_p), + ("TerminateProcesses", idc_terminateprocesses), ("DetachProcesses", idc_detachprocesses), ("EndSession", c_void_p), ("GetExitCode", c_void_p), @@ -118,7 +128,7 @@ ("SetEventCallbacksWide", c_void_p), ("CreateProcess2", c_void_p), ("CreateProcess2Wide", c_void_p), - ("CreateProcessAndAttach2", c_void_p), + ("CreateProcessAndAttach2", idc_createprocessandattach2), ("CreateProcessAndAttach2Wide", c_void_p), ("PushOutputLinePrefix", c_void_p), ("PushOutputLinePrefixWide", c_void_p), @@ -183,3 +193,19 @@ res = self.vt.DetachProcesses(self.client) aborter(res, "DetachProcesses") return + + def TerminateProcesses(self): + res = self.vt.TerminateProcesses(self.client) + aborter(res, "TerminateProcesses") + return + + def CreateProcessAndAttach2(self, cmdline): + options = DEBUG_CREATE_PROCESS_OPTIONS() + options.CreateFlags = 0x2 # DEBUG_ONLY_THIS_PROCESS + options.EngCreateFlags = 0 + options.VerifierFlags = 0 + options.Reserved = 0 + attach_flags = 0 + res = self.vt.CreateProcessAndAttach2(self.client, 0, cmdline.encode("ascii"), byref(options), sizeof(options), None, None, 0, attach_flags) + aborter(res, "CreateProcessAndAttach2") + return diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/control.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/control.py --- a/debuginfo-tests/dexter/dex/debugger/dbgeng/control.py +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/control.py @@ -79,6 +79,7 @@ idc_getexecutionstatus = wrp(c_ulong_p) idc_getstacktraceex = wrp(c_ulonglong, c_ulonglong, c_ulonglong, PDEBUG_STACK_FRAME_EX, c_ulong, c_ulong_p) idc_evaluate = wrp(c_char_p, c_ulong, PDEBUG_VALUE, c_ulong_p) + idc_setengineoptions = wrp(c_ulong) _fields_ = [ ("QueryInterface", c_void_p), ("AddRef", c_void_p), @@ -136,7 +137,7 @@ ("GetEngineOptions", c_void_p), ("AddEngineOptions", c_void_p), ("RemoveEngineOptions", c_void_p), - ("SetEngineOptions", c_void_p), + ("SetEngineOptions", idc_setengineoptions), ("GetSystemErrorControl", c_void_p), ("SetSystemErrorControl", c_void_p), ("GetTextMacro", c_void_p), @@ -403,3 +404,8 @@ # Also produce a type name... return getattr(ptr.U, extract_map[val_type][0]), extract_map[val_type][1] + + def SetEngineOptions(self, opt): + res = self.vt.SetEngineOptions(self.control, opt) + aborter(res, "SetEngineOptions") + return diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py --- a/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py @@ -32,13 +32,13 @@ def _custom_init(self): try: res = setup.setup_everything(self.context.options.executable) - self.client, self.hProcess = res + self.client = res self.running = True except Exception as e: raise Exception('Failed to start debuggee: {}'.format(e)) def _custom_exit(self): - setup.cleanup(self.client, self.hProcess) + setup.cleanup(self.client) def _load_interface(self): arch = platform.architecture()[0] diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py --- a/debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py @@ -69,77 +69,39 @@ # 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) + Client.Control.SetEngineOptions(0x20) # DEBUG_ENGOPT_INITIAL_BREAK + + Client.CreateProcessAndAttach2(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. + res = Client.Control.WaitForEvent(timeout=1000) + if res == S_FALSE: + # The debugee apparently didn't do anything at all. Rather than risk + # hanging, bail out at this point. + client.TerminateProcesses() + raise Exception("Debuggee did not start in a timely manner") - # 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) + # Enable line stepping. Client.Control.Execute("l+t") + # Enable C++ expression interpretation. Client.Control.SetExpressionSyntax(cpp=True) + # We've requested to break into the process at the earliest opportunity, + # and WaitForEvent'ing means we should have reached that break state. + # Now set a breakpoint on the main symbol, and "go" until we reach it. 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, @@ -149,7 +111,7 @@ # completely hanging in the case of a environmental/programming error. res = Client.Control.WaitForEvent(timeout=5000) if res == S_FALSE: - Kernel32.TerminateProcess(hProcess, 1) + client.TerminateProcesses() raise Exception("Debuggee did not reach main function in a timely manner") break_on_all_but_main(Client.Control, Client.Symbols, offset) @@ -160,7 +122,7 @@ for x in range(filts[0], filts[0] + filts[1]): Client.Control.SetExceptionFilterSecondCommand(x, "qd") - return Client, hProcess + return Client def step_once(client): client.Control.Execute("p") @@ -179,7 +141,5 @@ while res is not None: res = step_once(client) -def cleanup(client, hProcess): - res = client.DetachProcesses() - Kernel32 = WinDLL("Kernel32") - Kernel32.TerminateProcess(hProcess, 1) +def cleanup(client): + client.TerminateProcesses()