//============================================================================ // // 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. //============================================================================ #include "bspf.hxx" #include "Dialog.hxx" #include "Debugger.hxx" #include "CartDebug.hxx" #include "CpuDebug.hxx" #include "RiotDebug.hxx" #include "TIADebug.hxx" #include "TiaOutputWidget.hxx" #include "DebuggerParser.hxx" #include "YaccParser.hxx" #include "M6502.hxx" #include "Expression.hxx" #include "FSNode.hxx" #include "Settings.hxx" #include "PromptWidget.hxx" #include "RomWidget.hxx" #include "ProgressDialog.hxx" #include "PackedBitArray.hxx" #include "Vec.hxx" #include "Base.hxx" using Common::Base; using std::hex; using std::dec; using std::setfill; using std::setw; using std::right; #ifdef CHEATCODE_SUPPORT #include "Cheat.hxx" #include "CheatManager.hxx" #endif #include "DebuggerParser.hxx" // TODO - use C++ streams instead of nasty C-strings and pointers // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DebuggerParser::DebuggerParser(Debugger& d, Settings& s) : debugger(d), settings(s), argCount(0) { } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // main entry point: PromptWidget calls this method. string DebuggerParser::run(const string& command) { #if 0 // this was our parser test code. Left for reference. static Expression *lastExpression; // special case: parser testing if(strncmp(command.c_str(), "expr ", 5) == 0) { delete lastExpression; commandResult = "parser test: status=="; int status = YaccParser::parse(command.c_str() + 5); commandResult += debugger.valueToString(status); commandResult += ", result=="; if(status == 0) { lastExpression = YaccParser::getResult(); commandResult += debugger.valueToString(lastExpression->evaluate()); } else { // delete lastExpression; // NO! lastExpression isn't valid (not 0 either) // It's the result of casting the last token // to Expression* (because of yacc's union). // As such, we can't and don't need to delete it // (However, it means yacc leaks memory on error) commandResult += "ERROR - "; commandResult += YaccParser::errorMessage(); } return commandResult; } if(command == "expr") { if(lastExpression) commandResult = "result==" + debugger.valueToString(lastExpression->evaluate()); else commandResult = "no valid expr"; return commandResult; } #endif string verb; getArgs(command, verb); commandResult.str(""); for(int i = 0; i < kNumCommands; ++i) { if(BSPF::equalsIgnoreCase(verb, commands[i].cmdString)) { if(validateArgs(i)) { myCommand = i; commands[i].executor(this); } if(commands[i].refreshRequired) debugger.myBaseDialog->loadConfig(); return commandResult.str(); } } return red("No such command (try \"help\")"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - string DebuggerParser::exec(const FilesystemNode& file, StringList* history) { if(file.exists()) { ifstream in(file.getPath()); if(!in.is_open()) return red("script file \'" + file.getShortPath() + "\' not found"); ostringstream buf; int count = 0; string command; while( !in.eof() ) { if(!getline(in, command)) break; run(command); if (history != nullptr) history->push_back(command); count++; } buf << "\nExecuted " << count << " commands from \"" << file.getShortPath() << "\""; return buf.str(); } else return red("script file \'" + file.getShortPath() + "\' not found"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerParser::outputCommandError(const string& errorMsg, int command) { string example = commands[command].extendedDesc.substr(commands[command].extendedDesc.find("Example:")); commandResult << red(errorMsg); if(!example.empty()) commandResult << endl << example; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Completion-related stuff: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerParser::getCompletions(const char* in, StringList& completions) const { // cerr << "Attempting to complete \"" << in << "\"" << endl; for(int i = 0; i < kNumCommands; ++i) { if(BSPF::matches(commands[i].cmdString, in)) completions.push_back(commands[i].cmdString); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Evaluate expression. Expressions always evaluate to a 16-bit value if // they're valid, or -1 if they're not. // decipher_arg may be called by the GUI as needed. It is also called // internally by DebuggerParser::run() int DebuggerParser::decipher_arg(const string& str) { bool derefByte=false, derefWord=false, lobyte=false, hibyte=false, bin=false, dec=false; int result; string arg = str; Base::Format defaultBase = Base::format(); if(defaultBase == Base::F_2) { bin=true; dec=false; } else if(defaultBase == Base::F_10) { bin=false; dec=true; } else { bin=false; dec=false; } if(arg.substr(0, 1) == "*") { derefByte = true; arg.erase(0, 1); } else if(arg.substr(0, 1) == "@") { derefWord = true; arg.erase(0, 1); } if(arg.substr(0, 1) == "<") { lobyte = true; arg.erase(0, 1); } else if(arg.substr(0, 1) == ">") { hibyte = true; arg.erase(0, 1); } if(arg.substr(0, 1) == "\\") { dec = false; bin = true; arg.erase(0, 1); } else if(arg.substr(0, 1) == "#") { dec = true; bin = false; arg.erase(0, 1); } else if(arg.substr(0, 1) == "$") { dec = false; bin = false; arg.erase(0, 1); } // Special cases (registers): const CpuState& state = static_cast(debugger.cpuDebug().getState()); if(arg == "a") result = state.A; else if(arg == "x") result = state.X; else if(arg == "y") result = state.Y; else if(arg == "p") result = state.PS; else if(arg == "s") result = state.SP; else if(arg == "pc" || arg == ".") result = state.PC; else { // Not a special, must be a regular arg: check for label first const char* a = arg.c_str(); result = debugger.cartDebug().getAddress(arg); if(result < 0) { // if not label, must be a number if(bin) { // treat as binary result = 0; while(*a != '\0') { result <<= 1; switch(*a++) { case '1': result++; break; case '0': break; default: return -1; } } } else if(dec) { result = 0; while(*a != '\0') { int digit = (*a++) - '0'; if(digit < 0 || digit > 9) return -1; result = (result * 10) + digit; } } else { // must be hex. result = 0; while(*a != '\0') { int hex = -1; char d = *a++; if(d >= '0' && d <= '9') hex = d - '0'; else if(d >= 'a' && d <= 'f') hex = d - 'a' + 10; else if(d >= 'A' && d <= 'F') hex = d - 'A' + 10; if(hex < 0) return -1; result = (result << 4) + hex; } } } } if(lobyte) result &= 0xff; else if(hibyte) result = (result >> 8) & 0xff; // dereference if we're supposed to: if(derefByte) result = debugger.peek(result); if(derefWord) result = debugger.dpeek(result); return result; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - string DebuggerParser::showWatches() { ostringstream buf; for(uInt32 i = 0; i < myWatches.size(); ++i) { if(myWatches[i] != "") { // Clear the args, since we're going to pass them to eval() argStrings.clear(); args.clear(); argCount = 1; argStrings.push_back(myWatches[i]); args.push_back(decipher_arg(argStrings[0])); if(args[0] < 0) buf << "BAD WATCH " << (i+1) << ": " << argStrings[0] << endl; else buf << " watch #" << (i+1) << " (" << argStrings[0] << ") -> " << eval() << endl; } } return buf.str(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Private methods below // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool DebuggerParser::getArgs(const string& command, string& verb) { int state = kIN_COMMAND, i = 0, length = int(command.length()); string curArg = ""; verb = ""; argStrings.clear(); args.clear(); // cerr << "Parsing \"" << command << "\"" << ", length = " << command.length() << endl; // First, pick apart string into space-separated tokens. // The first token is the command verb, the rest go in an array do { char c = command[i++]; switch(state) { case kIN_COMMAND: if(c == ' ') state = kIN_SPACE; else verb += c; break; case kIN_SPACE: if(c == '{') state = kIN_BRACE; else if(c != ' ') { state = kIN_ARG; curArg += c; } break; case kIN_BRACE: if(c == '}') { state = kIN_SPACE; argStrings.push_back(curArg); // cerr << "{" << curArg << "}" << endl; curArg = ""; } else { curArg += c; } break; case kIN_ARG: if(c == ' ') { state = kIN_SPACE; argStrings.push_back(curArg); curArg = ""; } else { curArg += c; } break; } // switch(state) } while(i < length); // Take care of the last argument, if there is one if(curArg != "") argStrings.push_back(curArg); argCount = uInt32(argStrings.size()); for(uInt32 arg = 0; arg < argCount; ++arg) { if(!YaccParser::parse(argStrings[arg].c_str())) { unique_ptr expr(YaccParser::getResult()); args.push_back(expr->evaluate()); } else args.push_back(-1); } return true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool DebuggerParser::validateArgs(int cmd) { // cerr << "entering validateArgs(" << cmd << ")" << endl; bool required = commands[cmd].parmsRequired; parameters* p = commands[cmd].parms; if(argCount == 0) { if(required) { commandResult.str(); outputCommandError("missing required argument(s)", cmd); return false; // needed args. didn't get 'em. } else return true; // no args needed, no args got } // Figure out how many arguments are required by the command uInt32 count = 0, argRequiredCount = 0; while(*p != kARG_END_ARGS && *p != kARG_MULTI_BYTE) { count++; p++; } // Evil hack: some commands intentionally take multiple arguments // In this case, the required number of arguments is unbounded argRequiredCount = (*p == kARG_END_ARGS) ? count : argCount; p = commands[cmd].parms; uInt32 curCount = 0; do { if(curCount >= argCount) break; uInt32 curArgInt = args[curCount]; string& curArgStr = argStrings[curCount]; switch(*p) { case kARG_DWORD: #if 0 // TODO - do we need error checking at all here? if(curArgInt > 0xffffffff) { commandResult.str(red("invalid word argument (must be 0-$ffffffff)")); return false; } #endif break; case kARG_WORD: if(curArgInt > 0xffff) { commandResult.str(red("invalid word argument (must be 0-$ffff)")); return false; } break; case kARG_BYTE: if(curArgInt > 0xff) { commandResult.str(red("invalid byte argument (must be 0-$ff)")); return false; } break; case kARG_BOOL: if(curArgInt != 0 && curArgInt != 1) { commandResult.str(red("invalid boolean argument (must be 0 or 1)")); return false; } break; case kARG_BASE_SPCL: if(curArgInt != 2 && curArgInt != 10 && curArgInt != 16 && curArgStr != "hex" && curArgStr != "dec" && curArgStr != "bin") { commandResult.str(red("invalid base (must be #2, #10, #16, \"bin\", \"dec\", or \"hex\")")); return false; } break; case kARG_LABEL: case kARG_FILE: break; // TODO: validate these (for now any string's allowed) case kARG_MULTI_BYTE: case kARG_MULTI_WORD: break; // FIXME: validate these (for now, any number's allowed) case kARG_END_ARGS: break; } curCount++; p++; } while(*p != kARG_END_ARGS && curCount < argRequiredCount); /* cerr << "curCount = " << curCount << endl << "argRequiredCount = " << argRequiredCount << endl << "*p = " << *p << endl << endl; */ if(curCount < argRequiredCount) { commandResult.str(); outputCommandError("missing required argument(s)", cmd); return false; } else if(argCount > curCount) { commandResult.str(); outputCommandError("too many arguments", cmd); return false; } return true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - string DebuggerParser::eval() { ostringstream buf; for(uInt32 i = 0; i < argCount; ++i) { if(args[i] < 0x10000) { string rlabel = debugger.cartDebug().getLabel(args[i], true); string wlabel = debugger.cartDebug().getLabel(args[i], false); bool validR = rlabel != "" && rlabel[0] != '$', validW = wlabel != "" && wlabel[0] != '$'; if(validR && validW) { if(rlabel == wlabel) buf << rlabel << "(R/W): "; else buf << rlabel << "(R) / " << wlabel << "(W): "; } else if(validR) buf << rlabel << "(R): "; else if(validW) buf << wlabel << "(W): "; } buf << "$" << Base::toString(args[i], Base::F_16); if(args[i] < 0x10000) buf << " %" << Base::toString(args[i], Base::F_2); buf << " #" << int(args[i]); if(i != argCount - 1) buf << endl; } return buf.str(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerParser::listTraps(bool listCond) { StringList names = debugger.m6502().getCondTrapNames(); commandResult << (listCond ? "trapifs:" : "traps:") << endl; for(uInt32 i = 0; i < names.size(); i++) { bool hasCond = names[i] != ""; if(hasCond == listCond) { 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"; if(hasCond) commandResult << " " << names[i]; commandResult << " " << debugger.cartDebug().getLabel(myTraps[i]->begin, true, 4); if(myTraps[i]->begin != myTraps[i]->end) commandResult << " " << debugger.cartDebug().getLabel(myTraps[i]->end, true, 4); commandResult << trapStatus(*myTraps[i]); commandResult << " + mirrors"; if(i != (names.size() - 1)) commandResult << endl; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - string DebuggerParser::trapStatus(const Trap& trap) { 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; } if(trap.begin != trap.end) { if(lble != "") { if (lblb != "") result << " "; else result << " ("; result << lble; } } if (lblb != "" || lble != "") result << ")"; return result.str(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - string DebuggerParser::saveScriptFile(string file) { // Append 'script' extension when necessary if(file.find_last_of('.') == string::npos) file += ".script"; FilesystemNode node(debugger.myOSystem.defaultSaveDir() + file); ofstream out(node.getPath()); if(!out.is_open()) return "Unable to save script to " + node.getShortPath(); FunctionDefMap funcs = debugger.getFunctionDefMap(); for(const auto& f: funcs) if (!debugger.isBuiltinFunction(f.first)) out << "function " << f.first << " {" << f.second << "}" << endl; for(const auto& w: myWatches) out << "watch " << w << endl; for(uInt32 i = 0; i < 0x10000; ++i) if(debugger.breakPoint(i)) out << "break " << Base::toString(i) << endl; StringList conds = debugger.m6502().getCondBreakNames(); for(const auto& cond : conds) out << "breakif {" << cond << "}" << endl; conds = debugger.m6502().getCondSaveStateNames(); for(const auto& cond : conds) out << "savestateif {" << cond << "}" << endl; StringList names = debugger.m6502().getCondTrapNames(); for(uInt32 i = 0; i < myTraps.size(); ++i) { bool read = myTraps[i]->read; bool write = myTraps[i]->write; bool hasCond = names[i] != ""; if(read && write) out << "trap"; else if(read) out << "trapread"; else if(write) out << "trapwrite"; if(hasCond) out << "if {" << names[i] << "}"; out << " " << Base::toString(myTraps[i]->begin); if(myTraps[i]->begin != myTraps[i]->end) out << " " << Base::toString(myTraps[i]->end); out << endl; } return "saved " + node.getShortPath() + " OK"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // executor methods for commands[] array. All are void, no args. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "a" void DebuggerParser::executeA() { debugger.cpuDebug().setA(uInt8(args[0])); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "base" void DebuggerParser::executeBase() { if(args[0] == 2 || argStrings[0] == "bin") Base::setFormat(Base::F_2); else if(args[0] == 10 || argStrings[0] == "dec") Base::setFormat(Base::F_10); else if(args[0] == 16 || argStrings[0] == "hex") Base::setFormat(Base::F_16); commandResult << "default base set to "; switch(Base::format()) { case Base::F_2: commandResult << "#2/bin"; break; case Base::F_10: commandResult << "#10/dec"; break; case Base::F_16: commandResult << "#16/hex"; break; default: commandResult << red("UNKNOWN"); break; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "break" void DebuggerParser::executeBreak() { uInt16 bp; if(argCount == 0) bp = debugger.cpuDebug().pc(); else bp = args[0]; debugger.toggleBreakPoint(bp); debugger.rom().invalidate(); if(debugger.breakPoint(bp)) commandResult << "set"; else commandResult << "cleared"; commandResult << " breakpoint at " << Base::toString(bp); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "breakif" void DebuggerParser::executeBreakif() { int res = YaccParser::parse(argStrings[0].c_str()); if(res == 0) { string condition = argStrings[0]; for(uInt32 i = 0; i < debugger.m6502().getCondBreakNames().size(); i++) { if(condition == debugger.m6502().getCondBreakNames()[i]) { executeDelbreakif(); return; } } uInt32 ret = debugger.m6502().addCondBreak( YaccParser::getResult(), argStrings[0] ); commandResult << "added breakif " << Base::toString(ret); } else commandResult << red("invalid expression"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "c" void DebuggerParser::executeC() { if(argCount == 0) debugger.cpuDebug().toggleC(); else if(argCount == 1) debugger.cpuDebug().setC(args[0]); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "cheat" // (see Stella manual for different cheat types) void DebuggerParser::executeCheat() { #ifdef CHEATCODE_SUPPORT if(argCount == 0) { outputCommandError("missing cheat code", myCommand); return; } for(uInt32 arg = 0; arg < argCount; ++arg) { const string& cheat = argStrings[arg]; if(debugger.myOSystem.cheat().add("DBG", cheat)) commandResult << "cheat code " << cheat << " enabled" << endl; else commandResult << red("invalid cheat code ") << cheat << endl; } #else commandResult << red("Cheat support not enabled\n"); #endif } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "clearbreaks" void DebuggerParser::executeClearbreaks() { debugger.clearAllBreakPoints(); debugger.m6502().clearCondBreaks(); commandResult << "all breakpoints cleared"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "clearconfig" void DebuggerParser::executeClearconfig() { if(argCount == 1) commandResult << debugger.cartDebug().clearConfig(args[0]); else commandResult << debugger.cartDebug().clearConfig(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "clearbreaks" void DebuggerParser::executeClearsavestateifs() { debugger.m6502().clearCondSaveStates(); commandResult << "all savestate points cleared"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "cleartraps" void DebuggerParser::executeCleartraps() { debugger.clearAllTraps(); debugger.m6502().clearCondTraps(); myTraps.clear(); commandResult << "all traps cleared"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "clearwatches" void DebuggerParser::executeClearwatches() { myWatches.clear(); commandResult << "all watches cleared"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "cls" void DebuggerParser::executeCls() { debugger.prompt().clearScreen(); commandResult << ""; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "code" void DebuggerParser::executeCode() { if(argCount != 2) { outputCommandError("specify start and end of range only", myCommand); return; } else if(args[1] < args[0]) { commandResult << red("start address must be <= end address"); return; } bool result = debugger.cartDebug().addDirective( CartDebug::CODE, args[0], args[1]); commandResult << (result ? "added" : "removed") << " CODE directive on range $" << hex << args[0] << " $" << hex << args[1]; debugger.rom().invalidate(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "colortest" void DebuggerParser::executeColortest() { commandResult << "test color: " << char((args[0]>>1) | 0x80) << inverse(" "); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "d" void DebuggerParser::executeD() { if(argCount == 0) debugger.cpuDebug().toggleD(); else if(argCount == 1) debugger.cpuDebug().setD(args[0]); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "data" void DebuggerParser::executeData() { if(argCount != 2) { outputCommandError("specify start and end of range only", myCommand); return; } else if(args[1] < args[0]) { commandResult << red("start address must be <= end address"); return; } bool result = debugger.cartDebug().addDirective( CartDebug::DATA, args[0], args[1]); commandResult << (result ? "added" : "removed") << " DATA directive on range $" << hex << args[0] << " $" << hex << args[1]; debugger.rom().invalidate(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "debugcolors" void DebuggerParser::executeDebugColors() { commandResult << debugger.tiaDebug().debugColors(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "define" void DebuggerParser::executeDefine() { // TODO: check if label already defined? debugger.cartDebug().addLabel(argStrings[0], args[1]); debugger.rom().invalidate(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "delbreakif" void DebuggerParser::executeDelbreakif() { if (debugger.m6502().delCondBreak(args[0])) commandResult << "removed breakif " << Base::toString(args[0]); else commandResult << red("no such breakif"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "delfunction" void DebuggerParser::executeDelfunction() { if(debugger.delFunction(argStrings[0])) commandResult << "removed function " << argStrings[0]; else commandResult << "function " << argStrings[0] << " built-in or not found"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "delsavestateif" void DebuggerParser::executeDelsavestateif() { if(debugger.m6502().delCondSaveState(args[0])) commandResult << "removed savestateif " << Base::toString(args[0]); else commandResult << red("no such savestateif"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "deltrap" void DebuggerParser::executeDeltrap() { int index = args[0]; if(debugger.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 << red("no such trap"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "delwatch" void DebuggerParser::executeDelwatch() { int which = args[0] - 1; if(which >= 0 && which < int(myWatches.size())) { Vec::removeAt(myWatches, which); commandResult << "removed watch"; } else commandResult << red("no such watch"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "disasm" void DebuggerParser::executeDisasm() { int start, lines = 20; if(argCount == 0) { start = debugger.cpuDebug().pc(); } else if(argCount == 1) { start = args[0]; } else if(argCount == 2) { start = args[0]; lines = args[1]; } else { outputCommandError("wrong number of arguments", myCommand); return; } commandResult << debugger.cartDebug().disassemble(start, lines); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "dump" void DebuggerParser::executeDump() { auto dump = [&](int start, int end) { for(int i = start; i <= end; i += 16) { // Print label every 16 bytes commandResult << Base::toString(i) << ": "; for(int j = i; j < i+16 && j <= end; ++j) { commandResult << Base::toString(debugger.peek(j)) << " "; if(j == i+7 && j != end) commandResult << "- "; } commandResult << endl; } }; // Error checking if(argCount > 1 && args[1] < args[0]) { commandResult << red("start address must be <= end address"); return; } if(argCount == 1) dump(args[0], args[0] + 127); else if(argCount == 2) dump(args[0], args[1]); else { outputCommandError("wrong number of arguments", myCommand); return; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "exec" void DebuggerParser::executeExec() { // Append 'script' extension when necessary string file = argStrings[0]; if(file.find_last_of('.') == string::npos) file += ".script"; FilesystemNode node(debugger.myOSystem.defaultSaveDir() + file); commandResult << exec(node); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "exitrom" void DebuggerParser::executeExitRom() { debugger.quit(true); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "frame" void DebuggerParser::executeFrame() { int count = 1; if(argCount != 0) count = args[0]; debugger.nextFrame(count); commandResult << "advanced " << dec << count << " frame(s)"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "function" void DebuggerParser::executeFunction() { if(args[0] >= 0) { commandResult << red("name already in use"); return; } int res = YaccParser::parse(argStrings[1].c_str()); if(res == 0) { debugger.addFunction(argStrings[0], argStrings[1], YaccParser::getResult()); commandResult << "added function " << argStrings[0] << " -> " << argStrings[1]; } else commandResult << red("invalid expression"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "gfx" void DebuggerParser::executeGfx() { if(argCount != 2) { outputCommandError("specify start and end of range only", myCommand); return; } else if(args[1] < args[0]) { commandResult << red("start address must be <= end address"); return; } bool result = debugger.cartDebug().addDirective( CartDebug::GFX, args[0], args[1]); commandResult << (result ? "added" : "removed") << " GFX directive on range $" << hex << args[0] << " $" << hex << args[1]; debugger.rom().invalidate(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "help" void DebuggerParser::executeHelp() { if(argCount == 0) // normal help, show all commands { // Find length of longest command uInt16 clen = 0; for(int i = 0; i < kNumCommands; ++i) { uInt16 len = commands[i].cmdString.length(); if(len > clen) clen = len; } commandResult << setfill(' '); for(int i = 0; i < kNumCommands; ++i) commandResult << setw(clen) << right << commands[i].cmdString << " - " << commands[i].description << endl; commandResult << debugger.builtinHelp(); } else // get help for specific command { for(int i = 0; i < kNumCommands; ++i) { if(argStrings[0] == commands[i].cmdString) { commandResult << " " << red(commands[i].description) << endl << commands[i].extendedDesc; break; } } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "jump" void DebuggerParser::executeJump() { int line = -1; int address = args[0]; // The specific address we want may not exist (it may be part of a data section) // If so, scroll backward a little until we find it while(((line = debugger.cartDebug().addressToLine(address)) == -1) && (address >= 0)) address--; if(line >= 0 && address >= 0) { debugger.rom().scrollTo(line); commandResult << "disassembly scrolled to address $" << Base::HEX4 << address; } else commandResult << "address $" << Base::HEX4 << args[0] << " doesn't exist"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "listbreaks" void DebuggerParser::executeListbreaks() { ostringstream buf; int count = 0; for(uInt32 i = 0; i <= 0xffff; ++i) { if(debugger.breakPoints().isSet(i)) { buf << debugger.cartDebug().getLabel(i, true, 4) << " "; if(! (++count % 8) ) buf << endl; } } if(count) commandResult << "breaks:" << endl << buf.str(); StringList conds = debugger.m6502().getCondBreakNames(); if(conds.size() > 0) { if(count) commandResult << endl; commandResult << "breakifs:" << endl; for(uInt32 i = 0; i < conds.size(); i++) { commandResult << Base::toString(i) << ": " << conds[i]; if(i != (conds.size() - 1)) commandResult << endl; } } if(commandResult.str() == "") commandResult << "no breakpoints set"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "listconfig" void DebuggerParser::executeListconfig() { if(argCount == 1) commandResult << debugger.cartDebug().listConfig(args[0]); else commandResult << debugger.cartDebug().listConfig(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "listfunctions" void DebuggerParser::executeListfunctions() { const FunctionDefMap& functions = debugger.getFunctionDefMap(); if(functions.size() > 0) { for(const auto& iter: functions) commandResult << iter.first << " -> " << iter.second << endl; } else commandResult << "no user-defined functions"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "listsavestateifs" void DebuggerParser::executeListsavestateifs() { ostringstream buf; int count = 0; StringList conds = debugger.m6502().getCondSaveStateNames(); if(conds.size() > 0) { if(count) commandResult << endl; commandResult << "savestateif:" << endl; for(uInt32 i = 0; i < conds.size(); i++) { commandResult << Base::toString(i) << ": " << conds[i]; if(i != (conds.size() - 1)) commandResult << endl; } } if(commandResult.str() == "") commandResult << "no savestateifs defined"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "listtraps" void DebuggerParser::executeListtraps() { StringList names = debugger.m6502().getCondTrapNames(); if(myTraps.size() != names.size()) { commandResult << "Internal error! Different trap sizes."; return; } if (names.size() > 0) { bool trapFound = false, trapifFound = false; for(uInt32 i = 0; i < names.size(); i++) if(names[i] == "") trapFound = true; else trapifFound = true; if(trapFound) listTraps(false); if(trapifFound) listTraps(true); } else commandResult << "no traps set"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "loadconfig" void DebuggerParser::executeLoadconfig() { commandResult << debugger.cartDebug().loadConfigFile(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "loadstate" void DebuggerParser::executeLoadstate() { if(args[0] >= 0 && args[0] <= 9) debugger.loadState(args[0]); else commandResult << red("invalid slot (must be 0-9)"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "n" void DebuggerParser::executeN() { if(argCount == 0) debugger.cpuDebug().toggleN(); else if(argCount == 1) debugger.cpuDebug().setN(args[0]); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "palette" void DebuggerParser::executePalette() { commandResult << debugger.tiaDebug().palette(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "pc" void DebuggerParser::executePc() { debugger.cpuDebug().setPC(args[0]); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "pgfx" void DebuggerParser::executePGfx() { if(argCount != 2) { outputCommandError("specify start and end of range only", myCommand); return; } else if(args[1] < args[0]) { commandResult << red("start address must be <= end address"); return; } bool result = debugger.cartDebug().addDirective( CartDebug::PGFX, args[0], args[1]); commandResult << (result ? "added" : "removed") << " PGFX directive on range $" << hex << args[0] << " $" << hex << args[1]; debugger.rom().invalidate(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "print" void DebuggerParser::executePrint() { commandResult << eval(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "ram" void DebuggerParser::executeRam() { if(argCount == 0) commandResult << debugger.cartDebug().toString(); else commandResult << debugger.setRAM(args); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "reset" void DebuggerParser::executeReset() { debugger.reset(); debugger.rom().invalidate(); commandResult << "reset system"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "rewind" void DebuggerParser::executeRewind() { executeWinds(false); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "riot" void DebuggerParser::executeRiot() { commandResult << debugger.riotDebug().toString(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "rom" void DebuggerParser::executeRom() { uInt16 addr = args[0]; for(uInt32 i = 1; i < argCount; ++i) { if(!(debugger.patchROM(addr++, args[i]))) { commandResult << red("patching ROM unsupported for this cart type"); return; } } // Normally the run() method calls loadConfig() on the debugger, // which results in all child widgets being redrawn. // The RomWidget is a special case, since we don't want to re-disassemble // any more than necessary. So we only do it by calling the following // method ... debugger.rom().invalidate(); commandResult << "changed " << (args.size() - 1) << " location(s)"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "row" void DebuggerParser::executeRow() { if(argCount != 2) { outputCommandError("specify start and end of range only", myCommand); return; } else if(args[1] < args[0]) { commandResult << red("start address must be <= end address"); return; } bool result = debugger.cartDebug().addDirective( CartDebug::ROW, args[0], args[1]); commandResult << (result ? "added" : "removed") << " ROW directive on range $" << hex << args[0] << " $" << hex << args[1]; debugger.rom().invalidate(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "run" void DebuggerParser::executeRun() { debugger.saveOldState(); debugger.quit(false); commandResult << "_EXIT_DEBUGGER"; // See PromptWidget for more info } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "runto" void DebuggerParser::executeRunTo() { const CartDebug& cartdbg = debugger.cartDebug(); const CartDebug::DisassemblyList& list = cartdbg.disassembly().list; uInt32 count = 0, max_iterations = uInt32(list.size()); // Create a progress dialog box to show the progress searching through the // disassembly, since this may be a time-consuming operation ostringstream buf; buf << "RunTo searching through " << max_iterations << " disassembled instructions"; ProgressDialog progress(debugger.myBaseDialog, debugger.lfont(), buf.str()); progress.setRange(0, max_iterations, 5); bool done = false; do { debugger.step(); // Update romlist to point to current PC int pcline = cartdbg.addressToLine(debugger.cpuDebug().pc()); if(pcline >= 0) { const string& next = list[pcline].disasm; done = (BSPF::findIgnoreCase(next, argStrings[0]) != string::npos); } // Update the progress bar progress.setProgress(count); } while(!done && ++count < max_iterations); progress.close(); if(done) commandResult << "found " << argStrings[0] << " in " << dec << count << " disassembled instructions"; else commandResult << argStrings[0] << " not found in " << dec << count << " disassembled instructions"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "runtopc" void DebuggerParser::executeRunToPc() { const CartDebug& cartdbg = debugger.cartDebug(); const CartDebug::DisassemblyList& list = cartdbg.disassembly().list; uInt32 count = 0; bool done = false; do { debugger.step(); // Update romlist to point to current PC int pcline = cartdbg.addressToLine(debugger.cpuDebug().pc()); done = (pcline >= 0) && (list[pcline].address == args[0]); } while(!done && ++count < list.size()); if(done) commandResult << "set PC to " << Base::HEX4 << args[0] << " in " << dec << count << " disassembled instructions"; else commandResult << "PC " << Base::HEX4 << args[0] << " not reached or found in " << dec << count << " disassembled instructions"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "s" void DebuggerParser::executeS() { debugger.cpuDebug().setSP(uInt8(args[0])); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "save" void DebuggerParser::executeSave() { commandResult << saveScriptFile(argStrings[0]); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "saveconfig" void DebuggerParser::executeSaveconfig() { commandResult << debugger.cartDebug().saveConfigFile(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "savedis" void DebuggerParser::executeSavedisassembly() { commandResult << debugger.cartDebug().saveDisassembly(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "saverom" void DebuggerParser::executeSaverom() { commandResult << debugger.cartDebug().saveRom(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "saveses" void DebuggerParser::executeSaveses() { // Create a file named with the current date and time time_t currtime; struct tm* timeinfo; char buffer[80]; time(&currtime); timeinfo = localtime(&currtime); strftime(buffer, 80, "session_%F_%H-%M-%S.txt", timeinfo); FilesystemNode file(debugger.myOSystem.defaultSaveDir() + buffer); if(debugger.prompt().saveBuffer(file)) commandResult << "saved " + file.getShortPath() + " OK"; else commandResult << "unable to save session"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "savesnap" void DebuggerParser::executeSavesnap() { debugger.tiaOutput().saveSnapshot(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "savestate" void DebuggerParser::executeSavestate() { if(args[0] >= 0 && args[0] <= 9) debugger.saveState(args[0]); else commandResult << red("invalid slot (must be 0-9)"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "savestateif" void DebuggerParser::executeSavestateif() { int res = YaccParser::parse(argStrings[0].c_str()); if(res == 0) { uInt32 ret = debugger.m6502().addCondSaveState( YaccParser::getResult(), argStrings[0]); commandResult << "added savestateif " << Base::toString(ret); } else commandResult << red("invalid expression"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "scanline" void DebuggerParser::executeScanline() { int count = 1; if(argCount != 0) count = args[0]; debugger.nextScanline(count); commandResult << "advanced " << dec << count << " scanline(s)"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "step" void DebuggerParser::executeStep() { commandResult << "executed " << dec << debugger.step() << " cycles"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "tia" void DebuggerParser::executeTia() { commandResult << debugger.tiaDebug().toString(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "trace" void DebuggerParser::executeTrace() { commandResult << "executed " << dec << debugger.trace() << " cycles"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "trap" void DebuggerParser::executeTrap() { executeTraps(true, true, "trap"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "trapif" void DebuggerParser::executeTrapif() { executeTraps(true, true, "trapif", true); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "trapread" void DebuggerParser::executeTrapread() { executeTraps(true, false, "trapread"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "trapreadif" void DebuggerParser::executeTrapreadif() { executeTraps(true, false, "trapreadif", true); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "trapwrite" void DebuggerParser::executeTrapwrite() { executeTraps(false, true, "trapwrite"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "trapwriteif" void DebuggerParser::executeTrapwriteif() { executeTraps(false, true, "trapwriteif", true); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Wrapper function for trap(if)s void DebuggerParser::executeTraps(bool read, bool write, const string& command, bool hasCond) { uInt32 ofs = hasCond ? 1 : 0; uInt32 begin = args[ofs]; uInt32 end = argCount == 2 + ofs ? args[1 + ofs] : begin; if(argCount < 1 + ofs) { outputCommandError("missing required argument(s)", myCommand); return; } if(argCount > 2 + ofs) { outputCommandError("too many arguments", myCommand); return; } if(begin > 0xFFFF || end > 0xFFFF) { commandResult << red("invalid word argument(s) (must be 0-$ffff)"); return; } if(begin > end) { commandResult << red("start address must be <= end address"); return; } // base addresses of 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); stringstream conditionBuf; // parenthesize provided and address range condition(s) (begin) if(hasCond) conditionBuf << "(" << argStrings[0] << ")&&("; // add address range condition(s) to provided condition if(read) { if(beginRead != endRead) conditionBuf << "__lastread>=" << Base::toString(beginRead) << "&&__lastread<=" << Base::toString(endRead); else conditionBuf << "__lastread==" << Base::toString(beginRead); } if(read && write) conditionBuf << "||"; if(write) { if(beginWrite != endWrite) conditionBuf << "__lastwrite>=" << Base::toString(beginWrite) << "&&__lastwrite<=" << Base::toString(endWrite); else conditionBuf << "__lastwrite==" << Base::toString(beginWrite); } // parenthesize provided condition (end) if(hasCond) conditionBuf << ")"; const string condition = conditionBuf.str(); int res = YaccParser::parse(condition.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 == condition) { if(debugger.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.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, condition }); } 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)) { case CartDebug::ADDR_TIA: { for(uInt32 i = 0; i <= 0xFFFF; ++i) { if((i & 0x1080) == 0x0000) { // @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; } case CartDebug::ADDR_IO: { for(uInt32 i = 0; i <= 0xFFFF; ++i) { if((i & 0x1280) == 0x0280 && (i & 0x029F) == (addr & 0x029F)) { if(read) add ? debugger.addReadTrap(i) : debugger.removeReadTrap(i); if(write) add ? debugger.addWriteTrap(i) : debugger.removeWriteTrap(i); } } break; } case CartDebug::ADDR_ZPRAM: { for(uInt32 i = 0; i <= 0xFFFF; ++i) { if((i & 0x1280) == 0x0080 && (i & 0x00FF) == (addr & 0x00FF)) { if(read) add ? debugger.addReadTrap(i) : debugger.removeReadTrap(i); if(write) add ? debugger.addWriteTrap(i) : debugger.removeWriteTrap(i); } } break; } case CartDebug::ADDR_ROM: { if(addr >= 0x1000 && addr <= 0xFFFF) { for(uInt32 i = 0x1000; i <= 0xFFFF; ++i) { if((i % 0x2000 >= 0x1000) && (i & 0x0FFF) == (addr & 0x0FFF)) { if(read) add ? debugger.addReadTrap(i) : debugger.removeReadTrap(i); if(write) add ? debugger.addWriteTrap(i) : debugger.removeWriteTrap(i); } } } break; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "type" void DebuggerParser::executeType() { uInt32 beg = args[0]; uInt32 end = argCount >= 2 ? args[1] : beg; if(beg > end) std::swap(beg, end); for(uInt32 i = beg; i <= end; ++i) { commandResult << Base::HEX4 << i << ": "; debugger.cartDebug().addressTypeAsString(commandResult, i); commandResult << endl; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "uhex" void DebuggerParser::executeUHex() { bool enable = !Base::hexUppercase(); Base::setHexUppercase(enable); settings.setValue("dbg.uhex", enable); debugger.rom().invalidate(); commandResult << "uppercase HEX " << (enable ? "enabled" : "disabled"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "undef" void DebuggerParser::executeUndef() { if(debugger.cartDebug().removeLabel(argStrings[0])) { debugger.rom().invalidate(); commandResult << argStrings[0] + " now undefined"; } else commandResult << red("no such label"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "unwind" void DebuggerParser::executeUnwind() { executeWinds(true); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "v" void DebuggerParser::executeV() { if(argCount == 0) debugger.cpuDebug().toggleV(); else if(argCount == 1) debugger.cpuDebug().setV(args[0]); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "watch" void DebuggerParser::executeWatch() { myWatches.push_back(argStrings[0]); commandResult << "added watch \"" << argStrings[0] << "\""; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // wrapper function for rewind/unwind commands // TODO: return and output (formatted) cycles void DebuggerParser::executeWinds(bool unwind) { uInt16 states; string type = unwind ? "unwind" : "rewind"; string message; if(argCount == 0) states = 1; else states = args[0]; uInt16 winds = unwind ? debugger.unwindStates(states, message) : debugger.rewindStates(states, message); if(winds > 0) { debugger.rom().invalidate(); commandResult << type << " by " << winds << " state" << (winds > 1 ? "s" : ""); commandResult << " (~" << message << ")"; } else commandResult << "no states left to " << type; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "x" void DebuggerParser::executeX() { debugger.cpuDebug().setX(uInt8(args[0])); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "y" void DebuggerParser::executeY() { debugger.cpuDebug().setY(uInt8(args[0])); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "z" void DebuggerParser::executeZ() { if(argCount == 0) debugger.cpuDebug().toggleZ(); else if(argCount == 1) debugger.cpuDebug().setZ(args[0]); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // List of all commands available to the parser DebuggerParser::Command DebuggerParser::commands[kNumCommands] = { { "a", "Set Accumulator to ", "Valid value is 0 - ff\nExample: a ff, a #10", true, true, { kARG_BYTE, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeA) }, { "base", "Set default base to ", "Base is hex, dec, or bin\nExample: base hex", true, true, { kARG_BASE_SPCL, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeBase) }, { "break", "Set/clear breakpoint at
", "Command is a toggle, default is current PC\nValid address is 0 - ffff\n" "Example: break, break f000", false, true, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeBreak) }, { "breakif", "Set breakpoint on ", "Condition can include multiple items, see documentation\nExample: breakif _scan>100", true, false, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeBreakif) }, { "c", "Carry Flag: set (0 or 1), or toggle (no arg)", "Example: c, c 0, c 1", false, true, { kARG_BOOL, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeC) }, { "cheat", "Use a cheat code (see manual for cheat types)", "Example: cheat 0040, cheat abff00", false, false, { kARG_LABEL, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeCheat) }, { "clearbreaks", "Clear all breakpoints", "Example: clearbreaks (no parameters)", false, true, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeClearbreaks) }, { "clearsavestateifs", "Clear all savestate points", "Example: clearsavestateifss (no parameters)", false, true, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeClearsavestateifs) }, { "clearconfig", "Clear Distella config directives [bank xx]", "Example: clearconfig 0, clearconfig 1", false, false, { kARG_WORD, kARG_MULTI_BYTE }, std::mem_fn(&DebuggerParser::executeClearconfig) }, { "cleartraps", "Clear all traps", "All traps cleared, including any mirrored ones\nExample: cleartraps (no parameters)", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeCleartraps) }, { "clearwatches", "Clear all watches", "Example: clearwatches (no parameters)", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeClearwatches) }, { "cls", "Clear prompt area of text", "Completely clears screen, but keeps history of commands", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeCls) }, { "code", "Mark 'CODE' range in disassembly", "Start and end of range required\nExample: code f000 f010", true, false, { kARG_WORD, kARG_MULTI_BYTE }, std::mem_fn(&DebuggerParser::executeCode) }, { "colortest", "Show value xx as TIA color", "Shows a color swatch for the given value\nExample: colortest 1f", true, false, { kARG_BYTE, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeColortest) }, { "d", "Carry Flag: set (0 or 1), or toggle (no arg)", "Example: d, d 0, d 1", false, true, { kARG_BOOL, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeD) }, { "data", "Mark 'DATA' range in disassembly", "Start and end of range required\nExample: data f000 f010", true, false, { kARG_WORD, kARG_MULTI_BYTE }, std::mem_fn(&DebuggerParser::executeData) }, { "debugcolors", "Show Fixed Debug Colors information", "Example: debugcolors (no parameters)", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeDebugColors) }, { "define", "Define label xx for address yy", "Example: define LABEL1 f100", true, true, { kARG_LABEL, kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeDefine) }, { "delbreakif", "Delete conditional breakif ", "Example: delbreakif 0", true, false, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeDelbreakif) }, { "delfunction", "Delete function with label xx", "Example: delfunction FUNC1", true, false, { kARG_LABEL, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeDelfunction) }, { "delsavestateif", "Delete conditional savestate point ", "Example: delsavestateif 0", true, false, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeDelsavestateif) }, { "deltrap", "Delete trap ", "Example: deltrap 0", true, false, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeDeltrap) }, { "delwatch", "Delete watch ", "Example: delwatch 0", true, false, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeDelwatch) }, { "disasm", "Disassemble address xx [yy lines] (default=PC)", "Disassembles from starting address (default=PC) for lines\n" "Example: disasm, disasm f000 100", false, false, { kARG_WORD, kARG_MULTI_BYTE }, std::mem_fn(&DebuggerParser::executeDisasm) }, { "dump", "Dump data at address [to yy]", "Example:\n" " dump f000 - dumps 128 bytes @ f000\n" " dump f000 f0ff - dumps all bytes from f000 to f0ff", true, false, { kARG_WORD, kARG_MULTI_BYTE }, std::mem_fn(&DebuggerParser::executeDump) }, { "exec", "Execute script file ", "Example: exec script.dat, exec auto.txt", true, true, { kARG_FILE, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeExec) }, { "exitrom", "Exit emulator, return to ROM launcher", "Self-explanatory", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeExitRom) }, { "frame", "Advance emulation by frames (default=1)", "Example: frame, frame 100", false, true, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeFrame) }, { "function", "Define function name xx for expression yy", "Example: function FUNC1 { ... }", true, false, { kARG_LABEL, kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeFunction) }, { "gfx", "Mark 'GFX' range in disassembly", "Start and end of range required\nExample: gfx f000 f010", true, false, { kARG_WORD, kARG_MULTI_BYTE }, std::mem_fn(&DebuggerParser::executeGfx) }, { "help", "help ", "Show all commands, or give function for help on that command\n" "Example: help, help code", false, false, { kARG_LABEL, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeHelp) }, { "jump", "Scroll disassembly to address xx", "Moves disassembly listing to address \nExample: jump f400", true, false, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeJump) }, { "listbreaks", "List breakpoints", "Example: listbreaks (no parameters)", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeListbreaks) }, { "listconfig", "List Distella config directives [bank xx]", "Example: listconfig 0, listconfig 1", false, false, { kARG_WORD, kARG_MULTI_BYTE }, std::mem_fn(&DebuggerParser::executeListconfig) }, { "listfunctions", "List user-defined functions", "Example: listfunctions (no parameters)", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeListfunctions) }, { "listsavestateifs", "List savestate points", "Example: listsavestateifs (no parameters)", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeListsavestateifs) }, { "listtraps", "List traps", "Lists all traps (read and/or write)\nExample: listtraps (no parameters)", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeListtraps) }, { "loadconfig", "Load Distella config file", "Example: loadconfig file.cfg", false, true, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeLoadconfig) }, { "loadstate", "Load emulator state xx (0-9)", "Example: loadstate 0, loadstate 9", true, true, { kARG_BYTE, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeLoadstate) }, { "n", "Negative Flag: set (0 or 1), or toggle (no arg)", "Example: n, n 0, n 1", false, true, { kARG_BOOL, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeN) }, { "palette", "Show current TIA palette", "Example: palette (no parameters)", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executePalette) }, { "pc", "Set Program Counter to address xx", "Example: pc f000", true, true, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executePc) }, { "pgfx", "Mark 'PGFX' range in disassembly", "Start and end of range required\nExample: pgfx f000 f010", true, false, { kARG_WORD, kARG_MULTI_BYTE }, std::mem_fn(&DebuggerParser::executePGfx) }, { "print", "Evaluate/print expression xx in hex/dec/binary", "Almost anything can be printed (constants, expressions, registers)\n" "Example: print pc, print f000", true, false, { kARG_DWORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executePrint) }, { "ram", "Show ZP RAM, or set address xx to yy1 [yy2 ...]", "Example: ram, ram 80 00 ...", false, true, { kARG_WORD, kARG_MULTI_BYTE }, std::mem_fn(&DebuggerParser::executeRam) }, { "reset", "Reset system to power-on state", "System is completely reset, just as if it was just powered on", false, true, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeReset) }, { "rewind", "Rewind state to last step/trace/scanline/frame...", "Example: rewind, rewind 5", false, true, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeRewind) }, { "riot", "Show RIOT timer/input status", "Display text-based output of the contents of the RIOT tab", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeRiot) }, { "rom", "Set ROM address xx to yy1 [yy2 ...]", "What happens here depends on the current bankswitching scheme\n" "Example: rom f000 00 01 ff ...", true, true, { kARG_WORD, kARG_MULTI_BYTE }, std::mem_fn(&DebuggerParser::executeRom) }, { "row", "Mark 'ROW' range in disassembly", "Start and end of range required\nExample: row f000 f010", true, false, { kARG_WORD, kARG_MULTI_BYTE }, std::mem_fn(&DebuggerParser::executeRow) }, { "run", "Exit debugger, return to emulator", "Self-explanatory", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeRun) }, { "runto", "Run until string xx in disassembly", "Advance until the given string is detected in the disassembly\n" "Example: runto lda", true, true, { kARG_LABEL, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeRunTo) }, { "runtopc", "Run until PC is set to value xx", "Example: runtopc f200", true, true, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeRunToPc) }, { "s", "Set Stack Pointer to value xx", "Accepts 8-bit value, Example: s f0", true, true, { kARG_BYTE, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeS) }, { "save", "Save breaks, watches, traps to file xx", "Example: save commands.txt", true, false, { kARG_FILE, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeSave) }, { "saveconfig", "Save Distella config file (with default name)", "Example: saveconfig", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeSaveconfig) }, { "savedis", "Save Distella disassembly (with default name)", "Example: savedis\n" "NOTE: saves to default save location", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeSavedisassembly) }, { "saverom", "Save (possibly patched) ROM (with default name)", "Example: saverom\n" "NOTE: saves to default save location", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeSaverom) }, { "saveses", "Save console session", "Example: saveses\n" "NOTE: saves to default save location", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeSaveses) }, { "savesnap", "Save current TIA image to PNG file", "Save snapshot to current snapshot save directory\n" "Example: savesnap (no parameters)", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeSavesnap) }, { "savestate", "Save emulator state xx (valid args 0-9)", "Example: savestate 0, savestate 9", true, false, { kARG_BYTE, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeSavestate) }, { "savestateif", "Create savestate on ", "Condition can include multiple items, see documentation\nExample: savestateif pc==f000", true, false, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeSavestateif) }, { "scanline", "Advance emulation by scanlines (default=1)", "Example: scanline, scanline 100", false, true, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeScanline) }, { "step", "Single step CPU [with count xx]", "Example: step, step 100", false, true, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeStep) }, { "tia", "Show TIA state", "Display text-based output of the contents of the TIA tab", false, false, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeTia) }, { "trace", "Single step CPU over subroutines [with count xx]", "Example: trace, trace 100", false, true, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeTrace) }, { "trap", "Trap read/write access to address(es) xx [yy]", "Set a R/W trap on the given address(es) and all mirrors\n" "Example: trap f000, trap f000 f100", true, false, { kARG_WORD, kARG_MULTI_BYTE }, 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]", "Set a read trap on the given address(es) and all mirrors\n" "Example: trapread f000, trapread f000 f100", true, false, { kARG_WORD, kARG_MULTI_BYTE }, 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]", "Set a write trap on the given address(es) and all mirrors\n" "Example: trapwrite f000, trapwrite f000 f100", true, false, { kARG_WORD, kARG_MULTI_BYTE }, 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]", "Example: type f000, type f000 f010", true, false, { kARG_WORD, kARG_MULTI_BYTE }, std::mem_fn(&DebuggerParser::executeType) }, { "uhex", "Toggle upper/lowercase HEX display", "Note: not all hex output can be changed\n" "Example: uhex (no parameters)", false, true, { kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeUHex) }, { "undef", "Undefine label xx (if defined)", "Example: undef LABEL1", true, true, { kARG_LABEL, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeUndef) }, { "unwind", "Unwind state to next step/trace/scanline/frame...", "Example: unwind, unwind 5", false, true, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeUnwind) }, { "v", "Overflow Flag: set (0 or 1), or toggle (no arg)", "Example: v, v 0, v 1", false, true, { kARG_BOOL, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeV) }, { "watch", "Print contents of address xx before every prompt", "Example: watch ram_80", true, false, { kARG_WORD, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeWatch) }, { "x", "Set X Register to value xx", "Valid value is 0 - ff\nExample: x ff, x #10", true, true, { kARG_BYTE, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeX) }, { "y", "Set Y Register to value xx", "Valid value is 0 - ff\nExample: y ff, y #10", true, true, { kARG_BYTE, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeY) }, { "z", "Zero Flag: set (0 or 1), or toggle (no arg)", "Example: z, z 0, z 1", false, true, { kARG_BOOL, kARG_END_ARGS }, std::mem_fn(&DebuggerParser::executeZ) } };