added option to log breaks and traps instead of interrupting emulation (resolves #741)

This commit is contained in:
Thomas Jentzsch 2021-05-06 10:29:45 +02:00
parent 54af434260
commit fbbb86f964
13 changed files with 164 additions and 22 deletions

View File

@ -12,6 +12,15 @@
Release History 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) 6.5.2 to 6.5.3 (April 20, 2021)
* Added context-sensitive help. * Added context-sensitive help.
@ -26,8 +35,6 @@
* Fixed immediate disassembling when switching options in debugger. * Fixed immediate disassembling when switching options in debugger.
-Have fun!
6.5.1 to 6.5.2 (February 25, 2021) 6.5.1 to 6.5.2 (February 25, 2021)

View File

@ -609,7 +609,7 @@ command that takes arguments.</p>
<h4><a name="Breakpoints">Breakpoints</a></h4> <h4><a name="Breakpoints">Breakpoints</a></h4>
<p>A breakpoint is a "hotspot" in your program that causes the emulator <p>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 &#185;. You can set as many
breakpoints as you like. The command is "break xx yy" where xx is any 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 expression and yy a bank number. Both arguments are optional. If you have
created a symbol file, you can use labels for the expression.</p> created a symbol file, you can use labels for the expression.</p>
@ -633,6 +633,9 @@ breakpoint on &amp; off, like a light switch.</p>
<p>You could also use "clearbreaks" to remove all the breakpoints. Also, <p>You could also use "clearbreaks" to remove all the breakpoints. Also,
there is a "listbreaks" command that will list them all.</p> there is a "listbreaks" command that will list them all.</p>
<p>&#185; By enabling "logbreaks" you can log the current state into
the System Log and continue emulation instead.</p>
<h4><a name="ConditionalBreaks">Conditional Breaks</a></h4> <h4><a name="ConditionalBreaks">Conditional Breaks</a></h4>
<p>A conditional breakpoint causes the emulator to enter the debugger when <p>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 loadconfig - Load DiStella config file
loadallstates - Load all emulator states loadallstates - Load all emulator states
loadstate - Load emulator state xx (0-9) 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) n - Negative Flag: set (0 or 1), or toggle (no arg)
palette - Show current TIA palette palette - Show current TIA palette
pc - Set Program Counter to address xx pc - Set Program Counter to address xx

View File

@ -56,7 +56,8 @@ void Logger::logMessage(const string& message, Level level)
cout << message << endl << std::flush; cout << message << endl << std::flush;
myLogMessages += message + "\n"; myLogMessages += message + "\n";
} }
else if(static_cast<int>(level) <= myLogLevel) else if(static_cast<int>(level) <= myLogLevel ||
level == Logger::Level::ALWAYS)
{ {
if(myLogToConsole) if(myLogToConsole)
cout << message << endl << std::flush; cout << message << endl << std::flush;

View File

@ -30,15 +30,16 @@ class Logger {
ERR = 0, // cannot use ERROR??? ERR = 0, // cannot use ERROR???
INFO = 1, INFO = 1,
DEBUG = 2, DEBUG = 2,
ALWAYS = 3,
MIN = ERR, MIN = ERR,
MAX = DEBUG MAX = ALWAYS
}; };
public: public:
static Logger& instance(); 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); static void error(const string& message);

View File

@ -206,7 +206,7 @@ int main(int ac, char* av[])
// Create the full OSystem after the settings, since settings are // Create the full OSystem after the settings, since settings are
// probably needed for defaults // probably needed for defaults
Logger::debug("Creating the OSystem ..."); Logger::log("Creating the OSystem ...");
if(!theOSystem->initialize(globalOpts)) if(!theOSystem->initialize(globalOpts))
{ {
Logger::error("ERROR: Couldn't create OSystem"); Logger::error("ERROR: Couldn't create OSystem");

View File

@ -123,6 +123,7 @@ bool Debugger::start(const string& message, int address, bool read,
{ {
if(myOSystem.eventHandler().enterDebugMode()) if(myOSystem.eventHandler().enterDebugMode())
{ {
myFirstLog = true;
// This must be done *after* we enter debug mode, // This must be done *after* we enter debug mode,
// so the message isn't erased // so the message isn't erased
ostringstream buf; ostringstream buf;
@ -442,6 +443,80 @@ bool Debugger::writeTrap(uInt16 t)
return writeTraps().isInitialized() && writeTraps().isSet(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) uInt8 Debugger::peek(uInt16 addr, Device::AccessFlags flags)
{ {

View File

@ -321,6 +321,7 @@ class Debugger : public DialogContainer
bool readTrap(uInt16 t); bool readTrap(uInt16 t);
bool writeTrap(uInt16 t); bool writeTrap(uInt16 t);
void clearAllTraps(); void clearAllTraps();
void log(const string& triggerMsg);
// Set a bunch of RAM locations at once // Set a bunch of RAM locations at once
string setRAM(IntArray& args); string setRAM(IntArray& args);
@ -363,6 +364,7 @@ class Debugger : public DialogContainer
static std::array<PseudoRegister, 16> ourPseudoRegisters; static std::array<PseudoRegister, 16> ourPseudoRegisters;
static constexpr Int8 ANY_BANK = -1; static constexpr Int8 ANY_BANK = -1;
bool myFirstLog{true};
private: private:
// rewind/unwind n states // rewind/unwind n states

View File

@ -31,6 +31,8 @@
#include "Expression.hxx" #include "Expression.hxx"
#include "FSNode.hxx" #include "FSNode.hxx"
#include "OSystem.hxx" #include "OSystem.hxx"
#include "System.hxx"
#include "M6502.hxx"
#include "Settings.hxx" #include "Settings.hxx"
#include "PromptWidget.hxx" #include "PromptWidget.hxx"
#include "RomWidget.hxx" #include "RomWidget.hxx"
@ -1664,6 +1666,16 @@ void DebuggerParser::executeLoadstate()
commandResult << red("invalid slot (must be 0-9)"); 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" // "n"
void DebuggerParser::executeN() void DebuggerParser::executeN()
@ -2475,7 +2487,7 @@ void DebuggerParser::executeZ()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// List of all commands available to the parser // List of all commands available to the parser
std::array<DebuggerParser::Command, 100> DebuggerParser::commands = { { DebuggerParser::CommandArray DebuggerParser::commands = { {
{ {
"a", "a",
"Set Accumulator to <value>", "Set Accumulator to <value>",
@ -3025,6 +3037,16 @@ std::array<DebuggerParser::Command, 100> DebuggerParser::commands = { {
std::mem_fn(&DebuggerParser::executeLoadstate) 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", "n",
"Negative Flag: set (0 or 1), or toggle (no arg)", "Negative Flag: set (0 or 1), or toggle (no arg)",

View File

@ -101,7 +101,8 @@ class DebuggerParser
std::array<Parameters, 10> parms; std::array<Parameters, 10> parms;
std::function<void (DebuggerParser*)> executor; std::function<void (DebuggerParser*)> executor;
}; };
static std::array<Command, 100> commands; using CommandArray = std::array<Command, 101>;
static CommandArray commands;
struct Trap struct Trap
{ {
@ -201,6 +202,7 @@ class DebuggerParser
void executeLoadallstates(); void executeLoadallstates();
void executeLoadconfig(); void executeLoadconfig();
void executeLoadstate(); void executeLoadstate();
void executeLogBreaks();
void executeN(); void executeN();
void executePalette(); void executePalette();
void executePc(); void executePc();

View File

@ -557,6 +557,8 @@ void PromptWidget::loadConfig()
print(instance().debugger().cartDebug().loadConfigFile() + "\n"); print(instance().debugger().cartDebug().loadConfigFile() + "\n");
print(instance().debugger().cartDebug().loadListFile() + "\n"); print(instance().debugger().cartDebug().loadListFile() + "\n");
print(instance().debugger().cartDebug().loadSymbolFile() + "\n"); print(instance().debugger().cartDebug().loadSymbolFile() + "\n");
if(instance().settings().getBool("dbg.logbreaks"))
print(DebuggerParser::inverse(" logbreaks enabled \n"));
print(PROMPT); print(PROMPT);
_promptStartPos = _promptEndPos = _currentPos; _promptStartPos = _promptEndPos = _currentPos;

View File

@ -93,6 +93,7 @@ void M6502::reset()
myGhostReadsTrap = mySettings.getBool("dbg.ghostreadstrap"); myGhostReadsTrap = mySettings.getBool("dbg.ghostreadstrap");
myReadFromWritePortBreak = devSettings ? mySettings.getBool("dev.rwportbreak") : false; myReadFromWritePortBreak = devSettings ? mySettings.getBool("dev.rwportbreak") : false;
myWriteToReadPortBreak = devSettings ? mySettings.getBool("dev.wrportbreak") : false; myWriteToReadPortBreak = devSettings ? mySettings.getBool("dev.wrportbreak") : false;
myLogBreaks = mySettings.getBool("dbg.logbreaks");
myLastBreakCycle = ULLONG_MAX; myLastBreakCycle = ULLONG_MAX;
} }
@ -243,11 +244,17 @@ inline void M6502::_execute(uInt64 cycles, DispatchResult& result)
myJustHitReadTrapFlag = myJustHitWriteTrapFlag = false; myJustHitReadTrapFlag = myJustHitWriteTrapFlag = false;
myLastBreakCycle = mySystem->cycles(); myLastBreakCycle = mySystem->cycles();
result.setDebugger(currentCycles, myHitTrapInfo.message,
if(myLogBreaks)
myDebugger->log(myHitTrapInfo.message);
else
{
result.setDebugger(currentCycles, myHitTrapInfo.message + " ",
read ? "Read trap" : "Write trap", read ? "Read trap" : "Write trap",
myHitTrapInfo.address, read); myHitTrapInfo.address, read);
return; return;
} }
}
if(myBreakPoints.isInitialized()) if(myBreakPoints.isInitialized())
{ {
@ -260,30 +267,44 @@ inline void M6502::_execute(uInt64 cycles, DispatchResult& result)
if(myBreakPoints.get(PC, bank) & BreakpointMap::ONE_SHOT) if(myBreakPoints.get(PC, bank) & BreakpointMap::ONE_SHOT)
{ {
myBreakPoints.erase(PC, bank); myBreakPoints.erase(PC, bank);
return;
} }
else else
{
if(myLogBreaks)
myDebugger->log("BP:");
else
{ {
ostringstream msg; ostringstream msg;
msg << "BP: $" << Common::Base::HEX4 << PC << ", bank #" << std::dec << int(bank); msg << "BP: $" << Common::Base::HEX4 << PC << ", bank #" << std::dec << int(bank);
result.setDebugger(currentCycles, msg.str(), "Breakpoint"); result.setDebugger(currentCycles, msg.str(), "Breakpoint");
}
return; return;
} }
} }
}
}
int cond = evalCondBreaks(); int cond = evalCondBreaks();
if(cond > -1) if(cond > -1)
{ {
ostringstream msg; ostringstream msg;
msg << "CBP[" << Common::Base::HEX2 << cond << "]: " << myCondBreakNames[cond];
myLastBreakCycle = mySystem->cycles(); myLastBreakCycle = mySystem->cycles();
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"); result.setDebugger(currentCycles, msg.str(), "Conditional breakpoint");
return; return;
} }
} }
}
int cond = evalCondSaveStates(); int cond = evalCondSaveStates();
if(cond > -1) if(cond > -1)

View File

@ -255,6 +255,8 @@ class M6502 : public Serializable
void setGhostReadsTrap(bool enable) { myGhostReadsTrap = enable; } void setGhostReadsTrap(bool enable) { myGhostReadsTrap = enable; }
void setReadFromWritePortBreak(bool enable) { myReadFromWritePortBreak = enable; } void setReadFromWritePortBreak(bool enable) { myReadFromWritePortBreak = enable; }
void setWriteToReadPortBreak(bool enable) { myWriteToReadPortBreak = enable; } void setWriteToReadPortBreak(bool enable) { myWriteToReadPortBreak = enable; }
void setLogBreaks(bool enable) { myLogBreaks = enable; }
bool getLogBreaks() { return myLogBreaks; }
#endif // DEBUGGER_SUPPORT #endif // DEBUGGER_SUPPORT
private: private:
@ -469,6 +471,7 @@ class M6502 : public Serializable
bool myReadFromWritePortBreak{false}; // trap on reads from write ports bool myReadFromWritePortBreak{false}; // trap on reads from write ports
bool myWriteToReadPortBreak{false}; // trap on writes to read ports bool myWriteToReadPortBreak{false}; // trap on writes to read ports
bool myStepStateByInstruction{false}; bool myStepStateByInstruction{false};
bool myLogBreaks{false}; // log breaks/taps and continue emulation
private: private:
// Following constructors and assignment operators not supported // Following constructors and assignment operators not supported

View File

@ -188,6 +188,7 @@ Settings::Settings()
setPermanent("dbg.fontstyle", "0"); setPermanent("dbg.fontstyle", "0");
setPermanent("dbg.uhex", "false"); setPermanent("dbg.uhex", "false");
setPermanent("dbg.ghostreadstrap", "true"); setPermanent("dbg.ghostreadstrap", "true");
setPermanent("dbg.logbreaks", "false");
setPermanent("dis.resolve", "true"); setPermanent("dis.resolve", "true");
setPermanent("dis.gfxformat", "2"); setPermanent("dis.gfxformat", "2");
setPermanent("dis.showaddr", "true"); setPermanent("dis.showaddr", "true");
@ -614,6 +615,7 @@ void Settings::usage() const
<< " normal)\n" << " normal)\n"
<< " -dbg.ghostreadstrap <1|0> Debugger traps on 'ghost' reads\n" << " -dbg.ghostreadstrap <1|0> Debugger traps on 'ghost' reads\n"
<< " -dbg.uhex <0|1> lower-/uppercase HEX display\n" << " -dbg.uhex <0|1> lower-/uppercase HEX display\n"
<< " -dbg.logbreaks <0|1> log breaks and traps and continue emulation\n"
<< " -break <address> Set a breakpoint at 'address'\n" << " -break <address> Set a breakpoint at 'address'\n"
<< " -debug Start in debugger mode\n" << " -debug Start in debugger mode\n"
<< endl << endl