SPU Executable Code Dumping Tool

This commit is contained in:
Eladash 2023-05-13 11:31:10 +03:00 committed by Ivan
parent 57070aa8ff
commit 514ef9a9c5
11 changed files with 394 additions and 38 deletions

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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>

View File

@ -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" />

View File

@ -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

View File

@ -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;

View 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."));
}
}
}

View 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;
};

View File

@ -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);

View File

@ -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;
}
}