From 975a6940a5a25fff91f09a47d9959964f1ad431e Mon Sep 17 00:00:00 2001 From: Thomas Jentzsch Date: Thu, 6 May 2021 10:29:45 +0200 Subject: [PATCH] added option to log breaks and traps instead of interrupting emulation (resolves #741) --- Changes.txt | 11 ++++- docs/debugger.html | 6 ++- src/common/Logger.cxx | 3 +- src/common/Logger.hxx | 5 ++- src/common/main.cxx | 2 +- src/debugger/Debugger.cxx | 75 +++++++++++++++++++++++++++++++ src/debugger/Debugger.hxx | 2 + src/debugger/DebuggerParser.cxx | 24 +++++++++- src/debugger/DebuggerParser.hxx | 4 +- src/debugger/gui/PromptWidget.cxx | 2 + src/emucore/M6502.cxx | 47 +++++++++++++------ src/emucore/M6502.hxx | 3 ++ src/emucore/Settings.cxx | 2 + 13 files changed, 164 insertions(+), 22 deletions(-) diff --git a/Changes.txt b/Changes.txt index 21b98a20a..d285253ad 100644 --- a/Changes.txt +++ b/Changes.txt @@ -12,6 +12,15 @@ Release History =========================================================================== +6.5.3 to 6.6 (??? ??, 202?) + + * Added web links for many games + + * Added optional logging of breaks and traps + +-Have fun! + + 6.5.2 to 6.5.3 (April 20, 2021) * Added context-sensitive help. @@ -26,8 +35,6 @@ * Fixed immediate disassembling when switching options in debugger. --Have fun! - 6.5.1 to 6.5.2 (February 25, 2021) diff --git a/docs/debugger.html b/docs/debugger.html index b30f9e6e2..8a3836630 100644 --- a/docs/debugger.html +++ b/docs/debugger.html @@ -609,7 +609,7 @@ command that takes arguments.

Breakpoints

A breakpoint is a "hotspot" in your program that causes the emulator -to stop emulating and jump into the debugger. You can set as many +to stop emulating and jump into the debugger ¹. You can set as many breakpoints as you like. The command is "break xx yy" where xx is any expression and yy a bank number. Both arguments are optional. If you have created a symbol file, you can use labels for the expression.

@@ -633,6 +633,9 @@ breakpoint on & off, like a light switch.

You could also use "clearbreaks" to remove all the breakpoints. Also, there is a "listbreaks" command that will list them all.

+

¹ By enabling "logbreaks" you can log the current state into +the System Log and continue emulation instead.

+

Conditional Breaks

A conditional breakpoint causes the emulator to enter the debugger when @@ -973,6 +976,7 @@ clearsavestateifs - Clear all savestate points loadconfig - Load DiStella config file loadallstates - Load all emulator states loadstate - Load emulator state xx (0-9) + logbreaks - Logs breaks and traps and continues emulation n - Negative Flag: set (0 or 1), or toggle (no arg) palette - Show current TIA palette pc - Set Program Counter to address xx diff --git a/src/common/Logger.cxx b/src/common/Logger.cxx index 36f30db6e..8ec1b13cf 100644 --- a/src/common/Logger.cxx +++ b/src/common/Logger.cxx @@ -56,7 +56,8 @@ void Logger::logMessage(const string& message, Level level) cout << message << endl << std::flush; myLogMessages += message + "\n"; } - else if(static_cast(level) <= myLogLevel) + else if(static_cast(level) <= myLogLevel || + level == Logger::Level::ALWAYS) { if(myLogToConsole) cout << message << endl << std::flush; diff --git a/src/common/Logger.hxx b/src/common/Logger.hxx index 314b3a19d..4e2cdffd1 100644 --- a/src/common/Logger.hxx +++ b/src/common/Logger.hxx @@ -30,15 +30,16 @@ class Logger { ERR = 0, // cannot use ERROR??? INFO = 1, DEBUG = 2, + ALWAYS = 3, MIN = ERR, - MAX = DEBUG + MAX = ALWAYS }; public: static Logger& instance(); - static void log(const string& message, Level level); + static void log(const string& message, Level level = Level::ALWAYS); static void error(const string& message); diff --git a/src/common/main.cxx b/src/common/main.cxx index 8b3f2144a..bef77c122 100644 --- a/src/common/main.cxx +++ b/src/common/main.cxx @@ -206,7 +206,7 @@ int main(int ac, char* av[]) // Create the full OSystem after the settings, since settings are // probably needed for defaults - Logger::debug("Creating the OSystem ..."); + Logger::log("Creating the OSystem ..."); if(!theOSystem->initialize(globalOpts)) { Logger::error("ERROR: Couldn't create OSystem"); diff --git a/src/debugger/Debugger.cxx b/src/debugger/Debugger.cxx index afab08bfa..39cc5a0ea 100644 --- a/src/debugger/Debugger.cxx +++ b/src/debugger/Debugger.cxx @@ -123,6 +123,7 @@ bool Debugger::start(const string& message, int address, bool read, { if(myOSystem.eventHandler().enterDebugMode()) { + myFirstLog = true; // This must be done *after* we enter debug mode, // so the message isn't erased ostringstream buf; @@ -442,6 +443,80 @@ bool Debugger::writeTrap(uInt16 t) return writeTraps().isInitialized() && writeTraps().isSet(t); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Debugger::log(const string& triggerMsg) +{ + const CartDebug::Disassembly& disasm = myCartDebug->disassembly(); + int pc = myCpuDebug->pc(); + + if(myFirstLog) + { + ostringstream msg; + + msg << "Trigger: Frame Scn Cy Pxl | PS A X Y SP | "; + if(myCartDebug->romBankCount() > 1) + if(myCartDebug->romBankCount() > 9) + msg << "Bk/"; + else + msg << "B/"; + msg << "Addr Code Disam"; + Logger::log(msg.str()); + myFirstLog = false; + } + + // First find the lines in the range, and determine the longest string + uInt16 start = pc & 0xFFF; + uInt32 list_size = uInt32(disasm.list.size()); + uInt32 pos; + + for(pos = 0; pos < list_size; ++pos) + { + const CartDebug::DisassemblyTag& tag = disasm.list[pos]; + + if((tag.address & 0xfff) >= start) + break; + } + + const CartDebug::DisassemblyTag& tag = disasm.list[pos]; + ostringstream msg; + + msg << std::left << std::setw(10) << std::setfill(' ') << triggerMsg; + msg << Base::toString(myTiaDebug->frameCount(), Base::Fmt::_10_5) << " " + << Base::toString(myTiaDebug->scanlines(), Base::Fmt::_10_3) << " " + << Base::toString(myTiaDebug->clocksThisLine() / 3, Base::Fmt::_10_02) << " " + << Base::toString(myTiaDebug->clocksThisLine() - 68, Base::Fmt::_10_3) << " | "; + msg << (myCpuDebug->n() ? "N" : "n") + << (myCpuDebug->v() ? "V" : "v") << "-" + << (myCpuDebug->b() ? "B" : "b") + << (myCpuDebug->d() ? "D" : "d") + << (myCpuDebug->i() ? "I" : "i") + << (myCpuDebug->z() ? "Z" : "z") + << (myCpuDebug->c() ? "C" : "c") << " " + << Base::HEX2 << myCpuDebug->a() << " " + << Base::HEX2 << myCpuDebug->x() << " " + << Base::HEX2 << myCpuDebug->y() << " " + << Base::HEX2 << myCpuDebug->sp() << " |"; + + if(myCartDebug->romBankCount() > 1) + { + if(myCartDebug->romBankCount() > 9) + msg << Base::toString(myCartDebug->getBank(pc), Base::Fmt::_10) << "/"; + else + msg << " " << myCartDebug->getBank(pc) << "/"; + } + else + msg << " "; + + msg << Base::HEX4 << pc << " " + << std::left << std::setw(8) << std::setfill(' ') << tag.bytes << " " + << tag.disasm.substr(0, 7); + + if(tag.disasm.length() > 8) + msg << tag.disasm.substr(8); + + Logger::log(msg.str()); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt8 Debugger::peek(uInt16 addr, Device::AccessFlags flags) { diff --git a/src/debugger/Debugger.hxx b/src/debugger/Debugger.hxx index 0e7de4c36..14c657de2 100644 --- a/src/debugger/Debugger.hxx +++ b/src/debugger/Debugger.hxx @@ -321,6 +321,7 @@ class Debugger : public DialogContainer bool readTrap(uInt16 t); bool writeTrap(uInt16 t); void clearAllTraps(); + void log(const string& triggerMsg); // Set a bunch of RAM locations at once string setRAM(IntArray& args); @@ -363,6 +364,7 @@ class Debugger : public DialogContainer static std::array ourPseudoRegisters; static constexpr Int8 ANY_BANK = -1; + bool myFirstLog{true}; private: // rewind/unwind n states diff --git a/src/debugger/DebuggerParser.cxx b/src/debugger/DebuggerParser.cxx index eb5904e58..32640d149 100644 --- a/src/debugger/DebuggerParser.cxx +++ b/src/debugger/DebuggerParser.cxx @@ -31,6 +31,8 @@ #include "Expression.hxx" #include "FSNode.hxx" #include "OSystem.hxx" +#include "System.hxx" +#include "M6502.hxx" #include "Settings.hxx" #include "PromptWidget.hxx" #include "RomWidget.hxx" @@ -1664,6 +1666,16 @@ void DebuggerParser::executeLoadstate() commandResult << red("invalid slot (must be 0-9)"); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void DebuggerParser::executeLogBreaks() +{ + bool enable = !debugger.mySystem.m6502().getLogBreaks(); + + debugger.mySystem.m6502().setLogBreaks(enable); + settings.setValue("dbg.logbreaks", enable); + commandResult << "logbreaks " << (enable ? "enabled" : "disabled"); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "n" void DebuggerParser::executeN() @@ -2475,7 +2487,7 @@ void DebuggerParser::executeZ() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // List of all commands available to the parser -std::array DebuggerParser::commands = { { +DebuggerParser::CommandArray DebuggerParser::commands = { { { "a", "Set Accumulator to ", @@ -3025,6 +3037,16 @@ std::array DebuggerParser::commands = { { std::mem_fn(&DebuggerParser::executeLoadstate) }, + { + "logbreaks", + "Toggle logging of breaks/traps and continue emulation", + "Example: logbreaks (no parameters)", + false, + true, + { Parameters::ARG_END_ARGS }, + std::mem_fn(&DebuggerParser::executeLogBreaks) + }, + { "n", "Negative Flag: set (0 or 1), or toggle (no arg)", diff --git a/src/debugger/DebuggerParser.hxx b/src/debugger/DebuggerParser.hxx index 61e7921f0..12345563a 100644 --- a/src/debugger/DebuggerParser.hxx +++ b/src/debugger/DebuggerParser.hxx @@ -101,7 +101,8 @@ class DebuggerParser std::array parms; std::function executor; }; - static std::array commands; + using CommandArray = std::array; + static CommandArray commands; struct Trap { @@ -201,6 +202,7 @@ class DebuggerParser void executeLoadallstates(); void executeLoadconfig(); void executeLoadstate(); + void executeLogBreaks(); void executeN(); void executePalette(); void executePc(); diff --git a/src/debugger/gui/PromptWidget.cxx b/src/debugger/gui/PromptWidget.cxx index 83dff3198..fb5146fdb 100644 --- a/src/debugger/gui/PromptWidget.cxx +++ b/src/debugger/gui/PromptWidget.cxx @@ -557,6 +557,8 @@ void PromptWidget::loadConfig() print(instance().debugger().cartDebug().loadConfigFile() + "\n"); print(instance().debugger().cartDebug().loadListFile() + "\n"); print(instance().debugger().cartDebug().loadSymbolFile() + "\n"); + if(instance().settings().getBool("dbg.logbreaks")) + print(DebuggerParser::inverse(" logbreaks enabled \n")); print(PROMPT); _promptStartPos = _promptEndPos = _currentPos; diff --git a/src/emucore/M6502.cxx b/src/emucore/M6502.cxx index cb3b6dfd4..5b1367e0f 100644 --- a/src/emucore/M6502.cxx +++ b/src/emucore/M6502.cxx @@ -93,6 +93,7 @@ void M6502::reset() myGhostReadsTrap = mySettings.getBool("dbg.ghostreadstrap"); myReadFromWritePortBreak = devSettings ? mySettings.getBool("dev.rwportbreak") : false; myWriteToReadPortBreak = devSettings ? mySettings.getBool("dev.wrportbreak") : false; + myLogBreaks = mySettings.getBool("dbg.logbreaks"); myLastBreakCycle = ULLONG_MAX; } @@ -162,7 +163,7 @@ inline void M6502::poke(uInt16 address, uInt8 value, Device::AccessFlags flags) { myJustHitWriteTrapFlag = true; stringstream msg; - msg << "WTrap[" << Common::Base::HEX2 << cond << "]" << (myTrapCondNames[cond].empty() ? ": " : "If: {" + myTrapCondNames[cond] + "} "); + msg << "WTrap[" << Common::Base::HEX2 << cond << "]" << (myTrapCondNames[cond].empty() ? ":" : "If: {" + myTrapCondNames[cond] + "}"); myHitTrapInfo.message = msg.str(); myHitTrapInfo.address = address; } @@ -243,10 +244,16 @@ inline void M6502::_execute(uInt64 cycles, DispatchResult& result) myJustHitReadTrapFlag = myJustHitWriteTrapFlag = false; myLastBreakCycle = mySystem->cycles(); - result.setDebugger(currentCycles, myHitTrapInfo.message, - read ? "Read trap" : "Write trap", - myHitTrapInfo.address, read); - return; + + if(myLogBreaks) + myDebugger->log(myHitTrapInfo.message); + else + { + result.setDebugger(currentCycles, myHitTrapInfo.message + " ", + read ? "Read trap" : "Write trap", + myHitTrapInfo.address, read); + return; + } } if(myBreakPoints.isInitialized()) @@ -260,15 +267,21 @@ inline void M6502::_execute(uInt64 cycles, DispatchResult& result) if(myBreakPoints.get(PC, bank) & BreakpointMap::ONE_SHOT) { myBreakPoints.erase(PC, bank); + return; } else { - ostringstream msg; + if(myLogBreaks) + myDebugger->log("BP:"); + else + { + ostringstream msg; - msg << "BP: $" << Common::Base::HEX4 << PC << ", bank #" << std::dec << int(bank); - result.setDebugger(currentCycles, msg.str(), "Breakpoint"); + msg << "BP: $" << Common::Base::HEX4 << PC << ", bank #" << std::dec << int(bank); + result.setDebugger(currentCycles, msg.str(), "Breakpoint"); + return; + } } - return; } } @@ -277,11 +290,19 @@ inline void M6502::_execute(uInt64 cycles, DispatchResult& result) { ostringstream msg; - msg << "CBP[" << Common::Base::HEX2 << cond << "]: " << myCondBreakNames[cond]; - myLastBreakCycle = mySystem->cycles(); - result.setDebugger(currentCycles, msg.str(), "Conditional breakpoint"); - return; + + if(myLogBreaks) + { + msg << "CBP[" << Common::Base::HEX2 << cond << "]:"; + myDebugger->log(msg.str()); + } + else + { + msg << "CBP[" << Common::Base::HEX2 << cond << "]: " << myCondBreakNames[cond]; + result.setDebugger(currentCycles, msg.str(), "Conditional breakpoint"); + return; + } } } diff --git a/src/emucore/M6502.hxx b/src/emucore/M6502.hxx index 99e414b92..4f120946e 100644 --- a/src/emucore/M6502.hxx +++ b/src/emucore/M6502.hxx @@ -255,6 +255,8 @@ class M6502 : public Serializable void setGhostReadsTrap(bool enable) { myGhostReadsTrap = enable; } void setReadFromWritePortBreak(bool enable) { myReadFromWritePortBreak = enable; } void setWriteToReadPortBreak(bool enable) { myWriteToReadPortBreak = enable; } + void setLogBreaks(bool enable) { myLogBreaks = enable; } + bool getLogBreaks() { return myLogBreaks; } #endif // DEBUGGER_SUPPORT private: @@ -469,6 +471,7 @@ class M6502 : public Serializable bool myReadFromWritePortBreak{false}; // trap on reads from write ports bool myWriteToReadPortBreak{false}; // trap on writes to read ports bool myStepStateByInstruction{false}; + bool myLogBreaks{false}; // log breaks/taps and continue emulation private: // Following constructors and assignment operators not supported diff --git a/src/emucore/Settings.cxx b/src/emucore/Settings.cxx index ebd34a1ca..81880794a 100644 --- a/src/emucore/Settings.cxx +++ b/src/emucore/Settings.cxx @@ -188,6 +188,7 @@ Settings::Settings() setPermanent("dbg.fontstyle", "0"); setPermanent("dbg.uhex", "false"); setPermanent("dbg.ghostreadstrap", "true"); + setPermanent("dbg.logbreaks", "false"); setPermanent("dis.resolve", "true"); setPermanent("dis.gfxformat", "2"); setPermanent("dis.showaddr", "true"); @@ -614,6 +615,7 @@ void Settings::usage() const << " normal)\n" << " -dbg.ghostreadstrap <1|0> Debugger traps on 'ghost' reads\n" << " -dbg.uhex <0|1> lower-/uppercase HEX display\n" + << " -dbg.logbreaks <0|1> log breaks and traps and continue emulation\n" << " -break

Set a breakpoint at 'address'\n" << " -debug Start in debugger mode\n" << endl