diff --git a/src/debugger/CartDebug.hxx b/src/debugger/CartDebug.hxx index 3c178dd44..a513c678a 100644 --- a/src/debugger/CartDebug.hxx +++ b/src/debugger/CartDebug.hxx @@ -30,6 +30,7 @@ class CartDebugWidget; #include "Cart.hxx" #include "DebuggerSystem.hxx" #include "System.hxx" +#include "M6502.hxx" // Function type for CartDebug instance methods class CartDebug; @@ -127,6 +128,11 @@ class CartDebug : public DebuggerSystem // write port area. int readFromWritePort(); + // Return the base (= non-mirrored) address of the last CPU read + int lastReadBaseAddress() { return mySystem.m6502().lastReadBaseAddress(); } + // Return the base (= non-mirrored) address of the last CPU write + int lastWriteBaseAddress() { return mySystem.m6502().lastWriteBaseAddress(); } + // The following two methods are meant to be used together // First, a call is made to disassemble(), which updates the disassembly // list; it will figure out when an actual complete disassembly is diff --git a/src/debugger/Debugger.cxx b/src/debugger/Debugger.cxx index ef8119ee6..b4d308cb1 100644 --- a/src/debugger/Debugger.cxx +++ b/src/debugger/Debugger.cxx @@ -85,7 +85,6 @@ static const char* const builtin_functions[][3] = { { "_diff0a", "*SWCHB & $40", "Left diff. set to A (hard)" }, { "_diff1b", "!(*SWCHB & $80)", "Right diff. set to B (easy)" }, { "_diff1a", "*SWCHB & $80", "Right diff. set to A (hard)" }, - // empty string marks end of list, do not remove { 0, 0, 0 } }; @@ -104,7 +103,10 @@ static const char* const pseudo_registers[][2] = { { "_rwport", "Address at which a read from a write port occurred" }, { "_scan", "Current scanline count" }, { "_vblank", "Whether vertical blank is enabled (1 or 0)" }, - { "_vsync", "Whether vertical sync is enabled (1 or 0)" }, + { "_vsync", "Whether vertical sync is enabled (1 or 0)" }, + // CPU address access functions: + /*{ "__lastread", "last CPU read address" }, + { "__lastwrite", "last CPU write address" },*/ // empty string marks end of list, do not remove { 0, 0 } @@ -166,7 +168,7 @@ FBInitStatus Debugger::initializeVideo() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool Debugger::start(const string& message, int address) +bool Debugger::start(const string& message, int address, bool read) { if(myOSystem.eventHandler().enterDebugMode()) { @@ -175,8 +177,7 @@ bool Debugger::start(const string& message, int address) ostringstream buf; buf << message; if(address > -1) - buf << Common::Base::HEX4 << address; - + buf << cartDebug().getLabel(address, read); myDialog->message().setText(buf.str()); return true; } @@ -367,24 +368,43 @@ bool Debugger::breakPoint(uInt16 bp) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void Debugger::toggleReadTrap(uInt16 t) +void Debugger::addReadTrap(uInt16 t) { readTraps().initialize(); - readTraps().toggle(t); + readTraps().add(t); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void Debugger::toggleWriteTrap(uInt16 t) +void Debugger::addWriteTrap(uInt16 t) { writeTraps().initialize(); - writeTraps().toggle(t); + writeTraps().add(t); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void Debugger::toggleTrap(uInt16 t) +void Debugger::addTrap(uInt16 t) { - toggleReadTrap(t); - toggleWriteTrap(t); + addReadTrap(t); + addWriteTrap(t); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Debugger::removeReadTrap(uInt16 t) +{ + readTraps().initialize(); + readTraps().remove(t); +} + +void Debugger::removeWriteTrap(uInt16 t) +{ + writeTraps().initialize(); + writeTraps().remove(t); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Debugger::removeTrap(uInt16 t) +{ + removeReadTrap(t); + removeWriteTrap(t); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -398,6 +418,48 @@ bool Debugger::writeTrap(uInt16 t) { return writeTraps().isInitialized() && writeTraps().isSet(t); } + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 Debugger::getBaseAddress(uInt32 addr, bool read) +{ + if((addr & 0x1080) == 0x0000) // (addr & 0b 0001 0000 1000 0000) == 0b 0000 0000 0000 0000 + if(read) + // ADDR_TIA read (%xxx0 xxxx 0xxx ????) + return addr & 0x000f; // 0b 0000 0000 0000 1111 + else + // ADDR_TIA write (%xxx0 xxxx 0x?? ????) + return addr & 0x003f; // 0b 0000 0000 0011 1111 + + // ADDR_ZPRAM (%xxx0 xx0x 1??? ????) + if((addr & 0x1280) == 0x0080) // (addr & 0b 0001 0010 1000 0000) == 0b 0000 0000 1000 0000 + return addr & 0x00ff; // 0b 0000 0000 1111 1111 + + // ADDR_ROM + if(addr & 0x1000) + return addr & 0x1fff; // 0b 0001 1111 1111 1111 + + // ADDR_IO read/write I/O registers (%xxx0 xx1x 1xxx x0??) + if((addr & 0x1284) == 0x0280) // (addr & 0b 0001 0010 1000 0100) == 0b 0000 0010 1000 0000 + return addr & 0x0283; // 0b 0000 0010 1000 0011 + + // ADDR_IO write timers (%xxx0 xx1x 1xx1 ?1??) + if(!read && (addr & 0x1294) == 0x0294) // (addr & 0b 0001 0010 1001 0100) == 0b 0000 0010 1001 0100 + return addr & 0x029f; // 0b 0000 0010 1001 1111 + + // ADDR_IO read timers (%xxx0 xx1x 1xxx ?1x0) + if(read && (addr & 0x1285) == 0x0284) // (addr & 0b 0001 0010 1000 0101) == 0b 0000 0010 1000 0100 + return addr & 0x028c; // 0b 0000 0010 1000 1100 + + // ADDR_IO read timer/PA7 interrupt (%xxx0 xx1x 1xxx x1x1) + if(read && (addr & 0x1285) == 0x0285) // (addr & 0b 0001 0010 1000 0101) == 0b 0000 0010 1000 0101 + return addr & 0x0285; // 0b 0000 0010 1000 0101 + + // ADDR_IO write PA7 edge control (%xxx0 xx1x 1xx0 x1??) + if(!read && (addr & 0x1294) == 0x0284) // (addr & 0b 0001 0010 1001 0100) == 0b 0000 0010 1000 0100 + return addr & 0x0287; // 0b 0000 0010 1000 0111 + + return 0; +} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Debugger::nextScanline(int lines) diff --git a/src/debugger/Debugger.hxx b/src/debugger/Debugger.hxx index bdefeb719..635e953ae 100644 --- a/src/debugger/Debugger.hxx +++ b/src/debugger/Debugger.hxx @@ -88,7 +88,7 @@ class Debugger : public DialogContainer @param message Message to display when entering debugger @param address An address associated with the message */ - bool start(const string& message = "", int address = -1); + bool start(const string& message = "", int address = -1, bool read = true); bool startWithFatalError(const string& message = ""); /** @@ -145,8 +145,8 @@ class Debugger : public DialogContainer TiaOutputWidget& tiaOutput() const { return myDialog->tiaOutput(); } PackedBitArray& breakPoints() const { return mySystem.m6502().breakPoints(); } - PackedBitArray& readTraps() const { return mySystem.m6502().readTraps(); } - PackedBitArray& writeTraps() const { return mySystem.m6502().writeTraps(); } + TrapArray& readTraps() const { return mySystem.m6502().readTraps(); } + TrapArray& writeTraps() const { return mySystem.m6502().writeTraps(); } /** Run the debugger command and return the result. @@ -214,6 +214,7 @@ class Debugger : public DialogContainer { mySystem.setAccessFlags(addr, flags); } void setBreakPoint(uInt16 bp, bool set); + uInt32 getBaseAddress(uInt32 addr, bool read); bool patchROM(uInt16 addr, uInt8 value); @@ -256,9 +257,12 @@ class Debugger : public DialogContainer void toggleBreakPoint(uInt16 bp); bool breakPoint(uInt16 bp); - void toggleReadTrap(uInt16 t); - void toggleWriteTrap(uInt16 t); - void toggleTrap(uInt16 t); + void addReadTrap(uInt16 t); + void addWriteTrap(uInt16 t); + void addTrap(uInt16 t); + void removeReadTrap(uInt16 t); + void removeWriteTrap(uInt16 t); + void removeTrap(uInt16 t); bool readTrap(uInt16 t); bool writeTrap(uInt16 t); void clearAllTraps(); diff --git a/src/debugger/DebuggerParser.cxx b/src/debugger/DebuggerParser.cxx index c1a4b12ac..7a95211c6 100644 --- a/src/debugger/DebuggerParser.cxx +++ b/src/debugger/DebuggerParser.cxx @@ -543,31 +543,32 @@ string DebuggerParser::eval() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -string DebuggerParser::trapStatus(uInt32 addr, bool& enabled) +string DebuggerParser::trapStatus(const Trap& trap) { - string result; - result += Base::toString(addr); - result += ": "; - bool r = debugger.readTrap(addr); - bool w = debugger.writeTrap(addr); - enabled = r || w; - if(r && w) - result += "read|write"; - else if(r) - result += "read"; - else if(w) - result += "write"; - else - result += "none"; - - const string& l = debugger.cartDebug().getLabel(addr, !w); - if(l != "") { - result += " ("; - result += l; - result += ")"; + stringstream result; + string lblb = debugger.cartDebug().getLabel(trap.begin, !trap.write); + string lble = debugger.cartDebug().getLabel(trap.end, !trap.write); + + if(lblb != "") { + result << " ("; + result << lblb; } - return result; + if(trap.begin != trap.end) + { + if(lble != "") + { + if (lblb != "") + result << " "; + else + result << " ("; + result << lble; + } + } + if (lblb != "" || lble != "") + result << ")"; + + return result.str(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -589,9 +590,10 @@ bool DebuggerParser::saveScriptFile(string file) if(debugger.breakPoint(i)) out << "break #" << i << endl; + // TODO: new trapif for(uInt32 i = 0; i < 0x10000; ++i) { - bool r = debugger.readTrap(i); + bool r = debugger.readTrap(i); bool w = debugger.writeTrap(i); if(r && w) @@ -744,8 +746,9 @@ void DebuggerParser::executeClearconfig() // "cleartraps" void DebuggerParser::executeCleartraps() { - myTraps.clear(); debugger.clearAllTraps(); + debugger.cpuDebug().m6502().clearCondTraps(); + myTraps.clear(); commandResult << "all traps cleared"; } @@ -861,6 +864,24 @@ void DebuggerParser::executeDelfunction() commandResult << "function " << argStrings[0] << " built-in or not found"; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// "deltrap" +void DebuggerParser::executeDeltrap() +{ + int index = args[0]; + + if(debugger.cpuDebug().m6502().delCondTrap(index)) + { + for(uInt32 addr = myTraps[index]->begin; addr <= myTraps[index]->end; ++addr) + executeTrapRW(addr, myTraps[index]->read, myTraps[index]->write, false); + // @sa666666: please check this: + Vec::removeAt(myTraps, index); + commandResult << "removed trap " << Base::toString(index); + } + else + commandResult << "no such trap"; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "delwatch" void DebuggerParser::executeDelwatch() @@ -1089,7 +1110,7 @@ void DebuggerParser::executeListbreaks() commandResult << "\nbreakifs:\n"; for(uInt32 i = 0; i < conds.size(); i++) { - commandResult << i << ": " << conds[i]; + commandResult << Base::toString(i) << ": " << conds[i]; if(i != (conds.size() - 1)) commandResult << endl; } } @@ -1127,11 +1148,37 @@ void DebuggerParser::executeListfunctions() // "listtraps" void DebuggerParser::executeListtraps() { - if(myTraps.size() > 0) + StringList names = debugger.cpuDebug().m6502().getCondTrapNames(); + + if(myTraps.size() != names.size()) { - bool enabled = true; - for(const auto& trap: myTraps) - commandResult << trapStatus(trap, enabled) << " + mirrors" << endl; + commandResult << "Internal error! Different trap sizes."; + return; + } + + if (names.size() > 0) + { + for(uInt32 i = 0; i < names.size(); i++) + { + commandResult << Base::toString(i) << ": "; + + if(myTraps[i]->read && myTraps[i]->write) + commandResult << "read|write"; + else if(myTraps[i]->read) + commandResult << "read "; + else if(myTraps[i]->write) + commandResult << " write"; + else + commandResult << "none"; + + commandResult << " " << names[i]; + commandResult << " " << Base::toString(myTraps[i]->begin); + if (myTraps[i]->begin != myTraps[i]->end) + commandResult << " " << Base::toString(myTraps[i]->end); + commandResult << trapStatus(*myTraps[i]); + commandResult << " + mirrors"; + if(i != (names.size() - 1)) commandResult << endl; + } } else commandResult << "no traps set"; @@ -1482,71 +1529,153 @@ void DebuggerParser::executeTrace() // "trap" void DebuggerParser::executeTrap() { - if(argCount > 2) - { - commandResult << red("Command takes one or two arguments") << endl; - return; - } + executeTraps(true, true, "trap"); +} - uInt32 beg = args[0]; - uInt32 end = argCount == 2 ? args[1] : beg; - if(beg > 0xFFFF || end > 0xFFFF) - { - commandResult << red("One or more addresses are invalid") << endl; - return; - } - - for(uInt32 addr = beg; addr <= end; ++addr) - executeTrapRW(addr, true, true); +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// "trapif" +void DebuggerParser::executeTrapif() +{ + executeTraps(true, true, "trapif", true); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "trapread" void DebuggerParser::executeTrapread() { - if(argCount > 2) - { - commandResult << red("Command takes one or two arguments") << endl; - return; - } + executeTraps(true, false, "trapread"); +} - uInt32 beg = args[0]; - uInt32 end = argCount == 2 ? args[1] : beg; - if(beg > 0xFFFF || end > 0xFFFF) - { - commandResult << red("One or more addresses are invalid") << endl; - return; - } - - for(uInt32 addr = beg; addr <= end; ++addr) - executeTrapRW(addr, true, false); +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// "trapreadif" +void DebuggerParser::executeTrapreadif() +{ + executeTraps(true, false, "trapreadif", true); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "trapwrite" void DebuggerParser::executeTrapwrite() { - if(argCount > 2) - { - commandResult << red("Command takes one or two arguments") << endl; - return; - } - - uInt32 beg = args[0]; - uInt32 end = argCount == 2 ? args[1] : beg; - if(beg > 0xFFFF || end > 0xFFFF) - { - commandResult << red("One or more addresses are invalid") << endl; - return; - } - - for(uInt32 addr = beg; addr <= end; ++addr) - executeTrapRW(addr, false, true); + executeTraps(false, true, "trapwrite"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// wrapper function for trap/trapread/trapwrite commands -void DebuggerParser::executeTrapRW(uInt32 addr, bool read, bool write) +// "trapwriteif" +void DebuggerParser::executeTrapwriteif() +{ + executeTraps(false, true, "trapwriteif", true); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Wrapper function for trap(if)s +void DebuggerParser::executeTraps(bool read, bool write, string command, bool hasCond) +{ + if(hasCond) + { + if(argCount < 1 || argCount > 3) + { + commandResult << red("Command takes one to three arguments"); + return; + } + } + else + { + if(argCount > 2) + { + commandResult << red("Command takes one or two arguments"); + return; + } + } + + int ofs = hasCond ? 1 : 0; + uInt32 begin = args[ofs]; + uInt32 end = argCount == ofs + 2 ? args[ofs + 1] : begin; + // mirrors + uInt32 beginRead = debugger.getBaseAddress(begin, true); + uInt32 endRead = debugger.getBaseAddress(end, true); + uInt32 beginWrite = debugger.getBaseAddress(begin, false); + uInt32 endWrite = debugger.getBaseAddress(end, false); + + if(begin > 0xFFFF || end > 0xFFFF || begin > end) + { + commandResult << red("One or more addresses are invalid"); + return; + } + + // parenthesize provided and address range condition(s) (begin) + stringstream parserBuf, displayBuf; + if(hasCond) + parserBuf << "(" << argStrings[0] << ")&&("; + + // add address range condition(s) to provided condition + if(read) + { + if(beginRead != endRead) + parserBuf << "__lastread>=" << Base::toString(beginRead) << "&&__lastread<=" << Base::toString(endRead); + else + parserBuf << "__lastread==" << Base::toString(beginRead); + } + if(read && write) + parserBuf << "||"; + if(write) + { + if(beginWrite != endWrite) + parserBuf << "__lastwrite>=" << Base::toString(beginWrite) << "&&__lastwrite<=" << Base::toString(endWrite); + else + parserBuf << "__lastwrite==" << Base::toString(beginWrite); + } + // parenthesize provided condition (end) + if(hasCond) + parserBuf << ")"; + + const string parserCondition = parserBuf.str(); + + int res = YaccParser::parse(parserCondition.c_str()); + if(res == 0) + { + // duplicates will remove each other + bool add = true; + for(uInt32 i = 0; i < myTraps.size(); i++) + { + if(myTraps[i]->begin == begin && myTraps[i]->end == end && + myTraps[i]->read == read && myTraps[i]->write == write && + myTraps[i]->condition == parserCondition) + { + if(debugger.cpuDebug().m6502().delCondTrap(i)) + { + add = false; + // @sa666666: please check this: + Vec::removeAt(myTraps, i); + commandResult << "Removed trap " << Base::toString(i); + break; + } + commandResult << "Internal error! Duplicate trap removal failed!"; + return; + } + } + if(add) + { + uInt32 ret = debugger.cpuDebug().m6502().addCondTrap( + YaccParser::getResult(), hasCond ? argStrings[0] : " "); + commandResult << "Added trap " << Base::toString(ret); + + // @sa666666: please check this: + myTraps.emplace_back(new Trap{ read, write, begin, end, parserCondition }); + } + + for(uInt32 addr = begin; addr <= end; ++addr) + executeTrapRW(addr, read, write, add); + } + else + { + commandResult << red("invalid expression"); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// wrapper function for trap(if)/trapread(if)/trapwrite(if) commands +void DebuggerParser::executeTrapRW(uInt32 addr, bool read, bool write, bool add) { switch(debugger.cartDebug().addressType(addr)) { @@ -1556,10 +1685,11 @@ void DebuggerParser::executeTrapRW(uInt32 addr, bool read, bool write) { if((i & 0x1080) == 0x0000) { - if(read && (i & 0x000F) == addr) - debugger.toggleReadTrap(i); - if(write && (i & 0x003F) == addr) - debugger.toggleWriteTrap(i); + // @sa666666: This seems wrong. E.g. trapread 40 4f will never trigger + if(read && (i & 0x000F) == (addr & 0x000F)) + add ? debugger.addReadTrap(i) : debugger.removeReadTrap(i); + if(write && (i & 0x003F) == (addr & 0x003F)) + add ? debugger.addWriteTrap(i) : debugger.removeWriteTrap(i); } } break; @@ -1568,10 +1698,12 @@ void DebuggerParser::executeTrapRW(uInt32 addr, bool read, bool write) { for(uInt32 i = 0; i <= 0xFFFF; ++i) { - if((i & 0x1080) == 0x0080 && (i & 0x0200) != 0x0000 && (i & 0x02FF) == addr) + if((i & 0x1280) == 0x0280 && (i & 0x029F) == (addr & 0x029F)) { - if(read) debugger.toggleReadTrap(i); - if(write) debugger.toggleWriteTrap(i); + if(read) + add ? debugger.addReadTrap(i) : debugger.removeReadTrap(i); + if(write) + add ? debugger.addWriteTrap(i) : debugger.removeWriteTrap(i); } } break; @@ -1580,10 +1712,12 @@ void DebuggerParser::executeTrapRW(uInt32 addr, bool read, bool write) { for(uInt32 i = 0; i <= 0xFFFF; ++i) { - if((i & 0x1080) == 0x0080 && (i & 0x0200) == 0x0000 && (i & 0x00FF) == addr) + if((i & 0x1280) == 0x0080 && (i & 0x00FF) == (addr & 0x00FF)) { - if(read) debugger.toggleReadTrap(i); - if(write) debugger.toggleWriteTrap(i); + if(read) + add ? debugger.addReadTrap(i) : debugger.removeReadTrap(i); + if(write) + add ? debugger.addWriteTrap(i) : debugger.removeWriteTrap(i); } } break; @@ -1596,21 +1730,16 @@ void DebuggerParser::executeTrapRW(uInt32 addr, bool read, bool write) { if((i % 0x2000 >= 0x1000) && (i & 0x0FFF) == (addr & 0x0FFF)) { - if(read) debugger.toggleReadTrap(i); - if(write) debugger.toggleWriteTrap(i); + if(read) + add ? debugger.addReadTrap(i) : debugger.removeReadTrap(i); + if(write) + add ? debugger.addWriteTrap(i) : debugger.removeWriteTrap(i); } } } break; } } - - bool trapEnabled = false; - const string& result = trapStatus(addr, trapEnabled); - if(trapEnabled) myTraps.insert(addr); - else myTraps.erase(addr); - - commandResult << result << " + mirrors" << endl; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1891,6 +2020,16 @@ DebuggerParser::Command DebuggerParser::commands[kNumCommands] = { std::mem_fn(&DebuggerParser::executeDelfunction) }, + { + "deltrap", + "Delete trap ", + "Example: deltrap 0", + true, + false, + { kARG_WORD, kARG_END_ARGS }, + std::mem_fn(&DebuggerParser::executeDeltrap) + }, + { "delwatch", "Delete watch ", @@ -2333,6 +2472,17 @@ DebuggerParser::Command DebuggerParser::commands[kNumCommands] = { std::mem_fn(&DebuggerParser::executeTrap) }, + { + "trapif", + "On trap R/W access to address(es) xx [yy]", + "Set a conditional R/W trap on the given address(es) and all mirrors\nCondition can include multiple items.\n" + "Example: trapif _scan>100 GRP0, trapif _bank==1 f000 f100", + true, + false, + { kARG_WORD, kARG_MULTI_BYTE }, + std::mem_fn(&DebuggerParser::executeTrapif) + }, + { "trapread", "Trap read access to address(es) xx [yy]", @@ -2344,6 +2494,17 @@ DebuggerParser::Command DebuggerParser::commands[kNumCommands] = { std::mem_fn(&DebuggerParser::executeTrapread) }, + { + "trapreadif", + "On trap read access to address(es) xx [yy]", + "Set a conditional read trap on the given address(es) and all mirrors\nCondition can include multiple items.\n" + "Example: trapreadif _scan>100 GRP0, trapreadif _bank==1 f000 f100", + true, + false, + { kARG_WORD, kARG_MULTI_BYTE }, + std::mem_fn(&DebuggerParser::executeTrapreadif) + }, + { "trapwrite", "Trap write access to address(es) xx [yy]", @@ -2355,6 +2516,17 @@ DebuggerParser::Command DebuggerParser::commands[kNumCommands] = { std::mem_fn(&DebuggerParser::executeTrapwrite) }, + { + "trapwriteif", + "On trap write access to address(es) xx [yy]", + "Set a conditional write trap on the given address(es) and all mirrors\nCondition can include multiple items.\n" + "Example: trapwriteif _scan>100 GRP0, trapwriteif _bank==1 f000 f100", + true, + false, + { kARG_WORD, kARG_MULTI_BYTE }, + std::mem_fn(&DebuggerParser::executeTrapwriteif) + }, + { "type", "Show disassembly type for address xx [yy]", diff --git a/src/debugger/DebuggerParser.hxx b/src/debugger/DebuggerParser.hxx index e9e403ac0..363fe2ecc 100644 --- a/src/debugger/DebuggerParser.hxx +++ b/src/debugger/DebuggerParser.hxx @@ -68,7 +68,7 @@ class DebuggerParser bool saveScriptFile(string file); private: - enum { kNumCommands = 72 }; + enum { kNumCommands = 76 }; // Constants for argument processing enum { @@ -100,7 +100,15 @@ class DebuggerParser parameters parms[10]; std::function executor; }; - + struct Trap + { + bool read; + bool write; + uInt32 begin; + uInt32 end; + string condition; + }; + // Reference to our debugger object Debugger& debugger; @@ -117,9 +125,12 @@ class DebuggerParser StringList myWatches; + // Keep track of traps (read and/or write) - std::set myTraps; - string trapStatus(uInt32 addr, bool& enabled); + vector> myTraps; + /*std::set myTraps; + std::vector myTrapIfs;*/ + string trapStatus(const Trap& trap); // List of available command methods void executeA(); @@ -139,8 +150,9 @@ class DebuggerParser void executeData(); void executeDebugColors(); void executeDefine(); - void executeDelbreakif(); + void executeDelbreakif(); void executeDelfunction(); + void executeDeltrap(); void executeDelwatch(); void executeDisasm(); void executeDump(); @@ -184,9 +196,13 @@ class DebuggerParser void executeTia(); void executeTrace(); void executeTrap(); + void executeTrapif(); void executeTrapread(); - void executeTrapwrite(); - void executeTrapRW(uInt32 addr, bool read, bool write); // not exposed by debugger + void executeTrapreadif(); + void executeTrapwrite(); + void executeTrapwriteif(); + void executeTraps(bool read, bool write, string command, bool cond = false); + void executeTrapRW(uInt32 addr, bool read, bool write, bool add = true); // not exposed by debugger void executeType(); void executeUHex(); void executeUndef(); diff --git a/src/debugger/TrapArray.hxx b/src/debugger/TrapArray.hxx new file mode 100644 index 000000000..4bbd9ca2e --- /dev/null +++ b/src/debugger/TrapArray.hxx @@ -0,0 +1,59 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2017 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef TRAP_ARRAY_HXX +#define TRAP_ARRAY_HXX + +#include "bspf.hxx" + +class TrapArray +{ +public: + TrapArray() : myInitialized(false) {} + + bool isSet(const uInt16 address) const { return myCount[address]; } + bool isClear(const uInt16 address) const { return myCount[address] == 0; } + + void add(const uInt16 address) { myCount[address]++; } + void remove(const uInt16 address) { myCount[address]--; } + //void toggle(uInt16 address) { myCount[address] ? remove(address) : add(address); } // TODO condition + + void initialize() { + if(!myInitialized) + memset(myCount, 0, sizeof(myCount)); + myInitialized = true; + } + void clearAll() { myInitialized = false; memset(myCount, 0, sizeof(myCount)); } + + bool isInitialized() const { return myInitialized; } + +private: + // The actual counts + uInt8 myCount[0x10000]; + + // Indicates whether we should treat this array as initialized + bool myInitialized; + +private: + // Following constructors and assignment operators not supported + TrapArray(const TrapArray&) = delete; + TrapArray(TrapArray&&) = delete; + TrapArray& operator=(const TrapArray&) = delete; + TrapArray& operator=(TrapArray&&) = delete; +}; + +#endif diff --git a/src/emucore/M6502.cxx b/src/emucore/M6502.cxx index 6aa430eec..7598a60b0 100644 --- a/src/emucore/M6502.cxx +++ b/src/emucore/M6502.cxx @@ -55,6 +55,8 @@ M6502::M6502(const Settings& settings) myLastAddress(0), myLastPeekAddress(0), myLastPokeAddress(0), + myLastPeekBaseAddress(0), + myLastPokeBaseAddress(0), myLastSrcAddressS(-1), myLastSrcAddressA(-1), myLastSrcAddressX(-1), @@ -65,7 +67,7 @@ M6502::M6502(const Settings& settings) { #ifdef DEBUGGER_SUPPORT myDebugger = nullptr; - myJustHitTrapFlag = false; + myJustHitReadTrapFlag = myJustHitWriteTrapFlag = false; #endif } @@ -98,7 +100,7 @@ void M6502::reset() // Load PC from the reset vector PC = uInt16(mySystem->peek(0xfffc)) | (uInt16(mySystem->peek(0xfffd)) << 8); - myLastAddress = myLastPeekAddress = myLastPokeAddress = 0; + myLastAddress = myLastPeekAddress = myLastPokeAddress = myLastPeekBaseAddress = myLastPokeBaseAddress; myLastSrcAddressS = myLastSrcAddressA = myLastSrcAddressX = myLastSrcAddressY = -1; myDataAddressForPoke = 0; @@ -120,18 +122,23 @@ inline uInt8 M6502::peek(uInt16 address, uInt8 flags) } //////////////////////////////////////////////// mySystem->incrementCycles(SYSTEM_CYCLES_PER_CPU); + uInt8 result = mySystem->peek(address, flags); + myLastPeekAddress = address; #ifdef DEBUGGER_SUPPORT if(myReadTraps.isInitialized() && myReadTraps.isSet(address)) { - myJustHitTrapFlag = true; - myHitTrapInfo.message = "RTrap: "; - myHitTrapInfo.address = address; + myLastPeekBaseAddress = myDebugger->getBaseAddress(myLastPeekAddress, true); // mirror handling + int cond = evalCondTraps(); + if(cond > -1) + { + myJustHitReadTrapFlag = true; + myHitTrapInfo.message = "RTrap(" + myTrapCondNames[cond] + "): "; + myHitTrapInfo.address = address; + } } #endif // DEBUGGER_SUPPORT - uInt8 result = mySystem->peek(address, flags); - myLastPeekAddress = address; return result; } @@ -146,19 +153,23 @@ inline void M6502::poke(uInt16 address, uInt8 value, uInt8 flags) myLastAddress = address; } //////////////////////////////////////////////// - mySystem->incrementCycles(SYSTEM_CYCLES_PER_CPU); + mySystem->incrementCycles(SYSTEM_CYCLES_PER_CPU); + mySystem->poke(address, value, flags); + myLastPokeAddress = address; #ifdef DEBUGGER_SUPPORT if(myWriteTraps.isInitialized() && myWriteTraps.isSet(address)) { - myJustHitTrapFlag = true; - myHitTrapInfo.message = "WTrap: "; - myHitTrapInfo.address = address; + myLastPokeBaseAddress = myDebugger->getBaseAddress(myLastPokeAddress, false); // mirror handling + int cond = evalCondTraps(); + if(cond > -1) + { + myJustHitWriteTrapFlag = true; + myHitTrapInfo.message = "WTrap(" + myTrapCondNames[cond] + "): "; + myHitTrapInfo.address = address; + } } -#endif // DEBUGGER_SUPPORT - - mySystem->poke(address, value, flags); - myLastPokeAddress = address; +#endif // DEBUGGER_SUPPORT } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -189,11 +200,11 @@ bool M6502::execute(uInt32 number) for(; !myExecutionStatus && (number != 0); --number) { #ifdef DEBUGGER_SUPPORT - if(myJustHitTrapFlag) + if(myJustHitReadTrapFlag || myJustHitWriteTrapFlag) { - if(myDebugger && myDebugger->start(myHitTrapInfo.message, myHitTrapInfo.address)) - { - myJustHitTrapFlag = false; + myJustHitReadTrapFlag = myJustHitWriteTrapFlag = false; + if(myDebugger && myDebugger->start(myHitTrapInfo.message, myHitTrapInfo.address, myJustHitReadTrapFlag)) + { return true; } } @@ -416,13 +427,15 @@ uInt32 M6502::addCondBreak(Expression* e, const string& name) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void M6502::delCondBreak(uInt32 brk) +bool M6502::delCondBreak(uInt32 brk) { if(brk < myBreakConds.size()) { Vec::removeAt(myBreakConds, brk); Vec::removeAt(myBreakCondNames, brk); + return true; } + return false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -437,4 +450,37 @@ const StringList& M6502::getCondBreakNames() const { return myBreakCondNames; } + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 M6502::addCondTrap(Expression* e, const string& name) +{ + myTrapConds.emplace_back(e); + myTrapCondNames.push_back(name); + return uInt32(myTrapConds.size() - 1); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool M6502::delCondTrap(uInt32 brk) +{ + if(brk < myTrapConds.size()) + { + Vec::removeAt(myTrapConds, brk); + Vec::removeAt(myTrapCondNames, brk); + return true; + } + return false; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void M6502::clearCondTraps() +{ + myTrapConds.clear(); + myTrapCondNames.clear(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +const StringList& M6502::getCondTrapNames() const +{ + return myTrapCondNames; +} #endif // DEBUGGER_SUPPORT diff --git a/src/emucore/M6502.hxx b/src/emucore/M6502.hxx index b22a5727f..2f5ec5c8f 100644 --- a/src/emucore/M6502.hxx +++ b/src/emucore/M6502.hxx @@ -24,6 +24,7 @@ #include "Expression.hxx" #include "PackedBitArray.hxx" + #include "TrapArray.hxx" #endif class Settings; @@ -148,7 +149,21 @@ class M6502 : public Serializable return myLastPokeAddress ? (myLastPokeAddress != myLastPeekAddress ? myLastPeekAddress : 0) : myLastPeekAddress; - } + } + + /** + Return the last address that was part of a read/peek. + + @return The address of the last read + */ + uInt16 lastReadBaseAddress() const { return myLastPeekBaseAddress; } + + /** + Return the last address that was part of a write/poke. + + @return The address of the last write + */ + uInt16 lastWriteBaseAddress() const { return myLastPokeBaseAddress; } /** Return the source of the address that was used for a write/poke. @@ -207,13 +222,24 @@ class M6502 : public Serializable void attach(Debugger& debugger); PackedBitArray& breakPoints() { return myBreakPoints; } - PackedBitArray& readTraps() { return myReadTraps; } - PackedBitArray& writeTraps() { return myWriteTraps; } + //PackedBitArray& readTraps() { return myReadTraps; } + //PackedBitArray& writeTraps() { return myWriteTraps; } + //PackedBitArray& readTrapIfs() { return myReadTrapIfs; } + //PackedBitArray& writeTrapIfs() { return myWriteTrapIfs; } + TrapArray& readTraps() { return myReadTraps; } + TrapArray& writeTraps() { return myWriteTraps; } + // methods for 'breakif' handling uInt32 addCondBreak(Expression* e, const string& name); - void delCondBreak(uInt32 brk); + bool delCondBreak(uInt32 brk); void clearCondBreaks(); const StringList& getCondBreakNames() const; + + // methods for 'trapif' handling + uInt32 addCondTrap(Expression* e, const string& name); + bool delCondTrap(uInt32 brk); + void clearCondTraps(); + const StringList& getCondTrapNames() const; #endif // DEBUGGER_SUPPORT private: @@ -329,6 +355,9 @@ class M6502 : public Serializable /// Indicates the last address which was accessed specifically /// by a peek or poke command uInt16 myLastPeekAddress, myLastPokeAddress; + /// Indicates the last base (= non-mirrored) address which was + /// accessed specifically by a peek or poke command + uInt16 myLastPeekBaseAddress, myLastPokeBaseAddress; /// Indicates the last address used to access data by a peek command /// for the CPU registers (S/A/X/Y) @@ -359,14 +388,25 @@ class M6502 : public Serializable return -1; // no break hit } + Int32 evalCondTraps() + { + for(uInt32 i = 0; i < myTrapConds.size(); i++) + if(myTrapConds[i]->evaluate()) + return i; + + return -1; // no trapif hit + } + /// Pointer to the debugger for this processor or the null pointer Debugger* myDebugger; // Addresses for which the specified action should occur - PackedBitArray myBreakPoints, myReadTraps, myWriteTraps; + PackedBitArray myBreakPoints;// , myReadTraps, myWriteTraps, myReadTrapIfs, myWriteTrapIfs; + TrapArray myReadTraps, myWriteTraps; // Did we just now hit a trap? - bool myJustHitTrapFlag; + bool myJustHitReadTrapFlag; + bool myJustHitWriteTrapFlag; struct HitTrapInfo { string message; int address; @@ -375,6 +415,8 @@ class M6502 : public Serializable vector> myBreakConds; StringList myBreakCondNames; + vector> myTrapConds; + StringList myTrapCondNames; #endif // DEBUGGER_SUPPORT private: diff --git a/src/windows/Stella.vcxproj b/src/windows/Stella.vcxproj index 54e8a5635..f4cf3e333 100644 --- a/src/windows/Stella.vcxproj +++ b/src/windows/Stella.vcxproj @@ -575,6 +575,7 @@ + diff --git a/src/windows/Stella.vcxproj.filters b/src/windows/Stella.vcxproj.filters index fcedc20ed..de22defe1 100644 --- a/src/windows/Stella.vcxproj.filters +++ b/src/windows/Stella.vcxproj.filters @@ -1751,6 +1751,9 @@ Header Files\debugger + + Header Files\debugger + diff --git a/src/yacc/YaccParser.cxx b/src/yacc/YaccParser.cxx index 6a010b720..ec16e5523 100644 --- a/src/yacc/YaccParser.cxx +++ b/src/yacc/YaccParser.cxx @@ -206,6 +206,10 @@ CartMethod getCartSpecial(char* ch) return &CartDebug::getBank; else if(BSPF::equalsIgnoreCase(ch, "_rwport")) return &CartDebug::readFromWritePort; + else if(BSPF::equalsIgnoreCase(ch, "__lastread")) + return &CartDebug::lastReadBaseAddress; + else if(BSPF::equalsIgnoreCase(ch, "__lastwrite")) + return &CartDebug::lastWriteBaseAddress; else return 0; }