Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle ProcessPicker via resolveDebugConfiguration #4509

Merged
merged 4 commits into from Apr 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion debugger.md
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
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
@@ -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
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
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
@@ -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)
WardenGnaw marked this conversation as resolved.
Show resolved Hide resolved
{
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
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()));
WardenGnaw marked this conversation as resolved.
Show resolved Hide resolved
// 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
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
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