Skip to content

Commit

Permalink
Support storing and applying of memory content
Browse files Browse the repository at this point in the history
Add commands to store and apply memory content as Intel HEX file
- Encapsulate behavior in new MemoryStorage class
- Trigger 'store' from Memory view, Variables view and command palette
- Trigger 'apply' from Memory view, Explorer view and command palette
- Use nrf-intel-hex library for read/write file licensed under BSD-3

Use quick inputs to guide user through necessary input
- Initialize as much of the input as possible through command args

Communicate with webview through messenger requests and notifications
-- Request to trigger store and apply from webview
-- Notify webview about any written memory so it can update properly

Minor improvements
- Move some common types and functionality into 'common' area
- Avoid bleeding Debug Adapter types into webview, use messaging types
- Common style: 'getVariables' -> 'getVariablesType'
- Provide utility functions and types for debug requests
- Fix 'Enter' handling for numpad by checking key value of event

Closes #50
  • Loading branch information
martin-fleck-at committed Mar 7, 2024
1 parent 7416dc9 commit 74525c8
Show file tree
Hide file tree
Showing 24 changed files with 657 additions and 143 deletions.
7 changes: 4 additions & 3 deletions media/options-widget.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@
flex-grow: 1;
}

.memory-options-widget .p-button {
align-self: end;
}

.memory-options-widget .edit-label-toggle {
position: absolute;
right: 24px;
top: 8px;
opacity: 0;
transition: opacity 0.2s;
}
Expand Down
40 changes: 39 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@
"fast-deep-equal": "^3.1.3",
"formik": "^2.4.5",
"memoize-one": "^6.0.0",
"nrf-intel-hex": "^1.4.0",
"primeflex": "^3.3.1",
"primereact": "^10.3.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"vscode-messenger": "^0.4.3",
"vscode-messenger-common": "^0.4.3",
"vscode-messenger-webview": "^0.4.3"
"vscode-messenger-webview": "^0.4.3",
"vscode-uri": "^3.0.8"
},
"devDependencies": {
"@types/node": "^12.20.0",
Expand Down Expand Up @@ -81,6 +83,16 @@
"command": "memory-inspector.show-variable",
"title": "Show in Memory Inspector",
"category": "Memory"
},
{
"command": "memory-inspector.store-file",
"title": "Store Memory as File",
"category": "Memory"
},
{
"command": "memory-inspector.apply-file",
"title": "Apply Memory from File",
"category": "Memory"
}
],
"menus": {
Expand All @@ -92,19 +104,45 @@
{
"command": "memory-inspector.show-variable",
"when": "false"
},
{
"command": "memory-inspector.store-file",
"when": "memory-inspector.canRead"
},
{
"command": "memory-inspector.apply-file",
"when": "memory-inspector.canWrite"
}
],
"debug/variables/context": [
{
"command": "memory-inspector.show-variable",
"when": "canViewMemory && memory-inspector.canRead"
},
{
"command": "memory-inspector.store-file",
"when": "canViewMemory && memory-inspector.canRead"
}
],
"view/item/context": [
{
"command": "memory-inspector.show-variable",
"when": "canViewMemory && memory-inspector.canRead"
}
],
"explorer/context": [
{
"command": "memory-inspector.apply-file",
"group": "debug",
"when": "memory-inspector.canWrite && resourceExtname === .hex"
}
],
"editor/context": [
{
"command": "memory-inspector.apply-file",
"group": "debug",
"when": "memory-inspector.canWrite && resourceExtname === .hex"
}
]
},
"customEditors": [
Expand Down
39 changes: 39 additions & 0 deletions src/common/debug-requests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/********************************************************************************
* Copyright (C) 2018 Red Hat, Inc. 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
********************************************************************************/
// inspired by https://github.com/eclipse-theia/theia/blob/master/packages/debug/src/browser/debug-session-connection.ts

import type { DebugProtocol } from '@vscode/debugprotocol';
import type { DebugSession } from 'vscode';

export interface DebugRequestTypes {
'evaluate': [DebugProtocol.EvaluateArguments, DebugProtocol.EvaluateResponse['body']]
'readMemory': [DebugProtocol.ReadMemoryArguments, DebugProtocol.ReadMemoryResponse['body']]
'writeMemory': [DebugProtocol.WriteMemoryArguments, DebugProtocol.WriteMemoryResponse['body']]
}

export async function sendRequest<K extends keyof DebugRequestTypes>(session: DebugSession,
command: K, args: DebugRequestTypes[K][0]): Promise<DebugRequestTypes[K][1]> {
return session.customRequest(command, args);
}

export namespace EvaluateExpression {
export function sizeOf(expression: string): string {
return `sizeof(${expression})`;
}
export function addressOf(expression: string): string {
return `&(${expression})`;
}
};
43 changes: 43 additions & 0 deletions src/common/intel-hex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/********************************************************************************
* Copyright (C) 2024 EclipseSource.
*
* 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 { URI, Utils } from 'vscode-uri';

export namespace IntelHEX {
export namespace FileExtensions {
export const All = [
// General
'hex', 'mcs', 'int', 'ihex', 'ihe', 'ihx',
// Platform-specific
'h80', 'h86', 'a43', 'a90',
// Binary or Intel hex
'obj', 'obl', 'obh', 'rom', 'eep'
];
export const Default = 'hex';

export function applyIfMissing(file: URI): URI {
const extWithDot = Utils.extname(file);
if (extWithDot.length === 0 || !IntelHEX.FileExtensions.All.includes(extWithDot.slice(1))) {
return URI.file(file.fsPath + '.' + IntelHEX.FileExtensions.Default);
}
return file;
};
};
export const DialogFilters = {
'Intel HEX Files': IntelHEX.FileExtensions.All,
'All Files': ['*']
};
};
10 changes: 8 additions & 2 deletions src/common/memory-range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ export interface MemoryRange {
endAddress?: string;
}

export interface WrittenMemory {
memoryReference: string;
offset?: number;
count?: number
}

/** Suitable for arithemetic */
export interface BigIntMemoryRange {
startAddress: bigint;
Expand Down Expand Up @@ -93,8 +99,8 @@ export function getAddressLength(padding: number, radix: Radix): number {
return Math.ceil(padding / Math.log2(radix));
}

export function toHexStringWithRadixMarker(target: bigint): string {
return `${getRadixMarker(Radix.Hexadecimal)}${getAddressString(target, Radix.Hexadecimal)}`;
export function toHexStringWithRadixMarker(target: bigint, paddedLength: number = 0): string {
return `${getRadixMarker(Radix.Hexadecimal)}${getAddressString(target, Radix.Hexadecimal, paddedLength)}`;
}

export interface VariableMetadata {
Expand Down
74 changes: 74 additions & 0 deletions src/common/memory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/********************************************************************************
* Copyright (C) 2024 EclipseSource.
*
* 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 type { DebugProtocol } from '@vscode/debugprotocol';
import { ReadMemoryResult } from './messaging';

export interface Memory {
address: bigint;
bytes: Uint8Array;
}

export function createMemoryFromRead(result: ReadMemoryResult): Memory {
if (!result?.data) { throw new Error('No memory provided!'); }
const address = BigInt(result.address);
const bytes = stringToBytesMemory(result.data);
return { bytes, address };
}

export function stringToBytesMemory(data: string): Uint8Array {
return Uint8Array.from(Buffer.from(data, 'base64'));
}

export function bytesToStringMemory(data: Uint8Array): string {
return Buffer.from(data).toString('base64');
}

export function validateMemoryReference(reference: string): string | undefined {
const asNumber = Number(reference);
// we allow an address that is not a number, e.g., an expression, but if it is a number it must be >= 0
return !isNaN(asNumber) && asNumber < 0 ? 'Value needs to be >= 0' : undefined;
}

export function validateOffset(offset: string): string | undefined {
const asNumber = Number(offset);
return isNaN(asNumber) ? 'No number provided' : undefined;
}

export function validateCount(count: string): string | undefined {
const asNumber = Number(count);
if (isNaN(asNumber)) {
return 'No number provided';
} else if (asNumber <= 0) {
return 'Value needs to be > 0';
}
}

export interface MemoryVariable extends DebugProtocol.Variable {
memoryReference: string;
}

export const isMemoryVariable = (variable: unknown): variable is MemoryVariable => !!variable && !!(variable as MemoryVariable).memoryReference;

export interface MemoryVariableNode {
variable: MemoryVariable;
sessionId: string;
}

export const isMemoryVariableNode = (node: unknown): node is MemoryVariableNode =>
!!node
&& isMemoryVariable((node as MemoryVariableNode).variable)
&& typeof (node as MemoryVariableNode).sessionId === 'string';
37 changes: 29 additions & 8 deletions src/common/messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,38 @@
import type { DebugProtocol } from '@vscode/debugprotocol';
import type { NotificationType, RequestType } from 'vscode-messenger-common';
import { MemoryViewSettings } from '../webview/utils/view-types';
import type { VariableRange } from './memory-range';
import type { VariableRange, WrittenMemory } from './memory-range';
import { DebugRequestTypes } from './debug-requests';
import { MemoryVariableNode } from './memory';
import { URI } from 'vscode-uri';

export type MemoryReadResult = DebugProtocol.ReadMemoryResponse['body'];
export type MemoryWriteResult = DebugProtocol.WriteMemoryResponse['body'];
// convenience types for easier readability and better semantics
export type MemoryOptions = Partial<DebugProtocol.ReadMemoryArguments>;

export type ReadMemoryArguments = DebugRequestTypes['readMemory'][0];
export type ReadMemoryResult = DebugRequestTypes['readMemory'][1];

export type WriteMemoryArguments = DebugRequestTypes['writeMemory'][0] & { count?: number };
export type WriteMemoryResult = DebugRequestTypes['writeMemory'][1];

export type StoreMemoryArguments = MemoryOptions | MemoryVariableNode;
export type StoreMemoryResult = void;

export type ApplyMemoryArguments = URI | undefined;
export type ApplyMemoryResult = MemoryOptions;

// Notifications
export const readyType: NotificationType<void> = { method: 'ready' };
export const logMessageType: RequestType<string, void> = { method: 'logMessage' };
export const setMemoryViewSettingsType: NotificationType<Partial<MemoryViewSettings>> = { method: 'setMemoryViewSettings' };
export const resetMemoryViewSettingsType: NotificationType<void> = { method: 'resetMemoryViewSettings' };
export const setTitleType: NotificationType<string> = { method: 'setTitle' };
export const setOptionsType: RequestType<Partial<DebugProtocol.ReadMemoryArguments | undefined>, void> = { method: 'setOptions' };
export const readMemoryType: RequestType<DebugProtocol.ReadMemoryArguments, MemoryReadResult> = { method: 'readMemory' };
export const writeMemoryType: RequestType<DebugProtocol.WriteMemoryArguments, MemoryWriteResult> = { method: 'writeMemory' };
export const getVariables: RequestType<DebugProtocol.ReadMemoryArguments, VariableRange[]> = { method: 'getVariables' };
export const memoryWrittenType: NotificationType<WrittenMemory> = { method: 'memoryWritten' };

// Requests
export const setOptionsType: RequestType<MemoryOptions, void> = { method: 'setOptions' };
export const logMessageType: RequestType<string, void> = { method: 'logMessage' };
export const readMemoryType: RequestType<ReadMemoryArguments, ReadMemoryResult> = { method: 'readMemory' };
export const writeMemoryType: RequestType<WriteMemoryArguments, WriteMemoryResult> = { method: 'writeMemory' };
export const getVariablesType: RequestType<ReadMemoryArguments, VariableRange[]> = { method: 'getVariables' };
export const storeMemoryType: RequestType<StoreMemoryArguments, void> = { method: 'storeMemory' };
export const applyMemoryType: RequestType<ApplyMemoryArguments, ApplyMemoryResult> = { method: 'applyMemory' };
3 changes: 3 additions & 0 deletions src/entry-points/browser/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,19 @@ import { AdapterRegistry } from '../../plugin/adapter-registry/adapter-registry'
import { MemoryProvider } from '../../plugin/memory-provider';
import { MemoryWebview } from '../../plugin/memory-webview-main';
import { CAdapter } from '../../plugin/adapter-registry/c-adapter';
import { MemoryStorage } from '../../plugin/memory-storage';

export const activate = async (context: vscode.ExtensionContext): Promise<AdapterRegistry> => {
const registry = new AdapterRegistry();
const memoryProvider = new MemoryProvider(registry);
const memoryView = new MemoryWebview(context.extensionUri, memoryProvider);
const memoryStorage = new MemoryStorage(memoryProvider);
const cAdapter = new CAdapter(registry);

registry.activate(context);
memoryProvider.activate(context);
memoryView.activate(context);
memoryStorage.activate(context);
cAdapter.activate(context);

return registry;
Expand Down
3 changes: 3 additions & 0 deletions src/entry-points/desktop/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,19 @@ import { AdapterRegistry } from '../../plugin/adapter-registry/adapter-registry'
import { MemoryProvider } from '../../plugin/memory-provider';
import { MemoryWebview } from '../../plugin/memory-webview-main';
import { CAdapter } from '../../plugin/adapter-registry/c-adapter';
import { MemoryStorage } from '../../plugin/memory-storage';

export const activate = async (context: vscode.ExtensionContext): Promise<AdapterRegistry> => {
const registry = new AdapterRegistry();
const memoryProvider = new MemoryProvider(registry);
const memoryView = new MemoryWebview(context.extensionUri, memoryProvider);
const memoryStorage = new MemoryStorage(memoryProvider);
const cAdapter = new CAdapter(registry);

memoryProvider.activate(context);
registry.activate(context);
memoryView.activate(context);
memoryStorage.activate(context);
cAdapter.activate(context);

return registry;
Expand Down
7 changes: 4 additions & 3 deletions src/plugin/adapter-registry/c-tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import * as vscode from 'vscode';
import { DebugProtocol } from '@vscode/debugprotocol';
import { AdapterVariableTracker, hexAddress, notADigit } from './adapter-capabilities';
import { toHexStringWithRadixMarker, VariableRange } from '../../common/memory-range';
import { sendRequest, EvaluateExpression } from '../../common/debug-requests';

export class CTracker extends AdapterVariableTracker {
/**
Expand All @@ -32,9 +33,9 @@ export class CTracker extends AdapterVariableTracker {
}
try {
const [addressResponse, sizeResponse] = await Promise.all([
session.customRequest('evaluate', <DebugProtocol.EvaluateArguments>{ expression: `&(${variable.name})`, context: 'watch', frameId: this.currentFrame }),
session.customRequest('evaluate', <DebugProtocol.EvaluateArguments>{ expression: `sizeof(${variable.name})`, context: 'watch', frameId: this.currentFrame }),
]) as DebugProtocol.EvaluateResponse['body'][];
sendRequest(session, 'evaluate', { expression: EvaluateExpression.addressOf(variable.name), context: 'watch', frameId: this.currentFrame }),
sendRequest(session, 'evaluate', { expression: EvaluateExpression.sizeOf(variable.name), context: 'watch', frameId: this.currentFrame })
]);
const addressPart = hexAddress.exec(addressResponse.result);
if (!addressPart) { return undefined; }
const startAddress = BigInt(addressPart[0]);
Expand Down
Loading

0 comments on commit 74525c8

Please sign in to comment.