Skip to content

Commit 26f6aa9

Browse files
committed
[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
1 parent b1309a1 commit 26f6aa9

File tree

5 files changed

+56
-68
lines changed

5 files changed

+56
-68
lines changed

debuginfo-tests/dexter/dex/debugger/dbgeng/README.md

-4
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,6 @@ information.
4040

4141
## Sharp edges
4242

43-
For reasons still unclear, using CreateProcessAndAttach never appears to
44-
allow the debuggee to resume, hence this implementation creates the
45-
debuggee process manually, attaches, and resumes.
46-
4743
On process startup, we set a breakpoint on main and then continue running
4844
to it. This has the potential to never complete -- although of course,
4945
there's no guarantee that the debuggee will ever do anything anyway.

debuginfo-tests/dexter/dex/debugger/dbgeng/client.py

+28-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ class DebugAttach(IntFlag):
2626
# UUID for DebugClient7 interface.
2727
DebugClient7IID = IID(0x13586be3, 0x542e, 0x481e, IID_Data4_Type(0xb1, 0xf2, 0x84, 0x97, 0xba, 0x74, 0xf9, 0xa9 ))
2828

29+
class DEBUG_CREATE_PROCESS_OPTIONS(Structure):
30+
_fields_ = [
31+
("CreateFlags", c_ulong),
32+
("EngCreateFlags", c_ulong),
33+
("VerifierFlags", c_ulong),
34+
("Reserved", c_ulong)
35+
]
36+
2937
class IDebugClient7(Structure):
3038
pass
3139

@@ -34,6 +42,8 @@ class IDebugClient7Vtbl(Structure):
3442
idc_queryinterface = wrp(POINTER(IID), POINTER(c_void_p))
3543
idc_attachprocess = wrp(c_longlong, c_long, c_long)
3644
idc_detachprocesses = wrp()
45+
idc_terminateprocesses = wrp()
46+
idc_createprocessandattach2 = wrp(c_ulonglong, c_char_p, c_void_p, c_ulong, c_char_p, c_char_p, c_ulong, c_ulong)
3747
_fields_ = [
3848
("QueryInterface", idc_queryinterface),
3949
("AddRef", c_void_p),
@@ -59,7 +69,7 @@ class IDebugClient7Vtbl(Structure):
5969
("ConnectSession", c_void_p),
6070
("StartServer", c_void_p),
6171
("OutputServers", c_void_p),
62-
("TerminateProcesses", c_void_p),
72+
("TerminateProcesses", idc_terminateprocesses),
6373
("DetachProcesses", idc_detachprocesses),
6474
("EndSession", c_void_p),
6575
("GetExitCode", c_void_p),
@@ -118,7 +128,7 @@ class IDebugClient7Vtbl(Structure):
118128
("SetEventCallbacksWide", c_void_p),
119129
("CreateProcess2", c_void_p),
120130
("CreateProcess2Wide", c_void_p),
121-
("CreateProcessAndAttach2", c_void_p),
131+
("CreateProcessAndAttach2", idc_createprocessandattach2),
122132
("CreateProcessAndAttach2Wide", c_void_p),
123133
("PushOutputLinePrefix", c_void_p),
124134
("PushOutputLinePrefixWide", c_void_p),
@@ -183,3 +193,19 @@ def DetachProcesses(self):
183193
res = self.vt.DetachProcesses(self.client)
184194
aborter(res, "DetachProcesses")
185195
return
196+
197+
def TerminateProcesses(self):
198+
res = self.vt.TerminateProcesses(self.client)
199+
aborter(res, "TerminateProcesses")
200+
return
201+
202+
def CreateProcessAndAttach2(self, cmdline):
203+
options = DEBUG_CREATE_PROCESS_OPTIONS()
204+
options.CreateFlags = 0x2 # DEBUG_ONLY_THIS_PROCESS
205+
options.EngCreateFlags = 0
206+
options.VerifierFlags = 0
207+
options.Reserved = 0
208+
attach_flags = 0
209+
res = self.vt.CreateProcessAndAttach2(self.client, 0, cmdline.encode("ascii"), byref(options), sizeof(options), None, None, 0, attach_flags)
210+
aborter(res, "CreateProcessAndAttach2")
211+
return

debuginfo-tests/dexter/dex/debugger/dbgeng/control.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class IDebugControl7Vtbl(Structure):
7979
idc_getexecutionstatus = wrp(c_ulong_p)
8080
idc_getstacktraceex = wrp(c_ulonglong, c_ulonglong, c_ulonglong, PDEBUG_STACK_FRAME_EX, c_ulong, c_ulong_p)
8181
idc_evaluate = wrp(c_char_p, c_ulong, PDEBUG_VALUE, c_ulong_p)
82+
idc_setengineoptions = wrp(c_ulong)
8283
_fields_ = [
8384
("QueryInterface", c_void_p),
8485
("AddRef", c_void_p),
@@ -136,7 +137,7 @@ class IDebugControl7Vtbl(Structure):
136137
("GetEngineOptions", c_void_p),
137138
("AddEngineOptions", c_void_p),
138139
("RemoveEngineOptions", c_void_p),
139-
("SetEngineOptions", c_void_p),
140+
("SetEngineOptions", idc_setengineoptions),
140141
("GetSystemErrorControl", c_void_p),
141142
("SetSystemErrorControl", c_void_p),
142143
("GetTextMacro", c_void_p),
@@ -403,3 +404,8 @@ def Evaluate(self, expr):
403404
# Also produce a type name...
404405

405406
return getattr(ptr.U, extract_map[val_type][0]), extract_map[val_type][1]
407+
408+
def SetEngineOptions(self, opt):
409+
res = self.vt.SetEngineOptions(self.control, opt)
410+
aborter(res, "SetEngineOptions")
411+
return

debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ def __init__(self, context, *args):
3232
def _custom_init(self):
3333
try:
3434
res = setup.setup_everything(self.context.options.executable)
35-
self.client, self.hProcess = res
35+
self.client = res
3636
self.running = True
3737
except Exception as e:
3838
raise Exception('Failed to start debuggee: {}'.format(e))
3939

4040
def _custom_exit(self):
41-
setup.cleanup(self.client, self.hProcess)
41+
setup.cleanup(self.client)
4242

4343
def _load_interface(self):
4444
arch = platform.architecture()[0]

debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py

+19-59
Original file line numberDiff line numberDiff line change
@@ -69,77 +69,39 @@ def break_on_all_but_main(Control, Symbols, main_offset):
6969
# All breakpoints are currently discarded: we just sys.exit for cleanup
7070
return
7171

72-
def 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-
104-
def 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-
11972
def setup_everything(binfile):
12073
from . import client
12174
from . import symbols
12275
Client = client.Client()
12376

124-
created_pid, created_tid, hProcess, hThread = process_creator(binfile)
77+
Client.Control.SetEngineOptions(0x20) # DEBUG_ENGOPT_INITIAL_BREAK
78+
79+
Client.CreateProcessAndAttach2(binfile)
12580

12681
# Load lines as well as general symbols
12782
sym_opts = Client.Symbols.GetSymbolOptions()
12883
sym_opts |= symbols.SymbolOptionFlags.SYMOPT_LOAD_LINES
12984
Client.Symbols.SetSymbolOptions(sym_opts)
13085

131-
Client.AttachProcess(created_pid)
86+
# Need to enter the debugger engine to let it attach properly.
87+
res = Client.Control.WaitForEvent(timeout=1000)
88+
if res == S_FALSE:
89+
# The debugee apparently didn't do anything at all. Rather than risk
90+
# hanging, bail out at this point.
91+
client.TerminateProcesses()
92+
raise Exception("Debuggee did not start in a timely manner")
13293

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)
94+
# Enable line stepping.
13695
Client.Control.Execute("l+t")
96+
# Enable C++ expression interpretation.
13797
Client.Control.SetExpressionSyntax(cpp=True)
13898

99+
# We've requested to break into the process at the earliest opportunity,
100+
# and WaitForEvent'ing means we should have reached that break state.
101+
# Now set a breakpoint on the main symbol, and "go" until we reach it.
139102
module_name = Client.Symbols.get_exefile_module_name()
140103
offset = Client.Symbols.GetOffsetByName("{}!main".format(module_name))
141104
breakpoint = Client.Control.AddBreakpoint2(offset=offset, enabled=True)
142-
thread_resumer(hProcess, hThread)
143105
Client.Control.SetExecutionStatus(control.DebugStatus.DEBUG_STATUS_GO)
144106

145107
# Problem: there is no guarantee that the client will ever reach main,
@@ -149,7 +111,7 @@ def setup_everything(binfile):
149111
# completely hanging in the case of a environmental/programming error.
150112
res = Client.Control.WaitForEvent(timeout=5000)
151113
if res == S_FALSE:
152-
Kernel32.TerminateProcess(hProcess, 1)
114+
client.TerminateProcesses()
153115
raise Exception("Debuggee did not reach main function in a timely manner")
154116

155117
break_on_all_but_main(Client.Control, Client.Symbols, offset)
@@ -160,7 +122,7 @@ def setup_everything(binfile):
160122
for x in range(filts[0], filts[0] + filts[1]):
161123
Client.Control.SetExceptionFilterSecondCommand(x, "qd")
162124

163-
return Client, hProcess
125+
return Client
164126

165127
def step_once(client):
166128
client.Control.Execute("p")
@@ -179,7 +141,5 @@ def main_loop(client):
179141
while res is not None:
180142
res = step_once(client)
181143

182-
def cleanup(client, hProcess):
183-
res = client.DetachProcesses()
184-
Kernel32 = WinDLL("Kernel32")
185-
Kernel32.TerminateProcess(hProcess, 1)
144+
def cleanup(client):
145+
client.TerminateProcesses()

0 commit comments

Comments
 (0)