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
===========================================================================
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)

View File

@ -609,7 +609,7 @@ command that takes arguments.</p>
<h4><a name="Breakpoints">Breakpoints</a></h4>
<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
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>
@ -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,
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>
<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
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

View File

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

View File

@ -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);

View File

@ -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");

View File

@ -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)
{

View File

@ -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<PseudoRegister, 16> ourPseudoRegisters;
static constexpr Int8 ANY_BANK = -1;
bool myFirstLog{true};
private:
// rewind/unwind n states

View File

@ -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::Command, 100> DebuggerParser::commands = { {
DebuggerParser::CommandArray DebuggerParser::commands = { {
{
"a",
"Set Accumulator to <value>",
@ -3025,6 +3037,16 @@ std::array<DebuggerParser::Command, 100> 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)",

View File

@ -101,7 +101,8 @@ class DebuggerParser
std::array<Parameters, 10> parms;
std::function<void (DebuggerParser*)> executor;
};
static std::array<Command, 100> commands;
using CommandArray = std::array<Command, 101>;
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();

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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 <address> Set a breakpoint at 'address'\n"
<< " -debug Start in debugger mode\n"
<< endl