debug: support data breakpoints by address (#206855)

This creates a new "Add Data Breakpoint at Address" action in the
breakpoints view when a debugger that supports the capability is
available. It prompts the user to enter their address in a quickpick,
and then allows them to choose appropriate data access settings.

The editor side of https://github.com/microsoft/debug-adapter-protocol/issues/455
This commit is contained in:
Connor Peet 2024-03-04 20:08:00 -08:00 committed by GitHub
parent 191be39e5a
commit d73fa8b14a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 325 additions and 100 deletions

View file

@ -5,7 +5,7 @@
import { DisposableMap, DisposableStore, IDisposable, toDisposable } 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, IInstructionBreakpoint, DebugConfigurationProviderTriggerKind, IDebugVisualization } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDataBreakpoint, IDebugSessionOptions, IInstructionBreakpoint, DebugConfigurationProviderTriggerKind, IDebugVisualization, DataBreakpointSetType } from 'vs/workbench/contrib/debug/common/debug';
import {
ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext,
IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto, IDataBreakpointDto, IStartDebuggingOptions, IDebugConfiguration, IThreadFocusDto, IStackFrameFocusDto
@ -225,7 +225,14 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
} else if (dto.type === 'function') {
this.debugService.addFunctionBreakpoint(dto.functionName, dto.id, dto.mode);
} else if (dto.type === 'data') {
this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist, dto.accessTypes, dto.accessType, dto.mode);
this.debugService.addDataBreakpoint({
description: dto.label,
src: { type: DataBreakpointSetType.Variable, dataId: dto.dataId },
canPersist: dto.canPersist,
accessTypes: dto.accessTypes,
accessType: dto.accessType,
mode: dto.mode
});
}
}
return Promise.resolve();
@ -436,19 +443,20 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
logMessage: fbp.logMessage,
functionName: fbp.name
};
} else if ('dataId' in bp) {
} else if ('src' in bp) {
const dbp = <IDataBreakpoint>bp;
return <IDataBreakpointDto>{
return {
type: 'data',
id: dbp.getId(),
dataId: dbp.dataId,
dataId: dbp.src.type === DataBreakpointSetType.Variable ? dbp.src.dataId : dbp.src.address,
enabled: dbp.enabled,
condition: dbp.condition,
hitCondition: dbp.hitCondition,
logMessage: dbp.logMessage,
accessType: dbp.accessType,
label: dbp.description,
canPersist: dbp.canPersist
};
} satisfies IDataBreakpointDto;
} else {
const sbp = <IBreakpoint>bp;
return <ISourceBreakpointDto>{

View file

@ -51,10 +51,11 @@ import { IEditorPane } from 'vs/workbench/common/editor';
import { IViewDescriptorService } from 'vs/workbench/common/views';
import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons';
import { DisassemblyView } from 'vs/workbench/contrib/debug/browser/disassemblyView';
import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_BREAKPOINT_HAS_MODES, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_BREAKPOINT_ITEM_TYPE, CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_IN_DEBUG_MODE, DEBUG_SCHEME, DebuggerString, IBaseBreakpoint, IBreakpoint, IBreakpointEditorContribution, IBreakpointUpdateData, IDataBreakpoint, IDebugModel, IDebugService, IEnablement, IExceptionBreakpoint, IFunctionBreakpoint, IInstructionBreakpoint, State } from 'vs/workbench/contrib/debug/common/debug';
import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_BREAKPOINT_HAS_MODES, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_BREAKPOINT_ITEM_IS_DATA_BYTES, CONTEXT_BREAKPOINT_ITEM_TYPE, CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_IN_DEBUG_MODE, CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED, DEBUG_SCHEME, DataBreakpointSetType, DataBreakpointSource, DebuggerString, IBaseBreakpoint, IBreakpoint, IBreakpointEditorContribution, IBreakpointUpdateData, IDataBreakpoint, IDataBreakpointInfoResponse, IDebugModel, IDebugService, IEnablement, IExceptionBreakpoint, IFunctionBreakpoint, IInstructionBreakpoint, State } from 'vs/workbench/contrib/debug/common/debug';
import { Breakpoint, DataBreakpoint, ExceptionBreakpoint, FunctionBreakpoint, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput';
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { INotificationService } from 'vs/platform/notification/common/notification';
const $ = dom.$;
@ -87,6 +88,7 @@ export class BreakpointsView extends ViewPane {
private ignoreLayout = false;
private menu: IMenu;
private breakpointItemType: IContextKey<string | undefined>;
private breakpointIsDataBytes: IContextKey<boolean | undefined>;
private breakpointHasMultipleModes: IContextKey<boolean>;
private breakpointSupportsCondition: IContextKey<boolean>;
private _inputBoxData: InputBoxData | undefined;
@ -120,6 +122,7 @@ export class BreakpointsView extends ViewPane {
this.menu = menuService.createMenu(MenuId.DebugBreakpointsContext, contextKeyService);
this._register(this.menu);
this.breakpointItemType = CONTEXT_BREAKPOINT_ITEM_TYPE.bindTo(contextKeyService);
this.breakpointIsDataBytes = CONTEXT_BREAKPOINT_ITEM_IS_DATA_BYTES.bindTo(contextKeyService);
this.breakpointHasMultipleModes = CONTEXT_BREAKPOINT_HAS_MODES.bindTo(contextKeyService);
this.breakpointSupportsCondition = CONTEXT_BREAKPOINT_SUPPORTS_CONDITION.bindTo(contextKeyService);
this.breakpointInputFocused = CONTEXT_BREAKPOINT_INPUT_FOCUSED.bindTo(contextKeyService);
@ -142,7 +145,7 @@ export class BreakpointsView extends ViewPane {
new ExceptionBreakpointInputRenderer(this, this.debugService, this.contextViewService),
this.instantiationService.createInstance(FunctionBreakpointsRenderer, this.menu, this.breakpointSupportsCondition, this.breakpointItemType),
new FunctionBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.labelService),
this.instantiationService.createInstance(DataBreakpointsRenderer, this.menu, this.breakpointHasMultipleModes, this.breakpointSupportsCondition, this.breakpointItemType),
this.instantiationService.createInstance(DataBreakpointsRenderer, this.menu, this.breakpointHasMultipleModes, this.breakpointSupportsCondition, this.breakpointItemType, this.breakpointIsDataBytes),
new DataBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.labelService),
this.instantiationService.createInstance(InstructionBreakpointsRenderer),
], {
@ -266,6 +269,7 @@ export class BreakpointsView extends ViewPane {
const session = this.debugService.getViewModel().focusedSession;
const conditionSupported = element instanceof ExceptionBreakpoint ? element.supportsCondition : (!session || !!session.capabilities.supportsConditionalBreakpoints);
this.breakpointSupportsCondition.set(conditionSupported);
this.breakpointIsDataBytes.set(element instanceof DataBreakpoint && element.src.type === DataBreakpointSetType.Address);
const secondary: IAction[] = [];
createAndFillInContextMenuActions(this.menu, { arg: e.element, shouldForwardArgs: false }, { primary: [], secondary }, 'inline');
@ -740,6 +744,7 @@ class DataBreakpointsRenderer implements IListRenderer<DataBreakpoint, IDataBrea
private breakpointHasMultipleModes: IContextKey<boolean>,
private breakpointSupportsCondition: IContextKey<boolean>,
private breakpointItemType: IContextKey<string | undefined>,
private breakpointIsDataBytes: IContextKey<boolean | undefined>,
@IDebugService private readonly debugService: IDebugService,
@ILabelService private readonly labelService: ILabelService
) {
@ -816,10 +821,12 @@ class DataBreakpointsRenderer implements IListRenderer<DataBreakpoint, IDataBrea
this.breakpointSupportsCondition.set(!session || !!session.capabilities.supportsConditionalBreakpoints);
this.breakpointHasMultipleModes.set(this.debugService.getModel().getBreakpointModes('data').length > 1);
this.breakpointItemType.set('dataBreakpoint');
this.breakpointIsDataBytes.set(dataBreakpoint.src.type === DataBreakpointSetType.Address);
createAndFillInActionBarActions(this.menu, { arg: dataBreakpoint, shouldForwardArgs: true }, { primary, secondary: [] }, 'inline');
data.actionBar.clear();
data.actionBar.push(primary, { icon: true, label: false });
breakpointIdToActionBarDomeNode.set(dataBreakpoint.getId(), data.actionBar.domNode);
this.breakpointIsDataBytes.reset();
}
disposeTemplate(templateData: IBaseBreakpointWithIconTemplateData): void {
@ -1421,6 +1428,166 @@ registerAction2(class extends Action2 {
}
});
abstract class MemoryBreakpointAction extends Action2 {
async run(accessor: ServicesAccessor, existingBreakpoint?: IDataBreakpoint): Promise<void> {
const debugService = accessor.get(IDebugService);
const session = debugService.getViewModel().focusedSession;
if (!session) {
return;
}
let defaultValue = undefined;
if (existingBreakpoint && existingBreakpoint.src.type === DataBreakpointSetType.Address) {
defaultValue = `${existingBreakpoint.src.address} + ${existingBreakpoint.src.bytes}`;
}
const quickInput = accessor.get(IQuickInputService);
const notifications = accessor.get(INotificationService);
const range = await this.getRange(quickInput, defaultValue);
if (!range) {
return;
}
let info: IDataBreakpointInfoResponse | undefined;
try {
info = await session.dataBytesBreakpointInfo(range.address, range.bytes);
} catch (e) {
notifications.error(localize('dataBreakpointError', "Failed to set data breakpoint at {0}: {1}", range.address, e.message));
}
if (!info?.dataId) {
return;
}
let accessType: DebugProtocol.DataBreakpointAccessType = 'write';
if (info.accessTypes && info.accessTypes?.length > 1) {
const accessTypes = info.accessTypes.map(type => ({ label: type }));
const selectedAccessType = await quickInput.pick(accessTypes, { placeHolder: localize('dataBreakpointAccessType', "Select the access type to monitor") });
if (!selectedAccessType) {
return;
}
accessType = selectedAccessType.label;
}
const src: DataBreakpointSource = { type: DataBreakpointSetType.Address, ...range };
if (existingBreakpoint) {
await debugService.removeDataBreakpoints(existingBreakpoint.getId());
}
await debugService.addDataBreakpoint({
description: info.description,
src,
canPersist: true,
accessTypes: info.accessTypes,
accessType: accessType,
initialSessionData: { session, dataId: info.dataId }
});
}
private getRange(quickInput: IQuickInputService, defaultValue?: string) {
return new Promise<{ address: string; bytes: number } | undefined>(resolve => {
const input = quickInput.createInputBox();
input.prompt = localize('dataBreakpointMemoryRangePrompt', "Enter a memory range in which to break");
input.placeholder = localize('dataBreakpointMemoryRangePlaceholder', 'Absolute range (0x1234 - 0x1300) or range of bytes after an address (0x1234 + 0xff)');
if (defaultValue) {
input.value = defaultValue;
input.valueSelection = [0, defaultValue.length];
}
input.onDidChangeValue(e => {
const err = this.parseAddress(e, false);
input.validationMessage = err?.error;
});
input.onDidAccept(() => {
const r = this.parseAddress(input.value, true);
if ('error' in r) {
input.validationMessage = r.error;
} else {
resolve(r);
}
input.dispose();
});
input.onDidHide(() => {
resolve(undefined);
input.dispose();
});
input.ignoreFocusOut = true;
input.show();
});
}
private parseAddress(range: string, isFinal: false): { error: string } | undefined;
private parseAddress(range: string, isFinal: true): { error: string } | { address: string; bytes: number };
private parseAddress(range: string, isFinal: boolean): { error: string } | { address: string; bytes: number } | undefined {
const parts = /^(\S+)\s*(?:([+-])\s*(\S+))?/.exec(range);
if (!parts) {
return { error: localize('dataBreakpointAddrFormat', 'Address should be a range of numbers the form "[Start] - [End]" or "[Start] + [Bytes]"') };
}
const isNum = (e: string) => isFinal ? /^0x[0-9a-f]*|[0-9]*$/i.test(e) : /^0x[0-9a-f]+|[0-9]+$/i.test(e);
const [, startStr, sign = '+', endStr = '1'] = parts;
for (const n of [startStr, endStr]) {
if (!isNum(n)) {
return { error: localize('dataBreakpointAddrStartEnd', 'Number must be a decimal integer or hex value starting with \"0x\", got {0}', n) };
}
}
if (!isFinal) {
return;
}
const start = BigInt(startStr);
const end = BigInt(endStr);
const address = `0x${start.toString(16)}`;
if (sign === '-') {
return { address, bytes: Number(start - end) };
}
return { address, bytes: Number(end) };
}
}
registerAction2(class extends MemoryBreakpointAction {
constructor() {
super({
id: 'workbench.debug.viewlet.action.addDataBreakpointOnAddress',
title: {
...localize2('addDataBreakpointOnAddress', "Add Data Breakpoint at Address"),
mnemonicTitle: localize({ key: 'miDataBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Data Breakpoint..."),
},
f1: true,
icon: icons.watchExpressionsAddDataBreakpoint,
menu: [{
id: MenuId.ViewTitle,
group: 'navigation',
order: 11,
when: ContextKeyExpr.and(CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED, ContextKeyExpr.equals('view', BREAKPOINTS_VIEW_ID))
}, {
id: MenuId.MenubarNewBreakpointMenu,
group: '1_breakpoints',
order: 4,
when: CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED
}]
});
}
});
registerAction2(class extends MemoryBreakpointAction {
constructor() {
super({
id: 'workbench.debug.viewlet.action.editDataBreakpointOnAddress',
title: localize2('editDataBreakpointOnAddress', "Edit Address..."),
menu: [{
id: MenuId.DebugBreakpointsContext,
when: ContextKeyExpr.and(CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED, CONTEXT_BREAKPOINT_ITEM_IS_DATA_BYTES),
group: 'navigation',
order: 15,
}]
});
}
});
registerAction2(class extends Action2 {
constructor() {
super({

View file

@ -79,6 +79,7 @@ export const watchExpressionsRemoveAll = registerIcon('watch-expressions-remove-
export const watchExpressionRemove = registerIcon('watch-expression-remove', Codicon.removeClose, localize('watchExpressionRemove', 'Icon for the Remove action in the watch view.'));
export const watchExpressionsAdd = registerIcon('watch-expressions-add', Codicon.add, localize('watchExpressionsAdd', 'Icon for the add action in the watch view.'));
export const watchExpressionsAddFuncBreakpoint = registerIcon('watch-expressions-add-function-breakpoint', Codicon.add, localize('watchExpressionsAddFuncBreakpoint', 'Icon for the add function breakpoint action in the watch view.'));
export const watchExpressionsAddDataBreakpoint = registerIcon('watch-expressions-add-data-breakpoint', Codicon.variableGroup, localize('watchExpressionsAddDataBreakpoint', 'Icon for the add data breakpoint action in the breakpoints view.'));
export const breakpointsRemoveAll = registerIcon('breakpoints-remove-all', Codicon.closeAll, localize('breakpointsRemoveAll', 'Icon for the Remove All action in the breakpoints view.'));
export const breakpointsActivate = registerIcon('breakpoints-activate', Codicon.activateBreakpoints, localize('breakpointsActivate', 'Icon for the activate action in the breakpoints view.'));

View file

@ -6,7 +6,7 @@
import * as aria from 'vs/base/browser/ui/aria/aria';
import { Action, IAction } from 'vs/base/common/actions';
import { distinct } from 'vs/base/common/arrays';
import { raceTimeout, RunOnceScheduler } from 'vs/base/common/async';
import { RunOnceScheduler, raceTimeout } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { isErrorWithActions } from 'vs/base/common/errorMessage';
import * as errors from 'vs/base/common/errors';
@ -24,7 +24,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files';
import { FileChangeType, FileChangesEvent, IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
@ -34,22 +34,21 @@ import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/work
import { EditorsOrder } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
import { AdapterManager } from 'vs/workbench/contrib/debug/browser/debugAdapterManager';
import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands';
import { ConfigurationManager } from 'vs/workbench/contrib/debug/browser/debugConfigurationManager';
import { DebugMemoryFileSystemProvider } from 'vs/workbench/contrib/debug/browser/debugMemory';
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
import { DebugTaskRunner, TaskRunResult } from 'vs/workbench/contrib/debug/browser/debugTaskRunner';
import { CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_HAS_DEBUGGED, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_UX, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_IN_DEBUG_MODE, debuggerDisabledMessage, DEBUG_MEMORY_SCHEME, getStateLabel, IAdapterManager, IBreakpoint, IBreakpointData, ICompound, IConfig, IConfigurationManager, IDebugConfiguration, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IEnablement, IExceptionBreakpoint, IGlobalConfig, ILaunch, IStackFrame, IThread, IViewModel, REPL_VIEW_ID, State, VIEWLET_ID, DEBUG_SCHEME, IBreakpointUpdateData } from 'vs/workbench/contrib/debug/common/debug';
import { CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_UX, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_HAS_DEBUGGED, CONTEXT_IN_DEBUG_MODE, DEBUG_MEMORY_SCHEME, DEBUG_SCHEME, IAdapterManager, IBreakpoint, IBreakpointData, IBreakpointUpdateData, ICompound, IConfig, IConfigurationManager, IDebugConfiguration, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IEnablement, IExceptionBreakpoint, IGlobalConfig, ILaunch, IStackFrame, IThread, IViewModel, REPL_VIEW_ID, State, VIEWLET_ID, debuggerDisabledMessage, getStateLabel } from 'vs/workbench/contrib/debug/common/debug';
import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot';
import { Debugger } from 'vs/workbench/contrib/debug/common/debugger';
import { Breakpoint, DataBreakpoint, DebugModel, FunctionBreakpoint, IInstructionBreakpointOptions, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { Breakpoint, DataBreakpoint, DebugModel, FunctionBreakpoint, IDataBreakpointOptions, IInstructionBreakpointOptions, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage';
import { DebugTelemetry } from 'vs/workbench/contrib/debug/common/debugTelemetry';
import { getExtensionHostDebugSession, saveAllBeforeDebugStart } from 'vs/workbench/contrib/debug/common/debugUtils';
import { ViewModel } from 'vs/workbench/contrib/debug/common/debugViewModel';
import { Debugger } from 'vs/workbench/contrib/debug/common/debugger';
import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput';
import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from 'vs/workbench/contrib/files/common/files';
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
@ -58,6 +57,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
export class DebugService implements IDebugService {
declare readonly _serviceBrand: undefined;
@ -1081,8 +1081,8 @@ export class DebugService implements IDebugService {
await this.sendFunctionBreakpoints();
}
async addDataBreakpoint(description: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType, mode: string | undefined): Promise<void> {
this.model.addDataBreakpoint({ description, dataId, canPersist, accessTypes, accessType, mode });
async addDataBreakpoint(opts: IDataBreakpointOptions): Promise<void> {
this.model.addDataBreakpoint(opts);
this.debugStorage.storeBreakpoints(this.model);
await this.sendDataBreakpoints();
this.debugStorage.storeBreakpoints(this.model);

View file

@ -29,7 +29,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ViewContainerLocation } from 'vs/workbench/common/views';
import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession';
import { AdapterEndEvent, IBreakpoint, IConfig, IDataBreakpoint, IDebugConfiguration, IDebugService, IDebugSession, IDebugSessionOptions, IDebugger, IExceptionBreakpoint, IExceptionInfo, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, LoadedSourceEvent, State, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug';
import { AdapterEndEvent, IBreakpoint, IConfig, IDataBreakpoint, IDataBreakpointInfoResponse, IDebugConfiguration, IDebugService, IDebugSession, IDebugSessionOptions, IDebugger, IExceptionBreakpoint, IExceptionInfo, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, LoadedSourceEvent, State, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug';
import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot';
import { DebugModel, ExpressionContainer, MemoryRegion, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
@ -41,6 +41,7 @@ import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecy
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { getActiveWindow } from 'vs/base/browser/dom';
import { mainWindow } from 'vs/base/browser/window';
import { isDefined } from 'vs/base/common/types';
const TRIGGERED_BREAKPOINT_MAX_DELAY = 1500;
@ -461,7 +462,7 @@ export class DebugSession implements IDebugSession, IDisposable {
breakpoints: breakpointsToSend.map(bp => bp.toDAP()),
sourceModified
});
if (response && response.body) {
if (response?.body) {
const data = new Map<string, DebugProtocol.Breakpoint>();
for (let i = 0; i < breakpointsToSend.length; i++) {
data.set(breakpointsToSend[i].getId(), response.body.breakpoints[i]);
@ -478,7 +479,7 @@ export class DebugSession implements IDebugSession, IDisposable {
if (this.raw.readyForBreakpoints) {
const response = await this.raw.setFunctionBreakpoints({ breakpoints: fbpts.map(bp => bp.toDAP()) });
if (response && response.body) {
if (response?.body) {
const data = new Map<string, DebugProtocol.Breakpoint>();
for (let i = 0; i < fbpts.length; i++) {
data.set(fbpts[i].getId(), response.body.breakpoints[i]);
@ -506,7 +507,7 @@ export class DebugSession implements IDebugSession, IDisposable {
} : { filters: exbpts.map(exb => exb.filter) };
const response = await this.raw.setExceptionBreakpoints(args);
if (response && response.body && response.body.breakpoints) {
if (response?.body && response.body.breakpoints) {
const data = new Map<string, DebugProtocol.Breakpoint>();
for (let i = 0; i < exbpts.length; i++) {
data.set(exbpts[i].getId(), response.body.breakpoints[i]);
@ -517,7 +518,19 @@ export class DebugSession implements IDebugSession, IDisposable {
}
}
async dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null; description: string; canPersist?: boolean } | undefined> {
dataBytesBreakpointInfo(address: string, bytes: number): Promise<IDataBreakpointInfoResponse | undefined> {
if (this.raw?.capabilities.supportsDataBreakpointBytes === false) {
throw new Error(localize('sessionDoesNotSupporBytesBreakpoints', "Session does not support breakpoints with bytes"));
}
return this._dataBreakpointInfo({ name: address, bytes, asAddress: true });
}
dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null; description: string; canPersist?: boolean } | undefined> {
return this._dataBreakpointInfo({ name, variablesReference });
}
private async _dataBreakpointInfo(args: DebugProtocol.DataBreakpointInfoArguments): Promise<{ dataId: string | null; description: string; canPersist?: boolean } | undefined> {
if (!this.raw) {
throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'data breakpoints info'));
}
@ -525,7 +538,7 @@ export class DebugSession implements IDebugSession, IDisposable {
throw new Error(localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints"));
}
const response = await this.raw.dataBreakpointInfo({ name, variablesReference });
const response = await this.raw.dataBreakpointInfo(args);
return response?.body;
}
@ -535,11 +548,24 @@ export class DebugSession implements IDebugSession, IDisposable {
}
if (this.raw.readyForBreakpoints) {
const response = await this.raw.setDataBreakpoints({ breakpoints: dataBreakpoints.map(bp => bp.toDAP()) });
if (response && response.body) {
const converted = await Promise.all(dataBreakpoints.map(async bp => {
try {
const dap = await bp.toDAP(this);
return { dap, bp };
} catch (e) {
return { bp, message: e.message };
}
}));
const response = await this.raw.setDataBreakpoints({ breakpoints: converted.map(d => d.dap).filter(isDefined) });
if (response?.body) {
const data = new Map<string, DebugProtocol.Breakpoint>();
for (let i = 0; i < dataBreakpoints.length; i++) {
data.set(dataBreakpoints[i].getId(), response.body.breakpoints[i]);
let i = 0;
for (const dap of converted) {
if (!dap.dap) {
data.set(dap.bp.getId(), dap.message);
} else if (i < response.body.breakpoints.length) {
data.set(dap.bp.getId(), response.body.breakpoints[i++]);
}
}
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
}
@ -553,7 +579,7 @@ export class DebugSession implements IDebugSession, IDisposable {
if (this.raw.readyForBreakpoints) {
const response = await this.raw.setInstructionBreakpoints({ breakpoints: instructionBreakpoints.map(ib => ib.toDAP()) });
if (response && response.body) {
if (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]);
@ -790,7 +816,7 @@ export class DebugSession implements IDebugSession, IDisposable {
}
const response = await this.raw.loadedSources({});
if (response && response.body && response.body.sources) {
if (response?.body && response.body.sources) {
return response.body.sources.map(src => this.getSource(src));
} else {
return [];
@ -959,7 +985,7 @@ export class DebugSession implements IDebugSession, IDisposable {
private async fetchThreads(stoppedDetails?: IRawStoppedDetails): Promise<void> {
if (this.raw) {
const response = await this.raw.threads();
if (response && response.body && response.body.threads) {
if (response?.body && response.body.threads) {
this.model.rawUpdate({
sessionId: this.getId(),
threads: response.body.threads,

View file

@ -39,7 +39,7 @@ import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewl
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { AbstractExpressionDataSource, AbstractExpressionsRenderer, IExpressionTemplateData, IInputBoxOptions, renderExpressionValue, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView';
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_VARIABLES_FOCUSED, DebugVisualizationType, IDataBreakpointInfoResponse, IDebugService, IExpression, IScope, IStackFrame, IViewModel, VARIABLES_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug';
import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_VARIABLES_FOCUSED, DataBreakpointSetType, DebugVisualizationType, IDataBreakpointInfoResponse, IDebugService, IExpression, IScope, IStackFrame, IViewModel, VARIABLES_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug';
import { getContextForVariable } from 'vs/workbench/contrib/debug/common/debugContext';
import { ErrorScope, Expression, Scope, StackFrame, Variable, VisualizedExpression, getUriForDebugMemory } from 'vs/workbench/contrib/debug/common/debugModel';
import { DebugVisualizer, IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers';
@ -766,7 +766,7 @@ CommandsRegistry.registerCommand({
handler: async (accessor: ServicesAccessor) => {
const debugService = accessor.get(IDebugService);
if (dataBreakpointInfoResponse) {
await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'write', undefined);
await debugService.addDataBreakpoint({ description: dataBreakpointInfoResponse.description, src: { type: DataBreakpointSetType.Variable, dataId: dataBreakpointInfoResponse.dataId! }, canPersist: !!dataBreakpointInfoResponse.canPersist, accessTypes: dataBreakpointInfoResponse.accessTypes, accessType: 'write' });
}
}
});
@ -777,7 +777,7 @@ CommandsRegistry.registerCommand({
handler: async (accessor: ServicesAccessor) => {
const debugService = accessor.get(IDebugService);
if (dataBreakpointInfoResponse) {
await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'readWrite', undefined);
await debugService.addDataBreakpoint({ description: dataBreakpointInfoResponse.description, src: { type: DataBreakpointSetType.Variable, dataId: dataBreakpointInfoResponse.dataId! }, canPersist: !!dataBreakpointInfoResponse.canPersist, accessTypes: dataBreakpointInfoResponse.accessTypes, accessType: 'readWrite' });
}
}
});
@ -788,7 +788,7 @@ CommandsRegistry.registerCommand({
handler: async (accessor: ServicesAccessor) => {
const debugService = accessor.get(IDebugService);
if (dataBreakpointInfoResponse) {
await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'read', undefined);
await debugService.addDataBreakpoint({ description: dataBreakpointInfoResponse.description, src: { type: DataBreakpointSetType.Variable, dataId: dataBreakpointInfoResponse.dataId! }, canPersist: !!dataBreakpointInfoResponse.canPersist, accessTypes: dataBreakpointInfoResponse.accessTypes, accessType: 'read' });
}
}
});

View file

@ -24,7 +24,7 @@ import { ITelemetryEndpoint } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IEditorPane } from 'vs/workbench/common/editor';
import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot';
import { IInstructionBreakpointOptions } from 'vs/workbench/contrib/debug/common/debugModel';
import { IDataBreakpointOptions, IInstructionBreakpointOptions } from 'vs/workbench/contrib/debug/common/debugModel';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { ITaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@ -62,6 +62,7 @@ export const CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD = new RawContextKey<boolea
export const CONTEXT_WATCH_ITEM_TYPE = new RawContextKey<string>('watchItemType', undefined, { type: 'string', description: nls.localize('watchItemType', "Represents the item type of the focused element in the WATCH view. For example: 'expression', 'variable'") });
export const CONTEXT_CAN_VIEW_MEMORY = new RawContextKey<boolean>('canViewMemory', undefined, { type: 'boolean', description: nls.localize('canViewMemory', "Indicates whether the item in the view has an associated memory refrence.") });
export const CONTEXT_BREAKPOINT_ITEM_TYPE = new RawContextKey<string>('breakpointItemType', undefined, { type: 'string', description: nls.localize('breakpointItemType', "Represents the item type of the focused element in the BREAKPOINTS view. For example: 'breakpoint', 'exceptionBreakppint', 'functionBreakpoint', 'dataBreakpoint'") });
export const CONTEXT_BREAKPOINT_ITEM_IS_DATA_BYTES = new RawContextKey<boolean>('breakpointItemBytes', undefined, { type: 'boolean', description: nls.localize('breakpointItemIsDataBytes', "Whether the breakpoint item is a data breakpoint on a byte range.") });
export const CONTEXT_BREAKPOINT_HAS_MODES = new RawContextKey<boolean>('breakpointHasModes', false, { type: 'boolean', description: nls.localize('breakpointHasModes', "Whether the breakpoint has multiple modes it can switch to.") });
export const CONTEXT_BREAKPOINT_SUPPORTS_CONDITION = new RawContextKey<boolean>('breakpointSupportsCondition', false, { type: 'boolean', description: nls.localize('breakpointSupportsCondition', "True when the focused breakpoint supports conditions.") });
export const CONTEXT_LOADED_SCRIPTS_SUPPORTED = new RawContextKey<boolean>('loadedScriptsSupported', false, { type: 'boolean', description: nls.localize('loadedScriptsSupported', "True when the focused sessions supports the LOADED SCRIPTS view") });
@ -78,6 +79,7 @@ export const CONTEXT_DEBUGGERS_AVAILABLE = new RawContextKey<boolean>('debuggers
export const CONTEXT_DEBUG_EXTENSION_AVAILABLE = new RawContextKey<boolean>('debugExtensionAvailable', true, { type: 'boolean', description: nls.localize('debugExtensionsAvailable', "True when there is at least one debug extension installed and enabled.") });
export const CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT = new RawContextKey<string>('debugProtocolVariableMenuContext', undefined, { type: 'string', description: nls.localize('debugProtocolVariableMenuContext', "Represents the context the debug adapter sets on the focused variable in the VARIABLES view.") });
export const CONTEXT_SET_VARIABLE_SUPPORTED = new RawContextKey<boolean>('debugSetVariableSupported', false, { type: 'boolean', description: nls.localize('debugSetVariableSupported', "True when the focused session supports 'setVariable' request.") });
export const CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED = new RawContextKey<boolean>('debugSetDataBreakpointAddressSupported', false, { type: 'boolean', description: nls.localize('debugSetDataBreakpointAddressSupported', "True when the focused session supports 'getBreakpointInfo' request on an address.") });
export const CONTEXT_SET_EXPRESSION_SUPPORTED = new RawContextKey<boolean>('debugSetExpressionSupported', false, { type: 'boolean', description: nls.localize('debugSetExpressionSupported', "True when the focused session supports 'setExpression' request.") });
export const CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED = new RawContextKey<boolean>('breakWhenValueChangesSupported', false, { type: 'boolean', description: nls.localize('breakWhenValueChangesSupported', "True when the focused session supports to break when value changes.") });
export const CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED = new RawContextKey<boolean>('breakWhenValueIsAccessedSupported', false, { type: 'boolean', description: nls.localize('breakWhenValueIsAccessedSupported', "True when the focused breakpoint supports to break when value is accessed.") });
@ -404,6 +406,7 @@ export interface IDebugSession extends ITreeElement {
sendBreakpoints(modelUri: uri, bpts: IBreakpoint[], sourceModified: boolean): Promise<void>;
sendFunctionBreakpoints(fbps: IFunctionBreakpoint[]): Promise<void>;
dataBreakpointInfo(name: string, variablesReference?: number): Promise<IDataBreakpointInfoResponse | undefined>;
dataBytesBreakpointInfo(address: string, bytes: number): Promise<IDataBreakpointInfoResponse | undefined>;
sendDataBreakpoints(dbps: IDataBreakpoint[]): Promise<void>;
sendInstructionBreakpoints(dbps: IInstructionBreakpoint[]): Promise<void>;
sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise<void>;
@ -607,12 +610,26 @@ export interface IExceptionBreakpoint extends IBaseBreakpoint {
readonly description: string | undefined;
}
export const enum DataBreakpointSetType {
Variable,
Address,
}
/**
* Source for a data breakpoint. A data breakpoint on a variable always has a
* `dataId` because it cannot reference that variable globally, but addresses
* can request info repeated and use session-specific data.
*/
export type DataBreakpointSource =
| { type: DataBreakpointSetType.Variable; dataId: string }
| { type: DataBreakpointSetType.Address; address: string; bytes: number };
export interface IDataBreakpoint extends IBaseBreakpoint {
readonly description: string;
readonly dataId: string;
readonly canPersist: boolean;
readonly src: DataBreakpointSource;
readonly accessType: DebugProtocol.DataBreakpointAccessType;
toDAP(): DebugProtocol.DataBreakpoint;
toDAP(session: IDebugSession): Promise<DebugProtocol.DataBreakpoint | undefined>;
}
export interface IInstructionBreakpoint extends IBaseBreakpoint {
@ -1144,7 +1161,7 @@ export interface IDebugService {
/**
* Adds a new data breakpoint.
*/
addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType, mode: string | undefined): Promise<void>;
addDataBreakpoint(opts: IDataBreakpointOptions): Promise<void>;
/**
* Updates an already existing data breakpoint.

View file

@ -22,7 +22,7 @@ import * as nls from 'vs/nls';
import { ILogService } from 'vs/platform/log/common/log';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { IEditorPane } from 'vs/workbench/common/editor';
import { DEBUG_MEMORY_SCHEME, DebugTreeItemCollapsibleState, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IBreakpointsChangeEvent, IDataBreakpoint, IDebugModel, IDebugSession, IDebugVisualizationTreeItem, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State } from 'vs/workbench/contrib/debug/common/debug';
import { DEBUG_MEMORY_SCHEME, DataBreakpointSetType, DataBreakpointSource, DebugTreeItemCollapsibleState, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IBreakpointsChangeEvent, IDataBreakpoint, IDebugModel, IDebugSession, IDebugVisualizationTreeItem, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State } from 'vs/workbench/contrib/debug/common/debug';
import { Source, UNKNOWN_SOURCE_LABEL, getUriFromSource } from 'vs/workbench/contrib/debug/common/debugSource';
import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage';
import { IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers';
@ -1150,15 +1150,18 @@ export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreak
export interface IDataBreakpointOptions extends IBaseBreakpointOptions {
description: string;
dataId: string;
src: DataBreakpointSource;
canPersist: boolean;
initialSessionData?: { session: IDebugSession; dataId: string };
accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined;
accessType: DebugProtocol.DataBreakpointAccessType;
}
export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint {
private readonly sessionDataIdForAddr = new WeakMap<IDebugSession, string | null>();
public readonly description: string;
public readonly dataId: string;
public readonly src: DataBreakpointSource;
public readonly canPersist: boolean;
public readonly accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined;
public readonly accessType: DebugProtocol.DataBreakpointAccessType;
@ -1169,15 +1172,36 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint {
) {
super(id, opts);
this.description = opts.description;
this.dataId = opts.dataId;
if ('dataId' in opts) { // back compat with old saved variables in 1.87
opts.src = { type: DataBreakpointSetType.Variable, dataId: opts.dataId as string };
}
this.src = opts.src;
this.canPersist = opts.canPersist;
this.accessTypes = opts.accessTypes;
this.accessType = opts.accessType;
if (opts.initialSessionData) {
this.sessionDataIdForAddr.set(opts.initialSessionData.session, opts.initialSessionData.dataId);
}
}
toDAP(): DebugProtocol.DataBreakpoint {
async toDAP(session: IDebugSession): Promise<DebugProtocol.DataBreakpoint | undefined> {
let dataId: string;
if (this.src.type === DataBreakpointSetType.Variable) {
dataId = this.src.dataId;
} else {
let sessionDataId = this.sessionDataIdForAddr.get(session);
if (!sessionDataId) {
sessionDataId = (await session.dataBytesBreakpointInfo(this.src.address, this.src.bytes))?.dataId;
if (!sessionDataId) {
return undefined;
}
this.sessionDataIdForAddr.set(session, sessionDataId);
}
dataId = sessionDataId;
}
return {
dataId: this.dataId,
dataId,
accessType: this.accessType,
condition: this.condition,
hitCondition: this.hitCondition,
@ -1188,7 +1212,7 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint {
return {
...super.toJSON(),
description: this.description,
dataId: this.dataId,
src: this.src,
accessTypes: this.accessTypes,
accessType: this.accessType,
canPersist: this.canPersist,

View file

@ -813,11 +813,22 @@ declare module DebugProtocol {
/** Reference to the variable container if the data breakpoint is requested for a child of the container. The `variablesReference` must have been obtained in the current suspended state. See 'Lifetime of Object References' in the Overview section for details. */
variablesReference?: number;
/** The name of the variable's child to obtain data breakpoint information for.
If `variablesReference` isn't specified, this can be an expression.
If `variablesReference` isn't specified, this can be an expression, or an address if `asAddress` is also true.
*/
name: string;
/** When `name` is an expression, evaluate it in the scope of this stack frame. If not specified, the expression is evaluated in the global scope. When `variablesReference` is specified, this property has no effect. */
frameId?: number;
/** If specified, a debug adapter should return information for the range of memory extending `bytes` number of bytes from the address or variable specified by `name`. Breakpoints set using the resulting data ID should pause on data access anywhere within that range.
Clients may set this property only if the `supportsDataBreakpointBytes` capability is true.
*/
bytes?: number;
/** If `true`, the `name` is a memory address and the debugger should interpret it as a decimal value, or hex value if it is prefixed with `0x`.
Clients may set this property only if the `supportsDataBreakpointBytes`
capability is true.
*/
asAddress?: boolean;
/** The mode of the desired breakpoint. If defined, this must be one of the `breakpointModes` the debug adapter advertised in its `Capabilities`. */
mode?: string;
}
@ -1680,42 +1691,6 @@ declare module DebugProtocol {
};
}
/** DataAddressBreakpointInfo request; value of command field is 'DataAddressBreakpointInfo'.
Obtains information on a possible data breakpoint that could be set on a memory address or memory address range.
Clients should only call this request if the corresponding capability `supportsDataAddressInfo` is true.
*/
interface DataAddressBreakpointInfoRequest extends Request {
// command: 'DataAddressBreakpointInfo';
arguments: DataAddressBreakpointInfoArguments;
}
/** Arguments for `dataAddressBreakpointInfo` request. */
interface DataAddressBreakpointInfoArguments {
/** The address of the data for which to obtain breakpoint information.
Treated as a hex value if prefixed with `0x`, or as a decimal value otherwise.
*/
address?: string;
/** If passed, requests breakpoint information for an exclusive byte range rather than a single address. The range extends the given number of `bytes` from the start `address`.
Treated as a hex value if prefixed with `0x`, or as a decimal value otherwise.
*/
bytes?: string;
}
/** Response to `dataAddressBreakpointInfo` request. */
interface DataAddressBreakpointInfoResponse extends Response {
body: {
/** An identifier for the data on which a data breakpoint can be registered with the `setDataBreakpoints` request or null if no data breakpoint is available. If a `variablesReference` or `frameId` is passed, the `dataId` is valid in the current suspended state, otherwise it's valid indefinitely. See 'Lifetime of Object References' in the Overview section for details. Breakpoints set using the `dataId` in the `setDataBreakpoints` request may outlive the lifetime of the associated `dataId`. */
dataId: string | null;
/** UI string that describes on what data the breakpoint is set on or why a data breakpoint is not available. */
description: string;
/** Attribute lists the available access types for a potential data breakpoint. A UI client could surface this information. */
accessTypes?: DataBreakpointAccessType[];
/** Attribute indicates that a potential data breakpoint could be persisted across sessions. */
canPersist?: boolean;
};
}
/** Information about the capabilities of a debug adapter. */
interface Capabilities {
/** The debug adapter supports the `configurationDone` request. */
@ -1788,8 +1763,6 @@ declare module DebugProtocol {
supportsBreakpointLocationsRequest?: boolean;
/** The debug adapter supports the `clipboard` context value in the `evaluate` request. */
supportsClipboardContext?: boolean;
/** The debug adapter supports the `dataAddressBreakpointInfo` request. */
supportsDataAddressInfo?: boolean;
/** The debug adapter supports stepping granularities (argument `granularity`) for the stepping requests. */
supportsSteppingGranularity?: boolean;
/** The debug adapter supports adding breakpoints based on instruction references. */
@ -1798,6 +1771,8 @@ declare module DebugProtocol {
supportsExceptionFilterOptions?: boolean;
/** The debug adapter supports the `singleThread` property on the execution requests (`continue`, `next`, `stepIn`, `stepOut`, `reverseContinue`, `stepBack`). */
supportsSingleThreadExecutionRequests?: boolean;
/** The debug adapter supports the `asAddress` and `bytes` fields in the `dataBreakpointInfo` request. */
supportsDataBreakpointBytes?: boolean;
/** Modes of breakpoints supported by the debug adapter, such as 'hardware' or 'software'. If present, the client may allow the user to select a mode and include it in its `setBreakpoints` request.
Clients may present the first applicable mode in this array as the 'default' mode in gestures that set breakpoints.

View file

@ -5,7 +5,7 @@
import { Emitter, Event } from 'vs/base/common/event';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_EXPRESSION_SELECTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugSession, IExpression, IExpressionContainer, IStackFrame, IThread, IViewModel } from 'vs/workbench/contrib/debug/common/debug';
import { CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_EXPRESSION_SELECTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugSession, IExpression, IExpressionContainer, IStackFrame, IThread, IViewModel } from 'vs/workbench/contrib/debug/common/debug';
import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils';
export class ViewModel implements IViewModel {
@ -34,6 +34,7 @@ export class ViewModel implements IViewModel {
private stepIntoTargetsSupported!: IContextKey<boolean>;
private jumpToCursorSupported!: IContextKey<boolean>;
private setVariableSupported!: IContextKey<boolean>;
private setDataBreakpointAtByteSupported!: IContextKey<boolean>;
private setExpressionSupported!: IContextKey<boolean>;
private multiSessionDebug!: IContextKey<boolean>;
private terminateDebuggeeSupported!: IContextKey<boolean>;
@ -52,6 +53,7 @@ export class ViewModel implements IViewModel {
this.stepIntoTargetsSupported = CONTEXT_STEP_INTO_TARGETS_SUPPORTED.bindTo(contextKeyService);
this.jumpToCursorSupported = CONTEXT_JUMP_TO_CURSOR_SUPPORTED.bindTo(contextKeyService);
this.setVariableSupported = CONTEXT_SET_VARIABLE_SUPPORTED.bindTo(contextKeyService);
this.setDataBreakpointAtByteSupported = CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED.bindTo(contextKeyService);
this.setExpressionSupported = CONTEXT_SET_EXPRESSION_SUPPORTED.bindTo(contextKeyService);
this.multiSessionDebug = CONTEXT_MULTI_SESSION_DEBUG.bindTo(contextKeyService);
this.terminateDebuggeeSupported = CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED.bindTo(contextKeyService);
@ -88,15 +90,16 @@ export class ViewModel implements IViewModel {
this._focusedSession = session;
this.contextKeyService.bufferChangeEvents(() => {
this.loadedScriptsSupportedContextKey.set(session ? !!session.capabilities.supportsLoadedSourcesRequest : false);
this.stepBackSupportedContextKey.set(session ? !!session.capabilities.supportsStepBack : false);
this.restartFrameSupportedContextKey.set(session ? !!session.capabilities.supportsRestartFrame : false);
this.stepIntoTargetsSupported.set(session ? !!session.capabilities.supportsStepInTargetsRequest : false);
this.jumpToCursorSupported.set(session ? !!session.capabilities.supportsGotoTargetsRequest : false);
this.setVariableSupported.set(session ? !!session.capabilities.supportsSetVariable : false);
this.setExpressionSupported.set(session ? !!session.capabilities.supportsSetExpression : false);
this.terminateDebuggeeSupported.set(session ? !!session.capabilities.supportTerminateDebuggee : false);
this.suspendDebuggeeSupported.set(session ? !!session.capabilities.supportSuspendDebuggee : false);
this.loadedScriptsSupportedContextKey.set(!!session?.capabilities.supportsLoadedSourcesRequest);
this.stepBackSupportedContextKey.set(!!session?.capabilities.supportsStepBack);
this.restartFrameSupportedContextKey.set(!!session?.capabilities.supportsRestartFrame);
this.stepIntoTargetsSupported.set(!!session?.capabilities.supportsStepInTargetsRequest);
this.jumpToCursorSupported.set(!!session?.capabilities.supportsGotoTargetsRequest);
this.setVariableSupported.set(!!session?.capabilities.supportsSetVariable);
this.setDataBreakpointAtByteSupported.set(!!session?.capabilities.supportsDataBreakpointBytes);
this.setExpressionSupported.set(!!session?.capabilities.supportsSetExpression);
this.terminateDebuggeeSupported.set(!!session?.capabilities.supportTerminateDebuggee);
this.suspendDebuggeeSupported.set(!!session?.capabilities.supportSuspendDebuggee);
this.disassembleRequestSupported.set(!!session?.capabilities.supportsDisassembleRequest);
this.focusedStackFrameHasInstructionPointerReference.set(!!stackFrame?.instructionPointerReference);
const attach = !!session && isSessionAttach(session);

View file

@ -19,7 +19,7 @@ import { NullLogService } from 'vs/platform/log/common/log';
import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { createBreakpointDecorations } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution';
import { getBreakpointMessageAndIcon, getExpandedBodySize } from 'vs/workbench/contrib/debug/browser/breakpointsView';
import { IBreakpointData, IBreakpointUpdateData, IDebugService, State } from 'vs/workbench/contrib/debug/common/debug';
import { DataBreakpointSetType, IBreakpointData, IBreakpointUpdateData, IDebugService, State } from 'vs/workbench/contrib/debug/common/debug';
import { Breakpoint, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel';
import { createTestSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test';
import { createMockDebugModel, mockUriIdentityService } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel';
@ -313,13 +313,13 @@ suite('Debug - Breakpoints', () => {
let eventCount = 0;
disposables.add(model.onDidChangeBreakpoints(() => eventCount++));
model.addDataBreakpoint({ description: 'label', dataId: 'id', canPersist: true, accessTypes: ['read'], accessType: 'read' }, '1');
model.addDataBreakpoint({ description: 'second', dataId: 'secondId', canPersist: false, accessTypes: ['readWrite'], accessType: 'readWrite' }, '2');
model.addDataBreakpoint({ description: 'label', src: { type: DataBreakpointSetType.Variable, dataId: 'id' }, canPersist: true, accessTypes: ['read'], accessType: 'read' }, '1');
model.addDataBreakpoint({ description: 'second', src: { type: DataBreakpointSetType.Variable, dataId: 'secondId' }, canPersist: false, accessTypes: ['readWrite'], accessType: 'readWrite' }, '2');
model.updateDataBreakpoint('1', { condition: 'aCondition' });
model.updateDataBreakpoint('2', { hitCondition: '10' });
const dataBreakpoints = model.getDataBreakpoints();
assert.strictEqual(dataBreakpoints[0].canPersist, true);
assert.strictEqual(dataBreakpoints[0].dataId, 'id');
assert.deepStrictEqual(dataBreakpoints[0].src, { type: DataBreakpointSetType.Variable, dataId: 'id' });
assert.strictEqual(dataBreakpoints[0].accessType, 'read');
assert.strictEqual(dataBreakpoints[0].condition, 'aCondition');
assert.strictEqual(dataBreakpoints[1].canPersist, false);
@ -374,7 +374,7 @@ suite('Debug - Breakpoints', () => {
assert.strictEqual(result.message, 'Disabled Logpoint');
assert.strictEqual(result.icon.id, 'debug-breakpoint-log-disabled');
model.addDataBreakpoint({ description: 'label', canPersist: true, accessTypes: ['read'], accessType: 'read', dataId: 'id' });
model.addDataBreakpoint({ description: 'label', canPersist: true, accessTypes: ['read'], accessType: 'read', src: { type: DataBreakpointSetType.Variable, dataId: 'id' } });
const dataBreakpoints = model.getDataBreakpoints();
result = getBreakpointMessageAndIcon(State.Stopped, true, dataBreakpoints[0], ls, model);
assert.strictEqual(result.message, 'Data Breakpoint');

View file

@ -13,7 +13,7 @@ import { NullLogService } from 'vs/platform/log/common/log';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
import { AdapterEndEvent, IAdapterManager, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IConfig, IConfigurationManager, IDataBreakpoint, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IDebugger, IExceptionBreakpoint, IExceptionInfo, IFunctionBreakpoint, IInstructionBreakpoint, ILaunch, IMemoryRegion, INewReplElementData, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, IViewModel, LoadedSourceEvent, State } from 'vs/workbench/contrib/debug/common/debug';
import { AdapterEndEvent, IAdapterManager, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IConfig, IConfigurationManager, IDataBreakpoint, IDataBreakpointInfoResponse, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IDebugger, IExceptionBreakpoint, IExceptionInfo, IFunctionBreakpoint, IInstructionBreakpoint, ILaunch, IMemoryRegion, INewReplElementData, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, IViewModel, LoadedSourceEvent, State } from 'vs/workbench/contrib/debug/common/debug';
import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot';
import { IInstructionBreakpointOptions } from 'vs/workbench/contrib/debug/common/debugModel';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
@ -114,7 +114,7 @@ export class MockDebugService implements IDebugService {
throw new Error('not implemented');
}
addDataBreakpoint(label: string, dataId: string, canPersist: boolean): Promise<void> {
addDataBreakpoint(): Promise<void> {
throw new Error('Method not implemented.');
}
@ -223,6 +223,10 @@ export class MockSession implements IDebugSession {
throw new Error('Method not implemented.');
}
dataBytesBreakpointInfo(address: string, bytes: number): Promise<IDataBreakpointInfoResponse | undefined> {
throw new Error('Method not implemented.');
}
dataBreakpointInfo(name: string, variablesReference?: number | undefined): Promise<{ dataId: string | null; description: string; canPersist?: boolean | undefined } | undefined> {
throw new Error('Method not implemented.');
}