1- using ClrDebug ;
1+ using System . Diagnostics ;
2+ using System . Runtime . InteropServices ;
3+ using System . Text ;
4+ using ClrDebug ;
25using SharpDbg . Infrastructure . Debugger . ExpressionEvaluator ;
36using SharpDbg . Infrastructure . Debugger . ExpressionEvaluator . Compiler ;
47using SharpDbg . Infrastructure . Debugger . PresentationHintModels ;
@@ -9,12 +12,145 @@ namespace SharpDbg.Infrastructure.Debugger;
912
1013public 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 ;
0 commit comments