diff --git a/Extension/package.json b/Extension/package.json index a1e2633e10..0b4141533b 100644 --- a/Extension/package.json +++ b/Extension/package.json @@ -4673,6 +4673,28 @@ } ] }, + "waitFor": { + "description": "%c_cpp.debuggers.waitFor.description%", + "type": "object", + "default": {}, + "properties": { + "enabled": { + "type": "boolean", + "description": "%c_cpp.debuggers.waitFor.enabled.description%", + "default": false + }, + "pattern": { + "type": "string", + "description": "%c_cpp.debuggers.waitFor.pattern.description%", + "default": "" + }, + "timeout": { + "type": "number", + "description": "%c_cpp.debuggers.waitFor.timeout.description%", + "default": 30000 + } + } + }, "filterStdout": { "type": "boolean", "description": "%c_cpp.debuggers.filterStdout.description%", diff --git a/Extension/src/Debugger/attachWaitFor.ts b/Extension/src/Debugger/attachWaitFor.ts new file mode 100644 index 0000000000..8408b38f38 --- /dev/null +++ b/Extension/src/Debugger/attachWaitFor.ts @@ -0,0 +1,84 @@ + +import * as vscode from 'vscode'; +import * as util from '../common'; +import { sleep } from '../Utility/Async/sleep'; +import { PsProcessParser } from './nativeAttach'; + +export class AttachWaitFor { + + constructor() { + this._channel = vscode.window.createOutputChannel('waitfor-attach'); + this.timeout = 30000 + } + + private _channel: vscode.OutputChannel; + private timeout: number; + + public async WaitForProcess(program: string, timeout: number): Promise { + if (timeout) { + this.timeout = timeout + } + + return await this.poll(program) + } + + //Naive poll mechanism, parses /proc for a while till a match is found + private async poll(program: string): Promise { + this._channel.clear() + const startTime = Date.now(); // Get the current time in milliseconds + let seen = new Set(); + let process: string | undefined; + while (true) { + const elapsedTime = Date.now() - startTime; + + if (elapsedTime >= this.timeout) { + console.log('Timeout reached. No process matched pattern.'); + return undefined + } + + const output: string = await util.execChildProcess(PsProcessParser.psLinuxCommand, undefined, this._channel) + const lines: string[] = output.split(/\r?\n/); + const processes: string[] = lines.slice(1); + const processAttach = PsProcessParser.ParseProcessFromPsArray(processes) + .sort((a, b) => { + if (a.name === undefined) { + if (b.name === undefined) { + return 0; + } + return 1; + } + if (b.name === undefined) { + return -1; + } + const aLower: string = a.name.toLowerCase(); + const bLower: string = b.name.toLowerCase(); + if (aLower === bLower) { + return 0; + } + return aLower < bLower ? -1 : 1; + }) + .map(p => p.toAttachItem()); + processAttach.forEach(p => { + if (!process && p.detail!.includes(program)) { + console.log("Found program waiting for with pid %s - info %s", p.id!, p.detail!) + process = p.id! + + // Send sigstop by default? + util.execChildProcess(`kill -STOP ${process}`, undefined, this._channel) + return + } + + if (seen.has(p.id!) == false && p.label != "ps" && !p.detail!.includes("ps")) { + seen.add(p.id!) + } + }) + + if (process) { + return process + } + + sleep(200) + } + } + +} diff --git a/Extension/src/Debugger/configurationProvider.ts b/Extension/src/Debugger/configurationProvider.ts index f4f8745016..19e0c639b0 100644 --- a/Extension/src/Debugger/configurationProvider.ts +++ b/Extension/src/Debugger/configurationProvider.ts @@ -22,6 +22,7 @@ import { PlatformInformation } from '../platform'; import { rsync, scp, ssh } from '../SSH/commands'; import * as Telemetry from '../telemetry'; import { AttachItemsProvider, AttachPicker, RemoteAttachPicker } from './attachToProcess'; +import { AttachWaitFor } from './attachWaitFor'; import { ConfigMenu, ConfigMode, ConfigSource, CppDebugConfiguration, DebuggerEvent, DebuggerType, DebugType, IConfiguration, IConfigurationSnippet, isDebugLaunchStr, MIConfigurations, PipeTransportConfigurations, TaskStatus, WindowsConfigurations, WSLConfigurations } from './configurations'; import { NativeAttachItemsProviderFactory } from './nativeAttach'; import { Environment, ParsedEnvironmentFile } from './ParsedEnvironmentFile'; @@ -347,16 +348,20 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv } } - // Pick process if process id is empty if (config.request === "attach" && !config.processId) { let processId: string | undefined; - if (config.pipeTransport || config.useExtendedRemote) { - const remoteAttachPicker: RemoteAttachPicker = new RemoteAttachPicker(); - processId = await remoteAttachPicker.ShowAttachEntries(config); + if (config.waitFor.enabled) { + const waitForAttach: AttachWaitFor = new AttachWaitFor() + processId = await waitForAttach.WaitForProcess(config.waitFor.pattern, config.waitFor.timeout) } else { - const attachItemsProvider: AttachItemsProvider = NativeAttachItemsProviderFactory.Get(); - const attacher: AttachPicker = new AttachPicker(attachItemsProvider); - processId = await attacher.ShowAttachEntries(token); + if (config.pipeTransport || config.useExtendedRemote) { + const remoteAttachPicker: RemoteAttachPicker = new RemoteAttachPicker(); + processId = await remoteAttachPicker.ShowAttachEntries(config); + } else { + const attachItemsProvider: AttachItemsProvider = NativeAttachItemsProviderFactory.Get(); + const attacher: AttachPicker = new AttachPicker(attachItemsProvider); + processId = await attacher.ShowAttachEntries(token); + } } if (processId) { diff --git a/Extension/src/Debugger/configurations.ts b/Extension/src/Debugger/configurations.ts index 96895c6da2..d7d514f951 100644 --- a/Extension/src/Debugger/configurations.ts +++ b/Extension/src/Debugger/configurations.ts @@ -97,7 +97,7 @@ function createLaunchString(name: string, type: string, executable: string): str "stopAtEntry": false, "cwd": "$\{fileDirname\}", "environment": [], -${ type === "cppdbg" ? `"externalConsole": false` : `"console": "externalTerminal"` } +${type === "cppdbg" ? `"externalConsole": false` : `"console": "externalTerminal"`} `; } @@ -106,6 +106,7 @@ function createAttachString(name: string, type: string, executable: string): str "name": "${name}", "type": "${type}", "request": "attach",{0} +"waitFor": false, `, [type === "cppdbg" ? `${os.EOL}"program": "${localize("enter.program.name", "enter program name, for example {0}", "$\{workspaceFolder\}" + "/" + executable).replace(/"/g, '')}",` : ""]); } @@ -114,11 +115,13 @@ function createRemoteAttachString(name: string, type: string, executable: string "name": "${name}", "type": "${type}", "request": "attach", +"waitFor": false, "program": "${localize("enter.program.name", "enter program name, for example {0}", "$\{workspaceFolder\}" + "/" + executable).replace(/"/g, '')}", "processId": "$\{command:pickRemoteProcess\}" `; } + function createPipeTransportString(pipeProgram: string, debuggerProgram: string, pipeArgs: string[] = []): string { return ` "pipeTransport": { @@ -164,7 +167,7 @@ export class MIConfigurations extends Configuration { \t${indentJsonString(createLaunchString(name, this.miDebugger, this.executable))}, \t"MIMode": "${this.MIMode}"{0}{1} }`, [this.miDebugger === "cppdbg" && os.platform() === "win32" ? `,${os.EOL}\t"miDebuggerPath": "/path/to/gdb"` : "", - this.additionalProperties ? `,${os.EOL}\t${indentJsonString(this.additionalProperties)}` : ""]); + this.additionalProperties ? `,${os.EOL}\t${indentJsonString(this.additionalProperties)}` : ""]); return { "label": configPrefix + name, @@ -182,7 +185,7 @@ export class MIConfigurations extends Configuration { \t${indentJsonString(createAttachString(name, this.miDebugger, this.executable))} \t"MIMode": "${this.MIMode}"{0}{1} }`, [this.miDebugger === "cppdbg" && os.platform() === "win32" ? `,${os.EOL}\t"miDebuggerPath": "/path/to/gdb"` : "", - this.additionalProperties ? `,${os.EOL}\t${indentJsonString(this.additionalProperties)}` : ""]); + this.additionalProperties ? `,${os.EOL}\t${indentJsonString(this.additionalProperties)}` : ""]); return { "label": configPrefix + name, diff --git a/Extension/src/common.ts b/Extension/src/common.ts index 8817767b16..5f5001b144 100644 --- a/Extension/src/common.ts +++ b/Extension/src/common.ts @@ -734,13 +734,18 @@ export function execChildProcess(process: string, workingDirectory?: string, cha } } - if (error) { - reject(error); - return; + if (stderr && stderr.length > 0) { + if (stderr.indexOf('screen size is bogus') >= 0) { + // ignore this error silently; see https://github.com/microsoft/vscode/issues/75932 + // see similar fix for the Node - Debug (Legacy) Extension at https://github.com/microsoft/vscode-node-debug/commit/5298920 + } else { + reject(error); + return; + } } - if (stderr && stderr.length > 0) { - reject(new Error(stderr)); + if (error) { + reject(error); return; }