Skip to content

Commit d3fb044

Browse files
authored
Merge pull request #702 from caldog20/capstone
Dynarec Disassembler GUI Widget using Capstone
2 parents a179173 + ed04a86 commit d3fb044

File tree

11 files changed

+329
-18
lines changed

11 files changed

+329
-18
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
*.sln.docstates
77
*.userprefs
88

9+
#Clion Files
10+
.idea/
11+
912
Debug/
1013
Release/
1114
ReleaseWithTracy/
@@ -86,4 +89,5 @@ vram-viewer.vert
8689
pcsx.lua
8790
DynarecOutput.map
8891
DynarecProfileData.txt
89-
sstate.proto
92+
DynarecDisassembly.txt
93+
sstate.proto

src/core/DynaRec_x64/recompiler.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,13 @@ class DynaRecCPU final : public PCSX::R3000Acpu {
163163
virtual void Reset() final;
164164
virtual void Shutdown() final;
165165
virtual bool isDynarec() final { return true; }
166-
167166
virtual void Execute() final {
168167
ZoneScoped; // Tell the Tracy profiler to do its thing
169168
(*m_dispatcher)(); // Jump to assembly dispatcher
170169
}
170+
// For the GUI dynarec disassembly widget
171+
virtual const uint8_t *getBufferPtr() final { return gen.getCode<const uint8_t*>(); }
172+
virtual const size_t getBufferSize() final { return gen.getSize(); }
171173

172174
// TODO: Make it less slow and bad
173175
// Possibly clear blocks more aggressively

src/core/ix86/iR3000A.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ class DynaRecCPU final : public PCSX::R3000Acpu {
8686
virtual void Shutdown() final;
8787
virtual void SetPGXPMode(uint32_t pgxpMode) final;
8888
virtual bool isDynarec() final { return true; }
89+
// For the GUI dynarec disassembly widget
90+
virtual const uint8_t *getBufferPtr() final { return gen.getCode<const uint8_t *>(); }
91+
virtual const size_t getBufferSize() final { return gen.getSize(); }
8992

9093
static void recClearWrapper(DynaRecCPU *that, uint32_t a, uint32_t s) { that->Clear(a, s); }
9194
static uint32_t psxExceptionWrapper(DynaRecCPU *that, int e, int32_t bd) {

src/core/psxinterpreter.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,13 @@ class InterpretedCPU final : public PCSX::R3000Acpu {
9898
virtual void Shutdown() override;
9999
virtual void SetPGXPMode(uint32_t pgxpMode) override;
100100
virtual bool isDynarec() override { return false; }
101-
102101
void maybeCancelDelayedLoad(uint32_t index) {
103102
unsigned other = m_currentDelayedLoad ^ 1;
104103
if (m_delayedLoadInfo[other].index == index) m_delayedLoadInfo[other].active = false;
105104
}
105+
// For the GUI dynarec disassembly widget
106+
virtual const uint8_t *getBufferPtr() final { return nullptr; }
107+
virtual const size_t getBufferSize() final { return 0; }
106108

107109
void psxTestSWInts();
108110

@@ -787,7 +789,7 @@ void InterpretedCPU::psxRFE(uint32_t code) {
787789
* Format: OP rs, rt, offset *
788790
*********************************************************/
789791
#define RepBranchi32(op) \
790-
if ((int32_t)_rRs_ op (int32_t)_rRt_) doBranch(_BranchTarget_, false);
792+
if ((int32_t)_rRs_ op(int32_t) _rRt_) doBranch(_BranchTarget_, false);
791793

792794
void InterpretedCPU::psxBEQ(uint32_t code) { RepBranchi32(==) } // Branch if Rs == Rt
793795
void InterpretedCPU::psxBNE(uint32_t code) { RepBranchi32(!=) } // Branch if Rs != Rt

src/core/r3000a.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@
1919

2020
#pragma once
2121

22-
#include <stdint.h>
23-
2422
#include <atomic>
23+
#include <cstdint>
2524
#include <memory>
2625
#include <type_traits>
2726

@@ -32,6 +31,7 @@
3231
#include "support/file.h"
3332
#include "support/hashtable.h"
3433

34+
3535
#if defined(__i386__) || defined(_M_IX86)
3636
#define DYNAREC_X86_32
3737
#elif defined(__x86_64) || defined(_M_AMD64)
@@ -264,6 +264,9 @@ class R3000Acpu {
264264
virtual void Shutdown() = 0;
265265
virtual void SetPGXPMode(uint32_t pgxpMode) = 0;
266266
virtual bool Implemented() = 0;
267+
// For the GUI dynarec disassembly widget
268+
virtual const uint8_t *getBufferPtr() = 0;
269+
virtual const size_t getBufferSize() = 0;
267270

268271
const std::string &getName() { return m_name; }
269272

src/gui/gui.cc

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,18 @@ static void drop_callback(GLFWwindow* window, int count, const char** paths) {
112112
s_this->magicOpen(paths[0]);
113113
}
114114

115+
static void ShowHelpMarker(const char* desc) {
116+
ImGui::SameLine();
117+
ImGui::TextDisabled("(?)");
118+
if (ImGui::IsItemHovered()) {
119+
ImGui::BeginTooltip();
120+
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
121+
ImGui::TextUnformatted(desc);
122+
ImGui::PopTextWrapPos();
123+
ImGui::EndTooltip();
124+
}
125+
}
126+
115127
void LoadImguiBindings(lua_State* lState);
116128

117129
ImFont* PCSX::GUI::loadFont(const PCSX::u8string& name, int size, ImGuiIO& io, const ImWchar* ranges, bool combine) {
@@ -865,6 +877,14 @@ void PCSX::GUI::endFrame() {
865877
}
866878
ImGui::MenuItem(_("Show Registers"), nullptr, &m_registers.m_show);
867879
ImGui::MenuItem(_("Show Assembly"), nullptr, &m_assembly.m_show);
880+
if (PCSX::g_emulator->m_psxCpu->isDynarec()) {
881+
ImGui::MenuItem(_("Show DynaRec Disassembly"), nullptr, &m_disassembly.m_show);
882+
} else {
883+
ImGui::MenuItem(_("Show DynaRec Disassembly"), nullptr, false, false);
884+
ShowHelpMarker(
885+
_(R"(DynaRec Disassembler is not available in Interpreted CPU mode. Try enabling [Dynarec CPU]
886+
in Configuration->Emulation, restart PCSX-Redux, then try again.)"));
887+
}
868888
ImGui::MenuItem(_("Show Breakpoints"), nullptr, &m_breakpoints.m_show);
869889
ImGui::MenuItem(_("Show Callstacks"), nullptr, &m_callstacks.m_show);
870890
ImGui::MenuItem(_("Breakpoint on vsync"), nullptr, &m_breakOnVSync);
@@ -1062,6 +1082,10 @@ void PCSX::GUI::endFrame() {
10621082
_("Assembly"));
10631083
}
10641084

1085+
if (m_disassembly.m_show && PCSX::g_emulator->m_psxCpu->isDynarec()) {
1086+
m_disassembly.draw(this, _("DynaRec Disassembler"));
1087+
}
1088+
10651089
if (m_breakpoints.m_show) {
10661090
m_breakpoints.draw(_("Breakpoints"));
10671091
}
@@ -1187,18 +1211,6 @@ void PCSX::GUI::endFrame() {
11871211
FrameMark
11881212
}
11891213

1190-
static void ShowHelpMarker(const char* desc) {
1191-
ImGui::SameLine();
1192-
ImGui::TextDisabled("(?)");
1193-
if (ImGui::IsItemHovered()) {
1194-
ImGui::BeginTooltip();
1195-
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
1196-
ImGui::TextUnformatted(desc);
1197-
ImGui::PopTextWrapPos();
1198-
ImGui::EndTooltip();
1199-
}
1200-
}
1201-
12021214
bool PCSX::GUI::configure() {
12031215
bool changed = false;
12041216
bool selectBiosDialog = false;

src/gui/gui.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "flags.h"
2929
#include "fmt/printf.h"
3030
#include "gui/widgets/assembly.h"
31+
#include "gui/widgets/dynarec_disassembly.h"
3132
#include "gui/widgets/breakpoints.h"
3233
#include "gui/widgets/callstacks.h"
3334
#include "gui/widgets/console.h"
@@ -246,6 +247,7 @@ class GUI final {
246247
MemoryEditorWrapper m_biosEditor;
247248
Widgets::Registers m_registers;
248249
Widgets::Assembly m_assembly;
250+
Widgets::Disassembly m_disassembly;
249251
Widgets::FileDialog m_openIsoFileDialog = {[]() { return _("Open Image"); }};
250252
Widgets::FileDialog m_openBinaryDialog = {[]() { return _("Open Binary"); }};
251253
Widgets::FileDialog m_selectBiosDialog = {[]() { return _("Select BIOS"); }};
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/***************************************************************************
2+
* Copyright (C) 2021 PCSX-Redux authors *
3+
* *
4+
* This program is free software; you can redistribute it and/or modify *
5+
* it under the terms of the GNU General Public License as published by *
6+
* the Free Software Foundation; either version 2 of the License, or *
7+
* (at your option) any later version. *
8+
* *
9+
* This program is distributed in the hope that it will be useful, *
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12+
* GNU General Public License for more details. *
13+
* *
14+
* You should have received a copy of the GNU General Public License *
15+
* along with this program; if not, write to the *
16+
* Free Software Foundation, Inc., *
17+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
18+
***************************************************************************/
19+
20+
#include "gui/widgets/dynarec_disassembly.h"
21+
22+
#include <capstone/capstone.h>
23+
24+
#include <cinttypes>
25+
#include <fstream>
26+
27+
#include "gui/gui.h"
28+
#include "imgui.h"
29+
30+
void PCSX::Widgets::Disassembly::writeFile() {
31+
std::ofstream file;
32+
// Open file - default location in resources directory
33+
file.open("DynarecDisassembly.txt", std::ios::app);
34+
// If file exists, write to it, otherwise return
35+
if (file) {
36+
for (auto i = 0; i < m_items.size(); ++i) {
37+
file << m_items[i];
38+
}
39+
} else {
40+
PCSX::g_system->printf("Disassembler Error: failed to open output file for disassembly.\n");
41+
m_showError = true;
42+
return;
43+
}
44+
// Close out file
45+
file.close();
46+
// If bad bit is set, there was an error, return -1
47+
if (file.fail()) {
48+
PCSX::g_system->printf("Disassembler Error: failed to write disassembly to output file.\n");
49+
m_showError = true;
50+
}
51+
}
52+
53+
void PCSX::Widgets::Disassembly::draw(GUI* gui, const char* title) {
54+
ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
55+
if (!ImGui::Begin(title, &m_show)) {
56+
ImGui::End();
57+
return;
58+
}
59+
// Disassemble button
60+
if (ImGui::Button("Disassemble Buffer")) {
61+
m_codeSize = disassembleBuffer();
62+
}
63+
ImGui::SameLine();
64+
// Save to File button
65+
if (ImGui::Button("Save to File")) {
66+
writeFile();
67+
}
68+
// Error popup
69+
if (m_showError) {
70+
ImGui::OpenPopup("Disassembler Error");
71+
if (ImGui::BeginPopupModal("Disassembler Error", NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
72+
ImGui::Text("Disassembly Failed.\nCheck Logs");
73+
if (ImGui::Button("Close")) {
74+
ImGui::CloseCurrentPopup();
75+
m_showError = false;
76+
}
77+
}
78+
ImGui::EndPopup();
79+
}
80+
// Close error popup
81+
if (ImGui::BeginPopupContextItem()) {
82+
if (ImGui::MenuItem("Close Disassembler")) {
83+
m_show = false;
84+
}
85+
ImGui::EndPopup();
86+
}
87+
88+
ImGui::Separator();
89+
// Clear items button
90+
if (ImGui::SmallButton("Clear")) {
91+
m_items.clear();
92+
m_codeSize = 0;
93+
}
94+
ImGui::SameLine();
95+
bool copy_to_clipboard = ImGui::SmallButton("Copy");
96+
97+
// Options menu
98+
if (ImGui::BeginPopup("Options")) {
99+
ImGui::Checkbox("Auto-scroll", &m_autoScroll);
100+
ImGui::Checkbox("Mono", &m_mono);
101+
ImGui::EndPopup();
102+
}
103+
104+
ImGui::SameLine();
105+
// Options, Filter
106+
if (ImGui::SmallButton("Options")) {
107+
ImGui::OpenPopup("Options");
108+
}
109+
110+
ImGui::SameLine();
111+
// Show buffer size returned from disassembly function
112+
ImGui::Text("Code size: %.2fMB", (double)m_codeSize / (1024 * 1024));
113+
ImGui::Separator();
114+
115+
if (m_mono) {
116+
gui->useMonoFont();
117+
}
118+
// Scrolling child window containing diassembly output
119+
ImGui::BeginChild("ScrollingRegion", ImVec2(0, -4), false, ImGuiWindowFlags_HorizontalScrollbar);
120+
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing
121+
122+
if (copy_to_clipboard) {
123+
ImGui::LogToClipboard();
124+
}
125+
// Loop through vec and display each string item in scrolling region
126+
for (auto& item : m_items) {
127+
ImGui::TextUnformatted(item.c_str());
128+
}
129+
130+
if (copy_to_clipboard) {
131+
ImGui::LogFinish();
132+
}
133+
134+
if (m_scrollToBottom || (m_autoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) {
135+
ImGui::SetScrollHereY(1.0f);
136+
}
137+
m_scrollToBottom = false;
138+
139+
ImGui::PopStyleVar();
140+
ImGui::EndChild();
141+
142+
if (m_mono) {
143+
ImGui::PopFont();
144+
}
145+
146+
ImGui::Separator();
147+
148+
// Auto-focus on window apparition
149+
ImGui::SetItemDefaultFocus();
150+
151+
ImGui::End();
152+
}
153+
154+
size_t PCSX::Widgets::Disassembly::disassembleBuffer() {
155+
csh handle;
156+
cs_insn* insn;
157+
size_t count;
158+
159+
// Get pointer to code buffer along with size of buffer
160+
const uint8_t* buffer = PCSX::g_emulator->m_psxCpu->getBufferPtr();
161+
const size_t bufferSize = PCSX::g_emulator->m_psxCpu->getBufferSize();
162+
// Check to ensure code buffer pointer is not null and size is not 0
163+
if (buffer == nullptr) {
164+
PCSX::g_system->printf("Disassembler Error: nullpointer to code buffer.\n");
165+
m_showError = true;
166+
return 0;
167+
} else if (bufferSize <= 0) {
168+
PCSX::g_system->printf("Disassembler Error: Invalid code buffer size.\n");
169+
m_showError = true;
170+
return 0;
171+
}
172+
// Attempt to initialize Capstone disassembler, if error log it and return
173+
if (cs_open(CS_ARCH, CS_MODE, &handle) != CS_ERR_OK) {
174+
PCSX::g_system->printf("Disassembler Error: Failed to initialize Capstone.\n");
175+
m_showError = true;
176+
return 0;
177+
}
178+
// Set SKIPDATA option as to not break disassembler
179+
cs_option(handle, CS_OPT_SKIPDATA, CS_OPT_ON);
180+
// Walk code buffer and try to disassemble
181+
count = cs_disasm(handle, buffer, bufferSize, 0x0, 0, &insn);
182+
if (count > 0) {
183+
for (size_t j = 0; j < count; j++) {
184+
// Write instruction (address, mnemonic, and operand to string
185+
std::string s =
186+
fmt::sprintf("%#010" PRIx64 ":\t\t%-12s\t%s\n", insn[j].address, insn[j].mnemonic, insn[j].op_str);
187+
// Log each instruction to the disassembly window string vector
188+
addInstruction(s);
189+
}
190+
// Call free to clean up memory allocated by capstone
191+
cs_free(insn, count);
192+
// to show the end of the disassembly for the disassembled buffer
193+
addInstruction("----End of disassembly----\n");
194+
// If disassembly failed, log the error and close out disassembler
195+
} else {
196+
cs_close(&handle);
197+
PCSX::g_system->printf("Disassembler Error: Failed to disassemble buffer.\n");
198+
m_showError = true;
199+
return 0;
200+
}
201+
// Successful disassembly, clean up disassembler instance and return successful result
202+
cs_close(&handle);
203+
return bufferSize;
204+
}

0 commit comments

Comments
 (0)