From 4bfd862016166c2077e262816a85e5ddfeecc27b Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Mon, 19 Apr 2021 17:09:56 -0700 Subject: [PATCH 1/4] Handle ProcessPicker via resolveDebugConfiguration VS Code commands are limited to only being able to have a single output. We will handle having an empty processId and show the dialog for ProcessPicker in resolveDebugConfigurationWithSubstitutedVariables. We need multiple outputs to handle the latest macOS on Apple M1. For Apple Silicon M1 (ARM64), we need to determine if we need to use the x86_64 or arm64 debugger. We are able to detect if the process is using S_TRANSLATED using the ps commandline with 'flags', if it is set with 0x20000, it is emulated. --- src/coreclr-debug/activate.ts | 2 + .../debugConfigurationProvider.ts | 51 +++++++++++++++++++ src/features/commands.ts | 9 ++-- src/features/processPicker.ts | 44 ++++++++++++---- 4 files changed, 90 insertions(+), 16 deletions(-) create mode 100644 src/coreclr-debug/debugConfigurationProvider.ts diff --git a/src/coreclr-debug/activate.ts b/src/coreclr-debug/activate.ts index 8c8ca72d7..698a9e5e8 100644 --- a/src/coreclr-debug/activate.ts +++ b/src/coreclr-debug/activate.ts @@ -13,6 +13,7 @@ import { EventStream } from '../EventStream'; import CSharpExtensionExports from '../CSharpExtensionExports'; import { getRuntimeDependencyPackageWithId } from '../tools/RuntimeDependencyPackageUtils'; import { getDotnetInfo, DotnetInfo } from '../utils/getDotnetInfo'; +import { CoreCLRConfigurationProvider } from './debugConfigurationProvider'; let _debugUtil: CoreClrDebugUtil = null; @@ -30,6 +31,7 @@ export async function activate(thisExtension: vscode.Extension + { + if (!debugConfiguration) + { + return null; + } + + // Process Id is empty, handle Attach to Process Dialog. + if (debugConfiguration.request === "attach" && !debugConfiguration.processId) + { + let attachItemsProvider = DotNetAttachItemsProviderFactory.Get(); + let attacher = new AttachPicker(attachItemsProvider); + const process: AttachItem = await attacher.SelectProcess(); + + if (process) + { + debugConfiguration.processId = process.id; + if (this.platformInformation.isMacOS() && this.platformInformation.architecture == 'arm64') + { + if (process.flags & 0x20000) + { + debugConfiguration.targetArchitecture = "x86_64"; + } + else + { + debugConfiguration.targetArchitecture = "arm64"; + } + } + } + else + { + throw new Error("No process was selected."); + } + } + + return debugConfiguration; + } +} \ No newline at end of file diff --git a/src/features/commands.ts b/src/features/commands.ts index df2312b56..74a669118 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -11,7 +11,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as protocol from '../omnisharp/protocol'; import * as vscode from 'vscode'; -import { DotNetAttachItemsProviderFactory, AttachPicker, RemoteAttachPicker } from './processPicker'; +import { RemoteAttachPicker } from './processPicker'; import { generateAssets } from '../assets'; import { ShowOmniSharpChannel, CommandDotNetRestoreStart, CommandDotNetRestoreProgress, CommandDotNetRestoreSucceeded, CommandDotNetRestoreFailed } from '../omnisharp/loggingEvents'; import { EventStream } from '../EventStream'; @@ -40,10 +40,9 @@ export default function registerCommands(context: vscode.ExtensionContext, serve // running the command activates the extension, which is all we need for installation to kickoff disposable.add(vscode.commands.registerCommand('csharp.downloadDebugger', () => { })); - // register process picker for attach - let attachItemsProvider = DotNetAttachItemsProviderFactory.Get(); - let attacher = new AttachPicker(attachItemsProvider); - disposable.add(vscode.commands.registerCommand('csharp.listProcess', async () => attacher.ShowAttachEntries())); + // register process picker for attach for legacy configurations. + disposable.add(vscode.commands.registerCommand('csharp.listProcess', async () => "")); + // Register command for generating tasks.json and launch.json assets. disposable.add(vscode.commands.registerCommand('dotnet.generateAssets', async (selectedIndex) => generateAssets(server, selectedIndex))); // Register command for remote process picker for attach diff --git a/src/features/processPicker.ts b/src/features/processPicker.ts index 2a2294df1..8575df9c4 100644 --- a/src/features/processPicker.ts +++ b/src/features/processPicker.ts @@ -14,6 +14,7 @@ import { getExtensionPath } from '../common'; export interface AttachItem extends vscode.QuickPickItem { id: string; + flags: number; } export interface AttachItemsProvider { @@ -39,6 +40,23 @@ export class AttachPicker { }); }); } + + public async SelectProcess(): Promise { + return this.attachItemsProvider.getAttachItems() + .then(processEntries => { + let attachPickOptions: vscode.QuickPickOptions = { + ignoreFocusOut: true, + matchOnDescription: true, + matchOnDetail: true, + placeHolder: "Select the process to attach to" + }; + + return vscode.window.showQuickPick(processEntries, attachPickOptions) + .then(chosenProcess => { + return chosenProcess; + }); + }); + } } interface IPipeTransportOptions { @@ -50,8 +68,8 @@ interface IPipeTransportOptions { export class RemoteAttachPicker { public static get commColumnTitle() { return Array(PsOutputParser.secondColumnCharacters).join("a"); } - public static get linuxPsCommand() { return `ps axww -o pid=,comm=${RemoteAttachPicker.commColumnTitle},args=`; } - public static get osxPsCommand() { return `ps axww -o pid=,comm=${RemoteAttachPicker.commColumnTitle},args= -c`; } + public static get linuxPsCommand() { return `ps axww -o pid=,flags=,comm=${RemoteAttachPicker.commColumnTitle},args=`; } + public static get osxPsCommand() { return `ps axww -o pid=,flags=,comm=${RemoteAttachPicker.commColumnTitle},args= -c`; } public static get debuggerCommand() { return "${debuggerCommand}"; } public static get scriptShellCmd() { return "sh -s"; } @@ -266,14 +284,15 @@ export class RemoteAttachPicker { } class Process { - constructor(public name: string, public pid: string, public commandLine: string) { } + constructor(public name: string, public pid: string, public commandLine: string, public flags: number) { } public toAttachItem(): AttachItem { return { label: this.name, description: this.pid, detail: this.commandLine, - id: this.pid + id: this.pid, + flags: this.flags }; } } @@ -404,17 +423,20 @@ export class PsOutputParser { // - any leading whitespace // - PID // - whitespace + // - flags + // - whitespace // - executable name --> this is PsAttachItemsProvider.secondColumnCharacters - 1 because ps reserves one character // for the whitespace separator // - whitespace // - args (might be empty) - const psEntry = new RegExp(`^\\s*([0-9]+)\\s+(.{${PsOutputParser.secondColumnCharacters - 1}})\\s+(.*)$`); + const psEntry = new RegExp(`^\\s*([0-9]+)\\s+([0-9a-fA-F]+)\\s+(.{${PsOutputParser.secondColumnCharacters - 1}})\\s+(.*)$`); const matches = psEntry.exec(line); - if (matches && matches.length === 4) { + if (matches && matches.length === 5) { const pid = matches[1].trim(); - const executable = matches[2].trim(); - const cmdline = matches[3].trim(); - return new Process(executable, pid, cmdline); + const flags = parseInt(matches[2].trim(), 16); // flags comes in as hex + const executable = matches[3].trim(); + const cmdline = matches[4].trim(); + return new Process(executable, pid, cmdline, flags); } } } @@ -444,7 +466,7 @@ export class WmicOutputParser { // Only public for tests. public static parseProcessFromWmic(processes: string): Process[] { let lines = processes.split(os.EOL); - let currentProcess: Process = new Process(null, null, null); + let currentProcess: Process = new Process(null, null, null, null); let processEntries: Process[] = []; for (let i = 0; i < lines.length; i++) { @@ -458,7 +480,7 @@ export class WmicOutputParser { // Each entry of processes has ProcessId as the last line if (line.startsWith(WmicOutputParser.wmicPidTitle)) { processEntries.push(currentProcess); - currentProcess = new Process(null, null, null); + currentProcess = new Process(null, null, null, null); } } From 4a72d1d8df29782c14f7c6586983a8bb08c0d06b Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Tue, 20 Apr 2021 13:02:48 -0700 Subject: [PATCH 2/4] Addressing PR issues Migrate remoteProcessPicker to also be in resolve configuration Handle 'clr' type for remote process picking. Addressing PR issues. Added comment about 0x20000 --- debugger.md | 2 +- package.json | 16 +++++----- scripts/remoteProcessPickerScript | 2 +- src/assets.ts | 2 +- src/coreclr-debug/activate.ts | 5 +-- .../debugConfigurationProvider.ts | 27 ++++++++++++---- src/features/commands.ts | 7 ++-- src/features/processPicker.ts | 32 ++++--------------- src/tools/OptionsSchema.json | 6 ++-- 9 files changed, 47 insertions(+), 52 deletions(-) diff --git a/debugger.md b/debugger.md index f1ca107c3..bfaa1b0d6 100644 --- a/debugger.md +++ b/debugger.md @@ -77,7 +77,7 @@ The C# debugger supports attaching to processes. To do this, switch to the Debug ![Debug launch configuration drop down](https://raw.githubusercontent.com/wiki/OmniSharp/omnisharp-vscode/images/debug-launch-configurations.png) -Select the '.NET Core Attach' configuration. Clicking the play button (or pressing F5) will then try to attach. In launch.json, if `processId` is set to `"${command:pickProcess}"` this will provide UI to select which process to attach to. +Select the '.NET Core Attach' configuration. Clicking the play button (or pressing F5) will then try to attach. In launch.json, if `processId` is set to `""` this will provide UI to select which process to attach to. #### Remote Debugging diff --git a/package.json b/package.json index 5f1fdd9c4..37c584a9a 100644 --- a/package.json +++ b/package.json @@ -1624,12 +1624,12 @@ "anyOf": [ { "type": "string", - "description": "The process id to attach to. Use \"${command:pickProcess}\" to get a list of running processes to attach to. If 'processId' used, 'processName' should not be used.", - "default": "${command:pickProcess}" + "description": "The process id to attach to. Use \"\" to get a list of running processes to attach to. If 'processId' used, 'processName' should not be used.", + "default": "" }, { "type": "integer", - "description": "The process id to attach to. Use \"${command:pickProcess}\" to get a list of running processes to attach to. If 'processId' used, 'processName' should not be used.", + "description": "The process id to attach to. Use \"\" to get a list of running processes to attach to. If 'processId' used, 'processName' should not be used.", "default": 0 } ] @@ -2062,7 +2062,7 @@ "name": ".NET Core Attach", "type": "coreclr", "request": "attach", - "processId": "^\"\\${command:pickProcess}\"" + "processId": "" } }, { @@ -2117,7 +2117,7 @@ "name": ".NET Core Attach", "type": "coreclr", "request": "attach", - "processId": "^\"\\${command:pickRemoteProcess}\"", + "processId": "", "pipeTransport": { "pipeCwd": "^\"\\${workspaceFolder}\"", "pipeProgram": "^\"${1:enter the fully qualified path for the pipe program name, for example '/usr/bin/ssh'}\"", @@ -2725,12 +2725,12 @@ "anyOf": [ { "type": "string", - "description": "The process id to attach to. Use \"${command:pickProcess}\" to get a list of running processes to attach to. If 'processId' used, 'processName' should not be used.", - "default": "${command:pickProcess}" + "description": "The process id to attach to. Use \"\" to get a list of running processes to attach to. If 'processId' used, 'processName' should not be used.", + "default": "" }, { "type": "integer", - "description": "The process id to attach to. Use \"${command:pickProcess}\" to get a list of running processes to attach to. If 'processId' used, 'processName' should not be used.", + "description": "The process id to attach to. Use \"\" to get a list of running processes to attach to. If 'processId' used, 'processName' should not be used.", "default": 0 } ] diff --git a/scripts/remoteProcessPickerScript b/scripts/remoteProcessPickerScript index fd2e75a76..4c6e94748 100644 --- a/scripts/remoteProcessPickerScript +++ b/scripts/remoteProcessPickerScript @@ -1 +1 @@ -uname && if [ "$(uname)" = "Linux" ] ; then ps -axww -o pid=,comm=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,args= ; exit; elif [ "$(uname)" = "Darwin" ] ; then ps -axww -o pid=,comm=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,args= -c; fi +uname && if [ "$(uname)" = "Linux" ] ; then ps -axww -o pid=,flags=,comm=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,args= ; exit; elif [ "$(uname)" = "Darwin" ] ; then ps -axww -o pid=,flags=,comm=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,args= -c; fi \ No newline at end of file diff --git a/src/assets.ts b/src/assets.ts index 96a5e9757..9ce3f4c1b 100644 --- a/src/assets.ts +++ b/src/assets.ts @@ -421,7 +421,7 @@ export function createAttachConfiguration(): string { "name": ".NET Core Attach", "type": "coreclr", "request": "attach", - "processId": "\${command:pickProcess}" + "processId": "" }; return JSON.stringify(configuration); diff --git a/src/coreclr-debug/activate.ts b/src/coreclr-debug/activate.ts index 698a9e5e8..e515ac285 100644 --- a/src/coreclr-debug/activate.ts +++ b/src/coreclr-debug/activate.ts @@ -13,7 +13,7 @@ import { EventStream } from '../EventStream'; import CSharpExtensionExports from '../CSharpExtensionExports'; import { getRuntimeDependencyPackageWithId } from '../tools/RuntimeDependencyPackageUtils'; import { getDotnetInfo, DotnetInfo } from '../utils/getDotnetInfo'; -import { CoreCLRConfigurationProvider } from './debugConfigurationProvider'; +import { DotnetDebugConfigurationProvider } from './debugConfigurationProvider'; let _debugUtil: CoreClrDebugUtil = null; @@ -31,7 +31,8 @@ export async function activate(thisExtension: vscode.Extension @@ -19,17 +19,30 @@ export class CoreCLRConfigurationProvider implements vscode.DebugConfigurationPr } // Process Id is empty, handle Attach to Process Dialog. - if (debugConfiguration.request === "attach" && !debugConfiguration.processId) + if (debugConfiguration.request === "attach" && !debugConfiguration.processId && !debugConfiguration.processName) { - let attachItemsProvider = DotNetAttachItemsProviderFactory.Get(); - let attacher = new AttachPicker(attachItemsProvider); - const process: AttachItem = await attacher.SelectProcess(); + let process: AttachItem = undefined; + if (debugConfiguration.pipeTransport) + { + process = await RemoteAttachPicker.ShowAttachEntries(debugConfiguration, this.platformInformation); + } + else + { + let attachItemsProvider = DotNetAttachItemsProviderFactory.Get(); + let attacher = new AttachPicker(attachItemsProvider); + process = await attacher.ShowAttachEntries(); + } if (process) { debugConfiguration.processId = process.id; - if (this.platformInformation.isMacOS() && this.platformInformation.architecture == 'arm64') + + if (debugConfiguration.type == "coreclr" && + this.platformInformation.isMacOS() && + this.platformInformation.architecture == 'arm64') { + // For Apple Silicon M1, it is possible that the process we are attaching to is being emulated as x86_64. + // The process is emulated if it has process flags has P_TRANSLATED (0x20000). if (process.flags & 0x20000) { debugConfiguration.targetArchitecture = "x86_64"; diff --git a/src/features/commands.ts b/src/features/commands.ts index 74a669118..efae7cc47 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -11,7 +11,6 @@ import * as fs from 'fs'; import * as path from 'path'; import * as protocol from '../omnisharp/protocol'; import * as vscode from 'vscode'; -import { RemoteAttachPicker } from './processPicker'; import { generateAssets } from '../assets'; import { ShowOmniSharpChannel, CommandDotNetRestoreStart, CommandDotNetRestoreProgress, CommandDotNetRestoreSucceeded, CommandDotNetRestoreFailed } from '../omnisharp/loggingEvents'; import { EventStream } from '../EventStream'; @@ -41,12 +40,12 @@ export default function registerCommands(context: vscode.ExtensionContext, serve disposable.add(vscode.commands.registerCommand('csharp.downloadDebugger', () => { })); // register process picker for attach for legacy configurations. - disposable.add(vscode.commands.registerCommand('csharp.listProcess', async () => "")); + disposable.add(vscode.commands.registerCommand('csharp.listProcess', () => "")); + disposable.add(vscode.commands.registerCommand('csharp.listRemoteProcess', () => "")); + // Register command for generating tasks.json and launch.json assets. disposable.add(vscode.commands.registerCommand('dotnet.generateAssets', async (selectedIndex) => generateAssets(server, selectedIndex))); - // Register command for remote process picker for attach - disposable.add(vscode.commands.registerCommand('csharp.listRemoteProcess', async (args) => RemoteAttachPicker.ShowAttachEntries(args, platformInfo))); disposable.add(vscode.commands.registerCommand('csharp.reportIssue', async () => reportIssue(vscode, eventStream, getDotnetInfo, platformInfo.isValidPlatformForMono(), optionProvider.GetLatestOptions(), monoResolver))); diff --git a/src/features/processPicker.ts b/src/features/processPicker.ts index 8575df9c4..900efe37b 100644 --- a/src/features/processPicker.ts +++ b/src/features/processPicker.ts @@ -24,24 +24,7 @@ export interface AttachItemsProvider { export class AttachPicker { constructor(private attachItemsProvider: AttachItemsProvider) { } - public async ShowAttachEntries(): Promise { - return this.attachItemsProvider.getAttachItems() - .then(processEntries => { - let attachPickOptions: vscode.QuickPickOptions = { - ignoreFocusOut: true, - matchOnDescription: true, - matchOnDetail: true, - placeHolder: "Select the process to attach to" - }; - - return vscode.window.showQuickPick(processEntries, attachPickOptions) - .then(chosenProcess => { - return chosenProcess ? chosenProcess.id : null; - }); - }); - } - - public async SelectProcess(): Promise { + public async ShowAttachEntries(): Promise { return this.attachItemsProvider.getAttachItems() .then(processEntries => { let attachPickOptions: vscode.QuickPickOptions = { @@ -214,7 +197,7 @@ export class RemoteAttachPicker { return args.map(arg => this.quoteArg(arg)).join(" "); } - public static async ShowAttachEntries(args: any, platformInfo: PlatformInformation): Promise { + public static async ShowAttachEntries(args: any, platformInfo: PlatformInformation): Promise { // Create remote attach output channel for errors. if (!RemoteAttachPicker._channel) { RemoteAttachPicker._channel = vscode.window.createOutputChannel('remote-attach'); @@ -228,13 +211,13 @@ export class RemoteAttachPicker { if (!name) { // Config name not found. - return Promise.reject(new Error("Name not defined in current configuration.")); + return Promise.reject(new Error("Name not defined in current configuration.")); } if (!args.pipeTransport || !args.pipeTransport.debuggerPath) { // Missing PipeTransport and debuggerPath, prompt if user wanted to just do local attach. - return Promise.reject(new Error("Configuration \"" + name + "\" in launch.json does not have a " + - "pipeTransport argument with debuggerPath for pickRemoteProcess. Use pickProcess for local attach.")); + return Promise.reject(new Error("Configuration \"" + name + "\" in launch.json does not have a " + + "pipeTransport argument with debuggerPath for remote process listing.")); } else { let pipeTransport = this.getPipeTransportOptions(args.pipeTransport, os.platform()); @@ -248,8 +231,7 @@ export class RemoteAttachPicker { placeHolder: "Select the process to attach to" }; return vscode.window.showQuickPick(processes, attachPickOptions); - }) - .then(item => { return item ? item.id : Promise.reject(new Error("Could not find a process id to attach.")); }); + }); } } @@ -423,7 +405,7 @@ export class PsOutputParser { // - any leading whitespace // - PID // - whitespace - // - flags + // - flags (hex value) // - whitespace // - executable name --> this is PsAttachItemsProvider.secondColumnCharacters - 1 because ps reserves one character // for the whitespace separator diff --git a/src/tools/OptionsSchema.json b/src/tools/OptionsSchema.json index f43342d28..8dc938413 100644 --- a/src/tools/OptionsSchema.json +++ b/src/tools/OptionsSchema.json @@ -406,12 +406,12 @@ "anyOf": [ { "type": "string", - "description": "The process id to attach to. Use \"${command:pickProcess}\" to get a list of running processes to attach to. If 'processId' used, 'processName' should not be used.", - "default": "${command:pickProcess}" + "description": "The process id to attach to. Use \"\" to get a list of running processes to attach to. If 'processId' used, 'processName' should not be used.", + "default": "" }, { "type": "integer", - "description": "The process id to attach to. Use \"${command:pickProcess}\" to get a list of running processes to attach to. If 'processId' used, 'processName' should not be used.", + "description": "The process id to attach to. Use \"\" to get a list of running processes to attach to. If 'processId' used, 'processName' should not be used.", "default": 0 } ] From 93f562c1cba9f2ae50cdb7131bbf5f3ab0e3d3dc Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Tue, 20 Apr 2021 13:42:44 -0700 Subject: [PATCH 3/4] Remove 'processId' from default config --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 37c584a9a..0fbbace4b 100644 --- a/package.json +++ b/package.json @@ -2061,8 +2061,7 @@ "body": { "name": ".NET Core Attach", "type": "coreclr", - "request": "attach", - "processId": "" + "request": "attach" } }, { @@ -2117,7 +2116,6 @@ "name": ".NET Core Attach", "type": "coreclr", "request": "attach", - "processId": "", "pipeTransport": { "pipeCwd": "^\"\\${workspaceFolder}\"", "pipeProgram": "^\"${1:enter the fully qualified path for the pipe program name, for example '/usr/bin/ssh'}\"", From 2a17228f569f1d29c1da18a6f357d402b4f4e924 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Tue, 20 Apr 2021 16:11:51 -0700 Subject: [PATCH 4/4] Remove 'processId' from assets.ts --- src/assets.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/assets.ts b/src/assets.ts index 9ce3f4c1b..5b931aec9 100644 --- a/src/assets.ts +++ b/src/assets.ts @@ -420,8 +420,7 @@ export function createAttachConfiguration(): string { const configuration = { "name": ".NET Core Attach", "type": "coreclr", - "request": "attach", - "processId": "" + "request": "attach" }; return JSON.stringify(configuration);