mirror of https://github.com/stella-emu/stella.git
added option to log breaks and traps instead of interrupting emulation (resolves #741)
This commit is contained in:
parent
54af434260
commit
fbbb86f964
11
Changes.txt
11
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)
|
||||
|
||||
|
|
|
@ -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 ¹. 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 & 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>¹ 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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)",
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue