Merge pull request #125737 from xisui-MSFT/dev/xisui/disassembly

Disassembly view
This commit is contained in:
Andre Weinand 2021-07-22 11:28:41 +02:00 committed by GitHub
commit 4f5f5de28f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1159 additions and 81 deletions

View file

@ -5,7 +5,7 @@
import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI as uri, UriComponents } from 'vs/base/common/uri';
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDataBreakpoint, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDataBreakpoint, IDebugSessionOptions, IInstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debug';
import {
ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext,
IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto, IDataBreakpointDto, IStartDebuggingOptions, IDebugConfiguration
@ -339,7 +339,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
return undefined;
}
private convertToDto(bps: (ReadonlyArray<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint>)): Array<ISourceBreakpointDto | IFunctionBreakpointDto | IDataBreakpointDto> {
private convertToDto(bps: (ReadonlyArray<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IInstructionBreakpoint>)): Array<ISourceBreakpointDto | IFunctionBreakpointDto | IDataBreakpointDto> {
return bps.map(bp => {
if ('name' in bp) {
const fbp = <IFunctionBreakpoint>bp;

View file

@ -6,8 +6,8 @@
import * as resources from 'vs/base/common/resources';
import * as dom from 'vs/base/browser/dom';
import { Action, IAction } from 'vs/base/common/actions';
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, IDebugModel, IDataBreakpoint, BREAKPOINTS_VIEW_ID, CONTEXT_BREAKPOINT_ITEM_TYPE, CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_IN_DEBUG_MODE, IBaseBreakpoint, IBreakpointEditorContribution, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINT_INPUT_FOCUSED } from 'vs/workbench/contrib/debug/common/debug';
import { ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, IDebugModel, IDataBreakpoint, BREAKPOINTS_VIEW_ID, CONTEXT_BREAKPOINT_ITEM_TYPE, CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_IN_DEBUG_MODE, IBaseBreakpoint, IBreakpointEditorContribution, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINT_INPUT_FOCUSED, IInstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debug';
import { ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, DataBreakpoint, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
@ -43,6 +43,8 @@ import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { Codicon } from 'vs/base/common/codicons';
import { equals } from 'vs/base/common/arrays';
import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput';
import { DisassemblyView } from 'vs/workbench/contrib/debug/browser/disassemblyView';
const $ = dom.$;
@ -57,10 +59,10 @@ function createCheckbox(): HTMLInputElement {
const MAX_VISIBLE_BREAKPOINTS = 9;
export function getExpandedBodySize(model: IDebugModel, countLimit: number): number {
const length = model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length + model.getDataBreakpoints().length;
const length = model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length + model.getDataBreakpoints().length + model.getInstructionBreakpoints().length;
return Math.min(countLimit, length) * 22;
}
type BreakpointItem = IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IExceptionBreakpoint;
type BreakpointItem = IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IExceptionBreakpoint | IInstructionBreakpoint;
interface InputBoxData {
breakpoint: IFunctionBreakpoint | IExceptionBreakpoint;
@ -120,7 +122,8 @@ export class BreakpointsView extends ViewPane {
new ExceptionBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.themeService),
this.instantiationService.createInstance(FunctionBreakpointsRenderer, this.menu, this.breakpointSupportsCondition, this.breakpointItemType),
this.instantiationService.createInstance(DataBreakpointsRenderer),
new FunctionBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.themeService, this.labelService)
new FunctionBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.themeService, this.labelService),
this.instantiationService.createInstance(InstructionBreakpointsRenderer),
], {
identityProvider: { getId: (element: IEnablement) => element.getId() },
multipleSelectionSupport: false,
@ -142,6 +145,8 @@ export class BreakpointsView extends ViewPane {
await this.debugService.removeFunctionBreakpoints(element.getId());
} else if (element instanceof DataBreakpoint) {
await this.debugService.removeDataBreakpoints(element.getId());
} else if (element instanceof InstructionBreakpoint) {
await this.debugService.removeInstructionBreakpoints(element.instructionReference);
}
});
@ -157,6 +162,10 @@ export class BreakpointsView extends ViewPane {
if (e.element instanceof Breakpoint) {
openBreakpointSource(e.element, e.sideBySide, e.editorOptions.preserveFocus || false, e.editorOptions.pinned || !e.editorOptions.preserveFocus, this.debugService, this.editorService);
}
if (e.element instanceof InstructionBreakpoint) {
const disassemblyView = await this.editorService.openEditor(DisassemblyViewInput.instance);
(disassemblyView as DisassemblyView).goToAddress(e.element.instructionReference);
}
if (e.browserEvent instanceof MouseEvent && e.browserEvent.detail === 2 && e.element instanceof FunctionBreakpoint && e.element !== this.inputBoxData?.breakpoint) {
// double click
this.renderInputBox({ breakpoint: e.element, type: 'name' });
@ -214,7 +223,8 @@ export class BreakpointsView extends ViewPane {
private onListContextMenu(e: IListContextMenuEvent<IEnablement>): void {
const element = e.element;
const type = element instanceof Breakpoint ? 'breakpoint' : element instanceof ExceptionBreakpoint ? 'exceptionBreakpoint' :
element instanceof FunctionBreakpoint ? 'functionBreakpoint' : element instanceof DataBreakpoint ? 'dataBreakpoint' : undefined;
element instanceof FunctionBreakpoint ? 'functionBreakpoint' : element instanceof DataBreakpoint ? 'dataBreakpoint' :
element instanceof InstructionBreakpoint ? 'instructionBreakpoint' : undefined;
this.breakpointItemType.set(type);
const session = this.debugService.getViewModel().focusedSession;
const conditionSupported = element instanceof ExceptionBreakpoint ? element.supportsCondition : (!session || !!session.capabilities.supportsConditionalBreakpoints);
@ -288,7 +298,7 @@ export class BreakpointsView extends ViewPane {
private get elements(): BreakpointItem[] {
const model = this.debugService.getModel();
const elements = (<ReadonlyArray<IEnablement>>model.getExceptionBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getDataBreakpoints()).concat(model.getBreakpoints());
const elements = (<ReadonlyArray<IEnablement>>model.getExceptionBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getDataBreakpoints()).concat(model.getBreakpoints()).concat(model.getInstructionBreakpoints());
return elements as BreakpointItem[];
}
@ -326,6 +336,9 @@ class BreakpointsDelegate implements IListVirtualDelegate<BreakpointItem> {
if (element instanceof DataBreakpoint) {
return DataBreakpointsRenderer.ID;
}
if (element instanceof InstructionBreakpoint) {
return InstructionBreakpointsRenderer.ID;
}
return '';
}
@ -362,6 +375,10 @@ interface IDataBreakpointTemplateData extends IBaseBreakpointWithIconTemplateDat
accessType: HTMLElement;
}
interface IInstructionBreakpointTemplateData extends IBaseBreakpointWithIconTemplateData {
address: HTMLElement;
}
interface IFunctionBreakpointInputTemplateData {
inputBox: InputBox;
checkbox: HTMLInputElement;
@ -673,6 +690,71 @@ class DataBreakpointsRenderer implements IListRenderer<DataBreakpoint, IDataBrea
}
}
class InstructionBreakpointsRenderer implements IListRenderer<IInstructionBreakpoint, IInstructionBreakpointTemplateData> {
constructor(
@IDebugService private readonly debugService: IDebugService,
@ILabelService private readonly labelService: ILabelService
) {
// noop
}
static readonly ID = 'instructionBreakpoints';
get templateId() {
return InstructionBreakpointsRenderer.ID;
}
renderTemplate(container: HTMLElement): IInstructionBreakpointTemplateData {
const data: IInstructionBreakpointTemplateData = Object.create(null);
data.breakpoint = dom.append(container, $('.breakpoint'));
data.icon = $('.icon');
data.checkbox = createCheckbox();
data.toDispose = [];
data.elementDisposable = [];
data.toDispose.push(dom.addStandardDisposableListener(data.checkbox, 'change', (e) => {
this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
}));
dom.append(data.breakpoint, data.icon);
dom.append(data.breakpoint, data.checkbox);
data.name = dom.append(data.breakpoint, $('span.name'));
data.address = dom.append(data.breakpoint, $('span.file-path'));
data.actionBar = new ActionBar(data.breakpoint);
data.toDispose.push(data.actionBar);
return data;
}
renderElement(breakpoint: IInstructionBreakpoint, index: number, data: IInstructionBreakpointTemplateData): void {
data.context = breakpoint;
data.breakpoint.classList.toggle('disabled', !this.debugService.getModel().areBreakpointsActivated());
data.name.textContent = breakpoint.instructionReference;
data.checkbox.checked = breakpoint.enabled;
const { message, icon } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), breakpoint, this.labelService);
data.icon.className = ThemeIcon.asClassName(icon);
data.breakpoint.title = breakpoint.message || message || '';
const debugActive = this.debugService.state === State.Running || this.debugService.state === State.Stopped;
if (debugActive && !breakpoint.verified) {
data.breakpoint.classList.add('disabled');
}
}
disposeElement(_element: IInstructionBreakpoint, _index: number, templateData: IInstructionBreakpointTemplateData): void {
dispose(templateData.elementDisposable);
}
disposeTemplate(templateData: IInstructionBreakpointTemplateData): void {
dispose(templateData.toDispose);
}
}
class FunctionBreakpointInputRenderer implements IListRenderer<IFunctionBreakpoint, IFunctionBreakpointInputTemplateData> {
constructor(
@ -927,7 +1009,7 @@ export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolea
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
}
export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: boolean, breakpoint: IBreakpoint | IFunctionBreakpoint | IDataBreakpoint, labelService?: ILabelService): { message?: string, icon: ThemeIcon } {
export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: boolean, breakpoint: BreakpointItem, labelService?: ILabelService): { message?: string, icon: ThemeIcon } {
const debugActive = state === State.Running || state === State.Stopped;
const breakpointIcon = breakpoint instanceof DataBreakpoint ? icons.dataBreakpoint : breakpoint instanceof FunctionBreakpoint ? icons.functionBreakpoint : breakpoint.logMessage ? icons.logBreakpoint : icons.breakpoint;
@ -985,6 +1067,32 @@ export function getBreakpointMessageAndIcon(state: State, breakpointsActivated:
};
}
if (breakpoint instanceof InstructionBreakpoint) {
if (!breakpoint.supported) {
return {
icon: breakpointIcon.unverified,
message: localize('instructionBreakpointUnsupported', "Instruction breakpoints not supported by this debug type"),
};
}
const messages: string[] = [];
if (breakpoint.message) {
messages.push(breakpoint.message);
} else if (breakpoint.instructionReference) {
messages.push(localize('instructionBreakpointAtAddress', "Instruction breakpoint at address {0}", breakpoint.instructionReference));
} else {
messages.push(localize('instructionBreakpoint', "Instruction breakpoint"));
}
if (breakpoint.hitCondition) {
messages.push(localize('hitCount', "Hit Count: {0}", breakpoint.hitCondition));
}
return {
icon: breakpointIcon.regular,
message: appendMessage(messages.join('\n'))
};
}
if (breakpoint.logMessage || breakpoint.condition || breakpoint.hitCondition) {
const messages: string[] = [];
@ -1099,6 +1207,8 @@ registerAction2(class extends Action2 {
await debugService.removeFunctionBreakpoints(breakpoint.getId());
} else if (breakpoint instanceof DataBreakpoint) {
await debugService.removeDataBreakpoints(breakpoint.getId());
} else if (breakpoint instanceof InstructionBreakpoint) {
await debugService.removeInstructionBreakpoints(breakpoint.instructionReference);
}
}
});

View file

@ -18,8 +18,8 @@ import { distinct } from 'vs/base/common/arrays';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { debugStackframe, debugStackframeFocused } from 'vs/workbench/contrib/debug/browser/debugIcons';
const topStackFrameColor = registerColor('editor.stackFrameHighlightBackground', { dark: '#ffff0033', light: '#ffff6673', hc: '#ffff0033' }, localize('topStackFrameLineHighlight', 'Background color for the highlight of line at the top stack frame position.'));
const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightBackground', { dark: '#7abd7a4d', light: '#cee7ce73', hc: '#7abd7a4d' }, localize('focusedStackFrameLineHighlight', 'Background color for the highlight of line at focused stack frame position.'));
export const topStackFrameColor = registerColor('editor.stackFrameHighlightBackground', { dark: '#ffff0033', light: '#ffff6673', hc: '#ffff0033' }, localize('topStackFrameLineHighlight', 'Background color for the highlight of line at the top stack frame position.'));
export const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightBackground', { dark: '#7abd7a4d', light: '#cee7ce73', hc: '#7abd7a4d' }, localize('focusedStackFrameLineHighlight', 'Background color for the highlight of line at focused stack frame position.'));
const stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;
// we need a separate decoration for glyph margin, since we do not want it on each line of a multi line statement.

View file

@ -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,10 @@ 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 { EditorExtensions } from 'vs/workbench/common/editor';
import { DisassemblyView, DisassemblyViewContribution } from 'vs/workbench/contrib/debug/browser/disassemblyView';
import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor';
import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput';
const debugCategory = nls.localize('debugCategory', "Debug");
registerColors();
@ -64,6 +68,7 @@ if (isWeb) {
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugToolBar, LifecyclePhase.Restored);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugContentProvider, LifecyclePhase.Eventually);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(StatusBarColorProvider, LifecyclePhase.Eventually);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DisassemblyViewContribution, LifecyclePhase.Eventually);
// Register Quick Access
Registry.as<IQuickAccessRegistry>(QuickAccessExtensions.Quickaccess).registerQuickAccessProvider({
@ -373,6 +378,13 @@ 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 disassembly view
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
EditorPaneDescriptor.create(DisassemblyView, DISASSEMBLY_VIEW_ID, nls.localize('disassembly', "Disassembly")),
[new SyncDescriptor(DisassemblyViewInput)]
);
// Register configuration
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({

View file

@ -8,7 +8,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IListService } from 'vs/platform/list/browser/listService';
import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, REPL_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, State, getStateLabel, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, REPL_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, State, getStateLabel, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, VIEWLET_ID, CONTEXT_DISASSEMBLY_VIEW_FOCUS } from 'vs/workbench/contrib/debug/common/debug';
import { Expression, Variable, Breakpoint, FunctionBreakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
@ -16,7 +16,7 @@ import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { openBreakpointSource } from 'vs/workbench/contrib/debug/browser/breakpointsView';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys';
@ -160,7 +160,12 @@ CommandsRegistry.registerCommand({
CommandsRegistry.registerCommand({
id: STEP_BACK_ID,
handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
getThreadAndRun(accessor, context, thread => thread.stepBack());
const contextKeyService = accessor.get(IContextKeyService);
if (CONTEXT_DISASSEMBLY_VIEW_FOCUS.getValue(contextKeyService)) {
getThreadAndRun(accessor, context, (thread: IThread) => thread.stepBack('instruction'));
} else {
getThreadAndRun(accessor, context, (thread: IThread) => thread.stepBack());
}
}
});
@ -256,7 +261,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
primary: isWeb ? (KeyMod.Alt | KeyCode.F10) : KeyCode.F10, // Browsers do not allow F10 to be binded so we have to bind an alternative
when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'),
handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
getThreadAndRun(accessor, context, (thread: IThread) => thread.next());
const contextKeyService = accessor.get(IContextKeyService);
if (CONTEXT_DISASSEMBLY_VIEW_FOCUS.getValue(contextKeyService)) {
getThreadAndRun(accessor, context, (thread: IThread) => thread.next('instruction'));
} else {
getThreadAndRun(accessor, context, (thread: IThread) => thread.next());
}
}
});
@ -267,7 +277,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
// Use a more flexible when clause to not allow full screen command to take over when F11 pressed a lot of times
when: CONTEXT_DEBUG_STATE.notEqualsTo('inactive'),
handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
getThreadAndRun(accessor, context, (thread: IThread) => thread.stepIn());
const contextKeyService = accessor.get(IContextKeyService);
if (CONTEXT_DISASSEMBLY_VIEW_FOCUS.getValue(contextKeyService)) {
getThreadAndRun(accessor, context, (thread: IThread) => thread.stepIn('instruction'));
} else {
getThreadAndRun(accessor, context, (thread: IThread) => thread.stepIn());
}
}
});
@ -277,7 +292,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
primary: KeyMod.Shift | KeyCode.F11,
when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'),
handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
getThreadAndRun(accessor, context, (thread: IThread) => thread.stepOut());
const contextKeyService = accessor.get(IContextKeyService);
if (CONTEXT_DISASSEMBLY_VIEW_FOCUS.getValue(contextKeyService)) {
getThreadAndRun(accessor, context, (thread: IThread) => thread.stepOut('instruction'));
} else {
getThreadAndRun(accessor, context, (thread: IThread) => thread.stepOut());
}
}
});

View file

@ -9,7 +9,7 @@ import { Range } from 'vs/editor/common/core/range';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { registerEditorAction, EditorAction, IActionOptions, EditorAction2 } from 'vs/editor/browser/editorExtensions';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, REPL_VIEW_ID, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, WATCH_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_EXCEPTION_WIDGET_VISIBLE } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, REPL_VIEW_ID, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, WATCH_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_EXCEPTION_WIDGET_VISIBLE, CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_LANGUAGE_SUPPORTS_DISASSEMBLE_REQUEST } from 'vs/workbench/contrib/debug/common/debug';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { openBreakpointSource } from 'vs/workbench/contrib/debug/browser/breakpointsView';
@ -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/common/disassemblyViewInput';
class ToggleBreakpointAction extends EditorAction2 {
constructor() {
@ -53,6 +54,7 @@ class ToggleBreakpointAction extends EditorAction2 {
}
async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]): Promise<void> {
// TODO: add disassembly F9
if (editor.hasModel()) {
const debugService = accessor.get(IDebugService);
const modelUri = editor.getModel().uri;
@ -133,6 +135,32 @@ class LogPointAction extends EditorAction2 {
}
}
class OpenDisassemblyViewAction extends EditorAction {
public static readonly ID = 'editor.debug.action.openDisassemblyView';
public static readonly LABEL = nls.localize('openDisassemblyView', "Open Disassembly View");
constructor() {
super({
id: OpenDisassemblyViewAction.ID,
label: OpenDisassemblyViewAction.LABEL,
alias: 'Debug: Go to Disassembly',
precondition: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, PanelFocusContext.toNegated(), CONTEXT_DEBUG_STATE.isEqualTo('stopped'), EditorContextKeys.editorTextFocus, CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_LANGUAGE_SUPPORTS_DISASSEMBLE_REQUEST),
contextMenuOpts: {
group: 'debug',
order: 5
}
});
}
async run(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]): Promise<void> {
if (editor.hasModel()) {
const editorService = accessor.get(IEditorService);
editorService.openEditor(DisassemblyViewInput.instance, { pinned: true });
}
}
}
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(OpenDisassemblyViewAction);
registerEditorAction(RunToCursorAction);
registerEditorAction(StepIntoTargetsAction);
registerEditorAction(SelectionToReplAction);

View file

@ -15,7 +15,7 @@ import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecy
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files';
import { DebugModel, FunctionBreakpoint, Breakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { DebugModel, FunctionBreakpoint, Breakpoint, DataBreakpoint, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { ViewModel } from 'vs/workbench/contrib/debug/common/debugViewModel';
import { ConfigurationManager } from 'vs/workbench/contrib/debug/browser/debugConfigurationManager';
import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from 'vs/workbench/contrib/files/common/files';
@ -30,7 +30,7 @@ import { IAction, Action } from 'vs/base/common/actions';
import { deepClone, equals } from 'vs/base/common/objects';
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IStackFrame, getStateLabel, IDebugSessionOptions, CONTEXT_DEBUG_UX, REPL_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, IGlobalConfig, CALLSTACK_VIEW_ID, IAdapterManager, IExceptionBreakpoint } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IStackFrame, getStateLabel, IDebugSessionOptions, CONTEXT_DEBUG_UX, REPL_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, IGlobalConfig, CALLSTACK_VIEW_ID, IAdapterManager, IExceptionBreakpoint, CONTEXT_DISASSEMBLY_VIEW_FOCUS } from 'vs/workbench/contrib/debug/common/debug';
import { getExtensionHostDebugSession } from 'vs/workbench/contrib/debug/common/debugUtils';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
@ -51,6 +51,7 @@ import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL } from 'vs/workbench/
import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust';
import { Debugger } from 'vs/workbench/contrib/debug/common/debugger';
import { IEditorInput } from 'vs/workbench/common/editor';
import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput';
export class DebugService implements IDebugService {
declare readonly _serviceBrand: undefined;
@ -72,6 +73,7 @@ export class DebugService implements IDebugService {
private inDebugMode!: IContextKey<boolean>;
private debugUx!: IContextKey<string>;
private breakpointsExist!: IContextKey<boolean>;
private disassemblyViewFocus!: IContextKey<boolean>;
private breakpointsToSendOnResourceSaved: Set<URI>;
private initializing = false;
private _initializingOptions: IDebugSessionOptions | undefined;
@ -122,6 +124,8 @@ export class DebugService implements IDebugService {
this.debugUx = CONTEXT_DEBUG_UX.bindTo(contextKeyService);
this.debugUx.set(this.debugStorage.loadDebugUxState());
this.breakpointsExist = CONTEXT_BREAKPOINTS_EXIST.bindTo(contextKeyService);
// Need to set disassemblyViewFocus here to make it in the same context as the debug event handlers
this.disassemblyViewFocus = CONTEXT_DISASSEMBLY_VIEW_FOCUS.bindTo(contextKeyService);
});
this.chosenEnvironments = this.debugStorage.loadChosenEnvironments();
@ -177,6 +181,16 @@ export class DebugService implements IDebugService {
}
}));
this.toDispose.push(this.model.onDidChangeBreakpoints(() => setBreakpointsExistContext()));
this.toDispose.push(editorService.onDidActiveEditorChange(() => {
this.contextKeyService.bufferChangeEvents(() => {
if (editorService.activeEditor === DisassemblyViewInput.instance) {
this.disassemblyViewFocus.set(true);
} else {
this.disassemblyViewFocus.reset();
}
});
}));
}
getModel(): IDebugModel {
@ -823,14 +837,18 @@ export class DebugService implements IDebugService {
if (stackFrame) {
const editor = await stackFrame.openInEditor(this.editorService, true);
if (editor) {
const control = editor.getControl();
if (stackFrame && isCodeEditor(control) && control.hasModel()) {
const model = control.getModel();
const lineNumber = stackFrame.range.startLineNumber;
if (lineNumber >= 1 && lineNumber <= model.getLineCount()) {
const lineContent = control.getModel().getLineContent(lineNumber);
aria.alert(nls.localize({ key: 'debuggingPaused', comment: ['First placeholder is the stack frame name, second is the line number, third placeholder is the reason why debugging is stopped, for example "breakpoint" and the last one is the file line content.'] },
"{0}:{1}, debugging paused {2}, {3}", stackFrame.source ? stackFrame.source.name : '', stackFrame.range.startLineNumber, thread && thread.stoppedDetails ? `, reason ${thread.stoppedDetails.reason}` : '', lineContent));
if (editor.input === DisassemblyViewInput.instance) {
// Go to address is invoked via setFocus
} else {
const control = editor.getControl();
if (stackFrame && isCodeEditor(control) && control.hasModel()) {
const model = control.getModel();
const lineNumber = stackFrame.range.startLineNumber;
if (lineNumber >= 1 && lineNumber <= model.getLineCount()) {
const lineContent = control.getModel().getLineContent(lineNumber);
aria.alert(nls.localize({ key: 'debuggingPaused', comment: ['First placeholder is the stack frame name, second is the line number, third placeholder is the reason why debugging is stopped, for example "breakpoint" and the last one is the file line content.'] },
"{0}:{1}, debugging paused {2}, {3}", stackFrame.source ? stackFrame.source.name : '', stackFrame.range.startLineNumber, thread && thread.stoppedDetails ? `, reason ${thread.stoppedDetails.reason}` : '', lineContent));
}
}
}
}
@ -885,6 +903,8 @@ export class DebugService implements IDebugService {
await this.sendFunctionBreakpoints();
} else if (breakpoint instanceof DataBreakpoint) {
await this.sendDataBreakpoints();
} else if (breakpoint instanceof InstructionBreakpoint) {
await this.sendInstructionBreakpoints();
} else {
await this.sendExceptionBreakpoints();
}
@ -966,6 +986,19 @@ export class DebugService implements IDebugService {
await this.sendDataBreakpoints();
}
async addInstructionBreakpoint(address: string, offset: number, condition?: string, hitCondition?: string): Promise<void> {
this.model.addInstructionBreakpoint(address, offset, condition, hitCondition);
this.debugStorage.storeBreakpoints(this.model);
await this.sendInstructionBreakpoints();
this.debugStorage.storeBreakpoints(this.model);
}
async removeInstructionBreakpoints(address?: string): Promise<void> {
this.model.removeInstructionBreakpoints(address);
this.debugStorage.storeBreakpoints(this.model);
await this.sendInstructionBreakpoints();
}
setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void {
this.model.setExceptionBreakpoints(data);
this.debugStorage.storeBreakpoints(this.model);
@ -981,6 +1014,7 @@ export class DebugService implements IDebugService {
await Promise.all(distinct(this.model.getBreakpoints(), bp => bp.uri.toString()).map(bp => this.sendBreakpoints(bp.uri, false, session)));
await this.sendFunctionBreakpoints(session);
await this.sendDataBreakpoints(session);
await this.sendInstructionBreakpoints(session);
// send exception breakpoints at the end since some debug adapters rely on the order
await this.sendExceptionBreakpoints(session);
}
@ -1010,6 +1044,16 @@ export class DebugService implements IDebugService {
});
}
private async sendInstructionBreakpoints(session?: IDebugSession): Promise<void> {
const breakpointsToSend = this.model.getInstructionBreakpoints().filter(fbp => fbp.enabled && this.model.areBreakpointsActivated());
await sendToOneOrAllSessions(this.model, session, async s => {
if (s.capabilities.supportsInstructionBreakpoints) {
await s.sendInstructionBreakpoints(breakpointsToSend);
}
});
}
private sendExceptionBreakpoints(session?: IDebugSession): Promise<void> {
const enabledExceptionBps = this.model.getExceptionBreakpoints().filter(exb => exb.enabled);

View file

@ -10,7 +10,7 @@ import severity from 'vs/base/common/severity';
import { Event, Emitter } from 'vs/base/common/event';
import { Position, IPosition } from 'vs/editor/common/core/position';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration, IReplElement, IStackFrame, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration, IReplElement, IStackFrame, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions, IInstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debug';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { mixin } from 'vs/base/common/objects';
import { Thread, ExpressionContainer, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel';
@ -36,6 +36,7 @@ import { filterExceptionsFromTelemetry } from 'vs/workbench/contrib/debug/common
import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
export class DebugSession implements IDebugSession {
@ -83,7 +84,8 @@ export class DebugSession implements IDebugSession {
@ILifecycleService lifecycleService: ILifecycleService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ICustomEndpointTelemetryService private readonly customEndpointTelemetryService: ICustomEndpointTelemetryService
@ICustomEndpointTelemetryService private readonly customEndpointTelemetryService: ICustomEndpointTelemetryService,
@IContextKeyService contextKeyService: IContextKeyService
) {
this._options = options || {};
if (this.hasSeparateRepl()) {
@ -451,6 +453,23 @@ export class DebugSession implements IDebugSession {
}
}
async sendInstructionBreakpoints(instructionBreakpoints: IInstructionBreakpoint[]): Promise<void> {
if (!this.raw) {
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'instruction breakpoints'));
}
if (this.raw.readyForBreakpoints) {
const response = await this.raw.setInstructionBreakpoints({ breakpoints: instructionBreakpoints });
if (response && response.body) {
const data = new Map<string, DebugProtocol.Breakpoint>();
for (let i = 0; i < instructionBreakpoints.length; i++) {
data.set(instructionBreakpoints[i].getId(), response.body.breakpoints[i]);
}
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
}
}
}
async breakpointsLocations(uri: URI, lineNumber: number): Promise<IPosition[]> {
if (!this.raw) {
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'breakpoints locations'));
@ -540,36 +559,47 @@ export class DebugSession implements IDebugSession {
await this.raw.restartFrame({ frameId }, threadId);
}
async next(threadId: number): Promise<void> {
private setLastSteppingGranularity(threadId: number, granularity?: DebugProtocol.SteppingGranularity) {
const thread = this.getThread(threadId);
if (thread) {
thread.lastSteppingGranularity = granularity;
}
}
async next(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {
if (!this.raw) {
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'next'));
}
await this.raw.next({ threadId });
this.setLastSteppingGranularity(threadId, granularity);
await this.raw.next({ threadId, granularity });
}
async stepIn(threadId: number, targetId?: number): Promise<void> {
async stepIn(threadId: number, targetId?: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {
if (!this.raw) {
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepIn'));
}
await this.raw.stepIn({ threadId, targetId });
this.setLastSteppingGranularity(threadId, granularity);
await this.raw.stepIn({ threadId, targetId, granularity });
}
async stepOut(threadId: number): Promise<void> {
async stepOut(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {
if (!this.raw) {
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepOut'));
}
await this.raw.stepOut({ threadId });
this.setLastSteppingGranularity(threadId, granularity);
await this.raw.stepOut({ threadId, granularity });
}
async stepBack(threadId: number): Promise<void> {
async stepBack(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {
if (!this.raw) {
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepBack'));
}
await this.raw.stepBack({ threadId });
this.setLastSteppingGranularity(threadId, granularity);
await this.raw.stepBack({ threadId, granularity });
}
async continue(threadId: number): Promise<void> {
@ -690,6 +720,15 @@ export class DebugSession implements IDebugSession {
return this.raw.cancel({ progressId });
}
async disassemble(memoryReference: string, offset: number, instructionOffset: number, instructionCount: number): Promise<DebugProtocol.DisassembledInstruction[] | undefined> {
if (!this.raw) {
return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'disassemble')));
}
const response = await this.raw.disassemble({ memoryReference, offset, instructionOffset, instructionCount, resolveSymbols: true });
return response?.body?.instructions;
}
//---- threads
getThread(threadId: number): Thread | undefined {
@ -837,6 +876,7 @@ export class DebugSession implements IDebugSession {
// Second retrieves the rest of the call stack. For performance reasons #25605
const promises = this.model.fetchCallStack(<Thread>thread);
const focus = async () => {
// Don't switch view when DisassemblyView is in focus.
if (!event.body.preserveFocusHint && thread.getCallStack().length) {
await this.debugService.focusStackFrame(undefined, thread);
if (thread.stoppedDetails) {

View file

@ -0,0 +1,618 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser';
import { Dimension, append, $, addStandardDisposableListener } from 'vs/base/browser/dom';
import { ITableRenderer, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table';
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
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 { CONTEXT_LANGUAGE_SUPPORTS_DISASSEMBLE_REQUEST, DISASSEMBLY_VIEW_ID, IDebugService, IInstructionBreakpoint, State } from 'vs/workbench/contrib/debug/common/debug';
import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons';
import { createStringBuilder } from 'vs/editor/common/core/stringBuilder';
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { dispose, Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';
import { topStackFrameColor } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution';
import { Color } from 'vs/base/common/color';
import { InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
interface IDisassembledInstructionEntry {
allowBreakpoint: boolean;
isBreakpointSet: boolean;
instruction: DebugProtocol.DisassembledInstruction;
instructionAddress?: bigint;
}
export class DisassemblyView extends EditorPane {
private static readonly NUM_INSTRUCTIONS_TO_LOAD = 50;
// Used in instruction renderer
private _fontInfo: BareFontInfo;
private _disassembledInstructions: WorkbenchTable<IDisassembledInstructionEntry> | undefined;
private _onDidChangeStackFrame: Emitter<void>;
private _previousDebuggingState: State;
private _instructionBpList: readonly IInstructionBreakpoint[] = [];
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService,
@IConfigurationService configurationService: IConfigurationService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IDebugService private readonly _debugService: IDebugService,
@IEditorService editorService: IEditorService,
) {
super(DISASSEMBLY_VIEW_ID, telemetryService, themeService, storageService);
this._disassembledInstructions = undefined;
this._onDidChangeStackFrame = new Emitter<void>();
this._previousDebuggingState = _debugService.state;
this._fontInfo = BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), getZoomLevel(), getPixelRatio());
this._register(configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('editor')) {
this._fontInfo = BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), getZoomLevel(), getPixelRatio());
this._disassembledInstructions?.rerender();
}
}));
}
get fontInfo() { return this._fontInfo; }
get currentInstructionAddress() {
return this._debugService.getViewModel().focusedStackFrame?.instructionPointerReference;
}
get onDidChangeStackFrame() { return this._onDidChangeStackFrame.event; }
protected createEditor(parent: HTMLElement): void {
const lineHeight = this.fontInfo.lineHeight;
const delegate = new class implements ITableVirtualDelegate<IDisassembledInstructionEntry>{
headerRowHeight: number = 0; // No header
getHeight(row: IDisassembledInstructionEntry): number {
return lineHeight;
}
};
const instructionRenderer = this._register(this._instantiationService.createInstance(InstructionRenderer, this));
this._disassembledInstructions = this._register(this._instantiationService.createInstance(WorkbenchTable,
'DisassemblyView', parent, delegate,
[
{
label: '',
tooltip: '',
weight: 0,
minimumWidth: this.fontInfo.lineHeight,
maximumWidth: this.fontInfo.lineHeight,
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),
instructionRenderer,
],
{
identityProvider: { getId: (e: IDisassembledInstructionEntry) => e.instruction.address },
horizontalScrolling: false,
overrideStyles: {
listBackground: editorBackground
},
multipleSelectionSupport: false,
setRowLineHeight: false,
openOnSingleClick: false,
accessibilityProvider: new AccessibilityProvider()
}
)) as WorkbenchTable<IDisassembledInstructionEntry>;
this.reloadDisassembly();
this._register(this._disassembledInstructions.onDidScroll(e => {
if (e.oldScrollTop > e.scrollTop && e.scrollTop < e.height) {
const topElement = Math.floor(e.scrollTop / this.fontInfo.lineHeight) + DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD;
this.scrollUp_LoadDisassembledInstructions(DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD).then((success) => {
if (success) {
this._disassembledInstructions!.reveal(topElement, 0);
}
});
} else if (e.oldScrollTop < e.scrollTop && e.scrollTop + e.height > e.scrollHeight - e.height) {
this.scrollDown_LoadDisassembledInstructions(DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD);
}
}));
this._register(this._debugService.getViewModel().onDidFocusStackFrame((stackFrame) => {
if (this._disassembledInstructions) {
this.goToAddress();
this._onDidChangeStackFrame.fire();
}
}));
// refresh breakpoints view
this._register(this._debugService.getModel().onDidChangeBreakpoints(bpEvent => {
if (bpEvent && this._disassembledInstructions) {
// draw viewable BP
let changed = false;
bpEvent.added?.forEach((bp) => {
if (bp instanceof InstructionBreakpoint) {
const index = this.getIndexFromAddress(bp.instructionReference);
if (index >= 0) {
this._disassembledInstructions!.row(index).isBreakpointSet = true;
changed = true;
}
}
});
bpEvent.removed?.forEach((bp) => {
if (bp instanceof InstructionBreakpoint) {
const index = this.getIndexFromAddress(bp.instructionReference);
if (index >= 0) {
this._disassembledInstructions!.row(index).isBreakpointSet = false;
changed = true;
}
}
});
// get an updated list so that items beyond the current range would render when reached.
this._instructionBpList = this._debugService.getModel().getInstructionBreakpoints();
if (changed) {
this._onDidChangeStackFrame.fire();
}
}
}));
this._register(this._debugService.onDidChangeState(e => {
if ((e === State.Running || e === State.Stopped) &&
(this._previousDebuggingState !== State.Running && this._previousDebuggingState !== State.Stopped)) {
// Just started debugging, clear the view
this._disassembledInstructions?.splice(0, this._disassembledInstructions.length);
}
this._previousDebuggingState = e;
}));
}
layout(dimension: Dimension): void {
if (this._disassembledInstructions) {
this._disassembledInstructions.layout(dimension.height);
}
}
/**
* Go to the address provided. If no address is provided, reveal the address of the currently focused stack frame.
*/
goToAddress(address?: string): void {
if (!this._disassembledInstructions) {
return;
}
if (!address) {
address = this.currentInstructionAddress;
}
if (!address) {
return;
}
const index = this.getIndexFromAddress(address);
if (index >= 0) {
// If the row is out of the viewport, reveal it
const topElement = Math.floor(this._disassembledInstructions.scrollTop / this.fontInfo.lineHeight);
const bottomElement = Math.floor((this._disassembledInstructions.scrollTop + this._disassembledInstructions.renderHeight) / this.fontInfo.lineHeight);
if (index > topElement && index < bottomElement) {
// Inside the viewport, don't do anything here
return;
} else if (index <= topElement && index > topElement - 5) {
// Not too far from top, review it at the top
return this._disassembledInstructions.reveal(index, 0);
} else if (index >= bottomElement && index < bottomElement + 5) {
// Not too far from bottom, review it at the bottom
return this._disassembledInstructions.reveal(index, 1);
} else {
// Far from the current viewport, reveal it
return this._disassembledInstructions.reveal(index, 0.5);
}
} else if (this._debugService.state === State.Stopped) {
// Address is not provided or not in the table currently, clear the table
// and reload if we are in the state where we can load disassembly.
return this.reloadDisassembly(address);
}
}
private async scrollUp_LoadDisassembledInstructions(instructionCount: number): Promise<boolean> {
if (this._disassembledInstructions && this._disassembledInstructions.length > 0) {
const address: string | undefined = this._disassembledInstructions?.row(0).instruction.address;
return this.loadDisassembledInstructions(address, -instructionCount, instructionCount - 1);
}
return false;
}
private async scrollDown_LoadDisassembledInstructions(instructionCount: number): Promise<boolean> {
if (this._disassembledInstructions && this._disassembledInstructions.length > 0) {
const address: string | undefined = this._disassembledInstructions?.row(this._disassembledInstructions?.length - 1).instruction.address;
return this.loadDisassembledInstructions(address, 1, instructionCount);
}
return false;
}
private async loadDisassembledInstructions(address: string | undefined, instructionOffset: number, instructionCount: number): Promise<boolean> {
// if address is null, then use current stack frame.
if (!address) {
address = this.currentInstructionAddress;
}
if (!address) {
return false;
}
// console.log(`DisassemblyView: loadDisassembledInstructions ${address}, ${instructionOffset}, ${instructionCount}`);
const session = this._debugService.getViewModel().focusedSession;
const resultEntries = await session?.disassemble(address, 0, instructionOffset, instructionCount);
if (session && resultEntries && this._disassembledInstructions) {
const newEntries: IDisassembledInstructionEntry[] = [];
for (let i = 0; i < resultEntries.length; i++) {
const found = this._instructionBpList.find(p => p.instructionReference === resultEntries[i].address);
newEntries.push({ allowBreakpoint: true, isBreakpointSet: found !== undefined, instruction: resultEntries[i] });
}
// request is either at the start or end
if (instructionOffset >= 0) {
this._disassembledInstructions.splice(this._disassembledInstructions.length, 0, newEntries);
} else {
this._disassembledInstructions.splice(0, 0, newEntries);
}
return true;
}
return false;
}
private getIndexFromAddress(instructionAddress: string): number {
if (this._disassembledInstructions && this._disassembledInstructions.length > 0) {
const address = BigInt(instructionAddress);
if (address) {
let startIndex = 0;
let endIndex = this._disassembledInstructions.length - 1;
const start = this._disassembledInstructions.row(startIndex);
const end = this._disassembledInstructions.row(endIndex);
this.ensureAddressParsed(start);
this.ensureAddressParsed(end);
if (start.instructionAddress! > address ||
end.instructionAddress! < address) {
return -1;
} else if (start.instructionAddress! === address) {
return startIndex;
} else if (end.instructionAddress! === address) {
return endIndex;
}
while (endIndex > startIndex) {
const midIndex = Math.floor((endIndex - startIndex) / 2) + startIndex;
const mid = this._disassembledInstructions.row(midIndex);
this.ensureAddressParsed(mid);
if (mid.instructionAddress! > address) {
endIndex = midIndex;
} else if (mid.instructionAddress! < address) {
startIndex = midIndex;
} else {
return midIndex;
}
}
return startIndex;
}
}
return -1;
}
private ensureAddressParsed(entry: IDisassembledInstructionEntry) {
if (entry.instructionAddress !== undefined) {
return;
} else {
entry.instructionAddress = BigInt(entry.instruction.address);
}
}
/**
* Clears the table and reload instructions near the target address
*/
private reloadDisassembly(targetAddress?: string) {
if (this._disassembledInstructions) {
this._disassembledInstructions.splice(0, this._disassembledInstructions.length);
this._instructionBpList = this._debugService.getModel().getInstructionBreakpoints();
this.loadDisassembledInstructions(targetAddress, -DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD, DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD * 2).then(() => {
// on load, set the target instruction in the middle of the page.
if (this._disassembledInstructions!.length > 0) {
this._disassembledInstructions!.reveal(Math.floor(this._disassembledInstructions!.length / 2), 0.5);
}
});
}
}
}
interface IBreakpointColumnTemplateData {
container: HTMLElement,
icon: HTMLElement,
disposables: IDisposable[]
}
class BreakpointRenderer implements ITableRenderer<IDisassembledInstructionEntry, IBreakpointColumnTemplateData> {
static readonly TEMPLATE_ID = 'breakpoint';
templateId: string = BreakpointRenderer.TEMPLATE_ID;
private readonly _breakpointIcon = 'codicon-' + icons.breakpoint.regular.id;
private readonly _breakpointHintIcon = 'codicon-' + icons.debugBreakpointHint.id;
private readonly _debugStackframe = 'codicon-' + icons.debugStackframe.id;
// private readonly _debugStackframeFocused = 'codicon-' + icons.debugStackframeFocused.id;
constructor(
private readonly _disassemblyView: DisassemblyView,
@IDebugService private readonly _debugService: IDebugService
) {
}
renderTemplate(container: HTMLElement): IBreakpointColumnTemplateData {
const icon = append(container, $('.disassembly-view'));
icon.classList.add('codicon');
icon.style.display = 'flex';
icon.style.alignItems = 'center';
icon.style.justifyContent = 'center';
return { container, icon, disposables: [] };
}
renderElement(element: IDisassembledInstructionEntry, index: number, templateData: IBreakpointColumnTemplateData, height: number | undefined): void {
const rerenderDebugStackframe = () => {
if (element.instruction.address === this._disassemblyView.currentInstructionAddress) {
templateData.icon.classList.add(this._debugStackframe);
} else {
templateData.icon.classList.remove(this._debugStackframe);
}
templateData.icon.classList.remove(this._breakpointHintIcon);
if (element.isBreakpointSet) {
templateData.icon.classList.add(this._breakpointIcon);
} else {
templateData.icon.classList.remove(this._breakpointIcon);
}
};
rerenderDebugStackframe();
templateData.disposables.push(this._disassemblyView.onDidChangeStackFrame(rerenderDebugStackframe));
// TODO: see getBreakpointMessageAndIcon in vs\workbench\contrib\debug\browser\breakpointEditorContribution.ts
// for more types of breakpoint icons
if (element.allowBreakpoint) {
templateData.disposables.push(addStandardDisposableListener(templateData.container, 'mouseover', () => {
templateData.icon.classList.add(this._breakpointHintIcon);
}));
templateData.disposables.push(addStandardDisposableListener(templateData.container, 'mouseout', () => {
templateData.icon.classList.remove(this._breakpointHintIcon);
}));
templateData.disposables.push(addStandardDisposableListener(templateData.container, 'click', () => {
// click show hint while waiting for BP to resolve.
templateData.icon.classList.add(this._breakpointHintIcon);
if (element.isBreakpointSet) {
this._debugService.removeInstructionBreakpoints(element.instruction.address);
} else if (element.allowBreakpoint && !element.isBreakpointSet) {
this._debugService.addInstructionBreakpoint(element.instruction.address, 0);
}
}));
}
}
disposeElement(element: IDisassembledInstructionEntry, index: number, templateData: IBreakpointColumnTemplateData, height: number | undefined): void {
dispose(templateData.disposables);
templateData.disposables = [];
}
disposeTemplate(templateData: IBreakpointColumnTemplateData): void { }
}
interface IInstructionColumnTemplateData {
// TODO: hover widget?
instruction: HTMLElement;
disposables: IDisposable[];
}
class InstructionRenderer extends Disposable implements ITableRenderer<IDisassembledInstructionEntry, IInstructionColumnTemplateData> {
static readonly TEMPLATE_ID = 'instruction';
private static readonly INSTRUCTION_ADDR_MIN_LENGTH = 25;
private static readonly INSTRUCTION_BYTES_MIN_LENGTH = 30;
templateId: string = InstructionRenderer.TEMPLATE_ID;
private _topStackFrameColor: Color | undefined;
// private _focusedStackFrameColor: Color | undefined;
constructor(
private readonly _disassemblyView: DisassemblyView,
@IThemeService themeService: IThemeService
) {
super();
this._topStackFrameColor = themeService.getColorTheme().getColor(topStackFrameColor);
// this._focusedStackFrameColor = themeService.getColorTheme().getColor(focusedStackFrameColor);
this._register(themeService.onDidColorThemeChange(e => {
this._topStackFrameColor = e.getColor(topStackFrameColor);
// this._focusedStackFrameColor = e.getColor(focusedStackFrameColor);
}));
}
renderTemplate(container: HTMLElement): IInstructionColumnTemplateData {
const instruction = append(container, $('.instruction'));
this.applyFontInfo(instruction);
return { instruction, disposables: [] };
}
renderElement(element: IDisassembledInstructionEntry, index: number, templateData: IInstructionColumnTemplateData, height: number | undefined): void {
const instruction = element.instruction;
const sb = createStringBuilder(10000);
sb.appendASCIIString(instruction.address);
let spacesToAppend = 10;
if (instruction.address.length < InstructionRenderer.INSTRUCTION_ADDR_MIN_LENGTH) {
spacesToAppend = InstructionRenderer.INSTRUCTION_ADDR_MIN_LENGTH - instruction.address.length;
}
for (let i = 0; i < spacesToAppend; i++) {
sb.appendASCII(0x00A0);
}
if (instruction.instructionBytes) {
sb.appendASCIIString(instruction.instructionBytes);
spacesToAppend = 10;
if (instruction.instructionBytes.length < InstructionRenderer.INSTRUCTION_BYTES_MIN_LENGTH) {
spacesToAppend = InstructionRenderer.INSTRUCTION_BYTES_MIN_LENGTH - instruction.instructionBytes.length;
}
for (let i = 0; i < spacesToAppend; i++) {
sb.appendASCII(0x00A0);
}
}
sb.appendASCIIString(instruction.instruction);
const innerText = sb.build();
templateData.instruction.innerText = innerText;
const rerenderBackground = () => {
if (element.instruction.address === this._disassemblyView.currentInstructionAddress) {
templateData.instruction.style.background = this._topStackFrameColor?.toString() || 'transparent';
} else {
templateData.instruction.style.background = 'transparent';
}
};
rerenderBackground();
templateData.disposables.push(this._disassemblyView.onDidChangeStackFrame(rerenderBackground));
}
disposeElement(element: IDisassembledInstructionEntry, index: number, templateData: IInstructionColumnTemplateData, height: number | undefined): void {
dispose(templateData.disposables);
templateData.disposables = [];
}
disposeTemplate(templateData: IInstructionColumnTemplateData): void { }
private applyFontInfo(element: HTMLElement) {
const fontInfo = this._disassemblyView.fontInfo;
element.style.fontFamily = fontInfo.getMassagedFontFamily();
element.style.fontWeight = fontInfo.fontWeight;
element.style.fontSize = fontInfo.fontSize + 'px';
element.style.fontFeatureSettings = fontInfo.fontFeatureSettings;
element.style.letterSpacing = fontInfo.letterSpacing + 'px';
}
}
class AccessibilityProvider implements IListAccessibilityProvider<IDisassembledInstructionEntry> {
getWidgetAriaLabel(): string {
return localize('disassemblyView', "Disassembly View");
}
getAriaLabel(element: IDisassembledInstructionEntry): string | null {
let label = '';
if (element.isBreakpointSet) {
label += localize('breakpointIsSet', "Breakpoint is set");
} else if (element.allowBreakpoint) {
label += localize('breakpointAllowed', "Can set breakpoint");
}
const instruction = element.instruction;
label += `, ${localize('instructionAddress', "Instruction address")}: ${instruction.address}`;
if (instruction.instructionBytes) {
label += `, ${localize('instructionBytes', "Instruction bytes")}: ${instruction.instructionBytes}`;
}
label += `, ${localize(`instructionText`, "Instruction")}: ${instruction.instruction}`;
return label;
}
}
export class DisassemblyViewContribution implements IWorkbenchContribution {
private readonly _onDidActiveEditorChangeListener: IDisposable;
private _onDidChangeModelLanguage: IDisposable | undefined;
private _languageSupportsDisassemleRequest: IContextKey<boolean> | undefined;
constructor(
@IEditorService editorService: IEditorService,
@IDebugService debugService: IDebugService,
@IContextKeyService contextKeyService: IContextKeyService
) {
contextKeyService.bufferChangeEvents(() => {
this._languageSupportsDisassemleRequest = CONTEXT_LANGUAGE_SUPPORTS_DISASSEMBLE_REQUEST.bindTo(contextKeyService);
});
const onDidActiveEditorChangeListener = () => {
if (this._onDidChangeModelLanguage) {
this._onDidChangeModelLanguage.dispose();
this._onDidChangeModelLanguage = undefined;
}
const activeTextEditorControl = editorService.activeTextEditorControl;
if (isCodeEditor(activeTextEditorControl)) {
const language = activeTextEditorControl.getModel()?.getLanguageIdentifier().language;
// TODO: instead of using idDebuggerInterestedInLanguage, have a specific ext point for languages
// support disassembly
this._languageSupportsDisassemleRequest?.set(!!language && debugService.getAdapterManager().isDebuggerInterestedInLanguage(language));
this._onDidChangeModelLanguage = activeTextEditorControl.onDidChangeModelLanguage(e => {
this._languageSupportsDisassemleRequest?.set(debugService.getAdapterManager().isDebuggerInterestedInLanguage(e.newLanguage));
});
} else {
this._languageSupportsDisassemleRequest?.set(false);
}
};
onDidActiveEditorChangeListener();
this._onDidActiveEditorChangeListener = editorService.onDidActiveEditorChange(onDidActiveEditorChangeListener);
}
dispose(): void {
this._onDidActiveEditorChangeListener.dispose();
this._onDidChangeModelLanguage?.dispose();
}
}

View file

@ -497,6 +497,22 @@ export class RawDebugSession implements IDisposable {
return Promise.reject(new Error('goto is not supported'));
}
async setInstructionBreakpoints(args: DebugProtocol.SetInstructionBreakpointsArguments): Promise<DebugProtocol.SetInstructionBreakpointsResponse | undefined> {
if (this.capabilities.supportsInstructionBreakpoints) {
return await this.send('setInstructionBreakpoints', args);
}
return Promise.reject(new Error('setInstructionBreakpoints is not supported'));
}
async disassemble(args: DebugProtocol.DisassembleArguments): Promise<DebugProtocol.DisassembleResponse | undefined> {
if (this.capabilities.supportsDisassembleRequest) {
return await this.send('disassemble', args);
}
return Promise.reject(new Error('disassemble is not supported'));
}
cancel(args: DebugProtocol.CancelArguments): Promise<DebugProtocol.CancelResponse | undefined> {
return this.send('cancel', args);
}

View file

@ -11,7 +11,7 @@ import { IJSONSchemaSnippet } from 'vs/base/common/jsonSchema';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { ITextModel as EditorIModel } from 'vs/editor/common/model';
import { IEditorPane, ITextEditorPane } from 'vs/workbench/common/editor';
import { IEditorPane } from 'vs/workbench/common/editor';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { Range, IRange } from 'vs/editor/common/core/range';
@ -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,9 @@ 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 CONTEXT_DISASSEMBLY_VIEW_FOCUS = new RawContextKey<boolean>('disassemblyViewFocus', false, { type: 'boolean', description: nls.localize('disassemblyViewFocus', "True when the Disassembly View is focused.") });
export const CONTEXT_LANGUAGE_SUPPORTS_DISASSEMBLE_REQUEST = new RawContextKey<boolean>('languageSupportsDisassembleRequest', false, { type: 'boolean', description: nls.localize('languageSupportsDisassembleRequest', "True when the language in the current editor supports disassemble request.") });
export const EDITOR_CONTRIBUTION_ID = 'editor.contrib.debug';
export const BREAKPOINT_EDITOR_CONTRIBUTION_ID = 'editor.contrib.breakpoint';
@ -258,6 +262,7 @@ export interface IDebugSession extends ITreeElement {
sendFunctionBreakpoints(fbps: IFunctionBreakpoint[]): Promise<void>;
dataBreakpointInfo(name: string, variablesReference?: number): Promise<IDataBreakpointInfoResponse | undefined>;
sendDataBreakpoints(dbps: IDataBreakpoint[]): Promise<void>;
sendInstructionBreakpoints(dbps: IInstructionBreakpoint[]): Promise<void>;
sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise<void>;
breakpointsLocations(uri: uri, lineNumber: number): Promise<IPosition[]>;
getDebugProtocolBreakpoint(breakpointId: string): DebugProtocol.Breakpoint | undefined;
@ -269,13 +274,14 @@ export interface IDebugSession extends ITreeElement {
evaluate(expression: string, frameId?: number, context?: string): Promise<DebugProtocol.EvaluateResponse | undefined>;
customRequest(request: string, args: any): Promise<DebugProtocol.Response | undefined>;
cancel(progressId: string): Promise<DebugProtocol.CancelResponse | undefined>;
disassemble(memoryReference: string, offset: number, instructionOffset: number, instructionCount: number): Promise<DebugProtocol.DisassembledInstruction[] | undefined>;
restartFrame(frameId: number, threadId: number): Promise<void>;
next(threadId: number): Promise<void>;
stepIn(threadId: number, targetId?: number): Promise<void>;
next(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void>;
stepIn(threadId: number, targetId?: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void>;
stepInTargets(frameId: number): Promise<{ id: number, label: string }[] | undefined>;
stepOut(threadId: number): Promise<void>;
stepBack(threadId: number): Promise<void>;
stepOut(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void>;
stepBack(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void>;
continue(threadId: number): Promise<void>;
reverseContinue(threadId: number): Promise<void>;
pause(threadId: number): Promise<void>;
@ -342,10 +348,10 @@ export interface IThread extends ITreeElement {
*/
readonly stopped: boolean;
next(): Promise<any>;
stepIn(): Promise<any>;
stepOut(): Promise<any>;
stepBack(): Promise<any>;
next(granularity?: DebugProtocol.SteppingGranularity): Promise<any>;
stepIn(granularity?: DebugProtocol.SteppingGranularity): Promise<any>;
stepOut(granularity?: DebugProtocol.SteppingGranularity): Promise<any>;
stepBack(granularity?: DebugProtocol.SteppingGranularity): Promise<any>;
continue(): Promise<any>;
pause(): Promise<any>;
terminate(): Promise<any>;
@ -367,12 +373,13 @@ export interface IStackFrame extends ITreeElement {
readonly range: IRange;
readonly source: Source;
readonly canRestart: boolean;
readonly instructionPointerReference?: string;
getScopes(): Promise<IScope[]>;
getMostSpecificScopes(range: IRange): Promise<ReadonlyArray<IScope>>;
forgetScopes(): void;
restart(): Promise<any>;
toString(): string;
openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean): Promise<ITextEditorPane | undefined>;
openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<IEditorPane | undefined>;
equals(other: IStackFrame): boolean;
}
@ -436,6 +443,12 @@ export interface IDataBreakpoint extends IBaseBreakpoint {
readonly accessType: DebugProtocol.DataBreakpointAccessType;
}
export interface IInstructionBreakpoint extends IBaseBreakpoint {
// instructionReference is the instruction 'address' from the debugger.
readonly instructionReference: string;
readonly offset?: number;
}
export interface IExceptionInfo {
readonly id?: string;
readonly description?: string;
@ -485,6 +498,7 @@ export interface IDebugModel extends ITreeElement {
getFunctionBreakpoints(): ReadonlyArray<IFunctionBreakpoint>;
getDataBreakpoints(): ReadonlyArray<IDataBreakpoint>;
getExceptionBreakpoints(): ReadonlyArray<IExceptionBreakpoint>;
getInstructionBreakpoints(): ReadonlyArray<IInstructionBreakpoint>;
getWatchExpressions(): ReadonlyArray<IExpression & IEvaluate>;
onDidChangeBreakpoints: Event<IBreakpointsChangeEvent | undefined>;
@ -496,9 +510,9 @@ export interface IDebugModel extends ITreeElement {
* An event describing a change to the set of [breakpoints](#debug.Breakpoint).
*/
export interface IBreakpointsChangeEvent {
added?: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint>;
removed?: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint>;
changed?: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint>;
added?: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IInstructionBreakpoint>;
removed?: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IInstructionBreakpoint>;
changed?: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IInstructionBreakpoint>;
sessionOnly: boolean;
}
@ -892,6 +906,18 @@ export interface IDebugService {
*/
removeDataBreakpoints(id?: string): Promise<void>;
/**
* Adds a new instruction breakpoint.
*/
addInstructionBreakpoint(address: string, offset: number, condition?: string, hitCondition?: string): Promise<void>;
/**
* Removes all instruction breakpoints. If address is passed only removes the instruction breakpoint with the passed address.
* The address should be the address string supplied by the debugger from the "Disassemble" request.
* Notifies debug adapter of breakpoint changes.
*/
removeInstructionBreakpoints(address?: string): Promise<void>;
setExceptionBreakpointCondition(breakpoint: IExceptionBreakpoint, condition: string | undefined): Promise<void>;
setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void;
@ -956,6 +982,7 @@ export interface IDebugService {
* Gets the current view model.
*/
getViewModel(): IViewModel;
}
// Editor interfaces

View file

@ -14,16 +14,17 @@ import { distinct, lastIndex } from 'vs/base/common/arrays';
import { Range, IRange } from 'vs/editor/common/core/range';
import {
ITreeElement, IExpression, IExpressionContainer, IDebugSession, IStackFrame, IExceptionBreakpoint, IBreakpoint, IFunctionBreakpoint, IDebugModel,
IThread, IRawModelUpdate, IScope, IRawStoppedDetails, IEnablement, IBreakpointData, IExceptionInfo, IBreakpointsChangeEvent, IBreakpointUpdateData, IBaseBreakpoint, State, IDataBreakpoint
IThread, IRawModelUpdate, IScope, IRawStoppedDetails, IEnablement, IBreakpointData, IExceptionInfo, IBreakpointsChangeEvent, IBreakpointUpdateData, IBaseBreakpoint, State, IDataBreakpoint, IInstructionBreakpoint
} from 'vs/workbench/contrib/debug/common/debug';
import { Source, UNKNOWN_SOURCE_LABEL, getUriFromSource } from 'vs/workbench/contrib/debug/common/debugSource';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextEditorPane } from 'vs/workbench/common/editor';
import { IEditorPane } from 'vs/workbench/common/editor';
import { mixin } from 'vs/base/common/objects';
import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput';
interface IDebugProtocolVariableWithContext extends DebugProtocol.Variable {
__vscodeVariableMenuContext?: string;
@ -321,14 +322,15 @@ export class StackFrame implements IStackFrame {
private scopes: Promise<Scope[]> | undefined;
constructor(
public thread: IThread,
public thread: Thread,
public frameId: number,
public source: Source,
public name: string,
public presentationHint: string | undefined,
public range: IRange,
private index: number,
public canRestart: boolean
public canRestart: boolean,
public instructionPointerReference?: string
) { }
getId(): string {
@ -385,7 +387,14 @@ export class StackFrame implements IStackFrame {
return sourceToString === UNKNOWN_SOURCE_LABEL ? this.name : `${this.name} (${sourceToString})`;
}
async openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<ITextEditorPane | undefined> {
async openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<IEditorPane | undefined> {
const threadStopReason = this.thread.stoppedDetails?.reason;
if (this.instructionPointerReference &&
(threadStopReason === 'instruction breakpoint' ||
(threadStopReason === 'step' && this.thread.lastSteppingGranularity === 'instruction'))) {
return editorService.openEditor(DisassemblyViewInput.instance, { pinned: true });
}
if (this.source.available) {
return this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned);
}
@ -404,6 +413,7 @@ export class Thread implements IThread {
public stoppedDetails: IRawStoppedDetails | undefined;
public stopped: boolean;
public reachedEndOfCallStack = false;
public lastSteppingGranularity: DebugProtocol.SteppingGranularity | undefined;
constructor(public session: IDebugSession, public name: string, public threadId: number) {
this.callStack = [];
@ -491,7 +501,7 @@ export class Thread implements IThread {
rsf.column,
rsf.endLine || rsf.line,
rsf.endColumn || rsf.column
), startFrame + index, typeof rsf.canRestart === 'boolean' ? rsf.canRestart : true);
), startFrame + index, typeof rsf.canRestart === 'boolean' ? rsf.canRestart : true, rsf.instructionPointerReference);
});
} catch (err) {
if (this.stoppedDetails) {
@ -518,20 +528,20 @@ export class Thread implements IThread {
return Promise.resolve(undefined);
}
next(): Promise<any> {
return this.session.next(this.threadId);
next(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {
return this.session.next(this.threadId, granularity);
}
stepIn(): Promise<any> {
return this.session.stepIn(this.threadId);
stepIn(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {
return this.session.stepIn(this.threadId, undefined, granularity);
}
stepOut(): Promise<any> {
return this.session.stepOut(this.threadId);
stepOut(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {
return this.session.stepOut(this.threadId, granularity);
}
stepBack(): Promise<any> {
return this.session.stepBack(this.threadId);
stepBack(granularity?: DebugProtocol.SteppingGranularity): Promise<any> {
return this.session.stepBack(this.threadId, granularity);
}
continue(): Promise<any> {
@ -568,6 +578,7 @@ interface IBreakpointSessionData extends DebugProtocol.Breakpoint {
supportsLogPoints: boolean;
supportsFunctionBreakpoints: boolean;
supportsDataBreakpoints: boolean;
supportsInstructionBreakpoints: boolean
sessionId: string;
}
@ -577,7 +588,8 @@ function toBreakpointSessionData(data: DebugProtocol.Breakpoint, capabilities: D
supportsHitConditionalBreakpoints: !!capabilities.supportsHitConditionalBreakpoints,
supportsLogPoints: !!capabilities.supportsLogPoints,
supportsFunctionBreakpoints: !!capabilities.supportsFunctionBreakpoints,
supportsDataBreakpoints: !!capabilities.supportsDataBreakpoints
supportsDataBreakpoints: !!capabilities.supportsDataBreakpoints,
supportsInstructionBreakpoints: !!capabilities.supportsInstructionBreakpoints
}, data);
}
@ -761,7 +773,6 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint {
return true;
}
override setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void {
super.setSessionData(sessionId, data);
if (!this._adapterData) {
@ -908,6 +919,41 @@ export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBre
}
}
export class InstructionBreakpoint extends BaseBreakpoint implements IInstructionBreakpoint {
constructor(
public instructionReference: string,
public offset: number,
public canPersist: boolean,
enabled: boolean,
hitCondition: string | undefined,
condition: string | undefined,
logMessage: string | undefined,
id = generateUuid()
) {
super(enabled, hitCondition, condition, logMessage, id);
}
override toJSON(): any {
const result = super.toJSON();
result.instructionReference = this.instructionReference;
result.offset = this.offset;
return result;
}
get supported(): boolean {
if (!this.data) {
return true;
}
return this.data.supportsInstructionBreakpoints;
}
override toString(): string {
return this.instructionReference;
}
}
export class ThreadAndSessionIds implements ITreeElement {
constructor(public sessionId: string, public threadId: number) { }
@ -929,6 +975,7 @@ export class DebugModel implements IDebugModel {
private exceptionBreakpoints: ExceptionBreakpoint[];
private dataBreakopints: DataBreakpoint[];
private watchExpressions: Expression[];
private instructionBreakpoints: InstructionBreakpoint[];
constructor(
debugStorage: DebugStorage,
@ -940,6 +987,7 @@ export class DebugModel implements IDebugModel {
this.exceptionBreakpoints = debugStorage.loadExceptionBreakpoints();
this.dataBreakopints = debugStorage.loadDataBreakpoints();
this.watchExpressions = debugStorage.loadWatchExpressions();
this.instructionBreakpoints = [];
this.sessions = [];
}
@ -1095,6 +1143,10 @@ export class DebugModel implements IDebugModel {
return this.exceptionBreakpoints;
}
getInstructionBreakpoints(): IInstructionBreakpoint[] {
return this.instructionBreakpoints;
}
setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void {
if (data) {
if (this.exceptionBreakpoints.length === data.length && this.exceptionBreakpoints.every((exbp, i) =>
@ -1197,6 +1249,16 @@ export class DebugModel implements IDebugModel {
}
}
});
this.instructionBreakpoints.forEach(ibp => {
if (!data) {
ibp.setSessionData(sessionId, undefined);
} else {
const ibpData = data.get(ibp.getId());
if (ibpData) {
ibp.setSessionData(sessionId, toBreakpointSessionData(ibpData, capabilites));
}
}
});
this._onDidChangeBreakpoints.fire({
sessionOnly: true
@ -1229,9 +1291,9 @@ export class DebugModel implements IDebugModel {
}
setEnablement(element: IEnablement, enable: boolean): void {
if (element instanceof Breakpoint || element instanceof FunctionBreakpoint || element instanceof ExceptionBreakpoint || element instanceof DataBreakpoint) {
const changed: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint> = [];
if (element.enabled !== enable && (element instanceof Breakpoint || element instanceof FunctionBreakpoint || element instanceof DataBreakpoint)) {
if (element instanceof Breakpoint || element instanceof FunctionBreakpoint || element instanceof ExceptionBreakpoint || element instanceof DataBreakpoint || element instanceof InstructionBreakpoint) {
const changed: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IInstructionBreakpoint> = [];
if (element.enabled !== enable && (element instanceof Breakpoint || element instanceof FunctionBreakpoint || element instanceof DataBreakpoint || element instanceof InstructionBreakpoint)) {
changed.push(element);
}
@ -1245,7 +1307,7 @@ export class DebugModel implements IDebugModel {
}
enableOrDisableAllBreakpoints(enable: boolean): void {
const changed: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint> = [];
const changed: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IInstructionBreakpoint> = [];
this.breakpoints.forEach(bp => {
if (bp.enabled !== enable) {
@ -1265,6 +1327,13 @@ export class DebugModel implements IDebugModel {
}
dbp.enabled = enable;
});
this.instructionBreakpoints.forEach(ibp => {
if (ibp.enabled !== enable) {
changed.push(ibp);
}
ibp.enabled = enable;
});
if (enable) {
this.breakpointsActivated = true;
}
@ -1326,6 +1395,24 @@ export class DebugModel implements IDebugModel {
this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });
}
addInstructionBreakpoint(address: string, offset: number, condition?: string, hitCondition?: string): void {
const newInstructionBreakpoint = new InstructionBreakpoint(address, offset, false, true, hitCondition, condition, undefined);
this.instructionBreakpoints.push(newInstructionBreakpoint);
this._onDidChangeBreakpoints.fire({ added: [newInstructionBreakpoint], sessionOnly: true });
}
removeInstructionBreakpoints(address?: string): void {
let removed: InstructionBreakpoint[];
if (address) {
removed = this.instructionBreakpoints.filter(fbp => fbp.instructionReference === address);
this.instructionBreakpoints = this.instructionBreakpoints.filter(fbp => fbp.instructionReference !== address);
} else {
removed = this.instructionBreakpoints;
this.instructionBreakpoints = [];
}
this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });
}
getWatchExpressions(): Expression[] {
return this.watchExpressions;
}

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { CONTEXT_EXPRESSION_SELECTED, IViewModel, IStackFrame, IDebugSession, IThread, IExpression, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED } from 'vs/workbench/contrib/debug/common/debug';
import { CONTEXT_EXPRESSION_SELECTED, IViewModel, IStackFrame, IDebugSession, IThread, IExpression, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED } from 'vs/workbench/contrib/debug/common/debug';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils';
@ -30,6 +30,7 @@ export class ViewModel implements IViewModel {
private setVariableSupported!: IContextKey<boolean>;
private multiSessionDebug!: IContextKey<boolean>;
private terminateDebuggeeSuported!: IContextKey<boolean>;
private disassembleRequestSupported!: IContextKey<boolean>;
constructor(private contextKeyService: IContextKeyService) {
contextKeyService.bufferChangeEvents(() => {
@ -43,6 +44,7 @@ export class ViewModel implements IViewModel {
this.setVariableSupported = CONTEXT_SET_VARIABLE_SUPPORTED.bindTo(contextKeyService);
this.multiSessionDebug = CONTEXT_MULTI_SESSION_DEBUG.bindTo(contextKeyService);
this.terminateDebuggeeSuported = CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED.bindTo(contextKeyService);
this.disassembleRequestSupported = CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED.bindTo(contextKeyService);
});
}
@ -78,6 +80,7 @@ export class ViewModel implements IViewModel {
this.jumpToCursorSupported.set(session ? !!session.capabilities.supportsGotoTargetsRequest : false);
this.setVariableSupported.set(session ? !!session.capabilities.supportsSetVariable : false);
this.terminateDebuggeeSuported.set(session ? !!session.capabilities.supportTerminateDebuggee : false);
this.disassembleRequestSupported.set(!!stackFrame?.instructionPointerReference && !!session?.capabilities.supportsDisassembleRequest);
const attach = !!session && isSessionAttach(session);
this.focusedSessionIsAttach.set(attach);
});

View file

@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { localize } from 'vs/nls';
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('disassemblyInputName', "Disassembly");
}
override matches(other: unknown): boolean {
return other instanceof DisassemblyViewInput;
}
}

View file

@ -224,6 +224,28 @@ suite('Debug - Breakpoints', () => {
assert.strictEqual(exceptionBreakpoints[1].enabled, false);
});
test('instruction breakpoints', () => {
let eventCount = 0;
model.onDidChangeBreakpoints(() => eventCount++);
//address: string, offset: number, condition?: string, hitCondition?: string
model.addInstructionBreakpoint('0xCCCCFFFF', 0);
assert.strictEqual(eventCount, 1);
let instructionBreakpoints = model.getInstructionBreakpoints();
assert.strictEqual(instructionBreakpoints.length, 1);
assert.strictEqual(instructionBreakpoints[0].instructionReference, '0xCCCCFFFF');
assert.strictEqual(instructionBreakpoints[0].offset, 0);
model.addInstructionBreakpoint('0xCCCCEEEE', 1);
assert.strictEqual(eventCount, 2);
instructionBreakpoints = model.getInstructionBreakpoints();
assert.strictEqual(instructionBreakpoints.length, 2);
assert.strictEqual(instructionBreakpoints[0].instructionReference, '0xCCCCFFFF');
assert.strictEqual(instructionBreakpoints[0].offset, 0);
assert.strictEqual(instructionBreakpoints[1].instructionReference, '0xCCCCEEEE');
assert.strictEqual(instructionBreakpoints[1].offset, 1);
});
test('data breakpoints', () => {
let eventCount = 0;
model.onDidChangeBreakpoints(() => eventCount++);

View file

@ -38,7 +38,7 @@ export function createMockSession(model: DebugModel, name = 'mockSession', optio
}
};
}
} as IDebugService, undefined!, undefined!, new TestConfigurationService({ debug: { console: { collapseIdenticalLines: true } } }), undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!);
} as IDebugService, undefined!, undefined!, new TestConfigurationService({ debug: { console: { collapseIdenticalLines: true } } }), undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!);
}
function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFrame, secondStackFrame: StackFrame } {
@ -378,7 +378,7 @@ suite('Debug - CallStack', () => {
override get state(): State {
return State.Stopped;
}
}(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!);
}(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!);
const runningSession = createMockSession(model);
model.addSession(runningSession);

View file

@ -7,7 +7,7 @@ import { URI as uri } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions, IEvaluate, IAdapterManager, IRawStoppedDetails } from 'vs/workbench/contrib/debug/common/debug';
import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions, IEvaluate, IAdapterManager, IRawStoppedDetails, IInstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debug';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import Severity from 'vs/base/common/severity';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
@ -86,6 +86,14 @@ export class MockDebugService implements IDebugService {
throw new Error('not implemented');
}
addInstructionBreakpoint(address: string, offset: number, condition?: string, hitCondition?: string): Promise<void> {
throw new Error('Method not implemented.');
}
removeInstructionBreakpoints(address?: string): Promise<void> {
throw new Error('Method not implemented.');
}
setExceptionBreakpointCondition(breakpoint: IExceptionBreakpoint, condition: string): Promise<void> {
throw new Error('Method not implemented.');
}
@ -327,6 +335,9 @@ export class MockSession implements IDebugSession {
sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise<void> {
throw new Error('Method not implemented.');
}
sendInstructionBreakpoints(dbps: IInstructionBreakpoint[]): Promise<void> {
throw new Error('Method not implemented.');
}
getDebugProtocolBreakpoint(breakpointId: string): DebugProtocol.Breakpoint | undefined {
throw new Error('Method not implemented.');
}
@ -351,16 +362,16 @@ export class MockSession implements IDebugSession {
restartFrame(frameId: number, threadId: number): Promise<void> {
throw new Error('Method not implemented.');
}
next(threadId: number): Promise<void> {
next(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {
throw new Error('Method not implemented.');
}
stepIn(threadId: number, targetId?: number): Promise<void> {
stepIn(threadId: number, targetId?: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {
throw new Error('Method not implemented.');
}
stepOut(threadId: number): Promise<void> {
stepOut(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {
throw new Error('Method not implemented.');
}
stepBack(threadId: number): Promise<void> {
stepBack(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise<void> {
throw new Error('Method not implemented.');
}
continue(threadId: number): Promise<void> {
@ -381,6 +392,9 @@ export class MockSession implements IDebugSession {
loadSource(resource: uri): Promise<DebugProtocol.SourceResponse> {
throw new Error('Method not implemented.');
}
disassemble(memoryReference: string, offset: number, instructionOffset: number, instructionCount: number): Promise<DebugProtocol.DisassembledInstruction[] | undefined> {
throw new Error('Method not implemented.');
}
terminate(restart = false): Promise<void> {
throw new Error('Method not implemented.');