Skip to content

Commit

Permalink
Implement Disassembly View and InstructionBreakpoints (#11186)
Browse files Browse the repository at this point in the history
  • Loading branch information
colin-grant-work authored Jul 6, 2022
1 parent c82d9c5 commit 70350c1
Show file tree
Hide file tree
Showing 21 changed files with 1,377 additions and 31 deletions.
34 changes: 34 additions & 0 deletions packages/core/src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,40 @@ export namespace ArrayUtils {
RightBeforeLeft = 1,
Equal = 0,
}

// Copied from https://github.com/microsoft/vscode/blob/9c29becfad5f68270b9b23efeafb147722c5feba/src/vs/base/common/arrays.ts
/**
* Performs a binary search algorithm over a sorted collection. Useful for cases
* when we need to perform a binary search over something that isn't actually an
* array, and converting data to an array would defeat the use of binary search
* in the first place.
*
* @param length The collection length.
* @param compareToKey A function that takes an index of an element in the
* collection and returns zero if the value at this index is equal to the
* search key, a negative number if the value precedes the search key in the
* sorting order, or a positive number if the search key precedes the value.
* @return A non-negative index of an element, if found. If not found, the
* result is -(n+1) (or ~n, using bitwise notation), where n is the index
* where the key should be inserted to maintain the sorting order.
*/
export function binarySearch2(length: number, compareToKey: (index: number) => number): number {
let low = 0;
let high = length - 1;

while (low <= high) {
const mid = ((low + high) / 2) | 0;
const comp = compareToKey(mid);
if (comp < 0) {
low = mid + 1;
} else if (comp > 0) {
high = mid - 1;
} else {
return mid;
}
}
return -(low + 1);
}
}

/**
Expand Down
88 changes: 81 additions & 7 deletions packages/debug/src/browser/breakpoint/breakpoint-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { StorageService } from '@theia/core/lib/browser';
import { Marker } from '@theia/markers/lib/common/marker';
import { MarkerManager } from '@theia/markers/lib/browser/marker-manager';
import URI from '@theia/core/lib/common/uri';
import { SourceBreakpoint, BREAKPOINT_KIND, ExceptionBreakpoint, FunctionBreakpoint, BaseBreakpoint } from './breakpoint-marker';
import { SourceBreakpoint, BREAKPOINT_KIND, ExceptionBreakpoint, FunctionBreakpoint, BaseBreakpoint, InstructionBreakpoint } from './breakpoint-marker';

export interface BreakpointsChangeEvent<T extends BaseBreakpoint> {
uri: URI
Expand All @@ -30,6 +30,7 @@ export interface BreakpointsChangeEvent<T extends BaseBreakpoint> {
}
export type SourceBreakpointsChangeEvent = BreakpointsChangeEvent<SourceBreakpoint>;
export type FunctionBreakpointsChangeEvent = BreakpointsChangeEvent<FunctionBreakpoint>;
export type InstructionBreakpointsChangeEvent = BreakpointsChangeEvent<InstructionBreakpoint>;

@injectable()
export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
Expand All @@ -38,6 +39,8 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {

static FUNCTION_URI = new URI('debug:function://');

static INSTRUCTION_URI = new URI('debug:instruction://');

protected readonly owner = 'breakpoint';

@inject(StorageService)
Expand All @@ -53,6 +56,9 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
protected readonly onDidChangeFunctionBreakpointsEmitter = new Emitter<FunctionBreakpointsChangeEvent>();
readonly onDidChangeFunctionBreakpoints = this.onDidChangeFunctionBreakpointsEmitter.event;

protected readonly onDidChangeInstructionBreakpointsEmitter = new Emitter<InstructionBreakpointsChangeEvent>();
readonly onDidChangeInstructionBreakpoints = this.onDidChangeInstructionBreakpointsEmitter.event;

override setMarkers(uri: URI, owner: string, newMarkers: SourceBreakpoint[]): Marker<SourceBreakpoint>[] {
const result = super.setMarkers(uri, owner, newMarkers);
const added: SourceBreakpoint[] = [];
Expand Down Expand Up @@ -128,7 +134,7 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
}
}
let didChangeFunction = false;
for (const breakpoint of this.getFunctionBreakpoints()) {
for (const breakpoint of (this.getFunctionBreakpoints() as BaseBreakpoint[]).concat(this.getInstructionBreakpoints())) {
if (breakpoint.enabled !== enabled) {
breakpoint.enabled = enabled;
didChangeFunction = true;
Expand Down Expand Up @@ -219,13 +225,74 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
this.onDidChangeFunctionBreakpointsEmitter.fire({ uri: BreakpointManager.FUNCTION_URI, added, removed, changed });
}

protected instructionBreakpoints: InstructionBreakpoint[] = [];

getInstructionBreakpoints(): ReadonlyArray<InstructionBreakpoint> {
return Object.freeze(this.instructionBreakpoints.slice());
}

hasBreakpoints(): boolean {
return !!this.getUris().next().value || !!this.functionBreakpoints.length;
return Boolean(this.getUris().next().value || this.functionBreakpoints.length || this.instructionBreakpoints.length);
}

protected setInstructionBreakpoints(newBreakpoints: InstructionBreakpoint[]): void {
const oldBreakpoints = new Map(this.instructionBreakpoints.map(breakpoint => [breakpoint.id, breakpoint]));
const currentBreakpoints = new Map(newBreakpoints.map(breakpoint => [breakpoint.id, breakpoint]));
const added = [];
const changed = [];
for (const [id, breakpoint] of currentBreakpoints.entries()) {
const old = oldBreakpoints.get(id);
if (old) {
changed.push(old);
} else {
added.push(breakpoint);
}
oldBreakpoints.delete(id);
}
const removed = Array.from(oldBreakpoints.values());
this.instructionBreakpoints = Array.from(currentBreakpoints.values());
this.fireOnDidChangeMarkers(BreakpointManager.INSTRUCTION_URI);
this.onDidChangeInstructionBreakpointsEmitter.fire({ uri: BreakpointManager.INSTRUCTION_URI, added, removed, changed });
}

addInstructionBreakpoint(address: string, offset: number, condition?: string, hitCondition?: string): void {
this.setInstructionBreakpoints(this.instructionBreakpoints.concat(InstructionBreakpoint.create({
instructionReference: address,
offset,
condition,
hitCondition,
})));
}

updateInstructionBreakpoint(id: string, options: Partial<Pick<InstructionBreakpoint, 'condition' | 'hitCondition' | 'enabled'>>): void {
const breakpoint = this.instructionBreakpoints.find(candidate => id === candidate.id);
if (breakpoint) {
Object.assign(breakpoint, options);
this.fireOnDidChangeMarkers(BreakpointManager.INSTRUCTION_URI);
this.onDidChangeInstructionBreakpointsEmitter.fire({ uri: BreakpointManager.INSTRUCTION_URI, changed: [breakpoint], added: [], removed: [] });
}
}

removeInstructionBreakpoint(address?: string): void {
if (!address) {
this.clearInstructionBreakpoints();
}
const breakpointIndex = this.instructionBreakpoints.findIndex(breakpoint => breakpoint.instructionReference === address);
if (breakpointIndex !== -1) {
const removed = this.instructionBreakpoints.splice(breakpointIndex, 1);
this.fireOnDidChangeMarkers(BreakpointManager.INSTRUCTION_URI);
this.onDidChangeInstructionBreakpointsEmitter.fire({ uri: BreakpointManager.INSTRUCTION_URI, added: [], changed: [], removed });
}
}

clearInstructionBreakpoints(): void {
this.setInstructionBreakpoints([]);
}

removeBreakpoints(): void {
this.cleanAllMarkers();
this.setFunctionBreakpoints([]);
this.setInstructionBreakpoints([]);
}

async load(): Promise<void> {
Expand All @@ -244,6 +311,9 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
if (data.exceptionBreakpoints) {
this.setExceptionBreakpoints(data.exceptionBreakpoints);
}
if (data.instructionBreakpoints) {
this.setInstructionBreakpoints(data.instructionBreakpoints);
}
}

save(): void {
Expand All @@ -261,17 +331,21 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
if (this.exceptionBreakpoints.size) {
data.exceptionBreakpoints = [...this.exceptionBreakpoints.values()];
}
if (this.instructionBreakpoints.length) {
data.instructionBreakpoints = this.instructionBreakpoints;
}
this.storage.setData('breakpoints', data);
}

}
export namespace BreakpointManager {
export interface Data {
breakpointsEnabled: boolean
breakpointsEnabled: boolean;
breakpoints: {
[uri: string]: SourceBreakpoint[]
[uri: string]: SourceBreakpoint[];
}
exceptionBreakpoints?: ExceptionBreakpoint[]
functionBreakpoints?: FunctionBreakpoint[]
exceptionBreakpoints?: ExceptionBreakpoint[];
functionBreakpoints?: FunctionBreakpoint[];
instructionBreakpoints?: InstructionBreakpoint[];
}
}
17 changes: 17 additions & 0 deletions packages/debug/src/browser/breakpoint/breakpoint-marker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,20 @@ export namespace FunctionBreakpoint {
};
}
}

export interface InstructionBreakpoint extends BaseBreakpoint, DebugProtocol.InstructionBreakpoint { }

export namespace InstructionBreakpoint {
export function create(raw: DebugProtocol.InstructionBreakpoint, existing?: InstructionBreakpoint): InstructionBreakpoint {
return {
...raw,
id: existing?.id ?? UUID.uuid4(),
enabled: existing?.enabled ?? true,
};
}

export function is(thing: BaseBreakpoint): thing is InstructionBreakpoint {
const candidate = thing as InstructionBreakpoint;
return 'instructionReference' in candidate && typeof candidate.instructionReference === 'string';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
import { DebugFunctionBreakpoint } from './model/debug-function-breakpoint';
import { DebugBreakpoint } from './model/debug-breakpoint';
import { nls } from '@theia/core/lib/common/nls';
import { DebugInstructionBreakpoint } from './model/debug-instruction-breakpoint';

export namespace DebugMenus {
export const DEBUG = [...MAIN_MENU_BAR, '6_debug'];
Expand Down Expand Up @@ -763,13 +764,13 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi
});
registry.registerCommand(DebugCommands.REMOVE_BREAKPOINT, {
execute: () => {
const selectedBreakpoint = this.selectedBreakpoint || this.selectedFunctionBreakpoint;
const selectedBreakpoint = this.selectedSettableBreakpoint;
if (selectedBreakpoint) {
selectedBreakpoint.remove();
}
},
isEnabled: () => !!this.selectedBreakpoint || !!this.selectedFunctionBreakpoint,
isVisible: () => !!this.selectedBreakpoint || !!this.selectedFunctionBreakpoint,
isEnabled: () => Boolean(this.selectedSettableBreakpoint),
isVisible: () => Boolean(this.selectedSettableBreakpoint),
});
registry.registerCommand(DebugCommands.REMOVE_LOGPOINT, {
execute: () => {
Expand Down Expand Up @@ -1178,6 +1179,18 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi
const breakpoint = this.selectedAnyBreakpoint;
return breakpoint && breakpoint instanceof DebugFunctionBreakpoint ? breakpoint : undefined;
}
get selectedInstructionBreakpoint(): DebugInstructionBreakpoint | undefined {
if (this.selectedAnyBreakpoint instanceof DebugInstructionBreakpoint) {
return this.selectedAnyBreakpoint;
}
}

get selectedSettableBreakpoint(): DebugFunctionBreakpoint | DebugInstructionBreakpoint | DebugSourceBreakpoint | undefined {
const selected = this.selectedAnyBreakpoint;
if (selected instanceof DebugFunctionBreakpoint || selected instanceof DebugInstructionBreakpoint || selected instanceof DebugSourceBreakpoint) {
return selected;
}
}

get variables(): DebugVariablesWidget | undefined {
const { currentWidget } = this.shell;
Expand Down Expand Up @@ -1431,5 +1444,4 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi

return true;
}

}
2 changes: 2 additions & 0 deletions packages/debug/src/browser/debug-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { QuickAccessContribution } from '@theia/core/lib/browser/quick-input/qui
import { DebugViewModel } from './view/debug-view-model';
import { DebugToolBar } from './view/debug-toolbar-widget';
import { DebugSessionWidget } from './view/debug-session-widget';
import { bindDisassemblyView } from './disassembly-view/disassembly-view-contribution';

export default new ContainerModule((bind: interfaces.Bind) => {
bindContributionProvider(bind, DebugContribution);
Expand Down Expand Up @@ -131,4 +132,5 @@ export default new ContainerModule((bind: interfaces.Bind) => {
createWidget: () => subwidget.createWidget(container),
}));
}
bindDisassemblyView(bind);
});
6 changes: 6 additions & 0 deletions packages/debug/src/browser/debug-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ export const debugPreferencesSchema: PreferenceSchema = {
'Always confirm if there are debug sessions.',
],
default: 'never'
},
'debug.disassemblyView.showSourceCode': {
description: nls.localize('theia/debug/disassembly-view/show-source-code', 'Show Source Code in Disassembly View.'),
type: 'boolean',
default: true,
}
}
};
Expand All @@ -71,6 +76,7 @@ export class DebugConfiguration {
'debug.inlineValues': boolean;
'debug.showInStatusBar': 'never' | 'always' | 'onFirstSessionStart';
'debug.confirmOnExit': 'never' | 'always';
'debug.disassemblyView.showSourceCode': boolean;
}

export const DebugPreferenceContribution = Symbol('DebugPreferenceContribution');
Expand Down
24 changes: 20 additions & 4 deletions packages/debug/src/browser/debug-session-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
// *****************************************************************************

/* eslint-disable @typescript-eslint/no-explicit-any */

import { DisposableCollection, Emitter, Event, MessageService, ProgressService, WaitUntilEvent } from '@theia/core';
import { LabelProvider, ApplicationShell } from '@theia/core/lib/browser';
import { ContextKey, ContextKeyService } from '@theia/core/lib/browser/context-key-service';
Expand All @@ -38,6 +36,7 @@ import { TaskIdentifier } from '@theia/task/lib/common';
import { DebugSourceBreakpoint } from './model/debug-source-breakpoint';
import { DebugFunctionBreakpoint } from './model/debug-function-breakpoint';
import * as monaco from '@theia/monaco-editor-core';
import { DebugInstructionBreakpoint } from './model/debug-instruction-breakpoint';

export interface WillStartDebugSession extends WaitUntilEvent {
}
Expand All @@ -56,8 +55,13 @@ export interface DidChangeBreakpointsEvent {
uri: URI
}

export interface DidFocusStackFrameEvent {
session: DebugSession;
frame: DebugStackFrame | undefined;
}

export interface DebugSessionCustomEvent {
readonly body?: any
readonly body?: any // eslint-disable-line @typescript-eslint/no-explicit-any
readonly event: string
readonly session: DebugSession
}
Expand Down Expand Up @@ -90,8 +94,11 @@ export class DebugSessionManager {
protected readonly onDidReceiveDebugSessionCustomEventEmitter = new Emitter<DebugSessionCustomEvent>();
readonly onDidReceiveDebugSessionCustomEvent: Event<DebugSessionCustomEvent> = this.onDidReceiveDebugSessionCustomEventEmitter.event;

protected readonly onDidFocusStackFrameEmitter = new Emitter<DidFocusStackFrameEvent>();
readonly onDidFocusStackFrame = this.onDidFocusStackFrameEmitter.event;

protected readonly onDidChangeBreakpointsEmitter = new Emitter<DidChangeBreakpointsEvent>();
readonly onDidChangeBreakpoints: Event<DidChangeBreakpointsEvent> = this.onDidChangeBreakpointsEmitter.event;
readonly onDidChangeBreakpoints = this.onDidChangeBreakpointsEmitter.event;
protected fireDidChangeBreakpoints(event: DidChangeBreakpointsEvent): void {
this.onDidChangeBreakpointsEmitter.fire(event);
}
Expand Down Expand Up @@ -417,6 +424,7 @@ export class DebugSessionManager {
}
this.fireDidChange(current);
}));
this.disposeOnCurrentSessionChanged.push(current.onDidFocusStackFrame(frame => this.onDidFocusStackFrameEmitter.fire({ session: current, frame })));
}
this.updateBreakpoints(previous, current);
this.open();
Expand Down Expand Up @@ -475,6 +483,14 @@ export class DebugSessionManager {
return this.breakpoints.getFunctionBreakpoints().map(origin => new DebugFunctionBreakpoint(origin, { labelProvider, breakpoints, editorManager }));
}

getInstructionBreakpoints(session = this.currentSession): DebugInstructionBreakpoint[] {
if (session && session.state > DebugState.Initializing) {
return session.getInstructionBreakpoints();
}
const { labelProvider, breakpoints, editorManager } = this;
return this.breakpoints.getInstructionBreakpoints().map(origin => new DebugInstructionBreakpoint(origin, { labelProvider, breakpoints, editorManager }));
}

getBreakpoints(session?: DebugSession): DebugSourceBreakpoint[];
getBreakpoints(uri: URI, session?: DebugSession): DebugSourceBreakpoint[];
getBreakpoints(arg?: URI | DebugSession, arg2?: DebugSession): DebugSourceBreakpoint[] {
Expand Down
Loading

0 comments on commit 70350c1

Please sign in to comment.