Skip to content

Commit

Permalink
Add support for switching debug sessions (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
thegecko authored Jan 14, 2025
1 parent 915701a commit f5ce0ce
Show file tree
Hide file tree
Showing 12 changed files with 286 additions and 111 deletions.
18 changes: 12 additions & 6 deletions media/options-widget.css
Original file line number Diff line number Diff line change
Expand Up @@ -77,19 +77,20 @@
padding-bottom: 16px;
}

.form-textfield > input {
.form-textfield>input {
width: 150px;
}

.form-texfield-long > input {
.form-texfield-long>input {
width: 200px;
}

.form-options button[type="submit"] {
/* Match height of inputs */
height: calc(var(--input-minHeight) * 1px);
margin-top: 1rem;
margin-top: 1.1rem;
align-self: start;
margin-right: auto;
}

.form-options-memory-read-argument-hint {
Expand All @@ -106,7 +107,7 @@
font-size: 11px;
text-transform: uppercase;
line-height: 22px;
margin:0;
margin: 0;
font-weight: normal;
}

Expand Down Expand Up @@ -145,10 +146,14 @@
padding-bottom: 0.5rem;
}

.advanced-options-session {
align-self: start;
width: 250px;
}

.advanced-options-toggle {
margin-left: auto;
align-self: start;
margin-top: 1.1rem
margin-top: 1.3rem
}

.p-accordion-content .advanced-options-content {
Expand Down Expand Up @@ -187,6 +192,7 @@
margin-top: 0px;
width: 210px;
}

.advanced-options-panel::-webkit-scrollbar-track {
background-color: var(--vscode-dropdown-listBackground);
}
Expand Down
7 changes: 7 additions & 0 deletions src/common/messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export type StoreMemoryResult = void;
export type ApplyMemoryArguments = URI | undefined;
export type ApplyMemoryResult = MemoryOptions;

export interface Session {
id: string
name: string;
}

export interface SessionContext {
sessionId?: string;
canRead: boolean;
Expand All @@ -50,6 +55,8 @@ export const readyType: NotificationType<void> = { method: 'ready' };
export const setMemoryViewSettingsType: NotificationType<Partial<MemoryViewSettings>> = { method: 'setMemoryViewSettings' };
export const setTitleType: NotificationType<string> = { method: 'setTitle' };
export const memoryWrittenType: NotificationType<WrittenMemory> = { method: 'memoryWritten' };
export const sessionsChangedType: NotificationType<Session[]> = { method: 'sessionsChanged' };
export const setSessionType: NotificationType<string> = { method: 'setSession' };
export const sessionContextChangedType: NotificationType<SessionContext> = { method: 'sessionContextChanged' };

// Requests
Expand Down
10 changes: 5 additions & 5 deletions src/entry-points/browser/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import * as vscode from 'vscode';
import { AdapterRegistry } from '../../plugin/adapter-registry/adapter-registry';
import { CAdapter } from '../../plugin/adapter-registry/c-adapter';
import { ContextTracker } from '../../plugin/context-tracker';
import { MemoryProvider } from '../../plugin/memory-provider';
import { MemoryProviderManager } from '../../plugin/memory-provider-manager';
import { MemoryStorage } from '../../plugin/memory-storage';
import { MemoryWebview } from '../../plugin/memory-webview-main';
import { SessionTracker } from '../../plugin/session-tracker';
Expand All @@ -27,14 +27,14 @@ export const activate = async (context: vscode.ExtensionContext): Promise<Adapte
const registry = new AdapterRegistry();
const sessionTracker = new SessionTracker();
new ContextTracker(sessionTracker);
const memoryProvider = new MemoryProvider(registry, sessionTracker);
const memoryView = new MemoryWebview(context.extensionUri, memoryProvider, sessionTracker);
const memoryStorage = new MemoryStorage(memoryProvider);
const memoryProviderManager = new MemoryProviderManager(registry, sessionTracker);
const memoryStorage = new MemoryStorage(sessionTracker, memoryProviderManager);
const memoryView = new MemoryWebview(context.extensionUri, memoryProviderManager, sessionTracker, memoryStorage);
const cAdapter = new CAdapter(registry);

registry.activate(context);
sessionTracker.activate(context);
memoryProvider.activate(context);
memoryProviderManager.activate(context);
memoryView.activate(context);
memoryStorage.activate(context);
cAdapter.activate(context);
Expand Down
10 changes: 5 additions & 5 deletions src/entry-points/desktop/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import * as vscode from 'vscode';
import { AdapterRegistry } from '../../plugin/adapter-registry/adapter-registry';
import { CAdapter } from '../../plugin/adapter-registry/c-adapter';
import { ContextTracker } from '../../plugin/context-tracker';
import { MemoryProvider } from '../../plugin/memory-provider';
import { MemoryProviderManager } from '../../plugin/memory-provider-manager';
import { MemoryStorage } from '../../plugin/memory-storage';
import { MemoryWebview } from '../../plugin/memory-webview-main';
import { SessionTracker } from '../../plugin/session-tracker';
Expand All @@ -27,14 +27,14 @@ export const activate = async (context: vscode.ExtensionContext): Promise<Adapte
const registry = new AdapterRegistry();
const sessionTracker = new SessionTracker();
new ContextTracker(sessionTracker);
const memoryProvider = new MemoryProvider(registry, sessionTracker);
const memoryView = new MemoryWebview(context.extensionUri, memoryProvider, sessionTracker);
const memoryStorage = new MemoryStorage(memoryProvider);
const memoryProviderManager = new MemoryProviderManager(registry, sessionTracker);
const memoryStorage = new MemoryStorage(sessionTracker, memoryProviderManager);
const memoryView = new MemoryWebview(context.extensionUri, memoryProviderManager, sessionTracker, memoryStorage);
const cAdapter = new CAdapter(registry);

registry.activate(context);
sessionTracker.activate(context);
memoryProvider.activate(context);
memoryProviderManager.activate(context);
memoryView.activate(context);
memoryStorage.activate(context);
cAdapter.activate(context);
Expand Down
47 changes: 47 additions & 0 deletions src/plugin/memory-provider-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/********************************************************************************
* Copyright (C) 2025 Ericsson, Arm and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import * as vscode from 'vscode';
import { AdapterRegistry } from './adapter-registry/adapter-registry';
import { MemoryProvider } from './memory-provider';
import { SessionTracker } from './session-tracker';

export class MemoryProviderManager {

protected memoryProviders = new Map<vscode.DebugSession, MemoryProvider>();

constructor(protected adapterRegistry: AdapterRegistry, protected sessionTracker: SessionTracker) {
}

public activate(context: vscode.ExtensionContext): void {
const createDebugAdapterTracker = (session: vscode.DebugSession): vscode.ProviderResult<vscode.DebugAdapterTracker> => {
const handlerForSession = this.adapterRegistry.getHandlerForSession(session.type);
const contributedTracker = handlerForSession?.initializeAdapterTracker?.(session);
return contributedTracker;
};
context.subscriptions.push(vscode.debug.registerDebugAdapterTrackerFactory('*', { createDebugAdapterTracker }));
}

public getProvider(sessionId: string | undefined): MemoryProvider {
const session = this.sessionTracker.assertSession(sessionId);

if (!this.memoryProviders.has(session)) {
this.memoryProviders.set(session, new MemoryProvider(session.id, this.adapterRegistry, this.sessionTracker));
}

return this.memoryProviders.get(session)!;
}
}
31 changes: 9 additions & 22 deletions src/plugin/memory-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
********************************************************************************/

import { DebugProtocol } from '@vscode/debugprotocol';
import * as vscode from 'vscode';
import { sendRequest } from '../common/debug-requests';
import { stringToBytesMemory } from '../common/memory';
import { VariableRange } from '../common/memory-range';
Expand All @@ -24,36 +23,24 @@ import { MemoryDisplaySettingsContribution } from '../common/webview-configurati
import { AdapterRegistry } from './adapter-registry/adapter-registry';
import { isSessionEvent, SessionTracker } from './session-tracker';

export interface LabeledUint8Array extends Uint8Array {
label?: string;
}

export class MemoryProvider {
protected scheduledOnDidMemoryWriteEvents: { [sessionidmemoryReference: string]: ((response: WriteMemoryResult) => void) | undefined } = {};

constructor(protected adapterRegistry: AdapterRegistry, protected sessionTracker: SessionTracker) {
constructor(protected sessionId: string, protected adapterRegistry: AdapterRegistry, protected sessionTracker: SessionTracker) {
this.sessionTracker.onSessionEvent(event => {
if (isSessionEvent('memory-written', event)) {
if (isSessionEvent('memory-written', event) && event.session.raw.id === this.sessionId) {
delete this.scheduledOnDidMemoryWriteEvents[event.session.raw.id + '_' + event.data.memoryReference];
}
});
}

public activate(context: vscode.ExtensionContext): void {
const createDebugAdapterTracker = (session: vscode.DebugSession): vscode.ProviderResult<vscode.DebugAdapterTracker> => {
const handlerForSession = this.adapterRegistry.getHandlerForSession(session.type);
const contributedTracker = handlerForSession?.initializeAdapterTracker?.(session);
return contributedTracker;
};
context.subscriptions.push(vscode.debug.registerDebugAdapterTrackerFactory('*', { createDebugAdapterTracker }));
}

public async readMemory(args: DebugProtocol.ReadMemoryArguments): Promise<ReadMemoryResult> {
return sendRequest(this.sessionTracker.assertDebugCapability(this.sessionTracker.activeSession, 'supportsReadMemoryRequest', 'read memory'), 'readMemory', args);
const session = this.sessionTracker.assertSession(this.sessionId);
return sendRequest(this.sessionTracker.assertDebugCapability(session, 'supportsReadMemoryRequest', 'read memory'), 'readMemory', args);
}

public async writeMemory(args: DebugProtocol.WriteMemoryArguments): Promise<WriteMemoryResult> {
const session = this.sessionTracker.assertDebugCapability(this.sessionTracker.activeSession, 'supportsWriteMemoryRequest', 'write memory');
const session = this.sessionTracker.assertDebugCapability(this.sessionTracker.assertSession(this.sessionId), 'supportsWriteMemoryRequest', 'write memory');
// Schedule a emit in case we don't retrieve a memory event
this.scheduledOnDidMemoryWriteEvents[session.id + '_' + args.memoryReference] = response => {
// We only send out a custom event if we don't expect the client to handle the memory event
Expand All @@ -72,26 +59,26 @@ export class MemoryProvider {
}

public async getVariables(variableArguments: DebugProtocol.ReadMemoryArguments): Promise<VariableRange[]> {
const session = this.sessionTracker.assertActiveSession('get variables');
const session = this.sessionTracker.assertSession(this.sessionId, 'get variables');
const handler = this.adapterRegistry?.getHandlerForSession(session.type);
if (handler?.getResidents) { return handler.getResidents(session, variableArguments); }
return handler?.getVariables?.(session) ?? [];
}

public async getAddressOfVariable(variableName: string): Promise<string | undefined> {
const session = this.sessionTracker.assertActiveSession('get address of variable');
const session = this.sessionTracker.assertSession(this.sessionId, 'get address of variable');
const handler = this.adapterRegistry?.getHandlerForSession(session.type);
return handler?.getAddressOfVariable?.(session, variableName);
}

public async getSizeOfVariable(variableName: string): Promise<bigint | undefined> {
const session = this.sessionTracker.assertActiveSession('get size of variable');
const session = this.sessionTracker.assertSession(this.sessionId, 'get size of variable');
const handler = this.adapterRegistry?.getHandlerForSession(session.type);
return handler?.getSizeOfVariable?.(session, variableName);
}

public async getMemoryDisplaySettingsContribution(): Promise<MemoryDisplaySettingsContribution> {
const session = this.sessionTracker.assertActiveSession('get memory display settings contribution');
const session = this.sessionTracker.assertSession(this.sessionId, 'get memory display settings contribution');
const handler = this.adapterRegistry?.getHandlerForSession(session.type);
return handler?.getMemoryDisplaySettings?.(session) ?? {};
}
Expand Down
40 changes: 27 additions & 13 deletions src/plugin/memory-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ import { toHexStringWithRadixMarker } from '../common/memory-range';
import { ApplyMemoryArguments, ApplyMemoryResult, MemoryOptions, StoreMemoryArguments } from '../common/messaging';
import { isWebviewContext } from '../common/webview-context';
import { MemoryProvider } from './memory-provider';
import { MemoryProviderManager } from './memory-provider-manager';
import { SessionTracker } from './session-tracker';

export const StoreCommandType = `${manifest.PACKAGE_NAME}.store-file`;
export const ApplyCommandType = `${manifest.PACKAGE_NAME}.apply-file`;
const StoreCommandType = `${manifest.PACKAGE_NAME}.store-file`;
const ApplyCommandType = `${manifest.PACKAGE_NAME}.apply-file`;

const VALID_FILE_NAME_CHARS = /[^a-zA-Z0-9 _-]/g;

Expand All @@ -49,19 +51,26 @@ interface ApplyMemoryOptions {
uri: vscode.Uri;
}

const getActiveSession = () => vscode.debug.activeDebugSession;

export class MemoryStorage {
constructor(protected memoryProvider: MemoryProvider) {
constructor(protected sessionTracker: SessionTracker, protected memoryProviderManager: MemoryProviderManager) {
}

public activate(context: vscode.ExtensionContext): void {
context.subscriptions.push(
vscode.commands.registerCommand(StoreCommandType, args => this.storeMemory(args)),
vscode.commands.registerCommand(ApplyCommandType, args => this.applyMemory(args))
vscode.commands.registerCommand(StoreCommandType, args => this.storeMemory(getActiveSession()?.id, args)),
vscode.commands.registerCommand(ApplyCommandType, args => this.applyMemory(getActiveSession()?.id, args))
);
}

public async storeMemory(args?: StoreMemoryArguments): Promise<void> {
const providedDefaultOptions = await this.storeArgsToOptions(args);
public async storeMemory(sessionId: string | undefined, args?: StoreMemoryArguments): Promise<void> {
// Even if we disable the command in VS Code through enablement or when condition, programmatic execution is still possible.
// However, we want to fail early in case the user tries to execute a disabled command
this.sessionTracker.assertDebugCapability(this.sessionTracker.assertSession(sessionId), 'supportsReadMemoryRequest', 'store memory');

const memoryProvider = this.memoryProviderManager.getProvider(sessionId);
const providedDefaultOptions = await this.storeArgsToOptions(memoryProvider, args);
const options = await this.getStoreMemoryOptions(providedDefaultOptions);
if (!options) {
// user aborted process
Expand All @@ -70,7 +79,7 @@ export class MemoryStorage {

const { outputFile, ...readArgs } = options;
try {
const memoryResponse = await this.memoryProvider.readMemory(readArgs);
const memoryResponse = await memoryProvider.readMemory(readArgs);
const memory = createMemoryFromRead(memoryResponse);
const memoryMap = new MemoryMap({ [Number(memory.address)]: memory.bytes });
await vscode.workspace.fs.writeFile(outputFile, new TextEncoder().encode(memoryMap.asHexString()));
Expand All @@ -89,7 +98,7 @@ export class MemoryStorage {
}
}

protected async storeArgsToOptions(args?: StoreMemoryArguments): Promise<Partial<StoreMemoryOptions>> {
protected async storeArgsToOptions(memoryProvider: MemoryProvider, args?: StoreMemoryArguments): Promise<Partial<StoreMemoryOptions>> {
if (!args) {
return {};
}
Expand All @@ -99,8 +108,8 @@ export class MemoryStorage {
if (isVariablesContext(args)) {
try {
const variableName = args.variable.evaluateName ?? args.variable.name;
const count = await this.memoryProvider.getSizeOfVariable(variableName);
const memoryReference = args.variable.memoryReference ?? await this.memoryProvider.getAddressOfVariable(variableName);
const count = await memoryProvider.getSizeOfVariable(variableName);
const memoryReference = args.variable.memoryReference ?? await memoryProvider.getAddressOfVariable(variableName);
return { count: Number(count), memoryReference, offset: 0, proposedOutputName: variableName };
} catch (error) {
// ignore, we are just using them as default values
Expand Down Expand Up @@ -153,7 +162,12 @@ export class MemoryStorage {
return { memoryReference, offset: Number(offset), count: Number(count), outputFile };
}

public async applyMemory(args?: ApplyMemoryArguments): Promise<ApplyMemoryResult> {
public async applyMemory(sessionId: string | undefined, args?: ApplyMemoryArguments): Promise<ApplyMemoryResult> {
// Even if we disable the command in VS Code through enablement or when condition, programmatic execution is still possible.
// However, we want to fail early in case the user tries to execute a disabled command
this.sessionTracker.assertDebugCapability(this.sessionTracker.assertSession(sessionId), 'supportsWriteMemoryRequest', 'apply memory');

const memoryProvider = this.memoryProviderManager.getProvider(sessionId);
const providedDefaultOptions = await this.applyArgsToOptions(args);
const options = await this.getApplyMemoryOptions(providedDefaultOptions);
if (!options) {
Expand All @@ -169,7 +183,7 @@ export class MemoryStorage {
memoryReference = toHexStringWithRadixMarker(address);
count = memory.length;
const data = bytesToStringMemory(memory);
await this.memoryProvider.writeMemory({ memoryReference, data });
await memoryProvider.writeMemory({ memoryReference, data });
}
await vscode.window.showInformationMessage(`Memory from '${vscode.workspace.asRelativePath(options.uri)}' applied.`);
return { memoryReference, count, offset: 0 };
Expand Down
Loading

0 comments on commit f5ce0ce

Please sign in to comment.