Skip to content

Commit 3552f89

Browse files
authored
Fix erroneous assumption of async mode when target does not support it (#440)
* Fix erroneous assumption of async mode when target does not support it When async mode is requested using `-gdb-set mi-async on`, check with GDB whether the request was actually granted once connected to a target. It may not be, depending on the capabilities of the target (in particular, the "native" target on Windows does not support async mode in GDB < 13), and in such a case, later decisions based on `gdb.getAsyncMode()` would be wrong. Signed-off-by: Christian Walther <[email protected]> * Move check for async mode after initCommands Signed-off-by: Christian Walther <[email protected]> --------- Signed-off-by: Christian Walther <[email protected]>
1 parent d40d45b commit 3552f89

File tree

6 files changed

+80
-0
lines changed

6 files changed

+80
-0
lines changed

src/desktop/GDBTargetDebugSession.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,14 @@ export class GDBTargetDebugSession extends GDBDebugSession {
620620
args.initCommands
621621
);
622622

623+
// Check for async mode support after initCommands have possibly
624+
// selected a different target
625+
if (this.gdb.getAsyncMode()) {
626+
if (await this.gdb.confirmAsyncMode()) {
627+
this.warnAsyncDisabled();
628+
}
629+
}
630+
623631
// Initialize UART
624632
if (target.uart !== undefined) {
625633
this.initializeUARTConnection(target.uart, target.host);

src/gdb/GDBBackend.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
import {
1818
MIBreakpointInsertOptions,
1919
MIBreakpointLocation,
20+
MIFeaturesResponse,
2021
MIShowResponse,
2122
sendDataEvaluateExpression,
2223
sendExecInterrupt,
@@ -39,6 +40,7 @@ export class GDBBackend extends events.EventEmitter implements IGDBBackend {
3940
private gdbVersion?: string;
4041
protected gdbAsync = false;
4142
protected gdbNonStop = false;
43+
protected asyncRequestedExplicitly = false;
4244
protected hardwareBreakpoint = false;
4345

4446
constructor(protected readonly processManager: IGDBProcessManager) {
@@ -80,6 +82,9 @@ export class GDBBackend extends events.EventEmitter implements IGDBBackend {
8082
this.emit('consoleStreamOutput', newChunk, 'stderr');
8183
});
8284
}
85+
this.asyncRequestedExplicitly = !!(
86+
requestArgs.gdbAsync || requestArgs.gdbNonStop
87+
);
8388
await this.setNonStopMode(requestArgs.gdbNonStop);
8489
await this.setAsyncMode(requestArgs.gdbAsync);
8590
}
@@ -112,6 +117,29 @@ export class GDBBackend extends events.EventEmitter implements IGDBBackend {
112117
return this.gdbAsync;
113118
}
114119

120+
// When you setAsyncMode(true), call this after selecting a target (either
121+
// explicitly using -target-select, or using -target-attach or -exec-run
122+
// which implicitly select the "native" target (see docs of `set
123+
// auto-connect-native-target`)) to check if the target actually supports
124+
// async mode. If not, it resets getAsyncMode() to false.
125+
// In particular, the "native" target on Windows does not support async mode
126+
// in GDB < 13.
127+
// Returns true iff async mode was explicitly requested in the launch/attach
128+
// arguments but is unsupported by the target.
129+
public async confirmAsyncMode() {
130+
const features = (
131+
(await this.sendCommand(
132+
'-list-target-features'
133+
)) as MIFeaturesResponse
134+
).features;
135+
const actualAsync =
136+
Array.isArray(features) && features.includes('async');
137+
const warningNeeded =
138+
this.gdbAsync && !actualAsync && this.asyncRequestedExplicitly;
139+
this.gdbAsync = actualAsync;
140+
return warningNeeded;
141+
}
142+
115143
public async setNonStopMode(isSet?: boolean) {
116144
if (isSet === undefined) {
117145
isSet = false;

src/gdb/GDBDebugSessionBase.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,15 @@ export abstract class GDBDebugSessionBase extends LoggingDebugSession {
352352
);
353353
}
354354

355+
protected warnAsyncDisabled() {
356+
this.sendEvent(
357+
new OutputEvent(
358+
'The target does not support gdbAsync. Most debugger interactions will only work while the target is stopped.',
359+
'important'
360+
)
361+
);
362+
}
363+
355364
protected async attachOrLaunchRequest(
356365
response: DebugProtocol.Response,
357366
request: 'launch' | 'attach',
@@ -384,10 +393,30 @@ export abstract class GDBDebugSessionBase extends LoggingDebugSession {
384393
this.sendEvent(
385394
new OutputEvent(`attached to process ${attachArgs.processId}`)
386395
);
396+
} else {
397+
if (this.gdb.getAsyncMode()) {
398+
// Checking whether the target supports async mode needs a
399+
// target connection. Launching will implicitly select the
400+
// "native" target (see docs of `set
401+
// auto-connect-native-target`), so we may just as well do it
402+
// explicitly here.
403+
await mi.sendTargetSelectRequest(this.gdb, {
404+
type: 'native',
405+
parameters: [],
406+
});
407+
}
387408
}
388409

389410
await this.gdb.sendCommands(args.initCommands);
390411

412+
// Check for async mode support after initCommands have possibly
413+
// selected a different target
414+
if (this.gdb.getAsyncMode()) {
415+
if (await this.gdb.confirmAsyncMode()) {
416+
this.warnAsyncDisabled();
417+
}
418+
}
419+
391420
if (request === 'launch') {
392421
const launchArgs = args as LaunchRequestArguments;
393422
if (launchArgs.arguments) {

src/mi/base.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ export interface MIShowResponse extends MIResponse {
1717
value?: string;
1818
}
1919

20+
/** Response to -list-features and -list-target-features */
21+
export interface MIFeaturesResponse extends MIResponse {
22+
features?: string[];
23+
}
24+
2025
export abstract class MIRequest<R> {
2126
public abstract send(backend: IGDBBackend): Promise<R>;
2227
}

src/types/gdb.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ export interface IGDBBackend extends EventEmitter {
8686

8787
getAsyncMode: () => boolean;
8888

89+
confirmAsyncMode: () => Promise<boolean>;
90+
8991
setNonStopMode: (isSet?: boolean) => Promise<void>;
9092

9193
isNonStopMode: () => boolean;

src/web/GDBTargetDebugSession.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,14 @@ export class GDBTargetDebugSession extends GDBDebugSession {
469469
args.initCommands
470470
);
471471

472+
// Check for async mode support after initCommands have possibly
473+
// selected a different target
474+
if (this.gdb.getAsyncMode()) {
475+
if (await this.gdb.confirmAsyncMode()) {
476+
this.warnAsyncDisabled();
477+
}
478+
}
479+
472480
// Load additional code/symbols
473481
if (args.imageAndSymbols) {
474482
if (args.imageAndSymbols.imageFileName) {

0 commit comments

Comments
 (0)