Skip to content

Commit f38f551

Browse files
authored
Extended remote support (#9569)
* Extended remote support
1 parent 93fcc05 commit f38f551

File tree

6 files changed

+152
-50
lines changed

6 files changed

+152
-50
lines changed

Extension/package.json

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2221,10 +2221,10 @@
22212221
"scope": "application"
22222222
},
22232223
"C_Cpp.inlayHints.autoDeclarationTypes.showOnLeft": {
2224-
"type": "boolean",
2225-
"default": false,
2226-
"markdownDescription": "%c_cpp.configuration.inlayHints.autoDeclarationTypes.showOnLeft.markdownDescription%",
2227-
"scope": "application"
2224+
"type": "boolean",
2225+
"default": false,
2226+
"markdownDescription": "%c_cpp.configuration.inlayHints.autoDeclarationTypes.showOnLeft.markdownDescription%",
2227+
"scope": "application"
22282228
},
22292229
"C_Cpp.inlayHints.parameterNames.enabled": {
22302230
"type": "boolean",
@@ -2239,10 +2239,10 @@
22392239
"scope": "application"
22402240
},
22412241
"C_Cpp.inlayHints.parameterNames.hideLeadingUnderscores": {
2242-
"type": "boolean",
2243-
"default": true,
2244-
"markdownDescription": "%c_cpp.configuration.inlayHints.parameterNames.hideLeadingUnderscores.markdownDescription%",
2245-
"scope": "application"
2242+
"type": "boolean",
2243+
"default": true,
2244+
"markdownDescription": "%c_cpp.configuration.inlayHints.parameterNames.hideLeadingUnderscores.markdownDescription%",
2245+
"scope": "application"
22462246
},
22472247
"C_Cpp.inlayHints.referenceOperator.enabled": {
22482248
"type": "boolean",
@@ -2251,10 +2251,10 @@
22512251
"scope": "application"
22522252
},
22532253
"C_Cpp.inlayHints.referenceOperator.showSpace": {
2254-
"type": "boolean",
2255-
"default": false,
2256-
"markdownDescription": "%c_cpp.configuration.inlayHints.referenceOperator.showSpace.markdownDescription%",
2257-
"scope": "application"
2254+
"type": "boolean",
2255+
"default": false,
2256+
"markdownDescription": "%c_cpp.configuration.inlayHints.referenceOperator.showSpace.markdownDescription%",
2257+
"scope": "application"
22582258
},
22592259
"C_Cpp.loggingLevel": {
22602260
"type": "string",
@@ -3079,6 +3079,11 @@
30793079
"description": "%c_cpp.debuggers.miDebuggerServerAddress.description%",
30803080
"default": "serveraddress:port"
30813081
},
3082+
"useExtendedRemote": {
3083+
"type": "boolean",
3084+
"description": "%c_cpp.debuggers.useExtendedRemote.description%",
3085+
"default": false
3086+
},
30823087
"stopAtEntry": {
30833088
"type": "boolean",
30843089
"description": "%c_cpp.debuggers.stopAtEntry.description%",
@@ -3727,8 +3732,7 @@
37273732
"type": "object",
37283733
"default": {},
37293734
"required": [
3730-
"program",
3731-
"processId"
3735+
"program"
37323736
],
37333737
"properties": {
37343738
"program": {
@@ -3776,6 +3780,11 @@
37763780
"description": "%c_cpp.debuggers.miDebuggerServerAddress.description%",
37773781
"default": "serveraddress:port"
37783782
},
3783+
"useExtendedRemote": {
3784+
"type": "boolean",
3785+
"description": "%c_cpp.debuggers.useExtendedRemote.description%",
3786+
"default": false
3787+
},
37793788
"processId": {
37803789
"anyOf": [
37813790
{
@@ -5445,4 +5454,4 @@
54455454
"y18n": "^5.0.5",
54465455
"minimist": "^1.2.6"
54475456
}
5448-
}
5457+
}

Extension/package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@
272272
"c_cpp.debuggers.miDebuggerPath.description": "The path to the MI debugger (such as gdb). When unspecified, it will search path first for the debugger.",
273273
"c_cpp.debuggers.miDebuggerArgs.description": "Additional arguments for the MI debugger (such as gdb).",
274274
"c_cpp.debuggers.miDebuggerServerAddress.description": "Network address of the MI Debugger Server to connect to (example: localhost:1234).",
275+
"c_cpp.debuggers.useExtendedRemote.description": "Connect to the MI Debugger Server with target extended-remote mode.",
275276
"c_cpp.debuggers.stopAtEntry.description": "Optional parameter. If true, the debugger should stop at the entrypoint of the target. If processId is passed, has no effect.",
276277
"c_cpp.debuggers.debugServerPath.description": "Optional full path to the debug server to launch. Defaults to null. It is used in conjunction with either \"miDebugServerAddress\" or your own server with a \"customSetupCommand\" that runs \"-target-select remote <server:port>\".",
277278
"c_cpp.debuggers.debugServerArgs.description": "Optional debug server args. Defaults to null.",

Extension/src/Debugger/attachToProcess.ts

Lines changed: 94 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -39,48 +39,58 @@ export class RemoteAttachPicker {
3939

4040
public async ShowAttachEntries(config: any): Promise<string | undefined> {
4141
this._channel.clear();
42+
let processes: AttachItem[];
4243

4344
const pipeTransport: any = config ? config.pipeTransport : undefined;
45+
const useExtendedRemote: any = config ? config.useExtendedRemote : undefined;
46+
const miDebuggerPath: any = config ? config.miDebuggerPath : undefined;
47+
const miDebuggerServerAddress: any = config ? config.miDebuggerServerAddress : undefined;
48+
49+
if (pipeTransport) {
50+
let pipeProgram: string | undefined;
51+
52+
if (os.platform() === 'win32' &&
53+
pipeTransport.pipeProgram &&
54+
!await util.checkFileExists(pipeTransport.pipeProgram)) {
55+
const pipeProgramStr: string = pipeTransport.pipeProgram.toLowerCase().trim();
56+
const expectedArch: debugUtils.ArchType = debugUtils.ArchType[process.arch as keyof typeof debugUtils.ArchType];
57+
58+
// Check for pipeProgram
59+
if (!await util.checkFileExists(config.pipeTransport.pipeProgram)) {
60+
pipeProgram = debugUtils.ArchitectureReplacer.checkAndReplaceWSLPipeProgram(pipeProgramStr, expectedArch);
61+
}
4462

45-
if (!pipeTransport) {
46-
throw new Error(localize("no.pipetransport", "Chosen debug configuration does not contain {0}", "pipeTransport"));
47-
}
48-
49-
let pipeProgram: string | undefined;
50-
51-
if (os.platform() === 'win32' &&
52-
pipeTransport.pipeProgram &&
53-
!await util.checkFileExists(pipeTransport.pipeProgram)) {
54-
const pipeProgramStr: string = pipeTransport.pipeProgram.toLowerCase().trim();
55-
const expectedArch: debugUtils.ArchType = debugUtils.ArchType[process.arch as keyof typeof debugUtils.ArchType];
63+
// If pipeProgram does not get replaced and there is a pipeCwd, concatenate with pipeProgramStr and attempt to replace.
64+
if (!pipeProgram && config.pipeTransport.pipeCwd) {
65+
const pipeCwdStr: string = config.pipeTransport.pipeCwd.toLowerCase().trim();
66+
const newPipeProgramStr: string = path.join(pipeCwdStr, pipeProgramStr);
5667

57-
// Check for pipeProgram
58-
if (!await util.checkFileExists(config.pipeTransport.pipeProgram)) {
59-
pipeProgram = debugUtils.ArchitectureReplacer.checkAndReplaceWSLPipeProgram(pipeProgramStr, expectedArch);
68+
if (!await util.checkFileExists(newPipeProgramStr)) {
69+
pipeProgram = debugUtils.ArchitectureReplacer.checkAndReplaceWSLPipeProgram(newPipeProgramStr, expectedArch);
70+
}
71+
}
6072
}
6173

62-
// If pipeProgram does not get replaced and there is a pipeCwd, concatenate with pipeProgramStr and attempt to replace.
63-
if (!pipeProgram && config.pipeTransport.pipeCwd) {
64-
const pipeCwdStr: string = config.pipeTransport.pipeCwd.toLowerCase().trim();
65-
const newPipeProgramStr: string = path.join(pipeCwdStr, pipeProgramStr);
66-
67-
if (!await util.checkFileExists(newPipeProgramStr)) {
68-
pipeProgram = debugUtils.ArchitectureReplacer.checkAndReplaceWSLPipeProgram(newPipeProgramStr, expectedArch);
69-
}
74+
if (!pipeProgram) {
75+
pipeProgram = pipeTransport.pipeProgram;
7076
}
71-
}
7277

73-
if (!pipeProgram) {
74-
pipeProgram = pipeTransport.pipeProgram;
75-
}
78+
const pipeArgs: string[] = pipeTransport.pipeArgs;
7679

77-
const pipeArgs: string[] = pipeTransport.pipeArgs;
80+
const argList: string = RemoteAttachPicker.createArgumentList(pipeArgs);
7881

79-
const argList: string = RemoteAttachPicker.createArgumentList(pipeArgs);
82+
const pipeCmd: string = `"${pipeProgram}" ${argList}`;
8083

81-
const pipeCmd: string = `"${pipeProgram}" ${argList}`;
84+
processes = await this.getRemoteOSAndProcesses(pipeCmd);
85+
} else if (!pipeTransport && useExtendedRemote) {
86+
if (!miDebuggerPath || !miDebuggerServerAddress) {
87+
throw new Error(localize("debugger.path.and.server.address.required", "{0} in debug configuration requires {1} and {2}", "useExtendedRemote", "miDebuggerPath", "miDebuggerServerAddress"));
88+
}
89+
processes = await this.getRemoteProcessesExtendedRemote(miDebuggerPath, miDebuggerServerAddress);
90+
} else {
91+
throw new Error(localize("no.pipetransport.useextendedremote", "Chosen debug configuration does not contain {0} or {1}", "pipeTransport", "useExtendedRemote"));
92+
}
8293

83-
const processes: AttachItem[]= await this.getRemoteOSAndProcesses(pipeCmd);
8494
const attachPickOptions: vscode.QuickPickOptions = {
8595
matchOnDetail: true,
8696
matchOnDescription: true,
@@ -118,8 +128,8 @@ export class RemoteAttachPicker {
118128
}
119129

120130
return `${outerQuote}sh -c ${innerQuote}uname && if [ ${parameterBegin}uname${parameterEnd} = ${escapedQuote}Linux${escapedQuote} ] ; ` +
121-
`then ${PsProcessParser.psLinuxCommand} ; elif [ ${parameterBegin}uname${parameterEnd} = ${escapedQuote}Darwin${escapedQuote} ] ; ` +
122-
`then ${PsProcessParser.psDarwinCommand}; fi${innerQuote}${outerQuote}`;
131+
`then ${PsProcessParser.psLinuxCommand} ; elif [ ${parameterBegin}uname${parameterEnd} = ${escapedQuote}Darwin${escapedQuote} ] ; ` +
132+
`then ${PsProcessParser.psDarwinCommand}; fi${innerQuote}${outerQuote}`;
123133
}
124134

125135
private async getRemoteOSAndProcesses(pipeCmd: string): Promise<AttachItem[]> {
@@ -167,6 +177,58 @@ export class RemoteAttachPicker {
167177
}
168178
}
169179

180+
private async getRemoteProcessesExtendedRemote(miDebuggerPath: string, miDebuggerServerAddress: string): Promise<AttachItem[]> {
181+
const args: string[] = [`-ex "target extended-remote ${miDebuggerServerAddress}"`, '-ex "info os processes"', '-batch'];
182+
let processListOutput: util.ProcessReturnType = await util.spawnChildProcess(miDebuggerPath, args);
183+
// The device may not be responsive for a while during the restart after image deploy. Retry 5 times.
184+
for (let i: number = 0; i < 5 && !processListOutput.succeeded; i++) {
185+
processListOutput = await util.spawnChildProcess(miDebuggerPath, args);
186+
}
187+
188+
if (!processListOutput.succeeded) {
189+
throw new Error(localize('failed.to.make.gdb.connection', 'Failed to make GDB connection: "{0}".', processListOutput.output));
190+
}
191+
const processes: AttachItem[] = this.parseProcessesFromInfoOsProcesses(processListOutput.output);
192+
if (!processes || processes.length === 0) {
193+
throw new Error(localize('failed.to.parse.processes', 'Failed to parse processes: "{0}".', processListOutput.output));
194+
}
195+
return processes;
196+
}
197+
198+
/**
199+
Format:
200+
pid usr command cores
201+
1 ?
202+
2 ?
203+
3 /usr/bin/sample 0
204+
4 root /usr/bin/gdbserver --multi :6000 0
205+
206+
Returns an AttachItem array, each item contains a label of "<user >command", and a pid.
207+
Unfortunately because the format of each line is not fixed, and everything except pid is optional, it's hard
208+
to get a better label.
209+
*/
210+
private parseProcessesFromInfoOsProcesses(processList: string): AttachItem[] {
211+
const lines: string[] = processList?.split('\n');
212+
if (!lines?.length) {
213+
return [];
214+
}
215+
216+
const processes: AttachItem[] = [];
217+
for (const line of lines) {
218+
const trimmedLine: string = line.trim();
219+
if (!trimmedLine.endsWith('?')) {
220+
const matches: RegExpMatchArray | null = trimmedLine.match(/^(\d+)\s+(.+?)\s+\d+$/);
221+
if (matches?.length === 3) {
222+
const id: string = matches[1];
223+
const userCommand: string = matches[2];
224+
processes.push({ label: userCommand, id });
225+
}
226+
}
227+
}
228+
229+
return processes;
230+
}
231+
170232
private static createArgumentList(args: string[]): string {
171233
let argsString: string = "";
172234

Extension/src/Debugger/configurationProvider.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import { expandAllStrings, ExpansionOptions, ExpansionVars } from '../expand';
2727
import { scp, ssh } from '../SSH/commands';
2828
import * as glob from 'glob';
2929
import { promisify } from 'util';
30+
import { AttachItemsProvider, AttachPicker, RemoteAttachPicker } from './attachToProcess';
31+
import { NativeAttachItemsProviderFactory } from './nativeAttach';
3032

3133
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
3234
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
@@ -336,6 +338,26 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
336338
}
337339
}
338340

341+
// Pick process if process id is empty
342+
if (config.request === "attach" && !config.processId) {
343+
let processId: string | undefined;
344+
if (config.pipeTransport || config.useExtendedRemote) {
345+
const remoteAttachPicker: RemoteAttachPicker = new RemoteAttachPicker();
346+
processId = await remoteAttachPicker.ShowAttachEntries(config);
347+
} else {
348+
const attachItemsProvider: AttachItemsProvider = NativeAttachItemsProviderFactory.Get();
349+
const attacher: AttachPicker = new AttachPicker(attachItemsProvider);
350+
processId = await attacher.ShowAttachEntries();
351+
}
352+
353+
if (processId) {
354+
config.processId = processId;
355+
} else {
356+
logger.getOutputChannelLogger().showErrorMessage("No process was selected.");
357+
return undefined;
358+
}
359+
}
360+
339361
return config;
340362
}
341363

Extension/src/Debugger/configurations.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ function createAttachString(name: string, type: string, executable: string): str
106106
"name": "${name}",
107107
"type": "${type}",
108108
"request": "attach",{0}
109-
"processId": "$\{command:pickProcess\}"
110109
`, [type === "cppdbg" ? `${os.EOL}"program": "${localize("enter.program.name", "enter program name, for example {0}", "$\{workspaceFolder\}" + "/" + executable).replace(/\"/g, "\\\"")}",` : ""]);
111110
}
112111

Extension/tools/OptionsSchema.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,11 @@
620620
"description": "%c_cpp.debuggers.miDebuggerServerAddress.description%",
621621
"default": "serveraddress:port"
622622
},
623+
"useExtendedRemote": {
624+
"type": "boolean",
625+
"description": "%c_cpp.debuggers.useExtendedRemote.description%",
626+
"default": false
627+
},
623628
"stopAtEntry": {
624629
"type": "boolean",
625630
"description": "%c_cpp.debuggers.stopAtEntry.description%",
@@ -719,8 +724,7 @@
719724
"type": "object",
720725
"default": {},
721726
"required": [
722-
"program",
723-
"processId"
727+
"program"
724728
],
725729
"properties": {
726730
"program": {
@@ -768,6 +772,11 @@
768772
"description": "%c_cpp.debuggers.miDebuggerServerAddress.description%",
769773
"default": "serveraddress:port"
770774
},
775+
"useExtendedRemote": {
776+
"type": "boolean",
777+
"description": "%c_cpp.debuggers.useExtendedRemote.description%",
778+
"default": false
779+
},
771780
"processId": {
772781
"anyOf": [
773782
{

0 commit comments

Comments
 (0)