[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