diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 60c3a74c017..cbde4c44826 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -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 @@ -337,7 +337,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb return undefined; } - private convertToDto(bps: (ReadonlyArray)): Array { + private convertToDto(bps: (ReadonlyArray)): Array { return bps.map(bp => { if ('name' in bp) { const fbp = bp; diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index c599e144f2a..5a0452e3cd2 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -959,6 +959,19 @@ export class DebugService implements IDebugService { await this.sendDataBreakpoints(); } + async addInstructionBreakpoint(address: string, offset: number, condition?: string, hitCondition?: string): Promise { + this.model.addInstructionBreakpoint(address, offset, condition, hitCondition); + this.debugStorage.storeBreakpoints(this.model); + await this.sendInstructionBreakpoints(); + this.debugStorage.storeBreakpoints(this.model); + } + + async removeInstructionBreakpoints(id?: string): Promise { + this.model.removeInstructionBreakpoints(id); + this.debugStorage.storeBreakpoints(this.model); + await this.sendInstructionBreakpoints(); + } + setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void { this.model.setExceptionBreakpoints(data); this.debugStorage.storeBreakpoints(this.model); @@ -1003,6 +1016,16 @@ export class DebugService implements IDebugService { }); } + private async sendInstructionBreakpoints(session?: IDebugSession): Promise { + const breakpointsToSend = this.model.getInstructionBreakpoints().filter(fbp => fbp.enabled && this.model.areBreakpointsActivated()); + + await sendToOneOrAllSessions(this.model, session, async s => { + if (s.capabilities.supportsDataBreakpoints) { + await s.sendInstructionBreakpoints(breakpointsToSend); + } + }); + } + private sendExceptionBreakpoints(session?: IDebugSession): Promise { const enabledExceptionBps = this.model.getExceptionBreakpoints().filter(exb => exb.enabled); diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 058ab97e711..5ffdc5473f0 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -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'; @@ -447,6 +447,23 @@ export class DebugSession implements IDebugSession { } } + async sendInstructionBreakpoints(instructionBreakpoints: IInstructionBreakpoint[]): Promise { + 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(); + 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 { if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'breakpoints locations')); @@ -536,36 +553,36 @@ export class DebugSession implements IDebugSession { await this.raw.restartFrame({ frameId }, threadId); } - async next(threadId: number): Promise { + async next(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise { if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'next')); } - await this.raw.next({ threadId }); + await this.raw.next({ threadId, granularity }); } - async stepIn(threadId: number, targetId?: number): Promise { + async stepIn(threadId: number, targetId?: number, granularity?: DebugProtocol.SteppingGranularity): Promise { if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepIn')); } - await this.raw.stepIn({ threadId, targetId }); + await this.raw.stepIn({ threadId, targetId, granularity }); } - async stepOut(threadId: number): Promise { + async stepOut(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise { if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepOut')); } - await this.raw.stepOut({ threadId }); + await this.raw.stepOut({ threadId, granularity }); } - async stepBack(threadId: number): Promise { + async stepBack(threadId: number, granularity?: DebugProtocol.SteppingGranularity): Promise { if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepBack')); } - await this.raw.stepBack({ threadId }); + await this.raw.stepBack({ threadId, granularity }); } async continue(threadId: number): Promise { @@ -686,6 +703,14 @@ export class DebugSession implements IDebugSession { return this.raw.cancel({ progressId }); } + async disassemble(memoryReference: string, offset: number, instructionOffset: number, instructionCount: number): Promise { + if (!this.raw) { + return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'disassemble'))); + } + + return this.raw.disassemble({ memoryReference, offset, instructionOffset, instructionCount, resolveSymbols: true }); + } + //---- threads getThread(threadId: number): Thread | undefined { diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 125cc091e2e..a3533aee1ed 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -497,6 +497,22 @@ export class RawDebugSession implements IDisposable { return Promise.reject(new Error('goto is not supported')); } + async setInstructionBreakpoints(args: DebugProtocol.SetInstructionBreakpointsArguments): Promise { + if (this.capabilities.supportsDisassembleRequest) { + return await this.send('setInstructionBreakpoints', args); + } + + return Promise.reject(new Error('setInstructionBreakpoints is not supported')); + } + + async disassemble(args: DebugProtocol.DisassembleArguments): Promise { + if (this.capabilities.supportsDisassembleRequest) { + return await this.send('disassemble', args); + } + + return Promise.reject(new Error('disassemble is not supported')); + } + cancel(args: DebugProtocol.CancelArguments): Promise { return this.send('cancel', args); } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 00dd54bb181..1c2a9efdecf 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -255,6 +255,7 @@ export interface IDebugSession extends ITreeElement { sendFunctionBreakpoints(fbps: IFunctionBreakpoint[]): Promise; dataBreakpointInfo(name: string, variablesReference?: number): Promise; sendDataBreakpoints(dbps: IDataBreakpoint[]): Promise; + sendInstructionBreakpoints(dbps: IInstructionBreakpoint[]): Promise; sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise; breakpointsLocations(uri: uri, lineNumber: number): Promise; getDebugProtocolBreakpoint(breakpointId: string): DebugProtocol.Breakpoint | undefined; @@ -433,6 +434,11 @@ export interface IDataBreakpoint extends IBaseBreakpoint { readonly accessType: DebugProtocol.DataBreakpointAccessType; } +export interface IInstructionBreakpoint extends IBaseBreakpoint { + readonly instructionReference: string; + readonly offset?: number; +} + export interface IExceptionInfo { readonly id?: string; readonly description?: string; @@ -493,9 +499,9 @@ export interface IDebugModel extends ITreeElement { * An event describing a change to the set of [breakpoints](#debug.Breakpoint). */ export interface IBreakpointsChangeEvent { - added?: Array; - removed?: Array; - changed?: Array; + added?: Array; + removed?: Array; + changed?: Array; sessionOnly: boolean; } @@ -887,6 +893,17 @@ export interface IDebugService { */ removeDataBreakpoints(id?: string): Promise; + /** + * Adds a new instruction breakpoint. + */ + addInstructionBreakpoint(address: string, offset: number, condition?: string, hitCondition?: string): Promise; + + /** + * Removes all instruction breakpoints. If id is passed only removes the instruction breakpoint with the passed id. + * Notifies debug adapter of breakpoint changes. + */ + removeInstructionBreakpoints(id?: string): Promise; + setExceptionBreakpointCondition(breakpoint: IExceptionBreakpoint, condition: string | undefined): Promise; setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void; diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index cf63563b197..4d9f9ba9f72 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -14,7 +14,7 @@ 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'; @@ -568,6 +568,7 @@ interface IBreakpointSessionData extends DebugProtocol.Breakpoint { supportsLogPoints: boolean; supportsFunctionBreakpoints: boolean; supportsDataBreakpoints: boolean; + supportsInstructionBreakpoints: boolean sessionId: string; } @@ -577,7 +578,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 +763,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 +909,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 +965,7 @@ export class DebugModel implements IDebugModel { private exceptionBreakpoints: ExceptionBreakpoint[]; private dataBreakopints: DataBreakpoint[]; private watchExpressions: Expression[]; + private instructionBreakpoints: InstructionBreakpoint[]; constructor( debugStorage: DebugStorage, @@ -940,6 +977,7 @@ export class DebugModel implements IDebugModel { this.exceptionBreakpoints = debugStorage.loadExceptionBreakpoints(); this.dataBreakopints = debugStorage.loadDataBreakpoints(); this.watchExpressions = debugStorage.loadWatchExpressions(); + this.instructionBreakpoints = []; this.sessions = []; } @@ -1095,6 +1133,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 +1239,16 @@ export class DebugModel implements IDebugModel { } } }); + this.instructionBreakpoints.forEach(ibp => { + if (!data) { + ibp.setSessionData(sessionId, undefined); + } else { + const dbpData = data.get(ibp.getId()); + if (dbpData) { + ibp.setSessionData(sessionId, toBreakpointSessionData(dbpData, capabilites)); + } + } + }); this._onDidChangeBreakpoints.fire({ sessionOnly: true @@ -1245,7 +1297,7 @@ export class DebugModel implements IDebugModel { } enableOrDisableAllBreakpoints(enable: boolean): void { - const changed: Array = []; + const changed: Array = []; this.breakpoints.forEach(bp => { if (bp.enabled !== enable) { @@ -1265,6 +1317,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 +1385,24 @@ export class DebugModel implements IDebugModel { this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false }); } + addInstructionBreakpoint(instructionReference: string, offset: number, condition?: string, hitCondition?: string): void { + const newInstructionBreakpoint = new InstructionBreakpoint(instructionReference, offset, false, true, hitCondition, condition, undefined); + this.instructionBreakpoints.push(newInstructionBreakpoint); + this._onDidChangeBreakpoints.fire({ added: [newInstructionBreakpoint], sessionOnly: true }); + } + + removeInstructionBreakpoints(id?: string): void { + let removed: InstructionBreakpoint[]; + if (id) { + removed = this.instructionBreakpoints.filter(fbp => fbp.getId() === id); + this.instructionBreakpoints = this.instructionBreakpoints.filter(fbp => fbp.getId() !== id); + } else { + removed = this.instructionBreakpoints; + this.instructionBreakpoints = []; + } + this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false }); + } + getWatchExpressions(): Expression[] { return this.watchExpressions; } diff --git a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts index ef687d5cbdf..42698b296de 100644 --- a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts @@ -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 { + throw new Error('Method not implemented.'); + } + + removeInstructionBreakpoints(address?: string): Promise { + throw new Error('Method not implemented.'); + } + setExceptionBreakpointCondition(breakpoint: IExceptionBreakpoint, condition: string): Promise { throw new Error('Method not implemented.'); } @@ -323,6 +331,9 @@ export class MockSession implements IDebugSession { sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise { throw new Error('Method not implemented.'); } + sendInstructionBreakpoints(dbps: IInstructionBreakpoint[]): Promise { + throw new Error('Method not implemented.'); + } getDebugProtocolBreakpoint(breakpointId: string): DebugProtocol.Breakpoint | undefined { throw new Error('Method not implemented.'); }