Machine code monitor debugger#705
Draft
chrisgleissner wants to merge 4 commits into
Draft
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR introduces a debugger-capable Machine Code Monitor with breakpoint support across U64 and U2 targets, plus new automation tooling (repro scripts + soak test) and documentation updates to validate and explain the new debug behaviors.
Changes:
- Adds a Debug mode execution backend (BRK-based stepping, breakpoints, reset/re-entry orchestration) with target-specific implementations (U64/U2).
- Extends monitor UI/input handling for debug actions, global reset behavior, and updated status/banking display.
- Adds new deterministic repro scripts, soak testing, and updates docs/snapshots/build files to cover the new functionality.
Reviewed changes
Copilot reviewed 57 out of 61 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/developer/machine-code-monitor/snapshots/expected_snapshots.json | Updates expected CPU/view status line fragments to the new CxOy format. |
| tools/developer/machine-code-monitor/regression_repro.py | Adds deterministic REST-driven repro cases for monitor regressions. |
| tools/developer/machine-code-monitor/monitor_debug_soak.py | Adds a telnet-based debug soak test with a lightweight 6510 model comparison. |
| tools/developer/machine-code-monitor/issue_repro.py | Adds autonomous REST repro cases for current monitor blockers. |
| tools/developer/machine-code-monitor/README.md | Documents debug tests/soak usage and new environment variables. |
| target/u64ii/riscv/ultimate/Makefile | Builds new monitor debug/breakpoint sources for U64II RISC-V. |
| target/u64/riscv/ultimate/Makefile | Builds new monitor debug/breakpoint sources for U64 RISC-V. |
| target/u64/nios2/ultimate/Makefile | Builds new monitor debug/breakpoint sources for U64 Nios2. |
| target/u2plus_L/riscv/ultimate/Makefile | Builds new monitor debug/breakpoint sources for U2+L RISC-V. |
| target/u2plus/nios/ultimate/Makefile | Builds new monitor debug/breakpoint sources for U2+ Nios. |
| target/u2/riscv/ultimate/Makefile | Builds new monitor debug/breakpoint sources for U2 RISC-V. |
| target/pc/linux/machinemonitortest/Makefile | Adds PC-side machinemonitordebugtest suite and required sources. |
| software/userinterface/userinterface.h | Adds active monitor tracking and reset re-entry hook into HostClient. |
| software/userinterface/userinterface.cc | Implements global reset shortcut handling and wires it into keymapper. |
| software/userinterface/ui_elements.cc | Treats keymapper -2 as “global accelerator consumed” to exit popups. |
| software/u64/u64_machine.h | Adds raw/visible poke/peek variants and “preserving freeze restore” write. |
| software/u64/u64_machine.cc | Implements raw/visible memory access helpers and improves serve-control handling. |
| software/test/monitor/machine_monitor_test_support.h | Extends FakeKeyboard to allow pushing a key ahead of scripted input. |
| software/test/monitor/machine_monitor_test_support.cc | Implements FakeKeyboard push-head and updates UI string_edit stub signature. |
| software/test/monitor/machine_monitor_bookmarks_test.cc | Updates expected bookmark popup strings and key sequences for new flows. |
| software/monitor/u64_memory_backend.h | Adds reset/debug-session support and observed live CPU port tracking. |
| software/monitor/u64_memory_backend.cc | Updates U64 backend mapping semantics and creates U64 debug sessions. |
| software/monitor/u2_memory_backend.h | Adds reset/debug-session support for U2 backend. |
| software/monitor/u2_memory_backend.cc | Implements U2 reset and debug-session creation. |
| software/monitor/run_machine_monitor.cc | Reworks monitor lifecycle for reset re-entry and interface swap teardown. |
| software/monitor/monitor_init.h | Adds weak global-reset-cancel hook for monitor/debug cancellation. |
| software/monitor/monitor_file_io.h | Adds debug-context resume/staging APIs to safely hand off to execution. |
| software/monitor/monitor_file_io.cc | Implements U64 NMI trampoline helpers and staged NMI handoff paths. |
| software/monitor/monitor_debug_u64.h | Declares U64 debug session factory and helper for step CPU port. |
| software/monitor/monitor_debug_u64.cc | Implements U64-specific BRK debug session with volatile ROM patching support. |
| software/monitor/monitor_debug_u2.h | Declares U2 debug session factory. |
| software/monitor/monitor_debug_u2.cc | Implements U2-specific BRK debug session (no visible ROM patching). |
| software/monitor/monitor_debug_session.h | Introduces the DebugSession interface and result codes for debugger ops. |
| software/monitor/monitor_debug_predictor.h | Adds instruction classification for stepping prediction. |
| software/monitor/monitor_debug_predictor.cc | Implements predictor using fast opcode cases + disassembler length fallback. |
| software/monitor/monitor_debug_brk_session.h | Declares shared BRK-based debug session implementation and patch tracking. |
| software/monitor/monitor_debug.h | Defines DebugContext and MonitorDebug footer/help formatting API. |
| software/monitor/monitor_debug.cc | Implements debug footer layout + help text formatting. |
| software/monitor/monitor_breakpoints.h | Adds in-memory breakpoint table, labels, and popup formatting. |
| software/monitor/monitor_breakpoints.cc | Implements slot allocation, normalization, and popup row formatting. |
| software/monitor/memory_backend.h | Adds backing-store classification helpers and debug-session/reset hooks. |
| software/monitor/machine_monitor.h | Extends monitor state, disasm lane, debug/breakpoint UI plumbing and APIs. |
| software/monitor/disassembler_6502.h | Exposes operand_spec() for shared operand classification. |
| software/monitor/disassembler_6502.cc | Renames illegal mnemonics and refactors operand parsing to use operand_spec(). |
| software/monitor/assembler_6502.cc | Canonicalizes additional illegal mnemonic aliases during assembly lookup. |
| software/io/usb/tests/usb_keyboard_queue_test.cpp | Adds regression for Ctrl+R mapping distinct from cursor-down behavior. |
| software/io/usb/keyboard_usb.cc | Maps Ctrl+R to KEY_CTRL_R in control keymap. |
| software/io/stream/keyboard_vt100.cc | Adds Ctrl+R decoding from stream input (0x12 / ESC+r). |
| software/io/c64/keyboard_c64.cc | Maps matrix Ctrl+R to KEY_CTRL_R instead of PETSCII 0x12 collision. |
| software/io/c64/keyboard.h | Introduces KEY_CTRL_R and documents why 0x12 cannot be used. |
| software/io/c64/c64_subsys.cc | Cancels debug waits on reset and normalizes formatting/whitespace. |
| software/io/c64/c64.h | Adds begin/end stopped-session helpers and a refreeze() convenience. |
| software/io/c64/c64.cc | Adds pristine ROM snapshot/restore on reset + stopped-session helpers + refreeze(). |
| software/infra/host.h | Adds host callback to request reset re-entry after C64 reset. |
| doc/machine_code_monitor.md | Updates public documentation for modes, status line, edit/debug/breakpoints. |
Comments suppressed due to low confidence (4)
software/monitor/disassembler_6502.cc:1
- Branch opcode templates were changed to use an operand spec of
rel(e.g.\"BCC rel\",\"BNE rel\"), butoperand_length()/format_operand()no longer have the branch-special-case and also don’t recognizerel. This will cause branch instructions to disassemble with the wrong operand length and likely render an empty/incorrect operand/target, breaking both UI and any predictor logic that relies on disassembly output. Fix by handlingrelexplicitly (length=1 and formatting$%04Xtarget), or by reinstating a branch-specific path keyed offspec == \"rel\".
#include "disassembler_6502.h"
software/monitor/disassembler_6502.cc:147
- Branch opcode templates were changed to use an operand spec of
rel(e.g.\"BCC rel\",\"BNE rel\"), butoperand_length()/format_operand()no longer have the branch-special-case and also don’t recognizerel. This will cause branch instructions to disassemble with the wrong operand length and likely render an empty/incorrect operand/target, breaking both UI and any predictor logic that relies on disassembly output. Fix by handlingrelexplicitly (length=1 and formatting$%04Xtarget), or by reinstating a branch-specific path keyed offspec == \"rel\".
!strncmp(spec, "$nn", 3) || !strncmp(spec, "#", 1)) {
return 1;
}
return 0;
}
tools/developer/machine-code-monitor/issue_repro.py:1
- This line assigns
session.dump_ui_screen(...)intomdt.wait_stable_dump, overwriting the imported function/attribute on themonitor_direct_testmodule. That is almost certainly unintended and can break subsequent calls that rely onmdt.wait_stable_dump. Change this to only assign the frame (e.g.,frame = session.dump_ui_screen(...)) or call the real wait helper if you intended to use it.
tools/developer/machine-code-monitor/README.md:1 monitor_debug_soak.py(as added in this PR) does not define--copy-roms-to-ramor--yes-copy-romsarguments, so this example command is not runnable as documented. Either update the README to match the actual CLI flags, or add the missing argparse options and implement the described behavior.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
chrisgleissner
marked this pull request as draft
June 6, 2026 16:08
|
Just: WOW! Thank you! |
chrisgleissner
force-pushed
the
feature/machine-code-monitor-debug
branch
2 times, most recently
from
June 27, 2026 06:41
b5797b2 to
e75f5b0
Compare
chrisgleissner
force-pushed
the
feature/machine-code-monitor-debug
branch
2 times, most recently
from
July 21, 2026 21:55
8f6b8f2 to
84e7892
Compare
BRK park/step engine with Step Into/Over/Out, run-to-cursor and breakpoints across RAM, RAM-under-ROM and ROM from the telnet, overlay and freeze UIs. U64 volatile ROM-image breakpoints and U2+L split-session support. Validated on U64: full UI x memory matrix + 1000-opcode oracle run.
chrisgleissner
force-pushed
the
feature/machine-code-monitor-debug
branch
from
July 22, 2026 00:57
84e7892 to
5ebf0df
Compare
…king
Contextless visible-ROM bp entry can miss the 6510 first fetch (closed U64 core served-ROM coherency). go()/continue now return DBG_ROM_ENTRY_UNCOHERENT ("ROM BP ENTRY MISSED") after the bounded no-reset relaunch instead of a bare timeout. Test harnesses wait the full go() budget and drop the 5x reset-retry; a genuine miss is surfaced as a documented-limitation SKIP/status. Adds a static anti-masking guard, reset/retry counters, host tests, and doc/machine-code-monitor-rom-fetch-coherency.md.
A contextless bp+Go whose first fetch misses can run into ROM and change $01; the firmware's no-reset in-place relaunch then re-enters with a stale bank, breaking downstream ROM stepping (exposed by dropping the reset-retry masking). The RAM-spin bootstrap now sets $00/$01 to canonical banking ($2F/$37) before jumping, so every entry - including a relaunch through it - lands with correct banking. jsr-runcursor-rts: 4/4 with fix (was 2/3).
Telnet wait_pc confirms a step via the debugger's STORE_* context ($03F0-$03F6, read by DMA) when a screen render is dropped under congestion (firmware aborts a telnet send it cannot deliver within SO_SNDTIMEO), instead of failing on the stale footer - not a retry, re-issues no command. Add a per-cell SIGALRM watchdog and post-cell C64 hard-wedge detection/logging so a stalled transport cannot hang the run for an hour or silently brick the device.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
This PR adds debugger support to the Machine Code Monitor:
Demo
The demo shows a small program cycling the background colour, followed by stepping through KERNAL and BASIC:
https://youtu.be/ECsqq5HKPlE
Background
This PR depends on prior Machine Code Monitor work (see PRs #358 and #704) which has already been merged by @GideonZ into the
test-mergebranch. Thetest-mergebranch in turn will eventually be merged tomaster.The target branch of this PR is therefore
test-merge, notmaster.Dependency hierarchy between branches:
Features
In the following overview, keyboard shortcuts are bolded.
Stepping and execution
Live CPU status
$0314/$0315and$0318/$0319.NV-BDIZC) are highlighted.Target prediction
Breakpoints
Free exploration while debugging
Multi-mode debugging
Convenient reset
Known Limitations
BRKopcode in the volatile ROM shadow as this feature only exists on a U64.Remaining Work
mcm-debug-wipbranch was merged, and all seven review comments on that PR were re-reviewed against this branch: three (heap-overflow clamp inscreen.cc,run_window_unfrozefreeze gating,get_live_cpu_portview contract) were already correctly resolved; one further real defect it flagged (the contextless visible-ROM relaunch settled on the wrong signal) was fixed here (see Testing).step over->step into->step out->continue to breakpoint->continue->reset) for all combinations of:Screenshots
Debugger
The following screenshot shows the debugger paused in the Kernal
SCNKEYsubroutine, which checks for pressed keys:Dbgflag in the top right shows that debug mode is active. At$EA87, the start of the subroutine,[KEY]marks a breakpoint.$EA98. PressingDwould take the highlighted branch to$EAFB, shown by the white target address. Without highlighting, the branch would not be taken.>....<. Here, the cursor row and next instruction happen to be the same.Entering and leaving Debug mode
Din Assembly view to enter Debug mode.C=+DorRUN/STOP.Debug footer
The debug footer shows:
The next opcode to be executed is marked with
>.....<and is also reflected by thePCvalue in the footer. This lets the developer move freely around memory while still keeping track of the active debug state.Breakpoints
Breakpoints are shown on the right side of assembly opcode lines as
[BRKx], wherexis the breakpoint index from0to9. Custom labels are shown as[LABL]. Lines with breakpoints are also highlighted.The breakpoint list popup is intentionally similar to the existing bookmarks popup. It supports jumping to breakpoints, deleting breakpoints, enabling or disabling breakpoints, and assigning custom labels.
Debug help page
Some rarely used monitor shortcuts have debug-specific meanings while Debug mode is active. These mappings are shown at the top of the help screen.
Design
The following chapter discusses key design considerations.
Breakpoints
BRKinstructions because the FPGA core does not expose hardware breakpoints or direct 6510 register access usable by the application-hosted monitor.MemoryBackend::create_debug_session()is the only seam between the monitor and platform-specific debug backends. Host tests use fake backends; firmware builds use the real U64 and U2 implementations.DebugSessionabstraction (over,trace,step_out,go). The shared BRK engine owns trap installation, sentinel polling, register capture, resume, cleanup, breakpoint orchestration and patch tracking.$00(i.e. the BRK opcode), let the CPU trap, read the register snapshot from the BRK catcher stub, then restore the saved byte. RAM breakpoints work on both U64 and U2.$0316/$0317. Any changes are temporary and cleaned up on leaving debug mode.Stepping
Dsteps overJSR,Ttraces into it,Oruns to the caller-side return, andGinstalls enabled breakpoints and free-runs. IfGstarts on a breakpoint, it first steps past it to avoid immediately re-trapping.JSR, rather than relying only on live stack inspection.ROM Support
Cleanup and Modes
RUN/STOP,C=+OandC=+X.C=+Xclears transient debug state, resets through the backend, and either reopens the monitor or exits cleanly depending on the mode.Implementation
Debug mode is implemented as a modal layer on top of the Assembly view. Normal monitor behaviour is unchanged.
The main source code files are:
machine_monitor.ccmonitor_debug.{h,cc}monitor_breakpoints.{h,cc}monitor_debug_session.hmonitor_debug_brk_session.ccmonitor_debug_u64.ccandmonitor_debug_u2.ccTesting
Hardware testing
Release matrix gate
tools/developer/machine-code-monitor/monitor_debug_matrix_gate.pyis the debugger release gate. It drives the full{Telnet, UI Overlay, UI Freeze} x {RAM, RAM-under-ROM, visible ROM}matrix at two repetitions (18 cells) and validates every step against two independent oracles.mcm6502.py) and against VICE, plus a per-cell 100-opcode dual-oracle trace and a separate 1000-opcode live run.3e01af5bremoves (see "ROM-breakpoint entry coherency hardening" below); ROM cold-entry is now reported honestly rather than masked, and the deterministic RAM / RAM-under-ROM cells remain the release gate.Fix validated by this run
The contextless visible-ROM breakpoint entry has a closed-core first-fetch edge: a freshly DMA-committed ROM-image
BRKcan lag the live 6510 instruction-fetch path, so abp+Gotoward a KERNAL/BASIC target can run past the breakpoint. The follow-up commit3e01af5bsupersedes the reset-retry handling described here; see "ROM-breakpoint entry coherency hardening" below.has_visible_rom_patch()).DBG_ROM_ENTRY_UNCOHERENTresult instead of a bare timeout, so the honest cause is reported rather than masked.Unit tests
All three host suites under
target/pc/linux/machinemonitortestpass: the core monitor suite, the bookmarks suite, and the debugger suite (software/test/monitor/machine_monitor_debug_test.cc, 173 cases including the newtest_contextless_visible_rom_relaunch_resettles_fetch_pathregression).machine_monitor_debug_test.cccoverage includes:C=+Xreset and monitor re-entry.End-to-end tests
Two Telnet E2E suites drive the deployed firmware and assert screen contents after each step, using the U64 REST API for memory setup. As of
3e01af5bthey no longer reset-retry the contextless visible-ROM entry: each entry is attempted once, waiting the full firmwarego()budget, and a genuine miss is surfaced as a documented-limitation SKIP (never a masked pass). See "ROM-breakpoint entry coherency hardening" below../tools/developer/machine-code-monitor/monitor_test.py --host <u64-ip>covers the core (non-debug) monitor as a regression guard: KERNAL visibility, disassembly and ASCII/hex formatting, inline edit, CPU/VIC bank cycling,Gexecution, bookmarks, binary-width cycling, save/load round-trips, and ASM navigation. Latest run: 32/32 passed, 0 skipped../tools/developer/machine-code-monitor/monitor_debug_test.py --host <u64-ip>is the broad debugger e2e (86 checks across stepping, breakpoints, RAM / RAM-under-ROM / visible-ROM entry, continue, cleanup, and exit-liveness). Latest run: 86/86 passed, 0 skipped (an excerpt is shown below).Output (excerpt):
Soak test
./tools/developer/machine-code-monitor/monitor_debug_soak.py --host <u64-ip>This long-running test loops high-risk scenarios such as freeze/refreeze handling, parking trampoline cleanup, and breakpoint re-entry.
ROM-breakpoint entry coherency hardening (follow-up
3e01af5b)Defect. A contextless visible-ROM breakpoint entry (arm a breakpoint in BASIC/KERNAL, then
Gointo it from a short RAM bootstrap, with no prior execution of that ROM line) can miss the 6510's first fetch: the closed U64 C64 core (delivered as the prebuiltexternal/u64.sof, sourced from a separate private repo pertarget/u64/nios2/updater/getbuild.sh) can serve a stale pre-patch byte to the live instruction fetch for a ROM line not re-fetched since the DMABRKwrite. It is rare and load-conditioned; on a healthy device it did not occur at idle (0 coherency misses in 20 first-fetch attempts, all clean at ~2s).Why the previous reset-retry was invalid. The harnesses masked this with a 5x reset-and-retry (
ROM_ENTRY_MAX_ATTEMPTS). A reset does not create fetch coherency; it only buys another independent probabilistic draw, so the "pass" was probabilistic retry, not correctness. Two problems were also conflated: the firmwarego()for this launch blocks its full breakpoint wait plus bounded relaunch budget (~15s) before returning, but the tests waited only ~6s and then reset, counting slow-but-succeeding entries as misses (the origin of the inflated "~37%" figure).No FPGA fix is possible in this repository. The C64 core (6510, VIC-II, ROM serving) is a prebuilt binary with no source or FPGA project here, and there is no hardware PC-compare/breakpoint register (the debugger is BRK-only). The fix is firmware- and test-side.
Fix.
go()/continue return the preciseDBG_ROM_ENTRY_UNCOHERENT(monitor showsROM BP ENTRY MISSED - RUN CODE FIRST) on a genuine contextless visible-ROM miss, after its bounded in-place, no-reset relaunch. Patches are restored (no staleBRK), the handler is uninstalled, and the debugger stays usable. No reset, no command replay.go()budget and drop the reset-retry entirely. A genuine miss is surfaced honestly as a documented-limitation SKIP (E2E) or a terminalROM_ENTRY_UNCOHERENTstatus (matrix gate), never a masked pass. The matrix gate prints reset/retry counters and asserts the prohibited categories (recovery_reset,command_retry,session_replay,transparent_reset) are zero.test_no_reset_retry_masking.py(a structural anti-masking guard that fails the build if a reset-retry loop reappears in a gate harness), host teststest_contextless_visible_rom_entry_reports_uncoherent_on_missandtest_ram_under_rom_timeout_is_not_reported_as_uncoherent, anddoc/machine-code-monitor-rom-fetch-coherency.md.Validation. Host suites green including the two new tests; anti-masking guard and matrix unit green;
./build-tool u64builds cleanly. U64 hardware (Elite, fw 3.15, FPGA 122, core 1.4B): the de-masked ROM E2E group passes 7/7 checks with zero reset-retries and zero skips on a fresh deploy, and idle contextless first-fetch entry is 14/14 clean hits. Note: a separate, pre-existing network-stack degradation appears under sustained rapid reset+telnet+REST cycling ("back-to-back degrades telnet") and is recovered by a fresh JTAG redeploy; it is not the fetch race and is not masked as one.Follow-up
2913a083(bootstrap CPU port). Dropping the reset-retry exposed a latent issue: a contextless bp+Go whose first fetch misses can briefly run into ROM and change$01, and the firmware's no-reset in-place relaunch then re-enters with a stale bank, breaking downstream ROM stepping. (The old reset-retry masked this by resetting to a clean boot.) The RAM-spin bootstrap now sets$00/$01to canonical banking ($2F/$37) before jumping into ROM, so every entry - including a relaunch through the bootstrap - lands with correct banking. Validated on U64 hardware:jsr-runcursor-rtsis 4/4 with the fix (fresh deploy each) vs 2/3 without it; the pre-hardening harness was 3/3 only because its reset-retry reset the machine on every slow entry.