/* DingusPPC - The Experimental PowerPC Macintosh emulator Copyright (C) 2018-20 divingkatae and maximum (theweirdo) spatium (Contact divingkatae#1017 or powermax#2286 on Discord for more info) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include "../cpu/ppc/ppcdisasm.h" #include "../cpu/ppc/ppcemu.h" #include "../cpu/ppc/ppcmmu.h" using namespace std; static uint32_t str2addr(string& addr_str) { try { return stoul(addr_str, NULL, 0); } catch (invalid_argument& exc) { throw invalid_argument(string("Cannot convert ") + addr_str); } } static uint32_t str2num(string& num_str) { try { return stol(num_str, NULL, 0); } catch (invalid_argument& exc) { throw invalid_argument(string("Cannot convert ") + num_str); } } static void show_help() { cout << "Debugger commands:" << endl; cout << " step -- execute single instruction" << endl; cout << " si -- shortcut for step" << endl; cout << " next -- same as step but treats subroutine calls" << endl; cout << " as single instructions." << endl; cout << " ni -- shortcut for next" << endl; cout << " until X -- execute until address X is reached" << endl; cout << " regs -- dump content of the GRPs" << endl; cout << " set R=X -- assign value X to register R" << endl; cout << " if R=loglevel, set the internal" << endl; cout << " log level to X whose range is -2...9" << endl; cout << " dump NT,X -- dump N memory cells of size T at address X" << endl; cout << " T can be b(byte), w(word), d(double)," << endl; cout << " q(quad) or c(character)." << endl; #ifdef PROFILER cout << " profiler -- show stats related to the processor" << endl; #endif cout << " disas N,X -- disassemble N instructions starting at address X" << endl; cout << " X can be any number or a known register name" << endl; cout << " disas with no arguments defaults to disas 1,pc" << endl; cout << " quit -- quit the debugger" << endl << endl; cout << "Pressing ENTER will repeat last command." << endl; } static void disasm(uint32_t count, uint32_t address) { PPCDisasmContext ctx; ctx.instr_addr = address; ctx.simplified = true; for (int i = 0; i < count; i++) { ctx.instr_code = mem_read_dbg(ctx.instr_addr, 4); cout << uppercase << hex << ctx.instr_addr; cout << " " << disassemble_single(&ctx) << endl; } } static void disasm_68k(uint32_t count, uint32_t address) { csh cs_handle; uint8_t code[8]; size_t code_size; uint64_t dis_addr; if (cs_open(CS_ARCH_M68K, CS_MODE_M68K_040, &cs_handle) != CS_ERR_OK) { cout << "Capstone initialization error" << endl; return; } cs_insn* insn = cs_malloc(cs_handle); for (; count > 0; count--) { for (int i = 0; i < sizeof(code); i++) { code[i] = mem_read_dbg(address + i, 1); } const uint8_t *code_ptr = code; code_size = sizeof(code); dis_addr = address; if (cs_disasm_iter(cs_handle, &code_ptr, &code_size, &dis_addr, insn)) { cout << uppercase << hex << insn->address; cout << " " << insn->mnemonic << " " << insn->op_str << endl; address = dis_addr; } else { cout << "DS.W " << hex << ((code[0] << 8) | code[1]) << endl; address += 2; } } cs_free(insn, 1); cs_close(&cs_handle); } static void dump_mem(string& params) { int cell_size, chars_per_line; bool is_char; uint32_t count, addr; uint64_t val; string num_type_str, addr_str; size_t separator_pos; separator_pos = params.find_first_of(","); if (separator_pos == std::string::npos) { cout << "dump: not enough arguments specified." << endl; return; } num_type_str = params.substr(0, params.find_first_of(",")); addr_str = params.substr(params.find_first_of(",") + 1); is_char = false; switch (num_type_str.back()) { case 'b': case 'B': cell_size = 1; break; case 'w': case 'W': cell_size = 2; break; case 'd': case 'D': cell_size = 4; break; case 'q': case 'Q': cell_size = 8; break; case 'c': case 'C': cell_size = 1; is_char = true; break; default: cout << "Invalid data type " << num_type_str << endl; return; } try { num_type_str = num_type_str.substr(0, num_type_str.length() - 1); count = str2addr(num_type_str); } catch (invalid_argument& exc) { cout << exc.what() << endl; return; } try { addr = str2addr(addr_str); } catch (invalid_argument& exc) { try { /* number conversion failed, trying reg name */ addr = get_reg(addr_str); } catch (invalid_argument& exc) { cout << exc.what() << endl; return; } } cout << "Dumping memory at address " << hex << addr << ":" << endl; chars_per_line = 0; try { for (int i = 0; i < count; addr += cell_size, i++) { if (chars_per_line + cell_size * 2 > 80) { cout << endl; chars_per_line = 0; } val = mem_read_dbg(addr, cell_size); if (is_char) { cout << (char)val; chars_per_line += cell_size; } else { cout << setw(cell_size * 2) << setfill('0') << uppercase << hex << val << " "; chars_per_line += cell_size * 2 + 2; } } } catch (invalid_argument& exc) { cout << exc.what() << endl; return; } cout << endl << endl; } void print_68k_regs() { int i; string reg; for (i = 0; i < 8; i++) { reg = "R" + to_string(i + 8); cout << "D" << dec << i << " : " << uppercase << hex << get_reg(reg) << endl; } for (i = 0; i < 7; i++) { reg = "R" + to_string(i + 16); cout << "A" << dec << i << " : " << uppercase << hex << get_reg(reg) << endl; } reg = "R1"; cout << "A7 : " << uppercase << hex << get_reg(reg) << endl; reg = "R24"; cout << "PC: " << uppercase << hex << get_reg(reg) - 2 << endl; reg = "R25"; cout << "SR: " << uppercase << hex << ((get_reg(reg) & 0xFF) << 8) << endl; reg = "R26"; cout << "CCR: " << uppercase << hex << get_reg(reg) << endl; } void enter_debugger() { string inp, cmd, addr_str, expr_str, reg_expr, last_cmd, reg_value_str, inst_string, inst_num_str; uint32_t addr, inst_grab; std::stringstream ss; int log_level, context; size_t separator_pos; context = 1; /* start with the PowerPC context */ cout << "Welcome to the DingusPPC command line debugger." << endl; cout << "Please enter a command or 'help'." << endl << endl; while (1) { cout << "ppcdbg> "; /* reset string stream */ ss.str(""); ss.clear(); cmd = ""; getline(cin, inp, '\n'); ss.str(inp); ss >> cmd; if (cmd.empty() && !last_cmd.empty()) { cmd = last_cmd; cout << cmd << endl; } if (cmd == "help") { show_help(); } else if (cmd == "quit") { break; } #ifdef PROFILER else if (cmd == "profiler") { cout << "Number of Supervisor Instructions Executed:" << supervisor_inst_num << endl; cout << "Exception Handler Ran:" << exceptions_performed << endl; cout << "Number of MMU Translations:" << mmu_translations_num << endl; } #endif else if (cmd == "regs") { if (context == 2) { print_68k_regs(); } else { print_gprs(); } } else if (cmd == "set") { ss >> expr_str; separator_pos = expr_str.find_first_of("="); if (separator_pos == std::string::npos) { cout << "set: not enough arguments specified." << endl; continue; } try { reg_expr = expr_str.substr(0, expr_str.find_first_of("=")); addr_str = expr_str.substr(expr_str.find_first_of("=") + 1); if (reg_expr == "loglevel") { log_level = str2num(addr_str); if (log_level < -2 || log_level > 9) { cout << "Log level must be in the range -2...9!" << endl; continue; } loguru::g_stderr_verbosity = log_level; } else { addr = str2addr(addr_str); set_reg(reg_expr, addr); } } catch (invalid_argument& exc) { cout << exc.what() << endl; } } else if (cmd == "step" || cmd == "si") { ppc_exec_single(); } else if (cmd == "next" || cmd == "ni") { addr_str = "PC"; addr = get_reg(addr_str) + 4; ppc_exec_until(addr); } else if (cmd == "until") { ss >> addr_str; try { addr = str2addr(addr_str); ppc_exec_until(addr); } catch (invalid_argument& exc) { cout << exc.what() << endl; } } else if (cmd == "disas") { expr_str = ""; ss >> expr_str; if (expr_str.length() > 0) { separator_pos = expr_str.find_first_of(","); if (separator_pos == std::string::npos) { cout << "disas: not enough arguments specified." << endl; continue; } inst_num_str = expr_str.substr(0, expr_str.find_first_of(",")); inst_grab = stol(inst_num_str, NULL, 0); addr_str = expr_str.substr(expr_str.find_first_of(",") + 1); try { addr = str2addr(addr_str); } catch (invalid_argument& exc) { try { /* number conversion failed, trying reg name */ if (context == 2 && (addr_str == "pc" || addr_str == "PC")) { addr_str = "R24"; addr = get_reg(addr_str) - 2; } else { addr = get_reg(addr_str); } } catch (invalid_argument& exc) { cout << exc.what() << endl; continue; } } try { if (context == 2) { disasm_68k(inst_grab, addr); } else { disasm(inst_grab, addr); } } catch (invalid_argument& exc) { cout << exc.what() << endl; } } else { /* disas without arguments defaults to disas 1,pc */ if (context == 2) { addr_str = "R24"; addr = get_reg(addr_str); disasm_68k(1, addr - 2); } else { addr_str = "PC"; addr = get_reg(addr_str); disasm(1, addr); } } } else if (cmd == "dump") { expr_str = ""; ss >> expr_str; dump_mem(expr_str); } else if (cmd == "context") { expr_str = ""; ss >> expr_str; if (expr_str == "ppc" || expr_str == "PPC") { context = 1; } else if (expr_str == "68k" || expr_str == "68K") { context = 2; } else { cout << "Unknown debugging context: " << expr_str << endl; } } else { cout << "Unknown command: " << cmd << endl; continue; } last_cmd = cmd; } }