Skip to content

Commit df41cc1

Browse files
authored
Implement debugee launch (#6)
1 parent 411bef9 commit df41cc1

File tree

4 files changed

+177
-6
lines changed

4 files changed

+177
-6
lines changed

src/SharpDbg.Infrastructure/Debugger/BreakpointManager.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,24 @@ public List<BreakpointInfo> GetAllBreakpoints()
185185
}
186186
}
187187

188+
/// <summary>
189+
/// Remove a breakpoint by id
190+
/// </summary>
191+
public bool RemoveBreakpoint(int id)
192+
{
193+
lock (_lock)
194+
{
195+
if (!_breakpoints.TryGetValue(id, out var bp)) return false;
196+
_breakpoints.Remove(id);
197+
if (_breakpointsByFile.TryGetValue(bp.FilePath, out var ids))
198+
{
199+
ids.Remove(id);
200+
if (ids.Count == 0) _breakpointsByFile.Remove(bp.FilePath);
201+
}
202+
return true;
203+
}
204+
}
205+
188206
/// <summary>
189207
/// Clear all breakpoints
190208
/// </summary>

src/SharpDbg.Infrastructure/Debugger/ManagedDebugger.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,15 @@ private static string GetFunctionFormattedName(CorDebugFunction function)
288288

289289
public void Dispose()
290290
{
291+
// Attempt a graceful disconnect from the debuggee without terminating it
292+
Disconnect(terminateDebuggee: false);
293+
294+
// Remove our managed handler from ICorDebug so native code can release references
295+
_corDebug?.SetManagedHandler(null);
296+
297+
// Unsubscribe from callbacks to avoid any further event dispatch
298+
_callbacks.OnAnyEvent -= OnAnyEvent;
299+
291300
Cleanup();
292301
_process = null;
293302
_corDebug = null;

src/SharpDbg.Infrastructure/Debugger/ManagedDebugger_RequestHandlers.cs

Lines changed: 147 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
using ClrDebug;
1+
using System.Diagnostics;
2+
using System.Runtime.InteropServices;
3+
using System.Text;
4+
using ClrDebug;
25
using SharpDbg.Infrastructure.Debugger.ExpressionEvaluator;
36
using SharpDbg.Infrastructure.Debugger.ExpressionEvaluator.Compiler;
47
using SharpDbg.Infrastructure.Debugger.PresentationHintModels;
@@ -9,12 +12,145 @@ namespace SharpDbg.Infrastructure.Debugger;
912

1013
public partial class ManagedDebugger
1114
{
15+
// Store launch info for deferred attach in ConfigurationDone
16+
private string? _pendingLaunchProgram;
17+
private string[]? _pendingLaunchArgs;
18+
private string? _pendingLaunchWorkingDirectory;
19+
private bool _pendingLaunchStopAtEntry;
20+
1221
/// <summary>
13-
/// Launch a process to debug
22+
/// Launch a process to debug using DbgShim's CreateProcessForLaunch.
23+
/// This properly launches the process suspended and waits for CLR startup.
1424
/// </summary>
1525
public void Launch(string program, string[] args, string? workingDirectory, Dictionary<string, string>? env, bool stopAtEntry)
1626
{
17-
throw new NotImplementedException("Launch is not implemented, use Attach instead. Use DOTNET_DefaultDiagnosticPortSuspend=1 env var to have the process wait for debugger attach, the resume it yourself after attaching with `new DiagnosticsClient(debuggableProcess.Id).ResumeRuntime()`");
27+
_logger?.Invoke($"Launching program: {program} {string.Join(' ', args ?? Array.Empty<string>())}");
28+
29+
// Store launch parameters for deferred execution in ConfigurationDone
30+
_pendingLaunchProgram = program;
31+
_pendingLaunchArgs = args;
32+
_pendingLaunchWorkingDirectory = workingDirectory;
33+
_pendingLaunchStopAtEntry = stopAtEntry;
34+
}
35+
36+
/// <summary>
37+
/// Actually perform the launch using DbgShim APIs
38+
/// </summary>
39+
private void PerformLaunch()
40+
{
41+
if (_pendingLaunchProgram == null)
42+
{
43+
_logger?.Invoke("No pending launch to perform");
44+
return;
45+
}
46+
47+
var program = _pendingLaunchProgram;
48+
var args = _pendingLaunchArgs ?? Array.Empty<string>();
49+
var workingDirectory = _pendingLaunchWorkingDirectory;
50+
var stopAtEntry = _pendingLaunchStopAtEntry;
51+
52+
// Clear pending launch
53+
_pendingLaunchProgram = null;
54+
_pendingLaunchArgs = null;
55+
_pendingLaunchWorkingDirectory = null;
56+
57+
// Build command line: "program" "arg1" "arg2" ...
58+
var commandLine = new StringBuilder();
59+
commandLine.Append('"').Append(program).Append('"');
60+
foreach (var arg in args)
61+
{
62+
commandLine.Append(' ').Append('"').Append(arg.Replace("\"", "\\\"")).Append('"');
63+
}
64+
65+
_logger?.Invoke($"Creating process for launch: {commandLine}");
66+
67+
// Initialize DbgShim
68+
var dbgShimPath = DbgShimResolver.Resolve();
69+
var dbgshim = new DbgShim(NativeLibrary.Load(dbgShimPath));
70+
71+
// Create process suspended
72+
var result = dbgshim.CreateProcessForLaunch(
73+
commandLine.ToString(),
74+
bSuspendProcess: true,
75+
lpEnvironment: IntPtr.Zero, // TODO: support environment variables
76+
lpCurrentDirectory: workingDirectory);
77+
78+
var processId = result.ProcessId;
79+
var resumeHandle = result.ResumeHandle;
80+
81+
_logger?.Invoke($"Process created suspended with PID: {processId}");
82+
83+
// Register for runtime startup callback
84+
IntPtr unregisterToken = IntPtr.Zero;
85+
var wait = new AutoResetEvent(false);
86+
CorDebug? cordebug = null;
87+
HRESULT callbackHr = HRESULT.E_FAIL;
88+
89+
try
90+
{
91+
unregisterToken = dbgshim.RegisterForRuntimeStartup(processId, (pCordb, parameter, hr) =>
92+
{
93+
cordebug = pCordb;
94+
callbackHr = hr;
95+
wait.Set();
96+
});
97+
98+
// Resume the process so CLR can start
99+
_logger?.Invoke("Resuming process...");
100+
dbgshim.ResumeProcess(resumeHandle);
101+
dbgshim.CloseResumeHandle(resumeHandle);
102+
103+
// Wait for CLR startup (with timeout)
104+
if (!wait.WaitOne(TimeSpan.FromSeconds(30)))
105+
{
106+
throw new InvalidOperationException("Timeout waiting for CLR to start in target process");
107+
}
108+
}
109+
finally
110+
{
111+
if (unregisterToken != IntPtr.Zero)
112+
{
113+
dbgshim.UnregisterForRuntimeStartup(unregisterToken);
114+
}
115+
}
116+
117+
if (cordebug == null)
118+
{
119+
throw new DebugException(callbackHr);
120+
}
121+
122+
_logger?.Invoke($"CLR started, initializing debugger for PID: {processId}");
123+
124+
// Initialize debugging session
125+
_corDebug = cordebug;
126+
_corDebug.Initialize();
127+
_corDebug.SetManagedHandler(_callbacks);
128+
129+
// Attach to the process (it's already waiting for us due to RegisterForRuntimeStartup)
130+
_process = _corDebug.DebugActiveProcess(processId, false);
131+
_isAttached = true;
132+
IsRunning = true;
133+
134+
_logger?.Invoke($"Successfully attached to process: {processId}");
135+
}
136+
137+
public bool RemoveBreakpoint(int id)
138+
{
139+
_logger?.Invoke($"RemoveBreakpoint: {id}");
140+
var bp = _breakpointManager.GetBreakpoint(id);
141+
if (bp == null) return false;
142+
if (bp.CorBreakpoint != null)
143+
{
144+
try
145+
{
146+
bp.CorBreakpoint.Activate(false);
147+
}
148+
catch (Exception ex)
149+
{
150+
_logger?.Invoke($"Error deactivating breakpoint: {ex.Message}");
151+
}
152+
}
153+
return _breakpointManager.RemoveBreakpoint(id);
18154
}
19155

20156
/// <summary>
@@ -27,14 +163,20 @@ public void Attach(int processId)
27163
}
28164

29165
/// <summary>
30-
/// Called when DAP configuration is complete - performs deferred attach
166+
/// Called when DAP configuration is complete - performs deferred launch or attach
31167
/// </summary>
32168
public void ConfigurationDone()
33169
{
34170
//System.Diagnostics.Debugger.Launch();
35171
_logger?.Invoke("ConfigurationDone");
36172

37-
if (_pendingAttachProcessId.HasValue)
173+
// If we have a pending launch, perform it
174+
if (_pendingLaunchProgram != null)
175+
{
176+
PerformLaunch();
177+
}
178+
// Otherwise check for pending attach
179+
else if (_pendingAttachProcessId.HasValue)
38180
{
39181
PerformAttach(_pendingAttachProcessId.Value);
40182
_pendingAttachProcessId = null;
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json"
2+
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
3+
"parallelizeTestCollections": false,
4+
"maxParallelThreads": 1
35
}

0 commit comments

Comments
 (0)