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

Support storing and applying of memory content #96

Merged
merged 7 commits into from Mar 13, 2024

Conversation

martin-fleck-at
Copy link
Contributor

@martin-fleck-at martin-fleck-at commented Mar 7, 2024

What it does

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
memory-store-apply.mp4

Closes #50

How to test

  • Run a debug session
  • Check how you can store memory (from the inspector, from the command, from the Variables window)
  • Try applying your stored memory (from the inspector, from the file context menu, from the file explorer context menu)

Review checklist

Reminder for reviewers

@@ -0,0 +1,39 @@
/********************************************************************************
* Copyright (C) 2018 Red Hat, Inc. and others.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never quite sure what to do with headers in copied code. Feels funny to have a header date older than the repo, though :-).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, that is a very interesting observation! I try to opt for the safest option but this really results in this slight repository paradox. If you want me to change it, let me know or maybe @planger knows whether there are any particular rules for the project.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I'd say that you've rewritten fairly thoroughly, so it likely makes sense to put your company and the current date in here.

Comment on lines 21 to 28
export interface DebugRequestTypes {
'evaluate': [DebugProtocol.EvaluateArguments, DebugProtocol.EvaluateResponse['body']]
'readMemory': [DebugProtocol.ReadMemoryArguments, DebugProtocol.ReadMemoryResponse['body']]
'writeMemory': [DebugProtocol.WriteMemoryArguments, DebugProtocol.WriteMemoryResponse['body']]
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sort of thing is maybe the only time I wish we had macros (and that the DAP had types for the body of responses!).

It sounds like you don't like the idea of using the DebugProtocol interfaces directly, but making this the primary declaration makes for ugly code in messaging.ts where we refer to the things declared here by array index.

  1. How strongly do you feel about not using the DAP types? We were already aliasing the body types for ergonomics, and we are in fact using them, so aliasing them doesn't change much.

  2. If you strongly prefer not using them directly anywhere but where we alias them, I think I'd prefer that we alias the types here for use elsewhere:

Suggested change
export interface DebugRequestTypes {
'evaluate': [DebugProtocol.EvaluateArguments, DebugProtocol.EvaluateResponse['body']]
'readMemory': [DebugProtocol.ReadMemoryArguments, DebugProtocol.ReadMemoryResponse['body']]
'writeMemory': [DebugProtocol.WriteMemoryArguments, DebugProtocol.WriteMemoryResponse['body']]
}
export type ReadMemoryArguments = DebugProtocol.ReadMemoryArguments;
export type ReadMemoryResult = DebugProtocol.ReadMemoryResponse['body'];
export type WriteMemoryArguments = DebugProtocol.WriteMemoryArguments;
export type WriteMemoryResult = DebugProtocol.WriteMemoryResponse['body'];
export type EvaluateArguments = DebugProtocol.EvaluateArguments;
export type EvaluateResult = DebugProtocol.EvaluateResponse['body'];
export interface DebugRequestTypes {
'evaluate': [EvaluateArguments, EvaluateResult]
'readMemory': [ReadMemoryArguments, ReadMemoryResult]
'writeMemory': [WriteMemoryArguments, WriteMemoryResult]
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely true! I wonder if the VS Code people had a discussion about that because as soon as you actually start using it, you really see how accessing the body of the responses makes for really long and really weird types ;-)

So my thought process in the split between the debug-requests.ts and messaging.ts is that the messaging is really the protocol between the extension's main side and the extension webview whereas the debug protocol messages are between the extension's main side and the debug adapter.

Therefore, bleeding any types from the debug protocol into the webview does not make much sense as it cannot directly access the debug adapter. Following that, it seemed logical to have a messaging layer in-between where we define the arguments and results of the main-webview communication. Sure, some of those types may be a 1:1 alias for a debug protocol message but we may also want to extend them at some point (like I do with the WriteMemoryArguments), make parts required or optional (with a lot of the Partial going around) or re-define them. So in a way, having all those DebugProtocol.ReadMemoryArguments in the webview and the options is really more a "coincidence" because it obviously represents memory reading well but and also avoids giving some of the usages a more specific semantic imho.

Based on that, I'm absolutely not opposed to using the DebugProtocol interfaces on the extension's main side within all the debug-related classes (which is why I also left them in the memory-provider). Of course I wished for their names to be shorter, so we could alias them as in the debug-requests.ts as you suggested but for me personally, that would not replace the type definitions that we do in messaging.ts.

Long story short: I think we should distinguish between the types used in the main-debug communication and the ones in the main-webview communication. However, maybe I just overthought the whole thing so I don't plan to die on this hill ;-) I'd be very happy to get your thoughts on that though.

src/common/debug-requests.ts Outdated Show resolved Hide resolved
src/webview/memory-webview-view.tsx Show resolved Hide resolved
src/common/memory-range.ts Outdated Show resolved Hide resolved
src/plugin/memory-webview-main.ts Outdated Show resolved Hide resolved
src/webview/components/memory-table.tsx Outdated Show resolved Hide resolved
src/webview/components/memory-table.tsx Show resolved Hide resolved
src/webview/components/memory-widget.tsx Outdated Show resolved Hide resolved
src/webview/memory-webview-view.tsx Outdated Show resolved Hide resolved
package.json Outdated
@@ -92,19 +104,45 @@
{
"command": "memory-inspector.show-variable",
"when": "false"
},
{
"command": "memory-inspector.store-file",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should disable the commands/buttons in the window if no debug session is active.
At the moment, I can click the buttons outside the debug session and get errors as expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a very good point!

We can see that the command is already disabled as it does not show up in the command palette anymore (F1 + 'memory' because we can no longer read or write. I moved the enablement condition directly to the command but it seems that VS code still allows us to simply execute the command via code and I don't see any other way to determine whether our command is enabled or not. The documentation even states it like that:

(Optional) Condition which must be true to enable the command in the UI (menu and keybindings). Does not prevent executing the command by other means, like the `executeCommand`-api.

I added a new commit that now shares the current session state with the memory inspector so we can properly determine the read/write capabilities and disable our buttons accordingly.

@martin-fleck-at martin-fleck-at force-pushed the issues/50 branch 2 times, most recently from 100e9d0 to 9c497d1 Compare March 8, 2024 12:09
@martin-fleck-at
Copy link
Contributor Author

@colin-grant-work Thank you for your detailed feedback! I tried to address everything in a follow up commit (after a rebase) and added another commit on top based on the feedback from Jens. Besides the type placement, I think I have addressed everything.

@martin-fleck-at martin-fleck-at force-pushed the issues/50 branch 2 times, most recently from c9f3c03 to 733088f Compare March 8, 2024 12:56
package.json Outdated Show resolved Hide resolved
@jreineckearm
Copy link
Contributor

@martin-fleck-at , re-tested functionality.

Did the ultimate test with storing memory from one of our existing debug tools to a hex file. And applying it with the Memory Inspector. And vice versa. Looks all healthy! :-)

The recent change to connect a memory window instance to a debug session ID has the unfortunate side effect of not being able to reuse a memory window anymore. This was possible, would be good to get that back. Use case: I debug something and use the Memory Inspector, I make a code change, then restart debug, and would like to inspect related changes in the previously opened window instance.

Some minor suggestions inline.

One bigger change that may be required and is possibly out of scope for this one is to honor MS-DAP memory events. Our debug adapter correctly sends them on memory contents change. But I see that the Memory Inspector debug tracker doesn't make use of them to trigger a refresh if in the loaded memory range of a window instance. At the moment you need to manually trigger a refresh after loading a hex file into memory.

@martin-fleck-at martin-fleck-at force-pushed the issues/50 branch 2 times, most recently from 8cd7612 to e945093 Compare March 12, 2024 13:57
@martin-fleck-at
Copy link
Contributor Author

@jreineckearm Thank you very much for your feedback! I rebased the change and added the new functionality on top: The memory inspector should now properly refresh automatically and we also support the memory event as requested here: #105.

Furthermore, I added the missing context menu entries for #51.

Could you please have another look at it?

@colin-grant-work
Copy link
Contributor

Quite a few changes since my last look - I'll read things over later today.

@martin-fleck-at
Copy link
Contributor Author

@colin-grant-work Thank you very much! I added them as separate commits so it shouldn't be too bad, I hope! I'll do a quick rebase then.

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 eclipse-cdt-cloud#50
- Move evaluation expression for addressOf and sizeOf to adapter
- Make 'toHexStringWithRadixMarker' more flexible
- Shorten validation messages for memory
- Align props and method names with existing method names, no prefixes
- Replace 'MemoryVariableNode' with 'IVariablesContext'
- Add comment on overlap calculation
- Make sure command enablement fails early
- VS Code always allows to execute a command programmatically
-- There is no way to determine whether a command is enabled or not

- Forward current session context to memory inspector
- Integrate commands with context menu
- React to 'memory' event from debug adapter
- Use 'Store Memory to File' as proper storage label
- Use workspace folder as default save location
- Minor fix for numpad key enter on memory options
@martin-fleck-at
Copy link
Contributor Author

@colin-grant-work @jreineckearm Everything should be good to review again ;-)

@jreineckearm
Copy link
Contributor

Thanks, @martin-fleck-at !

From a functionality point of view, things work as outlined in your last comment. This is a really nice new feature!

  • Reusing existing and configured Memory Inspector windows for subsequent debug sessions works again.
  • The apply/store buttons are now disabled if debug session is not active to prevent users from provoking errors when functionality simply can't work.
  • Context menu has been extended by the two commands. Getting crowded there. Need to consider grouping commands by sections/sub-menus when adding new functionality. But I guess we need think about a similar "redesign" for the in-window options menu going forward.
  • Apply/store still works as expected.
  • Open and save dialogs now start at ${workspaceFolder}.
  • "memory" event support works nicely. I can see the effect now when editing variables and using commands in the Debug Console of our Arm Debugger debug adapter extension.

Minor improvement for later (not this PR)

  • We have a context menu entry on the variables view to store data for variables living in memory. But there is no counter-part to quickly load a data set into a variable.

Copy link
Contributor

@jreineckearm jreineckearm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks for incorporating the feedback! LGTM!
Sorry, wrong Github window. But applies here of course, too. :-)
(Anyone who has the power feel free to delete this comment, and please do not merge until @colin-grant-work had a chance to review).

@@ -0,0 +1,39 @@
/********************************************************************************
* Copyright (C) 2018 Red Hat, Inc. and others.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I'd say that you've rewritten fairly thoroughly, so it likely makes sense to put your company and the current date in here.

src/common/debug-requests.ts Outdated Show resolved Hide resolved
src/common/messaging.ts Outdated Show resolved Hide resolved
src/plugin/adapter-registry/c-tracker.ts Outdated Show resolved Hide resolved
src/plugin/external-views.ts Show resolved Hide resolved
src/plugin/memory-provider.ts Outdated Show resolved Hide resolved
src/plugin/memory-provider.ts Outdated Show resolved Hide resolved
- Use short (PascalCase) names for generic arguments
- Adapt copyright header in debug-requests
- Remove optional 'count' parameter in messaging and memory-provider
- Remove caching of session context and simply re-create if necessary
- Execute variable queries in parallel instead of sequentially
- Fix typo in comment
@martin-fleck-at
Copy link
Contributor Author

@colin-grant-work @jreineckearm I pushed an update that should address the latest concerns.

@@ -42,13 +42,14 @@ export class CTracker extends AdapterVariableTracker {
return undefined;
}
try {
const variableAddress = await this.getAddressOfVariable(variable.name, session);
const [variableAddress, variableSize] = await Promise.all([
variable.memoryReference && hexAddress.test(variable.memoryReference) ? variable.memoryReference : this.getAddressOfVariable(variable.name, session),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consider different integer representations? Not sure if all debug adapters would always return a hex value for an address (although IMO the sensible thing to do).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point! But I'd rather defer this discussion into a separate issue. The previous behavior also used the hexAddress. I did add a commit that unifies that check but I still think it does not necessarily have to be part of this PR, do you agree?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine with me. TBF, I'd expect addresses to be returned as hex values anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this isn't standardized, I'd probably prefer a check for any (easily parsed) number format so that 16 is valid in addition to 0x10. That would follow the convention for things like the ReadMemoryResponse and DisassembledInstruction where at least hex and decimal are both considered acceptable formats.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic, thank you! I added #107 to track this. It shouldn't be too much work but it needs to be discussed first (all formats vs "only" hexadecimal/decimal because they are mentioned in the DAP).

Comment on lines +316 to +332
protected async storeMemory(storeArguments: 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
if (!this.memoryProvider.createContext().canRead) {
throw new Error('Cannot read memory, no valid debug session.');
}
return vscode.commands.executeCommand(StoreCommandType, storeArguments);
}

protected async applyMemory(): Promise<MemoryOptions> {
// 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
if (!this.memoryProvider.createContext().canWrite) {
throw new Error('Cannot write memory, no valid debug session.');
}
return vscode.commands.executeCommand(ApplyCommandType);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a follow-up: since we're sending a context to the view that includes information about the session that that window is associated with, these requests could handle that association and check the relevant session, rather than defaulting to the active session. Re: #97.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very good point and I believe that should be handled as part of #97 as we definitely want to do this for all requests (read, write, store, apply, get variables).

Copy link
Contributor

@colin-grant-work colin-grant-work left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy with the changes. Both of my last comments can be handled as a follow-up, though I'd like it if the address parsing were handled in this PR.

@martin-fleck-at
Copy link
Contributor Author

@colin-grant-work Thank you very much! I'll push an update for the decimal address support in a few minutes!

- Provide decimal address regex and function to extract address
- Ensure we can properly serialize bigints when logging data
- Ensure we return a proper variable range if we only have the address
@martin-fleck-at
Copy link
Contributor Author

@colin-grant-work If you agree with the latest commit, please feel free to merge this PR.

@colin-grant-work colin-grant-work merged commit bbf3e9d into eclipse-cdt-cloud:main Mar 13, 2024
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Load/Save memory content
3 participants