disassembly view: initial commit

This commit is contained in:
Xinyu Sui
2021-07-07 12:35:51 -07:00
committed by Yue (Felix) Huang
parent 1cbd1c6b65
commit 45bdf6bb6f
6 changed files with 276 additions and 2 deletions
@@ -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<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
EditorDescriptor.create(DisassemblyView, DISASSEMBLY_VIEW_ID, nls.localize('disassembly', "Disassembly")),
[new SyncDescriptor(DisassemblyViewInput)]
);
// Register configuration
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
@@ -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<void>();
private readonly _onDidDebuggersExtPointRead = new Emitter<void>();
private breakpointModeIdsSet = new Set<string>();
private disassemblyModeIdsSet = new Set<string>();
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 {
@@ -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<void> {
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);
@@ -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<IDisassembledInstructionEntry> | 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<IEditorOptions>('editor');
configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('editor')) {
this._editorOptions = configurationService.getValue<IEditorOptions>('editor');
// TODO: refresh view
}
});
this._disassembledInstructions = null;
}
protected createEditor(parent: HTMLElement): void {
const lineHeight = this._editorOptions.lineHeight!;
const delegate = new class implements ITableVirtualDelegate<IDisassembledInstructionEntry>{
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<IDisassembledInstructionEntry>;
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<IDisassembledInstructionEntry, IBreakpointColumnTemplateData> {
static readonly TEMPLATE_ID = 'breakpoint';
templateId: string = BreakpointRenderer.TEMPLATE_ID;
renderTemplate(container: HTMLElement): IBreakpointColumnTemplateData {
const icon = append(container, $<HTMLImageElement>('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<IDisassembledInstructionEntry, IInstructionColumnTemplateData> {
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;
}
}
@@ -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<boolean>
export const CONTEXT_EXCEPTION_WIDGET_VISIBLE = new RawContextKey<boolean>('exceptionWidgetVisible', false, { type: 'boolean', description: nls.localize('exceptionWidgetVisible', "True when the exception widget is visible.") });
export const CONTEXT_MULTI_SESSION_REPL = new RawContextKey<boolean>('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<boolean>('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<boolean>('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';
@@ -127,6 +127,31 @@ export const breakpointsExtPoint = extensionsRegistry.ExtensionsRegistry.registe
}
});
export interface IDisassemblyContribution {
language: string;
}
// disassembly extension point
export const disassemblyExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IDisassemblyContribution[]>({
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 = {