@@ -85,6 +85,12 @@ interface StreamOutput {
8585 category : string ;
8686}
8787
88+ // Interface for a pending pause request, used with pauseIfRunning() logic
89+ interface PendingPauseRequest {
90+ threadId : number | undefined ; // All threads if undefined
91+ resolveFunc ?: ( value : void | PromiseLike < void > ) => void ;
92+ }
93+
8894export function hexToBase64 ( hex : string ) : string {
8995 // The buffer will ignore incomplete bytes (unpaired digits), so we need to catch that early
9096 if ( hex . length % 2 !== 0 ) {
@@ -153,18 +159,27 @@ export abstract class GDBDebugSessionBase extends LoggingDebugSession {
153159 protected threads : ThreadWithStatus [ ] = [ ] ;
154160 protected missingThreadNames = false ;
155161
162+ /**
163+ * State variables for pauseIfNeeded/continueIfNeeded logic, mostly used for
164+ * temporary pause to insert breakpoints while the target is running.
165+ */
156166 // promise that resolves once the target stops so breakpoints can be inserted
157167 protected waitPausedPromise ?: Promise < void > ;
158168 // resolve function of waitPausedPromise while waiting, undefined otherwise
159169 protected waitPaused ?: ( value ?: void | PromiseLike < void > ) => void ;
160170 // the thread id that we were waiting for
161171 protected waitPausedThreadId = 0 ;
162- // set to true if the target was interrupted where inteneded , and should
172+ // set to true if the target was interrupted where intended , and should
163173 // therefore be resumed after breakpoints are inserted.
164174 protected waitPausedNeeded = false ;
165175 // reference count of operations requiring pausing, to make sure only the
166176 // first of them pauses, and the last to complete resumes
167177 protected pauseCount = 0 ;
178+
179+ // Pending requests to pause a thread silently (without sending stopped event).
180+ // At the moment only used with pauseIfRunning().
181+ protected silentPauseRequests : PendingPauseRequest [ ] = [ ] ;
182+
168183 // keeps track of where in the configuration phase (between initialize event
169184 // and configurationDone response) we are
170185 protected configuringState : ConfiguringState = ConfiguringState . INITIAL ;
@@ -282,16 +297,23 @@ export abstract class GDBDebugSessionBase extends LoggingDebugSession {
282297 */
283298 protected customResetRequest ( response : DebugProtocol . Response ) {
284299 if ( this . customResetCommands ) {
285- this . gdb
286- . sendCommands ( this . customResetCommands )
287- . then ( ( ) => this . sendResponse ( response ) )
288- . catch ( ( ) =>
300+ this . pauseIfRunning ( )
301+ . then ( ( ) => {
302+ // Behavior after reset very much depends on the commands used.
303+ // So, hard to make assumptions when expected state is reached.
304+ // Hence, implement stop-after-reset behavior unless commands
305+ // set running.
306+ this . gdb . sendCommands ( this . customResetCommands ) . then ( ( ) => {
307+ this . sendResponse ( response ) ;
308+ } ) ;
309+ } )
310+ . catch ( ( ) => {
289311 this . sendErrorResponse (
290312 response ,
291313 1 ,
292314 'The custom reset command failed'
293- )
294- ) ;
315+ ) ;
316+ } ) ;
295317 }
296318 }
297319
@@ -628,6 +650,43 @@ export abstract class GDBDebugSessionBase extends LoggingDebugSession {
628650 }
629651 }
630652
653+ /**
654+ * Pause thread if running and suppress stopped event.
655+ * In contrast to pauseIfNeeded(), there is no expectation to resume after a debugger operation.
656+ *
657+ * Notes:
658+ * - NOT to be called during configuration phase.
659+ * - No requireAsync, supposed to be removed from pauseIfNeeded in future.
660+ */
661+ protected async pauseIfRunning ( ) : Promise < void > {
662+ // Check if running before actual wait promise
663+ if ( ! this . isRunning ) {
664+ // At least one thread is already stopped, good enough
665+ // to execute custom reset commands.
666+ return ;
667+ }
668+ return new Promise < void > ( async ( resolve ) => {
669+ // Get current thread ID in non-stop mode
670+ const currentThreadId = this . gdb . isNonStopMode ( )
671+ ? await this . gdb . queryCurrentThreadId ( )
672+ : undefined ;
673+ // Push pause request to be handled silently when stop event arrives.
674+ // threadId = undefined means all threads.
675+ // Note: threadId usage matches continueIfNeeded() behavior.
676+ this . silentPauseRequests . push ( {
677+ threadId : currentThreadId ,
678+ resolveFunc : resolve ,
679+ } ) ;
680+ if ( this . gdb . isNonStopMode ( ) ) {
681+ // Send pause command to any thread (use current)
682+ this . gdb . pause ( currentThreadId ) ;
683+ } else {
684+ this . gdb . pause ( ) ;
685+ }
686+ // The promise resolves when pushed resolveFunc gets called.
687+ } ) ;
688+ }
689+
631690 protected async dataBreakpointInfoRequest (
632691 response : DebugProtocol . DataBreakpointInfoResponse ,
633692 args : DebugProtocol . DataBreakpointInfoArguments
@@ -2402,6 +2461,24 @@ export abstract class GDBDebugSessionBase extends LoggingDebugSession {
24022461 return this . sendContinuedEvent ( getThreadId ( result ) ) ;
24032462 }
24042463
2464+ private handleSilentPauseRequests ( threadId ?: number ) : boolean {
2465+ // Resolve requests with exact threadId match.
2466+ // Note: Expecting request with threadId=undefined gets satisfied
2467+ // by handling a stopped event for allThreads. If this doesn't hold,
2468+ // below conditions should be changed to resolve all threads, i.e.
2469+ // also those with a defined threadId.
2470+ const requestsToResolve = this . silentPauseRequests . filter (
2471+ ( request ) => request . threadId === threadId
2472+ ) ;
2473+ this . silentPauseRequests = this . silentPauseRequests . filter (
2474+ ( request ) => request . threadId !== threadId
2475+ ) ;
2476+ requestsToResolve . forEach ( ( request ) => {
2477+ request . resolveFunc ?.( ) ;
2478+ } ) ;
2479+ return requestsToResolve . length > 0 ;
2480+ }
2481+
24052482 protected handleGDBAsync ( resultClass : string , resultData : any ) {
24062483 const updateIsRunning = ( ) => {
24072484 this . isRunning = this . threads . length ? true : false ;
@@ -2441,24 +2518,32 @@ export abstract class GDBDebugSessionBase extends LoggingDebugSession {
24412518 thread . lastRunToken = undefined ;
24422519 }
24432520 }
2444- if (
2445- this . waitPaused &&
2446- resultData . reason === 'signal-received' &&
2447- ( this . waitPausedThreadId === id ||
2448- this . waitPausedThreadId === - 1 )
2449- ) {
2450- suppressHandleGDBStopped = true ;
2521+ if ( resultData . reason === 'signal-received' ) {
2522+ if (
2523+ this . waitPaused &&
2524+ ( this . waitPausedThreadId === id ||
2525+ this . waitPausedThreadId === - 1 )
2526+ ) {
2527+ suppressHandleGDBStopped = true ;
2528+ }
2529+ // Handle separately to avoid short-circuiting effects
2530+ if ( this . handleSilentPauseRequests ( id ) ) {
2531+ suppressHandleGDBStopped = true ;
2532+ }
24512533 }
24522534 } else {
24532535 for ( const thread of this . threads ) {
24542536 thread . running = false ;
24552537 thread . lastRunToken = undefined ;
24562538 }
2457- if (
2458- this . waitPaused &&
2459- resultData . reason === 'signal-received'
2460- ) {
2461- suppressHandleGDBStopped = true ;
2539+ if ( resultData . reason === 'signal-received' ) {
2540+ if ( this . waitPaused ) {
2541+ suppressHandleGDBStopped = true ;
2542+ }
2543+ // Handle separately to avoid short-circuiting effects
2544+ if ( this . handleSilentPauseRequests ( ) ) {
2545+ suppressHandleGDBStopped = true ;
2546+ }
24622547 }
24632548 }
24642549
0 commit comments