[debuginfo-tests] 26f6aa9 - [debuginfo-tests] Fix Dexter process creation failure on Windows
Jeremy Morse via llvm-commits
llvm-commits at lists.llvm.org
Thu Feb 13 04:47:07 PST 2020
Author: Jeremy Morse
Date: 2020-02-13T12:46:32Z
New Revision: 26f6aa9e3ba47c5ccb9d837310b59a5087549b7d
URL: https://github.com/llvm/llvm-project/commit/26f6aa9e3ba47c5ccb9d837310b59a5087549b7d
DIFF: https://github.com/llvm/llvm-project/commit/26f6aa9e3ba47c5ccb9d837310b59a5087549b7d.diff
LOG: [debuginfo-tests] Fix Dexter process creation failure on Windows
When writing the Windows dbgeng driver for Dexter, I couldn't work out why it
would either launch a process and leave it free running, or if I started the
process suspended, never do anything with it. The result was a hack to create
and attach processes manually. This has been flaking out on Reids Windows
buildbot, and clearly wasn't a good solution.
Digging into this, it turns out that the "normal" cdb / windbg behaviour of
breaking whenever we attach to a process is not the default: it has to be
explicitly requested from the debug engine. This patch does so (by setting
DEBUG_ENGOPT_INITIAL_BREAK in the engine options), after which we can simply
call "CreateProcessAndAttach2" and everything automagically works.
No test for this behaviour: everything was just broken before.
Differential Revision: https://reviews.llvm.org/D74409
Added:
Modified:
debuginfo-tests/dexter/dex/debugger/dbgeng/README.md
debuginfo-tests/dexter/dex/debugger/dbgeng/client.py
debuginfo-tests/dexter/dex/debugger/dbgeng/control.py
debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py
debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py
Removed:
################################################################################
diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/README.md b/debuginfo-tests/dexter/dex/debugger/dbgeng/README.md
index f9b864206d30..208e6289fdab 100644
--- a/debuginfo-tests/dexter/dex/debugger/dbgeng/README.md
+++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/README.md
@@ -40,10 +40,6 @@ information.
## 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
index a65e4ded2f33..22d4652fcac5 100644
--- a/debuginfo-tests/dexter/dex/debugger/dbgeng/client.py
+++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/client.py
@@ -26,6 +26,14 @@ class DebugAttach(IntFlag):
# 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 @@ class IDebugClient7Vtbl(Structure):
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 @@ class IDebugClient7Vtbl(Structure):
("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 @@ class IDebugClient7Vtbl(Structure):
("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 @@ def DetachProcesses(self):
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
index 38585c83f708..5f23e2d22d8f 100644
--- a/debuginfo-tests/dexter/dex/debugger/dbgeng/control.py
+++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/control.py
@@ -79,6 +79,7 @@ class IDebugControl7Vtbl(Structure):
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 @@ class IDebugControl7Vtbl(Structure):
("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 @@ def Evaluate(self, expr):
# 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
index 66d01f03e8f5..db7ea9161400 100644
--- a/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py
+++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py
@@ -32,13 +32,13 @@ def __init__(self, context, *args):
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
index 30a62f6dd42b..26360f680d23 100644
--- a/debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py
+++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py
@@ -69,77 +69,39 @@ def break_on_all_but_main(Control, Symbols, main_offset):
# 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 @@ def setup_everything(binfile):
# 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 @@ def setup_everything(binfile):
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 @@ def main_loop(client):
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()
More information about the llvm-commits
mailing list