Skip to content

Commit

Permalink
Handle ProcessPicker via resolveDebugConfiguration (#4509)
Browse files Browse the repository at this point in the history
* 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.

* 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

* Remove 'processId' from default config

* Remove 'processId' from assets.ts
  • Loading branch information
WardenGnaw authored Apr 21, 2021
1 parent ad19c37 commit 5497895
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 42 deletions.
2 changes: 1 addition & 1 deletion debugger.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <kbd>F5</kbd>) 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 <kbd>F5</kbd>) 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

Expand Down
16 changes: 7 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
]
Expand Down Expand Up @@ -2061,8 +2061,7 @@
"body": {
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "^\"\\${command:pickProcess}\""
"request": "attach"
}
},
{
Expand Down Expand Up @@ -2117,7 +2116,6 @@
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "^\"\\${command:pickRemoteProcess}\"",
"pipeTransport": {
"pipeCwd": "^\"\\${workspaceFolder}\"",
"pipeProgram": "^\"${1:enter the fully qualified path for the pipe program name, for example '/usr/bin/ssh'}\"",
Expand Down Expand Up @@ -2725,12 +2723,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
}
]
Expand Down
2 changes: 1 addition & 1 deletion scripts/remoteProcessPickerScript
Original file line number Diff line number Diff line change
@@ -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
3 changes: 1 addition & 2 deletions src/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,7 @@ export function createAttachConfiguration(): string {
const configuration = {
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "\${command:pickProcess}"
"request": "attach"
};

return JSON.stringify(configuration);
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr-debug/activate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { EventStream } from '../EventStream';
import CSharpExtensionExports from '../CSharpExtensionExports';
import { getRuntimeDependencyPackageWithId } from '../tools/RuntimeDependencyPackageUtils';
import { getDotnetInfo, DotnetInfo } from '../utils/getDotnetInfo';
import { DotnetDebugConfigurationProvider } from './debugConfigurationProvider';

let _debugUtil: CoreClrDebugUtil = null;

Expand All @@ -30,6 +31,8 @@ export async function activate(thisExtension: vscode.Extension<CSharpExtensionEx
}

const factory = new DebugAdapterExecutableFactory(platformInformation, eventStream, thisExtension.packageJSON, thisExtension.extensionPath);
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('coreclr', new DotnetDebugConfigurationProvider(platformInformation)));
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('clr', new DotnetDebugConfigurationProvider(platformInformation)));
context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('coreclr', factory));
context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('clr', factory));
}
Expand Down
64 changes: 64 additions & 0 deletions src/coreclr-debug/debugConfigurationProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';

import { RemoteAttachPicker, DotNetAttachItemsProviderFactory, AttachPicker, AttachItem } from '../features/processPicker';
import { PlatformInformation } from '../platform';

export class DotnetDebugConfigurationProvider implements vscode.DebugConfigurationProvider {
constructor(public platformInformation: PlatformInformation) {}

public async resolveDebugConfigurationWithSubstitutedVariables(folder: vscode.WorkspaceFolder | undefined, debugConfiguration: vscode.DebugConfiguration, token?: vscode.CancellationToken): Promise<vscode.DebugConfiguration>
{
if (!debugConfiguration)
{
return null;
}

// Process Id is empty, handle Attach to Process Dialog.
if (debugConfiguration.request === "attach" && !debugConfiguration.processId && !debugConfiguration.processName)
{
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 (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";
}
else
{
debugConfiguration.targetArchitecture = "arm64";
}
}
}
else
{
throw new Error("No process was selected.");
}
}

return debugConfiguration;
}
}
12 changes: 5 additions & 7 deletions src/features/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { DotNetAttachItemsProviderFactory, AttachPicker, RemoteAttachPicker } from './processPicker';
import { generateAssets } from '../assets';
import { ShowOmniSharpChannel, CommandDotNetRestoreStart, CommandDotNetRestoreProgress, CommandDotNetRestoreSucceeded, CommandDotNetRestoreFailed } from '../omnisharp/loggingEvents';
import { EventStream } from '../EventStream';
Expand Down Expand Up @@ -40,14 +39,13 @@ 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', () => ""));
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)));

Expand Down
42 changes: 23 additions & 19 deletions src/features/processPicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { getExtensionPath } from '../common';

export interface AttachItem extends vscode.QuickPickItem {
id: string;
flags: number;
}

export interface AttachItemsProvider {
Expand All @@ -23,7 +24,7 @@ export interface AttachItemsProvider {
export class AttachPicker {
constructor(private attachItemsProvider: AttachItemsProvider) { }

public async ShowAttachEntries(): Promise<string> {
public async ShowAttachEntries(): Promise<AttachItem> {
return this.attachItemsProvider.getAttachItems()
.then(processEntries => {
let attachPickOptions: vscode.QuickPickOptions = {
Expand All @@ -35,7 +36,7 @@ export class AttachPicker {

return vscode.window.showQuickPick(processEntries, attachPickOptions)
.then(chosenProcess => {
return chosenProcess ? chosenProcess.id : null;
return chosenProcess;
});
});
}
Expand All @@ -50,8 +51,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"; }

Expand Down Expand Up @@ -196,7 +197,7 @@ export class RemoteAttachPicker {
return args.map(arg => this.quoteArg(arg)).join(" ");
}

public static async ShowAttachEntries(args: any, platformInfo: PlatformInformation): Promise<string> {
public static async ShowAttachEntries(args: any, platformInfo: PlatformInformation): Promise<AttachItem> {
// Create remote attach output channel for errors.
if (!RemoteAttachPicker._channel) {
RemoteAttachPicker._channel = vscode.window.createOutputChannel('remote-attach');
Expand All @@ -210,13 +211,13 @@ export class RemoteAttachPicker {

if (!name) {
// Config name not found.
return Promise.reject<string>(new Error("Name not defined in current configuration."));
return Promise.reject<AttachItem>(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<string>(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<AttachItem>(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());

Expand All @@ -230,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<string>(new Error("Could not find a process id to attach.")); });
});
}
}

Expand Down Expand Up @@ -266,14 +266,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
};
}
}
Expand Down Expand Up @@ -404,17 +405,20 @@ export class PsOutputParser {
// - any leading whitespace
// - PID
// - whitespace
// - flags (hex value)
// - 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);
}
}
}
Expand Down Expand Up @@ -444,7 +448,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++) {
Expand All @@ -458,7 +462,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);
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/tools/OptionsSchema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
]
Expand Down

0 comments on commit 5497895

Please sign in to comment.