mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-23 03:02:53 +01:00
SPU Executable Code Dumping Tool
This commit is contained in:
parent
57070aa8ff
commit
514ef9a9c5
@ -1105,7 +1105,7 @@ void spu_thread::dump_regs(std::string& ret) const
|
||||
fmt::append(ret, "%08x %08x %08x %08x", r.u32r[0], r.u32r[1], r.u32r[2], r.u32r[3]);
|
||||
}
|
||||
|
||||
if (i3 >= 0x80 && is_exec_code(i3))
|
||||
if (i3 >= 0x80 && is_exec_code(i3, ls))
|
||||
{
|
||||
dis_asm.disasm(i3);
|
||||
fmt::append(ret, " -> %s", dis_asm.last_opcode);
|
||||
@ -1197,7 +1197,7 @@ std::vector<std::pair<u32, u32>> spu_thread::dump_callstack_list() const
|
||||
return true;
|
||||
}
|
||||
|
||||
return !addr || !is_exec_code(addr);
|
||||
return !addr || !is_exec_code(addr, ls);
|
||||
};
|
||||
|
||||
if (is_invalid(lr))
|
||||
@ -3912,7 +3912,7 @@ bool spu_thread::check_mfc_interrupts(u32 next_pc)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool spu_thread::is_exec_code(u32 addr) const
|
||||
bool spu_thread::is_exec_code(u32 addr, const u8* ls_ptr)
|
||||
{
|
||||
if (addr & ~0x3FFFC)
|
||||
{
|
||||
@ -3922,7 +3922,7 @@ bool spu_thread::is_exec_code(u32 addr) const
|
||||
for (u32 i = 0; i < 30; i++)
|
||||
{
|
||||
const u32 addr0 = addr + (i * 4);
|
||||
const u32 op = _ref<u32>(addr0);
|
||||
const u32 op = read_from_ptr<be_t<u32>>(ls_ptr + addr0);
|
||||
const auto type = s_spu_itype.decode(op);
|
||||
|
||||
if (type == spu_itype::UNK || !op)
|
||||
@ -5925,20 +5925,84 @@ void spu_thread::fast_call(u32 ls_addr)
|
||||
gpr[1]._u32[3] = old_stack;
|
||||
}
|
||||
|
||||
spu_exec_object spu_thread::capture_memory_as_elf(std::span<spu_memory_segment_dump_data> segs, u32 pc_hint)
|
||||
{
|
||||
spu_exec_object spu_exec;
|
||||
spu_exec.set_error(elf_error::ok);
|
||||
|
||||
std::vector<u8> all_data(SPU_LS_SIZE);
|
||||
|
||||
for (auto& seg : segs)
|
||||
{
|
||||
std::vector<uchar> data(seg.segment_size);
|
||||
|
||||
if (auto [vm_addr, ok] = vm::try_get_addr(seg.src_addr); ok)
|
||||
{
|
||||
if (!vm::try_access(vm_addr, data.data(), data.size(), false))
|
||||
{
|
||||
spu_log.error("capture_memory_as_elf(): Failed to read {0x%x..0x%x}, aborting capture.", +vm_addr, vm_addr + seg.segment_size - 1);
|
||||
spu_exec.set_error(elf_error::stream_data);
|
||||
return spu_exec;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(data.data(), seg.src_addr, data.size());
|
||||
}
|
||||
|
||||
std::memcpy(all_data.data() + seg.ls_addr, data.data(), data.size());
|
||||
|
||||
auto& prog = spu_exec.progs.emplace_back(SYS_SPU_SEGMENT_TYPE_COPY, seg.flags & 0x7, seg.ls_addr, seg.segment_size, 8, std::move(data));
|
||||
|
||||
prog.p_paddr = prog.p_vaddr;
|
||||
|
||||
spu_log.success("Segment: p_type=0x%x, p_vaddr=0x%x, p_filesz=0x%x, p_memsz=0x%x", prog.p_type, prog.p_vaddr, prog.p_filesz, prog.p_memsz);
|
||||
}
|
||||
|
||||
|
||||
u32 pc0 = pc_hint;
|
||||
|
||||
if (pc_hint != umax)
|
||||
{
|
||||
for (pc0 = pc_hint; pc0; pc0 -= 4)
|
||||
{
|
||||
const u32 op = read_from_ptr<be_t<u32>>(all_data.data(), pc0 - 4);
|
||||
|
||||
// Try to find function entry (if they are placed sequentially search for BI $LR of previous function)
|
||||
if (!op || op == 0x35000000u || s_spu_itype.decode(op) == spu_itype::UNK)
|
||||
{
|
||||
if (is_exec_code(pc0, all_data.data()))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (pc0 = 0; pc0 < SPU_LS_SIZE; pc0 += 4)
|
||||
{
|
||||
const spu_opcode_t op{read_from_ptr<be_t<u32>>(all_data.data(), pc0)};
|
||||
|
||||
// Try to find a function entry (very basic)
|
||||
if (is_exec_code(pc0, all_data.data()))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spu_exec.header.e_entry = pc0;
|
||||
|
||||
return spu_exec;
|
||||
}
|
||||
|
||||
bool spu_thread::capture_state()
|
||||
{
|
||||
ensure(state & cpu_flag::wait);
|
||||
|
||||
spu_exec_object spu_exec;
|
||||
|
||||
// Save data as an executable segment, even the SPU stack
|
||||
// In the past, an optimization was made here to save only non-zero chunks of data
|
||||
// But Ghidra didn't like accessing memory out of chunks (pretty common)
|
||||
// So it has been reverted
|
||||
auto& prog = spu_exec.progs.emplace_back(SYS_SPU_SEGMENT_TYPE_COPY, 0x7, 0, SPU_LS_SIZE, 8, std::vector<uchar>(ls, ls + SPU_LS_SIZE));
|
||||
|
||||
prog.p_paddr = prog.p_vaddr;
|
||||
spu_log.success("Segment: p_type=0x%x, p_vaddr=0x%x, p_filesz=0x%x, p_memsz=0x%x", prog.p_type, prog.p_vaddr, prog.p_filesz, prog.p_memsz);
|
||||
spu_memory_segment_dump_data single_seg{.ls_addr = 0, .src_addr = ls, .segment_size = SPU_LS_SIZE};
|
||||
spu_exec_object spu_exec = capture_memory_as_elf({&single_seg, 1}, pc);
|
||||
|
||||
std::string name;
|
||||
|
||||
@ -5957,22 +6021,6 @@ bool spu_thread::capture_state()
|
||||
fmt::append(name, "RawSPU.%u", lv2_id);
|
||||
}
|
||||
|
||||
u32 pc0 = pc;
|
||||
|
||||
for (; pc0; pc0 -= 4)
|
||||
{
|
||||
be_t<u32> op;
|
||||
std::memcpy(&op, prog.bin.data() + pc0 - 4, 4);
|
||||
|
||||
// Try to find function entry (if they are placed sequentially search for BI $LR of previous function)
|
||||
if (!op || op == 0x35000000u || s_spu_itype.decode(op) == spu_itype::UNK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
spu_exec.header.e_entry = pc0;
|
||||
|
||||
name = vfs::escape(name, true);
|
||||
std::replace(name.begin(), name.end(), ' ', '_');
|
||||
|
||||
@ -6027,7 +6075,7 @@ bool spu_thread::capture_state()
|
||||
}
|
||||
|
||||
rewind = std::make_shared<utils::serial>();
|
||||
(*rewind)(std::span(prog.bin.data(), prog.bin.size())); // span serialization doesn't remember size which is what we need
|
||||
(*rewind)(std::span(spu_exec.progs[0].bin.data(), spu_exec.progs[0].bin.size())); // span serialization doesn't remember size which is what we need
|
||||
serialize_common(*rewind);
|
||||
|
||||
// TODO: Save and restore decrementer state properly
|
||||
|
@ -9,6 +9,10 @@
|
||||
#include "util/logs.hpp"
|
||||
#include "util/to_endian.hpp"
|
||||
|
||||
#include "Loader/ELF.h"
|
||||
|
||||
#include <span>
|
||||
|
||||
LOG_CHANNEL(spu_log, "SPU");
|
||||
|
||||
struct lv2_event_queue;
|
||||
@ -596,6 +600,14 @@ enum class spu_type : u32
|
||||
isolated,
|
||||
};
|
||||
|
||||
struct spu_memory_segment_dump_data
|
||||
{
|
||||
u32 ls_addr;
|
||||
const u8* src_addr;
|
||||
u32 segment_size;
|
||||
u32 flags = umax;
|
||||
};
|
||||
|
||||
class spu_thread : public cpu_thread
|
||||
{
|
||||
public:
|
||||
@ -804,7 +816,7 @@ public:
|
||||
void set_events(u32 bits);
|
||||
void set_interrupt_status(bool enable);
|
||||
bool check_mfc_interrupts(u32 next_pc);
|
||||
bool is_exec_code(u32 addr) const; // Only a hint, do not rely on it other than debugging purposes
|
||||
static bool is_exec_code(u32 addr, const u8* ls_ptr); // Only a hint, do not rely on it other than debugging purposes
|
||||
u32 get_ch_count(u32 ch);
|
||||
s64 get_ch_value(u32 ch);
|
||||
bool set_ch_value(u32 ch, u32 value);
|
||||
@ -816,6 +828,7 @@ public:
|
||||
std::array<std::shared_ptr<utils::serial>, 32> rewind_captures; // shared_ptr to avoid header inclusion
|
||||
u8 current_rewind_capture_idx = 0;
|
||||
|
||||
static spu_exec_object capture_memory_as_elf(std::span<spu_memory_segment_dump_data> segs, u32 pc_hint = umax);
|
||||
bool capture_state();
|
||||
bool try_load_debug_capture();
|
||||
void wakeup_delay(u32 div = 1) const;
|
||||
|
@ -354,6 +354,11 @@ public:
|
||||
|
||||
std::vector<u8> save(std::vector<u8>&& init = std::vector<u8>{}) const
|
||||
{
|
||||
if (get_error() != elf_error::ok)
|
||||
{
|
||||
return std::move(init);
|
||||
}
|
||||
|
||||
fs::file stream = fs::make_stream<std::vector<u8>>(std::move(init));
|
||||
|
||||
const bool fixup_shdrs = shdrs.empty() || shdrs[0].sh_type != sec_type::sht_null;
|
||||
@ -437,15 +442,10 @@ public:
|
||||
elf_object& set_error(elf_error error)
|
||||
{
|
||||
// Setting an error causes the state to clear if there was no error before
|
||||
// Trying to set elf_error::ok is ignored
|
||||
if (error != elf_error::ok)
|
||||
{
|
||||
if (m_error == elf_error::ok)
|
||||
if (m_error == elf_error::ok && error != elf_error::ok)
|
||||
clear();
|
||||
|
||||
m_error = error;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -225,6 +225,9 @@
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_downloader.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_elf_memory_dumping_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_emu_settings.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
@ -477,6 +480,9 @@
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_downloader.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_elf_memory_dumping_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_emu_settings.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
@ -694,6 +700,7 @@
|
||||
<ClCompile Include="rpcs3qt\custom_table_widget_item.cpp" />
|
||||
<ClCompile Include="rpcs3qt\debugger_list.cpp" />
|
||||
<ClCompile Include="rpcs3qt\downloader.cpp" />
|
||||
<ClCompile Include="rpcs3qt\elf_memory_dumping_dialog.cpp" />
|
||||
<ClCompile Include="rpcs3qt\fatal_error_dialog.cpp" />
|
||||
<ClCompile Include="rpcs3qt\flow_layout.cpp" />
|
||||
<ClCompile Include="rpcs3qt\flow_widget.cpp" />
|
||||
@ -1170,6 +1177,16 @@
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent"</Command>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\elf_memory_dumping_dialog.h">
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg"</Command>
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DHAVE_SDL2 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\libsdl-org\SDL\include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg"</Command>
|
||||
</CustomBuild>
|
||||
<ClInclude Include="rpcs3qt\emu_settings_type.h" />
|
||||
<CustomBuild Include="rpcs3qt\render_creator.h">
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>
|
||||
|
@ -1002,6 +1002,15 @@
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_flow_widget.cpp">
|
||||
<Filter>Generated Files\Release</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rpcs3qt\elf_memory_dumping_dialog.cpp">
|
||||
<Filter>Gui\dev tools</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_elf_memory_dumping_dialog.cpp">
|
||||
<Filter>Generated Files\Debug</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_elf_memory_dumping_dialog.cpp">
|
||||
<Filter>Generated Files\Release</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Input\ds4_pad_handler.h">
|
||||
@ -1471,6 +1480,9 @@
|
||||
<CustomBuild Include="rpcs3qt\flow_widget_item.h">
|
||||
<Filter>Gui\flow_layout</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\elf_memory_dumping_dialog.h">
|
||||
<Filter>Gui\dev tools</Filter>
|
||||
</CustomBuild>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="rpcs3.ico" />
|
||||
|
@ -20,6 +20,7 @@ set(SRC_FILES
|
||||
downloader.cpp
|
||||
_discord_utils.cpp
|
||||
emu_settings.cpp
|
||||
elf_memory_dumping_dialog.cpp
|
||||
fatal_error_dialog.cpp
|
||||
find_dialog.cpp
|
||||
flow_layout.cpp
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "register_editor_dialog.h"
|
||||
#include "instruction_editor_dialog.h"
|
||||
#include "memory_viewer_panel.h"
|
||||
#include "elf_memory_dumping_dialog.h"
|
||||
#include "gui_settings.h"
|
||||
#include "debugger_list.h"
|
||||
#include "breakpoint_list.h"
|
||||
@ -289,7 +290,7 @@ void debugger_frame::keyPressEvent(QKeyEvent* event)
|
||||
QLabel* l = new QLabel(tr(
|
||||
"Keys Ctrl+G: Go to typed address."
|
||||
"\nKeys Ctrl+B: Open breakpoints settings."
|
||||
"\nKeys Alt+S: Capture SPU images of selected SPU."
|
||||
"\nKeys Alt+S: Capture SPU images of selected SPU or generalized form when used from PPU."
|
||||
"\nKeys Alt+R: Load last saved SPU state capture."
|
||||
"\nKey D: SPU MFC commands logger, MFC debug setting must be enabled."
|
||||
"\nKey D: Also PPU calling history logger, interpreter and non-zero call history size must be used."
|
||||
@ -569,6 +570,12 @@ void debugger_frame::keyPressEvent(QKeyEvent* event)
|
||||
|
||||
if (modifiers & Qt::AltModifier)
|
||||
{
|
||||
if (cpu->id_type() == 1)
|
||||
{
|
||||
new elf_memory_dumping_dialog(pc, m_gui_settings, this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cpu->id_type() != 2)
|
||||
{
|
||||
return;
|
||||
|
224
rpcs3/rpcs3qt/elf_memory_dumping_dialog.cpp
Normal file
224
rpcs3/rpcs3qt/elf_memory_dumping_dialog.cpp
Normal file
@ -0,0 +1,224 @@
|
||||
#include "elf_memory_dumping_dialog.h"
|
||||
#include "Utilities/Config.h"
|
||||
|
||||
#include "Emu/Cell/SPUThread.h"
|
||||
|
||||
#include "qt_utils.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QCoreApplication>
|
||||
#include <QFontDatabase>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QMessageBox>
|
||||
#include <QLineEdit>
|
||||
#include <QLabel>
|
||||
|
||||
LOG_CHANNEL(gui_log, "GUI");
|
||||
|
||||
Q_DECLARE_METATYPE(spu_memory_segment_dump_data);
|
||||
|
||||
elf_memory_dumping_dialog::elf_memory_dumping_dialog(u32 ppu_debugger_addr, std::shared_ptr<gui_settings> _gui_settings, QWidget* parent)
|
||||
: QDialog(parent), m_gui_settings(std::move(_gui_settings))
|
||||
{
|
||||
setWindowTitle(tr("SPU ELF Dumper"));
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
m_seg_list = new QListWidget();
|
||||
|
||||
// Font
|
||||
const int pSize = 10;
|
||||
QFont mono = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
mono.setPointSize(pSize);
|
||||
|
||||
m_seg_list->setMinimumWidth(gui::utils::get_label_width(tr("PPU Address: 0x00000000, LS Address: 0x00000, Segment Size: 0x00000, Flags: 0x0")));
|
||||
|
||||
// Address expression input
|
||||
auto make_hex_edit = [mono](u32 max_digits)
|
||||
{
|
||||
QLineEdit* le = new QLineEdit();
|
||||
le->setFont(mono);
|
||||
le->setMaxLength(max_digits + 2);
|
||||
le->setPlaceholderText("0x" + QStringLiteral("0").repeated(max_digits));
|
||||
le->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("^(0[xX])?0*[a-fA-F0-9]{0,%1}$").arg(max_digits))));
|
||||
return le;
|
||||
};
|
||||
|
||||
m_segment_size_input = make_hex_edit(5);
|
||||
m_ppu_address_input = make_hex_edit(8);
|
||||
m_ls_address_input = make_hex_edit(5);
|
||||
m_segment_flags_input = make_hex_edit(1);
|
||||
m_segment_flags_input->setText("0x7"); // READ WRITE EXEC
|
||||
m_ppu_address_input->setText(QStringLiteral("0x%x").arg(ppu_debugger_addr & -0x10000, 2, 16)); // SPU code segments are usually 128 bytes aligned, let's make it even 64k so the user would have to type himself the lower part to avoid human errors.
|
||||
|
||||
QPushButton* add_segment_button = new QPushButton(QStringLiteral("+"));
|
||||
add_segment_button->setToolTip(tr("Add new segment"));
|
||||
add_segment_button->setFixedWidth(add_segment_button->sizeHint().height()); // Make button square
|
||||
connect(add_segment_button, &QAbstractButton::clicked, this, &elf_memory_dumping_dialog::add_new_segment);
|
||||
|
||||
QPushButton* remove_segment_button = new QPushButton(QStringLiteral("-"));
|
||||
remove_segment_button->setToolTip(tr("Remove segment"));
|
||||
remove_segment_button->setFixedWidth(remove_segment_button->sizeHint().height()); // Make button square
|
||||
remove_segment_button->setEnabled(false);
|
||||
connect(remove_segment_button, &QAbstractButton::clicked, this, &elf_memory_dumping_dialog::remove_segment);
|
||||
|
||||
QPushButton* save_to_file = new QPushButton(tr("Save To ELF"));
|
||||
save_to_file->setToolTip(tr("Save To An ELF file"));
|
||||
connect(save_to_file, &QAbstractButton::clicked, this, &elf_memory_dumping_dialog::save_to_file);
|
||||
|
||||
QHBoxLayout* hbox_input = new QHBoxLayout;
|
||||
hbox_input->addWidget(new QLabel(tr("Segment Size:")));
|
||||
hbox_input->addWidget(m_segment_size_input);
|
||||
hbox_input->addSpacing(5);
|
||||
|
||||
hbox_input->addWidget(new QLabel(tr("PPU Address:")));
|
||||
hbox_input->addWidget(m_ppu_address_input);
|
||||
hbox_input->addSpacing(5);
|
||||
hbox_input->addWidget(new QLabel(tr("LS Address:")));
|
||||
hbox_input->addWidget(m_ls_address_input);
|
||||
hbox_input->addSpacing(5);
|
||||
hbox_input->addWidget(new QLabel(tr("Flags:")));
|
||||
hbox_input->addWidget(m_segment_flags_input);
|
||||
|
||||
QHBoxLayout* hbox_save_and_edit = new QHBoxLayout;
|
||||
hbox_save_and_edit->addStretch(2);
|
||||
hbox_save_and_edit->addWidget(add_segment_button);
|
||||
hbox_save_and_edit->addSpacing(4);
|
||||
hbox_save_and_edit->addWidget(remove_segment_button);
|
||||
hbox_save_and_edit->addSpacing(4);
|
||||
hbox_save_and_edit->addWidget(save_to_file);
|
||||
|
||||
QVBoxLayout* vbox = new QVBoxLayout();
|
||||
vbox->addLayout(hbox_input);
|
||||
vbox->addSpacing(5);
|
||||
vbox->addWidget(m_seg_list);
|
||||
vbox->addSpacing(5);
|
||||
vbox->addLayout(hbox_save_and_edit);
|
||||
|
||||
setLayout(vbox);
|
||||
|
||||
connect(m_seg_list, &QListWidget::currentRowChanged, this, [this, remove_segment_button](int row)
|
||||
{
|
||||
remove_segment_button->setEnabled(row >= 0 && m_seg_list->item(row));
|
||||
});
|
||||
|
||||
show();
|
||||
}
|
||||
|
||||
void elf_memory_dumping_dialog::add_new_segment()
|
||||
{
|
||||
QStringList errors;
|
||||
auto interpret = [&](QString text, QString error_field) -> u32
|
||||
{
|
||||
bool ok = false;
|
||||
|
||||
// Parse expression (or at least used to, was nuked to remove the need for QtJsEngine)
|
||||
const QString fixed_expression = QRegularExpression(QRegularExpression::anchoredPattern("a .*|^[A-Fa-f0-9]+$")).match(text).hasMatch() ? "0x" + text : text;
|
||||
const u32 res = static_cast<u32>(fixed_expression.toULong(&ok, 16));
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
errors << error_field;
|
||||
return umax;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
spu_memory_segment_dump_data data{};
|
||||
data.segment_size = interpret(m_segment_size_input->text(), tr("Segment Size"));
|
||||
data.src_addr = vm::get_super_ptr(interpret(m_ppu_address_input->text(), tr("PPU Address")));
|
||||
data.ls_addr = interpret(m_ls_address_input->text(), tr("LS Address"));
|
||||
data.flags = interpret(m_segment_flags_input->text(), tr("Segment Flags"));
|
||||
|
||||
if (!errors.isEmpty())
|
||||
{
|
||||
QMessageBox::warning(this, tr("Failed To Add Segment"), tr("Segment parameters are incorrect:\n%1").arg(errors.join('\n')));
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.segment_size % 4)
|
||||
{
|
||||
QMessageBox::warning(this, tr("Failed To Add Segment"), tr("SPU segment size must be 4 bytes aligned."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.segment_size + data.ls_addr > SPU_LS_SIZE || data.segment_size == 0 || data.segment_size % 4)
|
||||
{
|
||||
QMessageBox::warning(this, tr("Failed To Add Segment"), tr("SPU segment range is invalid."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vm::check_addr(vm::try_get_addr(data.src_addr).first, vm::page_readable, data.segment_size))
|
||||
{
|
||||
QMessageBox::warning(this, tr("Failed To Add Segment"), tr("PPU address range is not accessible."));
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_seg_list->count(); ++i)
|
||||
{
|
||||
ensure(m_seg_list->item(i)->data(Qt::UserRole).canConvert<spu_memory_segment_dump_data>());
|
||||
const auto seg_stored = m_seg_list->item(i)->data(Qt::UserRole).value<spu_memory_segment_dump_data>();
|
||||
|
||||
const auto stored_max = seg_stored.src_addr + seg_stored.segment_size;
|
||||
const auto data_max = data.src_addr + data.segment_size;
|
||||
|
||||
if (seg_stored.src_addr < data_max && data.src_addr < stored_max)
|
||||
{
|
||||
QMessageBox::warning(this, tr("Failed To Add Segment"), tr("SPU segment overlaps with previous SPU segment(s)\n"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto item = new QListWidgetItem(tr("PPU Address: 0x%0, LS Address: 0x%1, Segment Size: 0x%2, Flags: 0x%3").arg(+vm::try_get_addr(data.src_addr).first, 2, 16).arg(data.ls_addr, 2, 16).arg(data.segment_size, 2, 16).arg(data.flags, 2, 16), m_seg_list);
|
||||
item->setData(Qt::UserRole, QVariant::fromValue(data));
|
||||
m_seg_list->setCurrentItem(item);
|
||||
}
|
||||
|
||||
void elf_memory_dumping_dialog::remove_segment()
|
||||
{
|
||||
const int row = m_seg_list->currentRow();
|
||||
if (row >= 0)
|
||||
{
|
||||
QListWidgetItem* item = m_seg_list->takeItem(row);
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
void elf_memory_dumping_dialog::save_to_file()
|
||||
{
|
||||
std::vector<spu_memory_segment_dump_data> segs;
|
||||
segs.reserve(m_seg_list->count());
|
||||
|
||||
for (int i = 0; i < m_seg_list->count(); ++i)
|
||||
{
|
||||
ensure(m_seg_list->item(i)->data(Qt::UserRole).canConvert<spu_memory_segment_dump_data>());
|
||||
const auto seg_stored = m_seg_list->item(i)->data(Qt::UserRole).value<spu_memory_segment_dump_data>();
|
||||
segs.emplace_back(seg_stored);
|
||||
}
|
||||
|
||||
if (segs.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const QString path_last_elf = m_gui_settings->GetValue(gui::fd_save_elf).toString();
|
||||
|
||||
const QString qpath = QFileDialog::getSaveFileName(this, tr("Capture"), path_last_elf, "SPU ELF (*.elf)" );
|
||||
const std::string path = qpath.toStdString();
|
||||
|
||||
if (!path.empty())
|
||||
{
|
||||
const auto result = spu_thread::capture_memory_as_elf({segs.data(), segs.size()}).save();
|
||||
|
||||
if (!result.empty() && fs::write_file(path, fs::rewrite, result))
|
||||
{
|
||||
gui_log.success("Saved ELF at %s", path);
|
||||
m_gui_settings->SetValue(gui::fd_save_elf, qpath);
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::warning(this, tr("Save Failure"), tr("Failed to save SPU ELF."));
|
||||
}
|
||||
}
|
||||
}
|
33
rpcs3/rpcs3qt/elf_memory_dumping_dialog.h
Normal file
33
rpcs3/rpcs3qt/elf_memory_dumping_dialog.h
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "util/types.hpp"
|
||||
#include "gui_settings.h"
|
||||
|
||||
#include <QListWidget>
|
||||
#include <QLineEdit>
|
||||
#include <QDialog>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class elf_memory_dumping_dialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit elf_memory_dumping_dialog(u32 ppu_debugger_pc, std::shared_ptr<gui_settings> _gui_settings, QWidget* parent = nullptr);
|
||||
|
||||
protected:
|
||||
void add_new_segment();
|
||||
void remove_segment();
|
||||
void save_to_file();
|
||||
|
||||
std::shared_ptr<gui_settings> m_gui_settings;
|
||||
|
||||
// UI variables needed in higher scope
|
||||
QListWidget* m_seg_list = nullptr;
|
||||
|
||||
QLineEdit* m_ls_address_input = nullptr;
|
||||
QLineEdit* m_segment_size_input = nullptr;
|
||||
QLineEdit* m_ppu_address_input = nullptr;
|
||||
QLineEdit* m_segment_flags_input = nullptr;
|
||||
};
|
@ -148,6 +148,7 @@ namespace gui
|
||||
const gui_save fd_ext_tar = gui_save(main_window, "lastExplorePathExTAR", "");
|
||||
const gui_save fd_insert_disc = gui_save(main_window, "lastExplorePathDISC", "");
|
||||
const gui_save fd_cfg_check = gui_save(main_window, "lastExplorePathCfgChk", "");
|
||||
const gui_save fd_save_elf = gui_save(main_window, "lastExplorePathSaveElf", "");
|
||||
|
||||
const gui_save mw_debugger = gui_save(main_window, "debuggerVisible", false);
|
||||
const gui_save mw_logger = gui_save(main_window, "loggerVisible", true);
|
||||
|
@ -87,7 +87,7 @@ void vfs_dialog_path_widget::remove_directory() const
|
||||
const int row = m_dir_list->currentRow();
|
||||
if (row > 0)
|
||||
{
|
||||
QListWidgetItem* item = m_dir_list->item(row);
|
||||
QListWidgetItem* item = m_dir_list->takeItem(row);
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user