You seem to be fixated on a design where the emulator components know absolutely nothing about the existence of a debugger (no "debugger cruft" in the chip classes, as you put it) I don't think that's a realistic approach, for a couple of reasons:
1) If the emulator doesn't know anything about the debugger, then the debugger conversely has to have extremely invasive knowledge of and access to the emulator. Result: you try to refactor a core component, your debugger breaks. If your design requires "#define private public" to work, you need to rethink your design.
2) The design of the emulator can unnecessarily limit what the debugger is able to do. For example, a debugger obviously needs to read from emulated memory, but reading e.g. SA-1 IRAM or BWRAM in bsnes causes a cothread switch, which will probably have bad effects if it happens from the debugger thread. Other chips have memory-mapped registers that latch counters or clear interrupt lines when read, which you likewise don't want the debugger to accidentally trigger. Historically, bsnes debuggers have handled this problem by hardcoding address ranges which the debugger isn't allowed to read from. This has two problems: (a) the address ranges with side effects on reads depend on the type of cartridge and expansion device installed, which further increases the amount of knowledge the debugger needs about the emulated system, and (b) it limits the capabilities of the debugger to a frankly unacceptable degree. Debugging SA-1 games is pretty much impossible if you can't see IRAM or BWRAM.
This is how I handle the debugger-reading-arbitrary-MMIO problem in bsnes-classic. It's shamelessly stolen from MAME, which implements a single, extremely powerful debugger across thousands of emulated systems. Yes, it requires every read/write handler in every chip to include the debugger test, but debugger_access() compiles down to nothing in a non-debug build, and all the debug-specific code for side-effect-free reads likewise gets optimized out as dead code.
1) If the emulator doesn't know anything about the debugger, then the debugger conversely has to have extremely invasive knowledge of and access to the emulator. Result: you try to refactor a core component, your debugger breaks. If your design requires "#define private public" to work, you need to rethink your design.
2) The design of the emulator can unnecessarily limit what the debugger is able to do. For example, a debugger obviously needs to read from emulated memory, but reading e.g. SA-1 IRAM or BWRAM in bsnes causes a cothread switch, which will probably have bad effects if it happens from the debugger thread. Other chips have memory-mapped registers that latch counters or clear interrupt lines when read, which you likewise don't want the debugger to accidentally trigger. Historically, bsnes debuggers have handled this problem by hardcoding address ranges which the debugger isn't allowed to read from. This has two problems: (a) the address ranges with side effects on reads depend on the type of cartridge and expansion device installed, which further increases the amount of knowledge the debugger needs about the emulated system, and (b) it limits the capabilities of the debugger to a frankly unacceptable degree. Debugging SA-1 games is pretty much impossible if you can't see IRAM or BWRAM.
This is how I handle the debugger-reading-arbitrary-MMIO problem in bsnes-classic. It's shamelessly stolen from MAME, which implements a single, extremely powerful debugger across thousands of emulated systems. Yes, it requires every read/write handler in every chip to include the debugger test, but debugger_access() compiles down to nothing in a non-debug build, and all the debug-specific code for side-effect-free reads likewise gets optimized out as dead code.