From 45bdf6bb6f6dff2ac009d5ebee6fbab8402d2d33 Mon Sep 17 00:00:00 2001 From: Xinyu Sui Date: Wed, 2 Jun 2021 14:17:58 -0700 Subject: [PATCH] disassembly view: initial commit --- .../debug/browser/debug.contribution.ts | 11 +- .../debug/browser/debugAdapterManager.ts | 12 +- .../debug/browser/debugEditorActions.ts | 29 +++ .../contrib/debug/browser/disassemblyView.ts | 199 ++++++++++++++++++ .../workbench/contrib/debug/common/debug.ts | 2 + .../contrib/debug/common/debugSchemas.ts | 25 +++ 6 files changed, 276 insertions(+), 2 deletions(-) create mode 100644 src/vs/workbench/contrib/debug/browser/disassemblyView.ts diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index c7fffde5224..8861d16e41f 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -16,7 +16,7 @@ import { CallStackView } from 'vs/workbench/contrib/debug/browser/callStackView' import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IDebugService, VIEWLET_ID, DEBUG_PANEL_ID, CONTEXT_IN_DEBUG_MODE, INTERNAL_CONSOLE_OPTIONS_SCHEMA, - CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, BREAKPOINT_EDITOR_CONTRIBUTION_ID, REPL_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, EDITOR_CONTRIBUTION_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, getStateLabel, State, CONTEXT_WATCH_ITEM_TYPE, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, + CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, BREAKPOINT_EDITOR_CONTRIBUTION_ID, REPL_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, EDITOR_CONTRIBUTION_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, getStateLabel, State, CONTEXT_WATCH_ITEM_TYPE, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, DISASSEMBLY_VIEW_ID, } from 'vs/workbench/contrib/debug/common/debug'; import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar'; import { DebugService } from 'vs/workbench/contrib/debug/browser/debugService'; @@ -50,6 +50,9 @@ import { registerColors } from 'vs/workbench/contrib/debug/browser/debugColors'; import { DebugEditorContribution } from 'vs/workbench/contrib/debug/browser/debugEditorContribution'; import { FileAccess } from 'vs/base/common/network'; import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { EditorDescriptor, IEditorRegistry } from 'vs/workbench/browser/editor'; +import { EditorExtensions } from 'vs/workbench/common/editor'; +import { DisassemblyView, DisassemblyViewInput } from 'vs/workbench/contrib/debug/browser/disassemblyView'; const debugCategory = nls.localize('debugCategory', "Debug"); registerColors(); @@ -373,6 +376,12 @@ viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('brea viewsRegistry.registerViews([{ id: WelcomeView.ID, name: WelcomeView.LABEL, containerIcon: icons.runViewIcon, ctorDescriptor: new SyncDescriptor(WelcomeView), order: 1, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer); viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), containerIcon: icons.loadedScriptsViewIcon, ctorDescriptor: new SyncDescriptor(LoadedScriptsView), order: 35, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); +// Register disassmelby editor +Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create(DisassemblyView, DISASSEMBLY_VIEW_ID, nls.localize('disassembly', "Disassembly")), + [new SyncDescriptor(DisassemblyViewInput)] +); + // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ diff --git a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts index 7f527846f82..7c319f99c86 100644 --- a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts @@ -18,7 +18,7 @@ import { IDebugConfiguration, IConfig, IDebugAdapterDescriptorFactory, IDebugAda import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { launchSchema, debuggersExtPoint, breakpointsExtPoint, presentationSchema } from 'vs/workbench/contrib/debug/common/debugSchemas'; +import { launchSchema, debuggersExtPoint, breakpointsExtPoint, presentationSchema, disassemblyExtPoint } from 'vs/workbench/contrib/debug/common/debugSchemas'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; @@ -41,6 +41,7 @@ export class AdapterManager implements IAdapterManager { private readonly _onDidRegisterDebugger = new Emitter(); private readonly _onDidDebuggersExtPointRead = new Emitter(); private breakpointModeIdsSet = new Set(); + private disassemblyModeIdsSet = new Set(); constructor( @IEditorService private readonly editorService: IEditorService, @@ -155,6 +156,15 @@ export class AdapterManager implements IAdapterManager { added.value.forEach(breakpoints => this.breakpointModeIdsSet.add(breakpoints.language)); }); }); + + disassemblyExtPoint.setHandler((extensions, delta) => { + delta.removed.forEach(removed => { + removed.value.forEach(disassembly => this.disassemblyModeIdsSet.delete(disassembly.language)); + }); + delta.added.forEach(added => { + added.value.forEach(disassembly => this.disassemblyModeIdsSet.add(disassembly.language)); + }); + }); } registerDebugAdapterFactory(debugTypes: string[], debugAdapterLauncher: IDebugAdapterFactory): IDisposable { diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 51e666b94e2..e03d2f378f7 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -26,6 +26,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { raceTimeout } from 'vs/base/common/async'; import { registerAction2, MenuId } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/browser/disassemblyView'; class ToggleBreakpointAction extends EditorAction2 { constructor() { @@ -133,6 +134,33 @@ class LogPointAction extends EditorAction2 { } } +class GoToDisassemblyAction extends EditorAction { + + public static readonly ID = 'editor.debug.action.goToDisassembly'; + public static readonly LABEL = nls.localize('goToDisassembly', "Go to Disassembly"); + + constructor() { + super({ + id: GoToDisassemblyAction.ID, + label: GoToDisassemblyAction.LABEL, + alias: 'Debug: Go to Disassembly', + precondition: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, PanelFocusContext.toNegated(), CONTEXT_DEBUG_STATE.isEqualTo('stopped'), EditorContextKeys.editorTextFocus), + contextMenuOpts: { + group: 'debug', + order: 5 + } + }); + } + + async run(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]): Promise { + const position = editor.getPosition(); + if (position && editor.hasModel()) { + const editorService = accessor.get(IEditorService); + editorService.openEditor(DisassemblyViewInput.instance); + } + } +} + export class RunToCursorAction extends EditorAction { public static readonly ID = 'editor.debug.action.runToCursor'; @@ -504,6 +532,7 @@ class CloseExceptionWidgetAction extends EditorAction { registerAction2(ToggleBreakpointAction); registerAction2(ConditionalBreakpointAction); registerAction2(LogPointAction); +registerEditorAction(GoToDisassemblyAction); registerEditorAction(RunToCursorAction); registerEditorAction(StepIntoTargetsAction); registerEditorAction(SelectionToReplAction); diff --git a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts new file mode 100644 index 00000000000..b3d94382b47 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts @@ -0,0 +1,199 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Dimension, append, $ } from 'vs/base/browser/dom'; +import { ITableRenderer, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { WorkbenchTable } from 'vs/platform/list/browser/listService'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; +import { EditorInput } from 'vs/workbench/common/editor'; +import { DISASSEMBLY_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; + +interface IDisassembledInstructionEntry { + allowBreakpoint: boolean; + isBreakpointSet: boolean; + instruction: DebugProtocol.DisassembledInstruction; +} + +export class DisassemblyView extends EditorPane { + + private _editorOptions: IEditorOptions; + private _disassembledInstructions: WorkbenchTable | null; + + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService + ) { + super(DISASSEMBLY_VIEW_ID, telemetryService, themeService, storageService); + + this._editorOptions = configurationService.getValue('editor'); + configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor')) { + this._editorOptions = configurationService.getValue('editor'); + // TODO: refresh view + } + }); + + this._disassembledInstructions = null; + } + + protected createEditor(parent: HTMLElement): void { + const lineHeight = this._editorOptions.lineHeight!; + const delegate = new class implements ITableVirtualDelegate{ + headerRowHeight: number = 0; // No header + getHeight(row: IDisassembledInstructionEntry): number { + return lineHeight; + } + }; + + this._disassembledInstructions = this._register(this._instantiationService.createInstance(WorkbenchTable, + 'DisassemblyView', parent, delegate, + [ + { + label: '', + tooltip: '', + weight: 0, + minimumWidth: 40, + maximumWidth: 40, + templateId: BreakpointRenderer.TEMPLATE_ID, + project(row: IDisassembledInstructionEntry): IDisassembledInstructionEntry { return row; } + }, + { + label: 'instructions', + tooltip: '', + weight: 0.3, + templateId: InstructionRenderer.TEMPLATE_ID, + project(row: IDisassembledInstructionEntry): IDisassembledInstructionEntry { return row; } + }, + ], + [ + this._instantiationService.createInstance(BreakpointRenderer), + this._instantiationService.createInstance(InstructionRenderer), + ], + { + identityProvider: { getId: (e: IDisassembledInstructionEntry) => e.instruction.address }, + horizontalScrolling: false, + overrideStyles: { + listBackground: editorBackground + }, + multipleSelectionSupport: false, + setRowLineHeight: false, + openOnSingleClick: false, + } + )) as WorkbenchTable; + + this.loadDisassembledInstructions('0x00005000'); + } + + layout(dimension: Dimension): void { + if (this._disassembledInstructions) { + this._disassembledInstructions.layout(dimension.height); + } + } + + private loadDisassembledInstructions(address: string): void { + const newEntries: IDisassembledInstructionEntry[] = []; + for (let i = 0; i < 50; i++) { + newEntries.push({ allowBreakpoint: true, isBreakpointSet: false, instruction: { address, instruction: 'instruction instruction, instruction' } }); + } + if (this._disassembledInstructions) { + // TODO: append/insert + this._disassembledInstructions.splice(0, this._disassembledInstructions.length, newEntries); + } + } + +} + +interface IBreakpointColumnTemplateData { + icon: HTMLImageElement +} + +class BreakpointRenderer implements ITableRenderer { + + static readonly TEMPLATE_ID = 'breakpoint'; + + templateId: string = BreakpointRenderer.TEMPLATE_ID; + + renderTemplate(container: HTMLElement): IBreakpointColumnTemplateData { + const icon = append(container, $('img.icon')); + return { icon }; + } + + renderElement(element: IDisassembledInstructionEntry, index: number, templateData: IBreakpointColumnTemplateData, height: number | undefined): void { + if (element.isBreakpointSet) { + // TODO: breakpoint icon + } + } + disposeTemplate(templateData: IBreakpointColumnTemplateData): void { } + +} + +interface IInstructionColumnTemplateData { + // TODO: hover widget? + instruction: HTMLElement; +} + +class InstructionRenderer implements ITableRenderer { + + static readonly TEMPLATE_ID = 'instruction'; + + templateId: string = InstructionRenderer.TEMPLATE_ID; + + renderTemplate(container: HTMLElement): IInstructionColumnTemplateData { + const instruction = append(container, $('instruction')); + return { instruction }; + } + + renderElement(element: IDisassembledInstructionEntry, index: number, templateData: IInstructionColumnTemplateData, height: number | undefined): void { + const instruction = element.instruction; + templateData.instruction.innerText = `${instruction.address}\t${instruction.instructionBytes}\t${instruction.instruction}`; + } + + disposeTemplate(templateData: IInstructionColumnTemplateData): void { } + +} + +export class DisassemblyViewInput extends EditorInput { + + static readonly ID = 'debug.disassemblyView.input'; + + override get typeId(): string { + return DisassemblyViewInput.ID; + } + + static _instance: DisassemblyViewInput; + static get instance() { + if (!DisassemblyViewInput._instance || DisassemblyViewInput._instance.isDisposed()) { + DisassemblyViewInput._instance = new DisassemblyViewInput(); + } + + return DisassemblyViewInput._instance; + } + + readonly resource = undefined; + + override getName(): string { + return localize('extensionsInputName', "Running Extensions"); + } + + override canSplit(): boolean { + return false; + } + + override matches(other: unknown): boolean { + return other instanceof DisassemblyViewInput; + } +} diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 68999dc311b..00dd54bb181 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -34,6 +34,7 @@ export const WATCH_VIEW_ID = 'workbench.debug.watchExpressionsView'; export const CALLSTACK_VIEW_ID = 'workbench.debug.callStackView'; export const LOADED_SCRIPTS_VIEW_ID = 'workbench.debug.loadedScriptsView'; export const BREAKPOINTS_VIEW_ID = 'workbench.debug.breakPointsView'; +export const DISASSEMBLY_VIEW_ID = 'workbench.debug.disassemblyView'; export const DEBUG_PANEL_ID = 'workbench.panel.repl'; export const REPL_VIEW_ID = 'workbench.panel.repl.view'; export const DEBUG_SERVICE_ID = 'debugService'; @@ -80,6 +81,7 @@ export const CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT = new RawContextKey export const CONTEXT_EXCEPTION_WIDGET_VISIBLE = new RawContextKey('exceptionWidgetVisible', false, { type: 'boolean', description: nls.localize('exceptionWidgetVisible', "True when the exception widget is visible.") }); export const CONTEXT_MULTI_SESSION_REPL = new RawContextKey('multiSessionRepl', false, { type: 'boolean', description: nls.localize('multiSessionRepl', "True when there is more than 1 debug console.") }); export const CONTEXT_MULTI_SESSION_DEBUG = new RawContextKey('multiSessionDebug', false, { type: 'boolean', description: nls.localize('multiSessionDebug', "True when there is more than 1 active debug session.") }); +export const CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED = new RawContextKey('disassembleRequestSupported', false, { type: 'boolean', description: nls.localize('disassembleRequestSupported', "True when the focused sessions supports disassemble request.") }); export const EDITOR_CONTRIBUTION_ID = 'editor.contrib.debug'; export const BREAKPOINT_EDITOR_CONTRIBUTION_ID = 'editor.contrib.breakpoint'; diff --git a/src/vs/workbench/contrib/debug/common/debugSchemas.ts b/src/vs/workbench/contrib/debug/common/debugSchemas.ts index a32cb7dd2e2..721ccecfb24 100644 --- a/src/vs/workbench/contrib/debug/common/debugSchemas.ts +++ b/src/vs/workbench/contrib/debug/common/debugSchemas.ts @@ -127,6 +127,31 @@ export const breakpointsExtPoint = extensionsRegistry.ExtensionsRegistry.registe } }); +export interface IDisassemblyContribution { + language: string; +} + +// disassembly extension point +export const disassemblyExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'disassembly', + jsonSchema: { + description: nls.localize('vscode.extension.contributes.disassembly', 'Contributes disassembly.'), + type: 'array', + defaultSnippets: [{ body: [{ language: '' }] }], + items: { + type: 'object', + additionalProperties: false, + defaultSnippets: [{ body: { language: '' } }], + properties: { + language: { + description: nls.localize('vscode.extension.contributes.disassembly.language', "Allow disassembly view for this language."), + type: 'string' + }, + } + } + } +}); + // debug general schema export const presentationSchema: IJSONSchema = {