Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Disassembly View and InstructionBreakpoints #11186

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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