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.
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.
+
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