diff --git a/src/backend/Core.hpp b/src/backend/Core.hpp index f7a48c75..88777b90 100644 --- a/src/backend/Core.hpp +++ b/src/backend/Core.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace n64 { struct Core { @@ -26,8 +27,16 @@ struct Core { void Run(float volumeL, float volumeR); void TogglePause() { pause = !pause; } [[nodiscard]] VI &GetVI() const { return cpu->GetMem().mmio.vi; } + inline void ToggleBreakpoint(s64 addr) { + if(breakpoints.contains(addr)){ + breakpoints.erase(addr); + return; + } + + breakpoints.insert(addr); + } - u32 breakpoint = 0; + std::set breakpoints{}; bool pause = true; u32 cycles = 0; diff --git a/src/backend/core/Disassembler.cpp b/src/backend/core/Disassembler.cpp index 2e284eed..b1fd9b47 100644 --- a/src/backend/core/Disassembler.cpp +++ b/src/backend/core/Disassembler.cpp @@ -67,12 +67,22 @@ Disassembler::DisassemblyResult Disassembler::DisassembleDetailed(const u32 addr } }; - auto formatComment = [&](const cs_mips_op &operand) { - return ""; + auto formatComment = [&](const cs_mips_op &operand) -> std::string { + switch (operand.type) { + case MIPS_OP_IMM: + return std::format("#{:X}", operand.is_unsigned ? operand.uimm : operand.imm); + case MIPS_OP_MEM: + return std::format("{}(0x{:X})", CapstoneToRegValue(operand.mem.base), operand.mem.disp); + case MIPS_OP_REG: + return std::format("{}", CapstoneToRegValue(operand.reg)); + default: + return "! Unknown !"; + } }; for (u8 i = 0; i < details->mips.op_count && i < 3; i++) { result.ops[i] = formatOperand(details->mips.operands[i]); + result.comment += formatComment(details->mips.operands[i]) + " "; result.full += result.ops[i].str + "\t"; } @@ -82,3 +92,107 @@ Disassembler::DisassemblyResult Disassembler::DisassembleDetailed(const u32 addr return result; } + +std::string Disassembler::CapstoneToRegValue(mips_reg reg) const { + n64::Registers& regs = n64::Core::GetInstance().cpu->GetRegs(); + u64 val = 0; + switch(reg) { + case MIPS_REG_ZERO: case MIPS_REG_ZERO_64: case MIPS_REG_ZERO_NM: + val = 0; + break; + case MIPS_REG_AT: case MIPS_REG_AT_64: case MIPS_REG_AT_NM: + val = regs.Read(1); + break; + case MIPS_REG_V0: case MIPS_REG_V0_64: + val = regs.Read(2); + break; + case MIPS_REG_V1: case MIPS_REG_V1_64: + val = regs.Read(3); + break; + case MIPS_REG_A0: case MIPS_REG_A0_64: + val = regs.Read(4); + break; + case MIPS_REG_A1: case MIPS_REG_A1_64: + val = regs.Read(5); + break; + case MIPS_REG_A2: case MIPS_REG_A2_64: + val = regs.Read(6); + break; + case MIPS_REG_A3: case MIPS_REG_A3_64: + val = regs.Read(7); + break; + case MIPS_REG_T0: case MIPS_REG_T0_64: + val = regs.Read(8); + break; + case MIPS_REG_T1: case MIPS_REG_T1_64: + val = regs.Read(9); + break; + case MIPS_REG_T2: case MIPS_REG_T2_64: + val = regs.Read(10); + break; + case MIPS_REG_T3: case MIPS_REG_T3_64: + val = regs.Read(11); + break; + case MIPS_REG_T4: case MIPS_REG_T4_64: + val = regs.Read(12); + break; + case MIPS_REG_T5: case MIPS_REG_T5_64: + val = regs.Read(13); + break; + case MIPS_REG_T6: case MIPS_REG_T6_64: + val = regs.Read(14); + break; + case MIPS_REG_T7: case MIPS_REG_T7_64: + val = regs.Read(15); + break; + case MIPS_REG_S0: case MIPS_REG_S0_64: + val = regs.Read(16); + break; + case MIPS_REG_S1: case MIPS_REG_S1_64: + val = regs.Read(17); + break; + case MIPS_REG_S2: case MIPS_REG_S2_64: + val = regs.Read(18); + break; + case MIPS_REG_S3: case MIPS_REG_S3_64: + val = regs.Read(19); + break; + case MIPS_REG_S4: case MIPS_REG_S4_64: + val = regs.Read(20); + break; + case MIPS_REG_S5: case MIPS_REG_S5_64: + val = regs.Read(21); + break; + case MIPS_REG_S6: case MIPS_REG_S6_64: + val = regs.Read(22); + break; + case MIPS_REG_S7: case MIPS_REG_S7_64: + val = regs.Read(23); + break; + case MIPS_REG_T8: case MIPS_REG_T8_64: + val = regs.Read(24); + break; + case MIPS_REG_T9: case MIPS_REG_T9_64: + val = regs.Read(25); + break; + case MIPS_REG_K0: case MIPS_REG_K0_64: + val = regs.Read(26); + break; + case MIPS_REG_K1: case MIPS_REG_K1_64: + val = regs.Read(27); + break; + case MIPS_REG_GP: case MIPS_REG_GP_64: + val = regs.Read(28); + break; + case MIPS_REG_SP: case MIPS_REG_SP_64: + val = regs.Read(29); + break; + case MIPS_REG_RA: case MIPS_REG_RA_64: + val = regs.Read(31); + break; + default: + return "! Unknown !"; + } + + return std::format("{} (= 0x{:016X})", cs_reg_name(handle, reg), val); +} \ No newline at end of file diff --git a/src/backend/core/Disassembler.hpp b/src/backend/core/Disassembler.hpp index 74e4acad..5a1d9c9a 100644 --- a/src/backend/core/Disassembler.hpp +++ b/src/backend/core/Disassembler.hpp @@ -25,10 +25,12 @@ struct Disassembler { return ret; } + [[nodiscard]] DisassemblyResult Disassemble(const u32 address) const; [[nodiscard]] DisassemblyResult DisassembleDetailed(u32 address, u32 instruction) const; [[nodiscard]] DisassemblyResult DisassembleSimple(u32 address, u32 instruction) const; private: + std::string CapstoneToRegValue(mips_reg reg) const; explicit Disassembler(const bool rsp) : rsp(rsp) { if (cs_open(CS_ARCH_MIPS, static_cast((rsp ? CS_MODE_32 : CS_MODE_64) | CS_MODE_BIG_ENDIAN), &handle) != CS_ERR_OK) { diff --git a/src/frontend/Debugger.cpp b/src/frontend/Debugger.cpp index 3f055482..d992f98d 100644 --- a/src/frontend/Debugger.cpp +++ b/src/frontend/Debugger.cpp @@ -2,79 +2,124 @@ #include #include +void BreakpointFunc(s64 addr, s64 startAddr, const Disassembler::DisassemblyResult&) { + n64::Core& core = n64::Core::GetInstance(); + bool isBroken = core.breakpoints.contains(addr + 4); + if(ImGui::Checkbox(std::format("##toggleBreakpoint{}", (addr - startAddr) / 4).c_str(), &isBroken)) { + core.ToggleBreakpoint(addr + 4); + } +} + +void AddressFunc(s64, s64, const Disassembler::DisassemblyResult& disasm) { + if(disasm.success) { + ImGui::TextColored(ImColor(0xffeaefb6), "%s", std::format("{:016X}:", disasm.address).c_str()); + return; + } + + ImGui::TextColored(ImColor(0xffeaefb6), "????????????????"); +} + +void InstructionFunc(s64, s64, const Disassembler::DisassemblyResult& disasm) { + if(!disasm.success) { + ImGui::TextColored(ImColor(0xffcbf1ae), "Disassembly unsuccessful..."); + return; + } + + ImGui::TextColored(ImColor(0xffcbf1ae), "%s", std::format("{} ", disasm.mnemonic).c_str()); + ImGui::SameLine(0, 0); + for(int i = 0; i < 3; i++) { + if(disasm.ops[i].str.empty()) + continue; + + if(i >= 2) { + ImGui::TextColored(ImColor(disasm.ops[i].color), "%s", disasm.ops[i].str.c_str()); + ImGui::SameLine(0, 0); + continue; + } + + std::string op_str = disasm.ops[i].str; + if(!disasm.ops[i+1].str.empty()) + op_str += ", "; + + ImGui::TextColored(ImColor(disasm.ops[i].color), "%s", op_str.c_str()); + ImGui::SameLine(0, 0); + } +} + +void CommentFunc(s64, s64, const Disassembler::DisassemblyResult& disasm) { + if(disasm.success) { + ImGui::TextColored(ImColor(0xff71efe5), "%s", std::format("{}", disasm.comment).c_str()); + return; + } + + ImGui::TextColored(ImColor(0xff71efe5), ""); +} + bool Debugger::render() { n64::Core& core = n64::Core::GetInstance(); - if(enabled && ImGui::Begin("Debugger", &enabled)) { - static u64 startAddr = 0xFFFF'FFFF'8000'0000; - static constexpr int addrStep = 4; - static bool followPC = false; - ImGui::BeginDisabled(followPC); - ImGui::InputScalar("Address", ImGuiDataType_U64, &startAddr, &addrStep, nullptr, "%016lX", ImGuiInputTextFlags_CharsHexadecimal); - ImGui::EndDisabled(); - ImGui::SameLine(); - ImGui::Checkbox("Follow program counter:", &followPC); - if(followPC) - startAddr = core.cpu->GetRegs().pc - 128; // TODO: arbitrary??? - if(ImGui::BeginTable("Disassembly", 3, ImGuiTableFlags_RowBg)) { - ImGui::TableSetupColumn("Address"); - ImGui::TableSetupColumn("Instruction"); - ImGui::TableSetupColumn("Comment"); - ImGui::TableHeadersRow(); + if(!enabled) + return false; - for(u64 addr = startAddr; addr < startAddr + MAX_LINES_OF_DISASM * sizeof(u32); addr += sizeof(u32)) { - auto disasm = Disassembler::GetInstance().Disassemble(addr); - auto isPc = addr == core.cpu->GetRegs().pc; - if(isPc) { - ImGui::PushStyleColor(ImGuiCol_TableRowBg, 0x809a9ade); - ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, 0x807777bf); - } - ImGui::TableNextRow(); - if(disasm.success) { - ImGui::TableSetColumnIndex(0); - ImGui::TextColored(ImColor(0xffeaefb6), "%s", std::format("{:016X}:", disasm.address).c_str()); - ImGui::TableSetColumnIndex(1); - ImGui::TextColored(ImColor(0xffcbf1ae), "%s", std::format("{} ", disasm.mnemonic).c_str()); - ImGui::SameLine(0, 0); - for(int i = 0; i < 3; i++) { - if(i < 2) { - if(!disasm.ops[i].str.empty()) { - std::string op_str = disasm.ops[i].str; - if(!disasm.ops[i+1].str.empty()) op_str += ", "; - ImGui::TextColored(ImColor(disasm.ops[i].color), "%s", op_str.c_str()); - ImGui::SameLine(0, 0); - } - } else { - if(!disasm.ops[i].str.empty()) { - ImGui::TextColored(ImColor(disasm.ops[i].color), "%s", disasm.ops[i].str.c_str()); - ImGui::SameLine(0, 0); - } - } - } - ImGui::TableSetColumnIndex(2); - ImGui::TextColored(ImColor(0xff71efe5), "%s", std::format("{}", disasm.comment).c_str()); - } else { - ImGui::TableSetColumnIndex(0); - ImGui::TextColored(ImColor(0xffeaefb6), "????????????????"); - ImGui::TableSetColumnIndex(1); - ImGui::TextColored(ImColor(0xffcbf1ae), "Disassembly unsuccessful..."); - ImGui::TableSetColumnIndex(2); - ImGui::TextColored(ImColor(0xff71efe5), ""); - } - if(isPc) { - ImGui::PopStyleColor(); - ImGui::PopStyleColor(); - } - } + static s64 startAddr = 0xFFFF'FFFF'8000'0000; + int step = 4, stepFast = 256; + static bool followPC = true; + + ImGui::Begin("Debugger", &enabled); - if(ImGui::TableGetHoveredColumn() == 2) { - // do the thing with the little fucking hover popup that shows the memory view - } + ImGui::BeginDisabled(followPC); + ImGui::InputScalar("Address", ImGuiDataType_S64, (void*)&startAddr, (void*)&step, (void*)&stepFast, "%016lX", ImGuiInputTextFlags_CharsHexadecimal); + ImGui::EndDisabled(); - ImGui::EndTable(); - } - ImGui::End(); + ImGui::Text("Follow program counter:"); + ImGui::SameLine(0,0); + + ImGui::Checkbox("##followPC", &followPC); + ImGui::SameLine(0,0); + + ImGui::Text("Add a breakpoint"); + ImGui::SameLine(0,0); + + if(followPC) + startAddr = core.cpu->GetRegs().pc - 256; // TODO: arbitrary??? + + if(ImGui::Button(core.breakpoints.contains(startAddr) ? "-" : "+")) { + core.ToggleBreakpoint(startAddr); } + ImGui::BeginTable("Disassembly", columns.size(), ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingFixedFit); + for(int i = 0; i < columns.size(); i++) + ImGui::TableSetupColumn(columns[i].name); + + ImGui::TableHeadersRow(); + + for(u64 addr = startAddr; addr < startAddr + MAX_LINES_OF_DISASM * sizeof(u32); addr += sizeof(u32)) { + auto disasm = Disassembler::GetInstance().Disassemble(addr); + auto shouldColorRed = addr == core.cpu->GetRegs().nextPC || core.breakpoints.contains(addr); + if(shouldColorRed) { + ImGui::PushStyleColor(ImGuiCol_TableRowBg, 0x809a9ade); + ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, 0x807777bf); + } + + ImGui::TableNextRow(); + for(int i = 0; i < columns.size(); i++) { + ImGui::TableSetColumnIndex(i); + columns[i].func(addr, startAddr, disasm); + } + + if(shouldColorRed) { + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + } + } + + if(ImGui::TableGetHoveredColumn() == 2) { + // TODO: do the thing with the little fucking hover popup that shows the memory view + } + + ImGui::EndTable(); + + ImGui::End(); + return true; } diff --git a/src/frontend/Debugger.hpp b/src/frontend/Debugger.hpp index 0f486a98..74d6ca5a 100644 --- a/src/frontend/Debugger.hpp +++ b/src/frontend/Debugger.hpp @@ -1,11 +1,28 @@ #pragma once #include +void BreakpointFunc(s64, s64, const Disassembler::DisassemblyResult&); +void AddressFunc(s64, s64, const Disassembler::DisassemblyResult&); +void InstructionFunc(s64, s64, const Disassembler::DisassemblyResult&); +void CommentFunc(s64, s64, const Disassembler::DisassemblyResult&); + class Debugger final { bool enabled = false; static constexpr auto MAX_LINES_OF_DISASM = 150; + + struct Column { + const char* name; + void (*func)(s64, s64, const Disassembler::DisassemblyResult&) = nullptr; + }; + + std::array columns = { + Column{"Breakpoint", &BreakpointFunc}, + Column{"Address", &AddressFunc}, + Column{"Instruction", &InstructionFunc}, + Column{"Comment", &CommentFunc}, + }; public: void Open() { enabled = true; } void Close() { enabled = false; } bool render(); -}; +}; \ No newline at end of file diff --git a/src/frontend/KaizenGui.cpp b/src/frontend/KaizenGui.cpp index 656bc459..fcc89577 100644 --- a/src/frontend/KaizenGui.cpp +++ b/src/frontend/KaizenGui.cpp @@ -247,6 +247,9 @@ void KaizenGui::RenderUI() { }, this, window.getHandle(), filters, 3, nullptr, false); } + if(minimized) + return; + if(core.romLoaded) { core.parallel.UpdateScreen(); return; @@ -271,6 +274,12 @@ void KaizenGui::run() { quit = true; emuThread.Stop(); break; + case SDL_EVENT_WINDOW_MINIMIZED: + minimized = true; + break; + case SDL_EVENT_WINDOW_RESTORED: + minimized = false; + break; } QueryDevices(e); HandleInput(e); diff --git a/src/frontend/KaizenGui.hpp b/src/frontend/KaizenGui.hpp index 432bd2b9..47899662 100644 --- a/src/frontend/KaizenGui.hpp +++ b/src/frontend/KaizenGui.hpp @@ -14,6 +14,7 @@ public: double fpsCounter = -1.0; bool fastForward = false; bool unlockFramerate = false; + bool minimized = false; SettingsWindow settingsWindow; RenderWidget vulkanWidget;