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

Adding changes to support use monovsdbg to debug wasm apps. #7220

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion omnisharptest/omnisharpUnitTests/assets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,8 @@ function createMSBuildWorkspaceInformation(
isExe = true,
isWebProject = false,
isBlazorWebAssemblyStandalone = false,
isBlazorWebAssemblyHosted = false
isBlazorWebAssemblyHosted = false,
isWebAssemblyProject = false
): ProjectDebugInformation[] {
return [
{
Expand All @@ -527,6 +528,7 @@ function createMSBuildWorkspaceInformation(
isWebProject: isWebProject,
isBlazorWebAssemblyHosted: isBlazorWebAssemblyHosted,
isBlazorWebAssemblyStandalone: isBlazorWebAssemblyStandalone,
isWebAssemblyProject: isWebAssemblyProject,
},
];
}
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,11 @@
"description": "%generateOptionsSchema.expressionEvaluationOptions.showRawValues.description%",
"default": false
},
"csharp.mono.debug.useVSDbg": {
"type": "boolean",
"description": "%generateOptionsSchema.useVSDbg.description%",
"default": false
},
"dotnet.unitTestDebuggingOptions": {
"type": "object",
"description": "%configuration.dotnet.unitTestDebuggingOptions%",
Expand Down Expand Up @@ -4818,6 +4823,11 @@
"type": "object",
"description": "Environment variables passed to dotnet. Only valid for hosted apps."
},
"useVSDbg": {
"type": "boolean",
"default": false,
"description": "%generateOptionsSchema.useVSDbg.description%"
thaystg marked this conversation as resolved.
Show resolved Hide resolved
},
"dotNetConfig": {
"description": "Options passed to the underlying .NET debugger. For more info, see https://github.com/dotnet/vscode-csharp/blob/main/debugger.md.",
"type": "object",
Expand Down
3 changes: 2 additions & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -435,5 +435,6 @@
"comment": [
"Markdown text between `` should not be translated or localized (they represent literal text) and the capitalization, spacing, and punctuation (including the ``) should not be altered."
]
}
},
"generateOptionsSchema.useVSDbg.description": "Enable new .NET 8+ Mono Debugger (preview)"
thaystg marked this conversation as resolved.
Show resolved Hide resolved
}
6 changes: 0 additions & 6 deletions src/coreclrDebug/activate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,6 @@ export async function activate(
new BaseVsDbgConfigurationProvider(platformInformation, csharpOutputChannel)
)
);
context.subscriptions.push(
vscode.debug.registerDebugConfigurationProvider(
'monovsdbg',
new BaseVsDbgConfigurationProvider(platformInformation, csharpOutputChannel)
)
);
disposables.add(vscode.debug.registerDebugAdapterDescriptorFactory('coreclr', factory));
disposables.add(vscode.debug.registerDebugAdapterDescriptorFactory('clr', factory));
disposables.add(vscode.debug.registerDebugAdapterDescriptorFactory('monovsdbg', factory));
Expand Down
1 change: 1 addition & 0 deletions src/csharpExtensionExports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface CSharpExtensionExports {
determineBrowserType: () => Promise<string | undefined>;
experimental: CSharpExtensionExperimentalExports;
getComponentFolder: (componentName: string) => string;
tryToUseVSDbgForMono: (urlStr: string) => Promise<[string, number, number]>;
}

export interface CSharpExtensionExperimentalExports {
Expand Down
5 changes: 5 additions & 0 deletions src/lsptoolshost/debugger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ export function registerDebugger(
context.subscriptions.push(
vscode.debug.registerDebugConfigurationProvider('coreclr', dotnetWorkspaceConfigurationProvider)
);

context.subscriptions.push(
vscode.debug.registerDebugConfigurationProvider('monovsdbg', dotnetWorkspaceConfigurationProvider)
);

context.subscriptions.push(
vscode.commands.registerCommand('dotnet.generateAssets', async (selectedIndex) =>
generateAssets(workspaceInformationProvider, selectedIndex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ import {
IWorkspaceDebugInformationProvider,
ProjectDebugInformation,
} from '../shared/IWorkspaceDebugInformationProvider';
import { isBlazorWebAssemblyHosted, isBlazorWebAssemblyProject, isWebProject } from '../shared/utils';
import {
isBlazorWebAssemblyHosted,
isBlazorWebAssemblyProject,
isWebProject,
isWebAssemblyProject,
} from '../shared/utils';
import { RoslynLanguageServer } from './roslynLanguageServer';
import {
ProjectDebugConfiguration,
Expand Down Expand Up @@ -50,6 +55,7 @@ export class RoslynWorkspaceDebugInformationProvider implements IWorkspaceDebugI
// LSP serializes and deserializes URIs as (URI formatted) strings not actual types. So convert to the actual type here.
const projects: ProjectDebugInformation[] | undefined = await mapAsync(response, async (p) => {
const webProject = isWebProject(p.projectPath);
const webAssemblyProject = isWebAssemblyProject(p.projectPath);
const webAssemblyBlazor = await isBlazorWebAssemblyProject(p.projectPath);
return {
projectPath: p.projectPath,
Expand All @@ -58,6 +64,7 @@ export class RoslynWorkspaceDebugInformationProvider implements IWorkspaceDebugI
targetsDotnetCore: p.targetsDotnetCore,
isExe: p.isExe,
isWebProject: webProject,
isWebAssemblyProject: webAssemblyProject,
isBlazorWebAssemblyHosted: isBlazorWebAssemblyHosted(
p.isExe,
webProject,
Expand Down
1 change: 1 addition & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ export async function activate(
getComponentFolder: (componentName) => {
return getComponentFolder(componentName, languageServerOptions);
},
tryToUseVSDbgForMono: BlazorDebugConfigurationProvider.tryToUseVSDbgForMono,
};
} else {
return {
Expand Down
1 change: 1 addition & 0 deletions src/omnisharp/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ export interface MSBuildProject {
IsWebProject: boolean;
IsBlazorWebAssemblyStandalone: boolean;
IsBlazorWebAssemblyHosted: boolean;
IsWebAssemblyProject: boolean;
}

export interface TargetFramework {
Expand Down
3 changes: 2 additions & 1 deletion src/omnisharp/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as vscode from 'vscode';
import { CancellationToken } from 'vscode-languageserver-protocol';
import {
isWebProject,
isWebAssemblyProject,
isBlazorWebAssemblyProject,
isBlazorWebAssemblyHosted,
findNetCoreTargetFramework,
Expand Down Expand Up @@ -171,7 +172,7 @@ export async function requestWorkspaceInformation(server: OmniSharpServer) {
if (response.MsBuild && response.MsBuild.Projects) {
for (const project of response.MsBuild.Projects) {
project.IsWebProject = isWebProject(project.Path);

project.IsWebAssemblyProject = isWebAssemblyProject(project.Path);
const isProjectBlazorWebAssemblyProject = await isBlazorWebAssemblyProject(project.Path);

const targetsDotnetCore =
Expand Down
1 change: 1 addition & 0 deletions src/omnisharpWorkspaceDebugInformationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class OmnisharpWorkspaceDebugInformationProvider implements IWorkspaceDeb
isWebProject: p.IsWebProject,
isBlazorWebAssemblyHosted: p.IsBlazorWebAssemblyHosted,
isBlazorWebAssemblyStandalone: p.IsBlazorWebAssemblyStandalone,
isWebAssemblyProject: p.IsWebAssemblyProject,
solutionPath: null,
};
});
Expand Down
167 changes: 164 additions & 3 deletions src/razor/src/blazorDebug/blazorDebugConfigurationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,22 @@ import { fileURLToPath } from 'url';
import * as vscode from 'vscode';
import { ChromeBrowserFinder, EdgeBrowserFinder } from '@vscode/js-debug-browsers';
import { RazorLogger } from '../razorLogger';
import { JS_DEBUG_NAME, SERVER_APP_NAME } from './constants';
import { ONLY_JS_DEBUG_NAME, MANAGED_DEBUG_NAME, JS_DEBUG_NAME, SERVER_APP_NAME } from './constants';
import { onDidTerminateDebugSession } from './terminateDebugHandler';
import showInformationMessage from '../../../shared/observers/utils/showInformationMessage';
import showErrorMessage from '../../../observers/utils/showErrorMessage';
import path = require('path');
import * as cp from 'child_process';
import { getExtensionPath } from '../../../common';

export class BlazorDebugConfigurationProvider implements vscode.DebugConfigurationProvider {
private static readonly autoDetectUserNotice: string = vscode.l10n.t(
'Run and Debug: auto-detection found {0} for a launch browser'
);
private static readonly edgeBrowserType: string = 'msedge';
private static readonly chromeBrowserType: string = 'chrome';
private static readonly pidsByUrl = new Map<string, number | undefined>();
private static readonly vsWebAssemblyBridgeOutputChannel = vscode.window.createOutputChannel('VsWebAssemblyBridge');

constructor(private readonly logger: RazorLogger, private readonly vscodeType: typeof vscode) {}

Expand Down Expand Up @@ -97,6 +102,7 @@ export class BlazorDebugConfigurationProvider implements vscode.DebugConfigurati
launchBrowser: {
enabled: false,
},
cascadeTerminateToConfigurations: [ONLY_JS_DEBUG_NAME, MANAGED_DEBUG_NAME, JS_DEBUG_NAME],
...configuration.dotNetConfig,
};

Expand Down Expand Up @@ -125,6 +131,13 @@ export class BlazorDebugConfigurationProvider implements vscode.DebugConfigurati
inspectUri: string,
url: string
) {
const wasmConfig = vscode.workspace.getConfiguration('csharp');
const useVSDbg = configuration.useVSDbg || wasmConfig.get<boolean>('mono.debug.useVSDbg') == true;
thaystg marked this conversation as resolved.
Show resolved Hide resolved
let portBrowserDebug = -1;
if (useVSDbg) {
[inspectUri, portBrowserDebug] = await this.attachToAppOnBrowser(folder, configuration);
}

const configBrowser = configuration.browser;
const browserType =
configBrowser === 'edge'
Expand All @@ -137,7 +150,7 @@ export class BlazorDebugConfigurationProvider implements vscode.DebugConfigurati
}

const browser = {
name: JS_DEBUG_NAME,
name: useVSDbg ? ONLY_JS_DEBUG_NAME : JS_DEBUG_NAME,
type: browserType,
request: 'launch',
timeout: configuration.timeout || 30000,
Expand All @@ -149,9 +162,13 @@ export class BlazorDebugConfigurationProvider implements vscode.DebugConfigurati
...configuration.browserConfig,
// When the browser debugging session is stopped, propogate
// this and terminate the debugging session of the Blazor dev server.
cascadeTerminateToConfigurations: [SERVER_APP_NAME],
cascadeTerminateToConfigurations: [SERVER_APP_NAME, MANAGED_DEBUG_NAME],
};

if (useVSDbg) {
browser.port = portBrowserDebug;
}

try {
/**
* The browser debugger will immediately launch after the
Expand Down Expand Up @@ -179,6 +196,150 @@ export class BlazorDebugConfigurationProvider implements vscode.DebugConfigurati
}
}

private async attachToAppOnBrowser(
folder: vscode.WorkspaceFolder | undefined,
configuration: vscode.DebugConfiguration
): Promise<[string, number]> {
const [inspectUriRet, portICorDebug, portBrowserDebug] =
await BlazorDebugConfigurationProvider.launchVsWebAssemblyBridge(configuration.url);

const cwd = configuration.cwd || '${workspaceFolder}';
const args = configuration.hosted ? [] : ['run'];
const app = {
name: MANAGED_DEBUG_NAME,
type: 'monovsdbg',
request: 'launch',
//program: 'C:\\diag\\icordebug_wasm_vscode\\test\\obj\\Debug\\net9.0\\test.dll',
args,
cwd,
cascadeTerminateToConfigurations: [ONLY_JS_DEBUG_NAME, SERVER_APP_NAME, JS_DEBUG_NAME],
...configuration.dotNetConfig,
};

app.monoDebuggerOptions = {
ip: '127.0.0.1',
port: portICorDebug,
platform: 'browser',
isServer: true,
//assetsPath: 'C:\\diag\\icordebug_wasm_vscode\\test\\bin\\Debug\\net9.0\\',
};

try {
await this.vscodeType.debug.startDebugging(folder, app);
const terminate = this.vscodeType.debug.onDidTerminateDebugSession(async (event) => {
if (process.platform !== 'win32') {
const blazorDevServer = 'blazor-devserver\\.dll';
const dir = folder && folder.uri && folder.uri.fsPath;
const regexEscapedDir = dir!.toLowerCase()!.replace(/\//g, '\\/');
const launchedApp = configuration.hosted
? app.program
: `${regexEscapedDir}.*${blazorDevServer}|${blazorDevServer}.*${regexEscapedDir}`;
await onDidTerminateDebugSession(event, this.logger, launchedApp);
terminate.dispose();
}
this.vscodeType.debug.stopDebugging();
});
} catch (error) {
this.logger.logError('[DEBUGGER] Error when launching application: ', error as Error);
}
return [inspectUriRet, portBrowserDebug];
}

private static async launchVsWebAssemblyBridge(urlStr: string): Promise<[string, number, number]> {
const dotnetPath = process.platform === 'win32' ? 'dotnet.exe' : 'dotnet';
const vsWebAssemblyBridge = path.join(
getExtensionPath(),
'.vswebassemblybridge',
'Microsoft.Diagnostics.BrowserDebugHost.dll'
);
const devToolsUrl = `http://localhost:0`; // Browser debugging port address
const spawnedProxyArgs = [
`${vsWebAssemblyBridge}`,
'--DevToolsUrl',
`${devToolsUrl}`,
'--UseVsDbg',
'true',
'--iCorDebugPort',
'-1',
'--OwnerPid',
`${process.pid}`,
];
const spawnedProxy = cp.spawn(dotnetPath, spawnedProxyArgs);

try {
let chunksProcessed = 0;
let proxyICorDebugPort = -1;
let proxyBrowserPort = -1;
for await (const output of spawnedProxy.stdout) {
// If we haven't found the URL in the first ten chunks processed
// then bail out.
if (chunksProcessed++ > 10) {
return ['', -1, -1];
}
BlazorDebugConfigurationProvider.vsWebAssemblyBridgeOutputChannel.appendLine(output);
// The debug proxy server outputs the port it is listening on in the
// standard output of the launched application. We need to pass this URL
// back to the debugger so we extract the URL from stdout using a regex.
// The debug proxy will not exit until killed via the `killDebugProxy`
// method so parsing stdout is necessary to extract the URL.
const matchExprProxyUrl = 'Now listening on: (?<url>.*)';
const matchExprICorDebugPort = 'Listening iCorDebug on: (?<port>.*)';
const matchExprBrowserPort = 'Waiting for browser on: (?<port>.*)';
const foundProxyUrl = `${output}`.match(matchExprProxyUrl);
const foundICorDebugPort = `${output}`.match(matchExprICorDebugPort);
const foundBrowserPort = `${output}`.match(matchExprBrowserPort);
const proxyUrlString = foundProxyUrl?.groups?.url;
if (foundICorDebugPort?.groups?.port != undefined) {
proxyICorDebugPort = Number(foundICorDebugPort?.groups?.port);
}
if (foundBrowserPort?.groups?.port != undefined) {
proxyBrowserPort = Number(foundBrowserPort?.groups?.port);
}
if (proxyUrlString) {
BlazorDebugConfigurationProvider.vsWebAssemblyBridgeOutputChannel.appendLine(
`Debugging proxy is running at: ${proxyUrlString}`
);
const oldPid = BlazorDebugConfigurationProvider.pidsByUrl.get(urlStr);
if (oldPid != undefined) {
process.kill(oldPid);
}
BlazorDebugConfigurationProvider.pidsByUrl.set(urlStr, spawnedProxy.pid);
const url = new URL(proxyUrlString);
return [
`${url.protocol.replace(`http`, `ws`)}//${url.host}{browserInspectUriPath}`,
proxyICorDebugPort,
proxyBrowserPort,
];
}
}

for await (const error of spawnedProxy.stderr) {
thaystg marked this conversation as resolved.
Show resolved Hide resolved
BlazorDebugConfigurationProvider.vsWebAssemblyBridgeOutputChannel.appendLine(`ERROR: ${error}`);
}
} catch (error: any) {
if (spawnedProxy.pid) {
BlazorDebugConfigurationProvider.vsWebAssemblyBridgeOutputChannel.appendLine(
`Error occured while spawning debug proxy. Terminating debug proxy server.`
);
process.kill(spawnedProxy.pid);
}
throw error;
}
return ['', -1, -1];
}

public static async tryToUseVSDbgForMono(urlStr: string): Promise<[string, number, number]> {
const wasmConfig = vscode.workspace.getConfiguration('csharp');
const useVSDbg = wasmConfig.get<boolean>('mono.debug.useVSDbg') == true;
if (useVSDbg) {
const [inspectUri, portICorDebug, portBrowserDebug] =
await BlazorDebugConfigurationProvider.launchVsWebAssemblyBridge(urlStr);

return [inspectUri, portICorDebug, portBrowserDebug];
}
return ['', -1, -1];
}

public static async determineBrowserType(): Promise<string | undefined> {
// There was no browser specified by the user, so we will do some auto-detection to find a browser,
// favoring Edge if multiple valid options are installed.
Expand Down
2 changes: 2 additions & 0 deletions src/razor/src/blazorDebug/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@

export const SERVER_APP_NAME = '.NET Application Server';
export const JS_DEBUG_NAME = 'Debug Blazor Web Assembly in Browser';
export const ONLY_JS_DEBUG_NAME = 'JavaScript Debugger';
export const MANAGED_DEBUG_NAME = 'Wasm Managed Debugger';
5 changes: 5 additions & 0 deletions src/shared/IWorkspaceDebugInformationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,9 @@ export interface ProjectDebugInformation {
* If this is a standalone blazor web assembly project.
*/
isBlazorWebAssemblyStandalone: boolean;

/**
* If this is a web assembly project.
*/
isWebAssemblyProject: boolean;
}
Loading
Loading