diff --git a/Extension/package.json b/Extension/package.json index b7cee40c5..e3cc83a34 100644 --- a/Extension/package.json +++ b/Extension/package.json @@ -4770,15 +4770,8 @@ "attach": { "type": "object", "default": {}, - "required": [ - "program" - ], + "required": [], "properties": { - "program": { - "type": "string", - "description": "%c_cpp.debuggers.program.description%", - "default": "${workspaceRoot}/a.out" - }, "targetArchitecture": { "type": "string", "description": "%c_cpp.debuggers.targetArchitecture.description%", @@ -4824,6 +4817,10 @@ "description": "%c_cpp.debuggers.useExtendedRemote.description%", "default": false }, + "program": { + "type": "string", + "markdownDescription": "%c_cpp.debuggers.program.attach.markdownDescription%" + }, "processId": { "markdownDescription": "%c_cpp.debuggers.processId.anyOf.markdownDescription%", "anyOf": [ @@ -5789,15 +5786,17 @@ "attach": { "type": "object", "default": {}, - "required": [ - "processId" - ], + "required": [], "properties": { "symbolSearchPath": { "type": "string", "description": "%c_cpp.debuggers.symbolSearchPath.description%", "default": "" }, + "program": { + "type": "string", + "markdownDescription": "%c_cpp.debuggers.program.attach.markdownDescription%" + }, "processId": { "markdownDescription": "%c_cpp.debuggers.processId.anyOf.markdownDescription%", "anyOf": [ diff --git a/Extension/package.nls.json b/Extension/package.nls.json index d8a745803..9e35d8d5a 100644 --- a/Extension/package.nls.json +++ b/Extension/package.nls.json @@ -931,7 +931,13 @@ "{Locked=\"`${command:pickProcess}`\"}" ] }, - "c_cpp.debuggers.symbolSearchPath.description": "Semicolon separated list of directories to use to search for symbol (that is, pdb) files. Example: \"c:\\dir1;c:\\dir2\".", + "c_cpp.debuggers.program.attach.markdownDescription": { + "message": "Optional full path to the program executable. When specified, the debugger will search for a running process matching this executable path and attach to it. If multiple processes match, a selection prompt will be shown. Use either `program` or `processId`, not both.", + "comment": [ + "{Locked=\"`program`\"} {Locked=\"`processId`\"}" + ] + }, + "c_cpp.debuggers.symbolSearchPath.description": "Semicolon separated list of directories to use to search for symbol (that is, pdb or .so) files. Example: \"c:\\dir1;c:\\dir2\".", "c_cpp.debuggers.dumpPath.description": "Optional full path to a dump file for the specified program. Example: \"c:\\temp\\app.dmp\". Defaults to null.", "c_cpp.debuggers.enableDebugHeap.description": "If false, the process will be launched with debug heap disabled. This sets the environment variable '_NO_DEBUG_HEAP' to '1'.", "c_cpp.debuggers.symbolLoadInfo.description": "Explicit control of symbol loading.", diff --git a/Extension/src/Debugger/configurationProvider.ts b/Extension/src/Debugger/configurationProvider.ts index 559011251..9c54aec01 100644 --- a/Extension/src/Debugger/configurationProvider.ts +++ b/Extension/src/Debugger/configurationProvider.ts @@ -23,6 +23,7 @@ import { PlatformInformation } from '../platform'; import { rsync, scp, ssh } from '../SSH/commands'; import * as Telemetry from '../telemetry'; import { AttachItemsProvider, AttachPicker, RemoteAttachPicker } from './attachToProcess'; +import { AttachItem, showQuickPick } from './attachQuickPick'; 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'; @@ -354,13 +355,23 @@ 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); - } else { - const attachItemsProvider: AttachItemsProvider = NativeAttachItemsProviderFactory.Get(); - const attacher: AttachPicker = new AttachPicker(attachItemsProvider); - processId = await attacher.ShowAttachEntries(token); + + // If program is specified, try to find the matching process by name + if (config.program) { + processId = await this.findProcessByProgramName(config.program, config, token); + } + + // Fall back to process picker if program wasn't specified or didn't match + if (!processId) { + // Show the process picker if no program is specified + 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) { @@ -1157,6 +1168,50 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv } return true; } + + private async findProcessByProgramName( + programPath: string, + config: CppDebugConfiguration, + token?: vscode.CancellationToken + ): Promise { + // Remote attach is not supported for program-based matching + if (config.pipeTransport || config.useExtendedRemote) { + return undefined; + } + + // Validate that the program path is valid + if (!await util.checkExecutableWithoutExtensionExists(programPath)) { + return undefined; + } + + const programBaseName: string = path.basename(programPath); + + // Get the process list using the same logic as interactive attach + const attachItemsProvider: AttachItemsProvider = NativeAttachItemsProviderFactory.Get(); + const processes: AttachItem[] = await attachItemsProvider.getAttachItems(token); + + // Prepare target name for matching (case-insensitive on Windows only) + let targetName: string = programBaseName; + if (isWindows) { + targetName = targetName.toLowerCase(); + targetName = targetName.endsWith(".exe") ? targetName : (targetName + ".exe"); + } + + // Find processes matching the program name + const matchingProcesses: AttachItem[] = processes.filter(p => { + const processName: string = isWindows ? p.label.toLowerCase() : p.label; + return processName === targetName; + }); + + if (matchingProcesses.length === 0) { + return undefined; + } else if (matchingProcesses.length === 1) { + return matchingProcesses[0].id; + } else { + // Multiple matches - let the user choose + return showQuickPick(() => Promise.resolve(matchingProcesses)); + } + } } export interface IConfigurationAssetProvider {