Pimp My IDE / Garage Dispatch
Back to garage
September 24, 2026 | debugger caches / remote targets / independent reads

Your debugger is a witness, not the scene.

A memory pane can show a coherent value that is wrong. The fix is not blind distrust. Trace the read path, ask a second witness, then force the first path to refresh.

The take: Treat every surprising debugger value as an observation with a route. Record the target state, backend, transport packet, cache policy, and one independent read before blaming the program.
Wire the Debugger Truth Bench

A successful write can look like a failed write.

Daniel Mangum provisioned two key slots on an nRF54LM20 and pushed each value to the same memory address while the CPU was halted. GDB showed the first value after the second push. A direct read through the J-Link access port showed the second value.[1]

The peripheral write worked. The normal debugger read returned a cached value. Both displays looked precise. Only one described current memory.

The pretty pane is the end of a chain.

VS Code documents that debugger extensions decide which launch settings, breakpoint types, expression syntax, and remote features they support.[2] A variable view therefore depends on more than the editor. It depends on the adapter, debug server, transport, target, and compiler information beneath it.

Do not ask only, "What value does the debugger show?" Ask, "Which path produced this value?"

Mangum's case crossed GDB, the Remote Serial Protocol, JLinkGDBServer, a memory cache, an Arm access port, and a peripheral that could update memory while the CPU stayed halted. Advancing the CPU invalidated the cache. The next ordinary read became fresh.[1]

Stale memory is one failure class.

Optimized code creates another. GDB's manual says optimized assembly can diverge from the source. Variables may move, disappear, or never exist in the generated program. The debugger maps the running program back to source with compiler debug information, but that map is less direct under optimization.[3]

Do not collapse these cases. A stale backend cache, an optimized-out variable, a hollow breakpoint, and a value from the wrong stack frame need different tests.

Make the transport visible.

GDB can log remote serial communication to a file and change the log radix. Its remote configuration also exposes packet controls for the active target.[4] Mangum enabled remote debugging and confirmed that the normal memory command and the direct access-port command took different packet routes.[1]

That trace turned a vague suspicion into a small experiment. The target address stayed fixed. The target state stayed fixed. The read path changed. The returned value changed with it.

Run a two-witness drill.

  1. Record the exact target state. Include halted or running, core, thread, stack frame, address, build, and optimization level.
  2. Repeat the read without changing target state. Save the value and route.
  3. Read the same storage through an independent path. Use a direct access port, target-side print, second probe, or post-stop memory dump when the platform supports it.
  4. Capture the adapter or transport trace. Confirm whether the two reads use the same backend command.
  5. Advance, invalidate, or reconnect through a documented mechanism. Read again and record which value changed.
  6. Keep the mismatch as a regression fixture. A debugger update should not erase the lesson.
Interactive makeover / debugger mismatch test card

Debugger Truth Bench.

Traditional purpose replaced: stare at one variable pane and retry commands. Better version: keep the target fixed, compare two read paths, log the route, and specify the refresh test before changing the program.

Clamp the observation

This teaching panel writes a test plan. It does not inspect your debugger or target.

Evidence circuits
PRIMARY ONLY0 / 4 circuits
0requirements specified

The display has no independent check.

The primary debugger path is the baseline witness. Keep the target state fixed before you compare another read path.

Write the test card

Why it is better: target state, read route, returned value, and refresh behavior stay separate. The completed panel says TEST SPEC READY. It does not claim the debugger is wrong or the program is correct.

Sources read, not vibes

Open the source log
  1. Daniel Mangum, "When the Debugger Lies": nRF54LM20 key-slot experiment, stale J-Link memory-cache behavior, direct access-port comparison, Remote Serial Protocol trace, cache control, and refresh after stepping the halted core.
  2. Visual Studio Code debugging documentation: debugger extension ownership of supported configurations, breakpoints, expression evaluation, and remote behavior. The page was updated September 22, 2026.
  3. GDB manual, "Debugging Optimized Code": source-to-instruction divergence, optimized-out variables, and limits of source-level debugging under optimization.
  4. GDB manual, "Remote Configuration": remote protocol logging, log radix, remote target settings, and packet controls.
  5. Hacker News discussion 49799306: the current discovery path and practitioner discussion. It does not verify the technical claims above.

Source boundary: Mangum reports one embedded debug path and reproduces the stale read. GDB documents general optimized-code and remote-transport behavior. VS Code documents its debugger interface and extension boundary. The bench is a test-plan writer. Real proof needs your target, build, adapter, transport log, values, and refresh result.