Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions src/gdb/GDBDebugSessionBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3111,6 +3111,8 @@ export abstract class GDBDebugSessionBase extends LoggingDebugSession {
const hexContent = base64ToHex(data);
await mi.sendDataWriteMemoryBytes(gdb, memoryReference, hexContent);
this.sendResponse(response);
// inform to Variables view that need to refresh data on the view
this.sendEvent(new InvalidatedEvent(['variables']));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

While this is probably enough invalidated for most actions. Did you look at whether we should invalidate stack, and other items? (dap link)

While most writes won't change stack state, if a memory write changes stored location of return pointer, or other saved registers in the stack things could change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Dear @jonahgraham ,

Thank you for bringing this up. It made me think about scenarios where a memory write could affect stack-related data as well.
In theory, we could use InvalidatedEvent(['all']) to refresh additional debugger views after a memory write.

However, I found it difficult to come up with a reliable test case for scenarios where a user modifies a saved return address or saved PC through writeMemory. Reproducing such cases would likely require preparing a specific stack layout, identifying the saved return address (or saved PC), and then modifying it through writeMemoryRequest().

For this change, I focused on the original Variables/Watch refresh issue, so I limited the invalidation scope to "variables".
I haven't explored the stack-related scenarios in detail yet. That feels like a separate investigation, since it would require understanding how memory writes can affect stack frames, saved registers, or return addresses, and how those changes should be reflected in the UI.

@duyhihi91 duyhihi91 Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Dear @jonahgraham ,

I investigated this scenario further on a Cortex-R52 target.
I modified a saved LR value on the stack through Memory Inspector. The saved return address changed from 0x40806758 to 0x4080675C.
With InvalidatedEvent(['variables']), VS Code refreshed scopes and variables, but did not request a new stack trace.

I also tested InvalidatedEvent(['all']). In that case, VS Code did issue new stackTrace requests. However, GDB initially returned the original stack frame because it was still using its cached frame information.

After explicitly invalidating GDB's cached frames with gdb.invalidate_cached_frames(), the backtrace changed from: 0x40806758 in main() at loader_main.c:100
to: 0x4080675C in main() at loader_main.c:108

Therefore, invalidating all DAP areas causes VS Code to refresh the Call Stack, but it may not be sufficient by itself because GDB's frame cache also needs to be considered.

This seems broader than the original Variables synchronization issue and may be better handled as a separate investigation.
So, I limited the invalidation scope to "variables". How about your idea?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@duyhihi91 that sounds like a reasonable conclusion and you fully answered my question now of "Did you look at whether we should invalidate stack, and other items?"

I am marking this resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would recommend to move the sending of the invalidated event to be under handleGDBNotify for the following reason:

  1. If a user triggers a writeMemoryRequest with the same exact data, then an invalidated event would be sent anyway.
  2. If for any reason the memory didn't properly change, the invalidated event would be sent nonetheless.

GDB notifications would offload this hassle of checking writing memory errors from us

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@duyhihi91 What do you think of @omarArm's suggestion? I think having the invalidated where it is currently works well and is pretty simple. I am not sure that the overhead of an extra invalidated event matters that much here. Does GDB elide the async notify if a memory write does not change values? If not, then this seems the same effect.

There is a different case that means using async notify would be good to do too. If a user uses CLI to modify memory, then it would be useful to generate the invalidated event.

@duyhihi91 duyhihi91 Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Dear @omarArm and @jonahgraham ,

Thank you for your feedback.

Omar's idea gives me a wide view of the general issues. I can see the benefit of moving the invalidated event closer to a GDB notification source, especially if we want to cover memory modifications coming from places other than the DAP writeMemory request (for example CLI commands).

I spent some time investigating the stack-related scenarios as well. During testing I found that GDB frame caching adds another layer of complexity. Even when the memory value changed and VS Code requested a new stack trace, GDB continued to return the old frame information until its cached frames were invalidated.

Because of that, the broader "any source of memory modification" case feels a bit beyond the scope of the original Variable refresh issue and may be better handled as a separate follow-up investigation.
That said, I agree there is value in the direction you're suggesting, especially if we decide to generalize memory change handling in the future.

For this PR, my goal is to keep the change minimal and address the Variables refresh issue reported for Memory Inspector writes.

} catch (err) {
this.sendErrorResponse(
response,
Expand Down
14 changes: 12 additions & 2 deletions src/integration-tests/mem.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ describe('Memory Test Suite', function () {
memoryReference: '&array',
};

it('can write memory', async function () {
it('can write memory and invalidates variables', async function () {
const addrOfArray = parseInt(
(
await dc.evaluateRequest({
Expand All @@ -190,12 +190,22 @@ describe('Memory Test Suite', function () {
})
).body.result
);
await dc.writeMemoryRequest(writeArguments);

const invalidatedEventPromise = dc.waitForEvent('invalidated');

const writeMemoryResponse = await dc.writeMemoryRequest(writeArguments);

expect(writeMemoryResponse.success).to.be.true;

const invalidatedEvent = await invalidatedEventPromise;

expect(invalidatedEvent.body.areas).to.deep.equal(['variables']);
const memory = await dc.readMemoryRequest({
memoryReference: '&array',
count: 10,
offset: 0,
});

verifyReadMemoryResponse(memory, newValue, addrOfArray);
});

Expand Down
Loading