dingusppc/debugger/debugger.cpp
Mihai Parparita 57e6e90002 Add support for the CUDA_RESTART_SYSTEM command
There are cases where when it's necessary (e.g. given uninitialized NVRAM,
the Beige G3 with the 10.2 install CD inserted will update the boot
device and restart to boot from it).

Restart support was done by wrapping the ppc_exec function in a loop and
checking for a restart power off reason. We also need to disconnect all
event listeners, since they will be recreated when the machine is
re-initialized.
2024-03-07 23:32:23 -08:00

821 lines
26 KiB
C++

/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-24 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 <https://www.gnu.org/licenses/>.
*/
#include <cpu/ppc/ppcdisasm.h>
#include <cpu/ppc/ppcemu.h>
#include <cpu/ppc/ppcmmu.h>
#include <devices/common/hwinterrupt.h>
#include <devices/common/ofnvram.h>
#include "memaccess.h"
#include <utils/profiler.h>
#include <array>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <loguru.hpp>
#include <map>
#include <memory>
#include <sstream>
#include <stdio.h>
#include <string>
#ifdef DEBUG_CPU_INT
#include <machines/machinebase.h>
#include <devices/common/viacuda.h>
#endif
#ifdef ENABLE_68K_DEBUGGER // optionally defined in CMakeLists.txt
#include <capstone/capstone.h>
#endif
using namespace std;
static uint32_t str2addr(string& addr_str) {
try {
return (uint32_t)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 (uint32_t)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 [N] -- execute single instruction" << endl;
cout << " N is an optional step count" << endl;
cout << " si [N] -- 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 << " go -- exit debugger and continue emulator execution" << endl;
cout << " regs -- dump content of the GRPs" << endl;
cout << " mregs -- dump content of the MMU registers" << 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;
cout << " profile C N -- run subcommand C on profile N" << endl;
cout << " supported subcommands:" << endl;
cout << " 'show' - show profile report" << endl;
cout << " 'reset' - reset profile variables" << 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 << " da N,X -- shortcut for disas" << endl;
#ifdef ENABLE_68K_DEBUGGER
cout << " context X -- switch to the debugging context X." << endl;
cout << " X can be either 'ppc' (default) or '68k'" << endl;
cout << " Use 68k for debugging emulated 68k code only." << endl;
#endif
cout << " printenv -- print current NVRAM settings." << endl;
cout << " setenv V N -- set NVRAM variable V to value N." << endl;
cout << " quit -- quit the debugger" << endl << endl;
cout << "Pressing ENTER will repeat last command." << endl;
}
#ifdef ENABLE_68K_DEBUGGER
static void disasm_68k(uint32_t count, uint32_t address) {
csh cs_handle;
uint8_t code[10];
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 (; power_on && count > 0; count--) {
/* prefetch opcode bytes (a 68k instruction can occupy 2...10 bytes) */
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;
// catch and handle F-Traps (Nanokernel calls) ourselves because
// Capstone will likely return no meaningful assembly for them
if ((code[0] & 0xF0) == 0xF0) {
goto print_bin;
}
if (cs_disasm_iter(cs_handle, &code_ptr, &code_size, &dis_addr, insn)) {
cout << uppercase << hex << insn->address << " ";
cout << setfill(' ');
cout << setw(10) << left << insn->mnemonic << insn->op_str << endl;
address = dis_addr;
} else {
print_bin:
cout << uppercase << hex << address << " ";
cout << setfill(' ');
cout << setw(10) << left << "dc.w" << "$" << hex <<
((code[0] << 8) | code[1]) << endl;
address += 2;
}
}
cs_free(insn, 1);
cs_close(&cs_handle);
}
/* emulator opcode table size --> 512 KB */
#define EMU_68K_TABLE_SIZE 0x80000
/** Execute one emulated 68k instruction. */
void exec_single_68k()
{
uint32_t emu_table_virt, cur_68k_pc, cur_instr_tab_entry, ppc_pc;
/* PPC r24 contains 68k PC advanced by two bytes
as part of instruction prefetching */
cur_68k_pc = get_reg(string("R24")) - 2;
/* PPC r29 contains base address of the emulator opcode table */
emu_table_virt = get_reg(string("R29")) & 0xFFF80000;
/* calculate address of the current opcode table entry as follows:
get_word(68k_PC) * entry_size + table_base */
cur_instr_tab_entry = mmu_read_vmem<uint16_t>(cur_68k_pc) * 8 + emu_table_virt;
/* grab the PPC PC too */
ppc_pc = get_reg(string("PC"));
//printf("cur_instr_tab_entry = %X\n", cur_instr_tab_entry);
/* because the first two PPC instructions for each emulated 68k opcode
are resided in the emulator opcode table, we need to execute them
one by one until the execution goes outside the opcode table. */
while (power_on && ppc_pc >= cur_instr_tab_entry && ppc_pc < cur_instr_tab_entry + 8) {
ppc_exec_single();
ppc_pc = get_reg(string("PC"));
}
/* Getting here means we're outside the emualtor opcode table.
Execute PPC code until we hit the opcode table again. */
ppc_exec_dbg(emu_table_virt, EMU_68K_TABLE_SIZE - 1);
}
/** Execute emulated 68k code until target_addr is reached. */
void exec_until_68k(uint32_t target_addr)
{
uint32_t emu_table_virt, ppc_pc;
emu_table_virt = get_reg(string("R29")) & 0xFFF80000;
while (power_on && target_addr != (get_reg(string("R24")) - 2)) {
ppc_pc = static_cast<uint32_t>(get_reg(string("PC")));
if (ppc_pc >= emu_table_virt && ppc_pc < (emu_table_virt + EMU_68K_TABLE_SIZE - 1)) {
ppc_exec_single();
} else {
ppc_exec_dbg(emu_table_virt, EMU_68K_TABLE_SIZE - 1);
}
}
}
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;
}
cout << "A7 : " << uppercase << hex << get_reg(string("R1")) << endl;
cout << "PC: " << uppercase << hex << get_reg(string("R24")) - 2 << endl;
cout << "SR: " << uppercase << hex << ((get_reg("R25") & 0xFF) << 8) << endl;
cout << "CCR: " << uppercase << hex << get_reg(string("R26")) << endl;
}
#endif // ENABLE_68K_DEBUGGER
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') << right;
cout << uppercase << hex << val << " ";
chars_per_line += cell_size * 2 + 2;
}
}
} catch (invalid_argument& exc) {
cout << exc.what() << endl;
return;
}
cout << endl << endl;
}
static void disasm(uint32_t count, uint32_t address) {
PPCDisasmContext ctx;
ctx.instr_addr = address;
ctx.simplified = true;
for (int i = 0; power_on && i < count; i++) {
ctx.instr_code = READ_DWORD_BE_A(mmu_translate_imem(ctx.instr_addr));
cout << uppercase << hex << ctx.instr_addr;
cout << " " << disassemble_single(&ctx) << endl;
}
}
static void print_gprs() {
string reg_name;
int i;
for (i = 0; i < 32; i++) {
reg_name = "R" + to_string(i);
cout << right << std::setw(3) << setfill(' ') << reg_name << " : " <<
setw(8) << setfill('0') << right << uppercase << hex << get_reg(reg_name) << setfill(' ');
if (i & 1) {
cout << endl;
} else {
cout << "\t\t";
}
}
array<string,6> sprs = {"PC", "LR", "CR", "CTR", "XER", "MSR"};
for (auto &spr : sprs) {
cout << right << std::setw(3) << setfill(' ') << spr << " : " <<
setw(8) << setfill('0') << uppercase << hex << get_reg(spr) << setfill(' ');
if (i & 1) {
cout << endl;
} else {
cout << "\t\t";
}
i++;
}
}
extern bool is_601;
static void print_mmu_regs()
{
printf("MSR : 0x%08X\n", ppc_state.msr);
printf("\nBAT registers:\n");
for (int i = 0; i < 4; i++) {
printf("IBAT%dU : 0x%08X, IBAT%dL : 0x%08X\n",
i, ppc_state.spr[528+i*2],
i, ppc_state.spr[529+i*2]);
}
if (!is_601) {
for (int i = 0; i < 4; i++) {
printf("DBAT%dU : 0x%08X, DBAT%dL : 0x%08X\n",
i, ppc_state.spr[536+i*2],
i, ppc_state.spr[537+i*2]);
}
}
printf("\n");
printf("SDR1 : 0x%08X\n", ppc_state.spr[SPR::SDR1]);
printf("\nSegment registers:\n");
for (int i = 0; i < 16; i++) {
printf("SR%-2d : 0x%08X\n", i, ppc_state.sr[i]);
}
}
#ifndef _WIN32
#include <signal.h>
#include <termios.h>
#include <unistd.h>
#include <sys/ioctl.h>
static struct sigaction old_act_sigint, new_act_sigint;
static struct sigaction old_act_sigterm, new_act_sigterm;
static struct termios orig_termios;
static void mysig_handler(int signum)
{
// restore original terminal state
tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
// restore original signal handler for SIGINT
signal(SIGINT, old_act_sigint.sa_handler);
signal(SIGTERM, old_act_sigterm.sa_handler);
LOG_F(INFO, "Old terminal state restored, SIG#=%d", signum);
// re-post signal
raise(signum);
}
#endif
void enter_debugger() {
string inp, cmd, addr_str, expr_str, reg_expr, last_cmd, reg_value_str,
inst_string, inst_num_str, profile_name, sub_cmd;
uint32_t addr, inst_grab;
std::stringstream ss;
int log_level, context;
size_t separator_pos;
bool did_message = false;
unique_ptr<OfConfigUtils> ofnvram = unique_ptr<OfConfigUtils>(new OfConfigUtils);
context = 1; /* start with the PowerPC context */
#ifndef _WIN32
struct winsize win_size_previous;
ioctl(0, TIOCGWINSZ, &win_size_previous);
#endif
while (1) {
if (power_off_reason == po_shut_down) {
power_off_reason = po_shutting_down;
break;
}
if (power_off_reason == po_restart) {
power_off_reason = po_restarting;
break;
}
power_on = true;
if (power_off_reason == po_starting_up) {
power_off_reason = po_none;
cmd = "go";
}
else if (power_off_reason == po_disassemble_on) {
inp = "si 1000000000";
ss.str("");
ss.clear();
ss.str(inp);
ss >> cmd;
}
else if (power_off_reason == po_disassemble_off) {
power_off_reason = po_none;
cmd = "go";
}
else
{
if (power_off_reason == po_enter_debugger) {
power_off_reason = po_entered_debugger;
}
if (!did_message) {
cout << "Welcome to the DingusPPC command line debugger." << endl;
cout << "Please enter a command or 'help'." << endl << endl;
did_message = true;
}
printf("%08X: dingusdbg> ", ppc_state.pc);
while (power_on) {
/* reset string stream */
ss.str("");
ss.clear();
cmd = "";
std::cin.clear();
getline(cin, inp, '\n');
ss.str(inp);
ss >> cmd;
#ifndef _WIN32
struct winsize win_size_current;
ioctl(0, TIOCGWINSZ, &win_size_current);
if (win_size_current.ws_col != win_size_previous.ws_col || win_size_current.ws_row != win_size_previous.ws_row) {
win_size_previous = win_size_current;
if (cmd.empty()) {
continue;
}
}
#endif
break;
}
}
if (power_off_reason == po_signal_interrupt) {
power_off_reason = po_enter_debugger;
// ignore command if interrupt happens because the input line is probably incomplete.
last_cmd = "";
continue;
}
if (cmd.empty() && !last_cmd.empty()) {
cmd = last_cmd;
cout << cmd << endl;
}
if (cmd == "help") {
cmd = "";
show_help();
} else if (cmd == "quit") {
cmd = "";
break;
} else if (cmd == "profile") {
cmd = "";
ss >> sub_cmd;
ss >> profile_name;
if (sub_cmd == "show") {
gProfilerObj->print_profile(profile_name);
} else if (sub_cmd == "reset") {
gProfilerObj->reset_profile(profile_name);
} else {
cout << "Unknown/empty subcommand " << sub_cmd << endl;
}
}
else if (cmd == "regs") {
cmd = "";
if (context == 2) {
#ifdef ENABLE_68K_DEBUGGER
print_68k_regs();
#endif
} else {
print_gprs();
}
} else if (cmd == "mregs") {
cmd = "";
print_mmu_regs();
} 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") {
int count;
expr_str = "";
ss >> expr_str;
if (expr_str.length() > 0) {
try {
count = str2num(expr_str);
} catch (invalid_argument& exc) {
cout << exc.what() << endl;
count = 1;
}
} else {
count = 1;
}
if (context == 2) {
#ifdef ENABLE_68K_DEBUGGER
for (; --count >= 0;) {
exec_single_68k();
}
#endif
} else {
for (; --count >= 0;) {
ppc_exec_single();
}
}
} else if (cmd == "next" || cmd == "ni") {
addr_str = "PC";
addr = (uint32_t)get_reg(addr_str) + 4;
ppc_exec_until(addr);
} else if (cmd == "until") {
ss >> addr_str;
try {
addr = str2addr(addr_str);
if (context == 2) {
#ifdef ENABLE_68K_DEBUGGER
exec_until_68k(addr);
#endif
} else {
ppc_exec_until(addr);
}
} catch (invalid_argument& exc) {
cout << exc.what() << endl;
}
} else if (cmd == "go") {
cmd = "";
power_on = true;
ppc_exec();
} else if (cmd == "disas" || cmd == "da") {
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(","));
try {
inst_grab = str2num(inst_num_str);
} catch (invalid_argument& exc) {
cout << exc.what() << endl;
continue;
}
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) {
#ifdef ENABLE_68K_DEBUGGER
disasm_68k(inst_grab, addr);
#endif
} else {
disasm(inst_grab, addr);
}
} catch (invalid_argument& exc) {
cout << exc.what() << endl;
}
} else {
/* disas without arguments defaults to disas 1,pc */
try {
if (context == 2) {
#ifdef ENABLE_68K_DEBUGGER
addr_str = "R24";
addr = get_reg(addr_str);
disasm_68k(1, addr - 2);
#endif
} else {
addr_str = "PC";
addr = (uint32_t)get_reg(addr_str);
disasm(1, addr);
}
} catch (invalid_argument& exc) {
cout << exc.what() << endl;
}
}
} else if (cmd == "dump") {
expr_str = "";
ss >> expr_str;
dump_mem(expr_str);
#ifdef ENABLE_68K_DEBUGGER
} else if (cmd == "context") {
cmd = "";
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;
}
#endif
} else if (cmd == "printenv") {
cmd = "";
if (ofnvram->init())
continue;
ofnvram->printenv();
} else if (cmd == "setenv") {
cmd = "";
string var_name, value;
ss >> var_name;
std::istream::sentry se(ss); // skip white space
getline(ss, value); // get everything up to eol
if (ofnvram->init())
continue;
ofnvram->setenv(var_name, value);
#ifndef _WIN32
} else if (cmd == "nvedit") {
cmd = "";
cout << "===== press CNTRL-C to save =====" << endl;
// save original terminal state
tcgetattr(STDIN_FILENO, &orig_termios);
struct termios new_termios = orig_termios;
new_termios.c_cflag &= ~(CSIZE | PARENB);
new_termios.c_cflag |= CS8;
new_termios.c_lflag &= ~(ISIG | NOFLSH | ICANON | ECHOCTL);
new_termios.c_lflag |= NOFLSH | ECHONL;
// new_termios.c_iflag &= ~(ICRNL | IGNCR);
// new_termios.c_iflag |= INLCR;
// new_termios.c_oflag &= ~(ONOCR | ONLCR);
// new_termios.c_oflag |= OPOST | OCRNL | ONLRET;
tcsetattr(STDIN_FILENO, TCSANOW, &new_termios);
// save original signal handler for SIGINT
// then redirect SIGINT to new handler
memset(&new_act_sigint, 0, sizeof(new_act_sigint));
new_act_sigint.sa_handler = mysig_handler;
sigaction(SIGINT, &new_act_sigint, &old_act_sigint);
// save original signal handler for SIGTERM
// then redirect SIGTERM to new handler
memset(&new_act_sigterm, 0, sizeof(new_act_sigterm));
new_act_sigterm.sa_handler = mysig_handler;
sigaction(SIGTERM, &new_act_sigterm, &old_act_sigterm);
getline(cin, inp, '\x03');
// restore original terminal state
tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
// restore original signal handler for SIGINT
signal(SIGINT, old_act_sigint.sa_handler);
// restore original signal handler for SIGTERM
signal(SIGTERM, old_act_sigterm.sa_handler);
if (ofnvram->init())
continue;
ofnvram->setenv("nvramrc", inp);
#endif
#ifdef DEBUG_CPU_INT
} else if (cmd == "amicint") {
cmd = "";
string value;
int irq_id;
ss >> value;
try {
irq_id = str2num(value);
} catch (invalid_argument& exc) {
cout << exc.what() << endl;
continue;
}
InterruptCtrl* int_ctrl = dynamic_cast<InterruptCtrl*>(
gMachineObj->get_comp_by_type(HWCompType::INT_CTRL));
int_ctrl->ack_int(irq_id, 1);
} else if (cmd == "viaint") {
cmd = "";
string value;
int irq_bit;
ss >> value;
try {
irq_bit = str2num(value);
} catch (invalid_argument& exc) {
cout << exc.what() << endl;
continue;
}
ViaCuda* via_obj = dynamic_cast<ViaCuda*>(gMachineObj->get_comp_by_name("ViaCuda"));
ppc_state.pc -= 4;
via_obj->assert_int(irq_bit);
#endif
} else {
if (!cmd.empty()) {
cout << "Unknown command: " << cmd << endl;
cmd = "";
}
}
last_cmd = cmd;
}
}