PS3 String Searcher: Implement instruction searching in embedded SPU images

This commit is contained in:
Eladash 2021-10-30 17:37:53 +03:00 committed by Megamouse
parent 0e20acdf55
commit edcc2a9e0d
3 changed files with 162 additions and 48 deletions

View File

@ -18,7 +18,7 @@ class CPUDisAsm
{
protected:
cpu_disasm_mode m_mode{};
const std::add_pointer_t<const u8> m_offset{};
const u8* m_offset{};
const u32 m_start_pc;
const std::add_pointer_t<const cpu_thread> m_cpu{};
u32 m_op = 0;
@ -64,10 +64,14 @@ public:
std::string last_opcode{};
u32 dump_pc{};
CPUDisAsm& change_mode(cpu_disasm_mode mode)
cpu_disasm_mode change_mode(cpu_disasm_mode mode)
{
m_mode = mode;
return *this;
return std::exchange(m_mode, mode);
}
const u8* change_ptr(const u8* ptr)
{
return std::exchange(m_offset, ptr);
}
protected:

View File

@ -2,6 +2,7 @@
#include "Emu/Memory/vm.h"
#include "Emu/Memory/vm_reservation.h"
#include "Emu/CPU/CPUDisAsm.h"
#include "Emu/Cell/SPUDisAsm.h"
#include "Emu/IdManager.h"
#include "Utilities/Thread.h"
@ -23,15 +24,59 @@
LOG_CHANNEL(gui_log, "GUI");
enum : int
constexpr auto qstr = QString::fromStdString;
enum search_mode : int
{
as_string,
as_hex,
as_f64,
as_f32,
as_inst,
no_mode = 1,
as_string = 2,
as_hex = 4,
as_f64 = 8,
as_f32 = 16,
as_inst = 32,
as_fake_spu_inst = 64,
};
template <>
void fmt_class_string<search_mode>::format(std::string& out, u64 arg)
{
if (!arg)
{
out += "No search modes have been selected";
}
for (int modes = static_cast<int>(arg); modes; modes &= modes - 1)
{
const int mode = modes & ~(modes - 1);
auto mode_s = [&]() -> std::string_view
{
switch (mode)
{
case as_string: return "String";
case as_hex: return "HEX bytes/integer";
case as_f64: return "Double";
case as_f32: return "Float";
case as_inst: return "Instruction";
case as_fake_spu_inst: return "SPU Instruction";
default: return "";
}
}();
if (mode_s.empty())
{
break;
}
if (modes != static_cast<int>(arg))
{
out += ", ";
}
out += mode_s;
}
}
memory_string_searcher::memory_string_searcher(QWidget* parent, std::shared_ptr<CPUDisAsm> disasm, std::string_view title)
: QDialog(parent)
, m_disasm(std::move(disasm))
@ -64,16 +109,42 @@ memory_string_searcher::memory_string_searcher(QWidget* parent, std::shared_ptr<
"\nWarning: this may reduce performance of the search."));
m_cbox_input_mode = new QComboBox(this);
m_cbox_input_mode->addItem("String", QVariant::fromValue(+as_string));
m_cbox_input_mode->addItem("HEX bytes/integer", QVariant::fromValue(+as_hex));
m_cbox_input_mode->addItem("Double", QVariant::fromValue(+as_f64));
m_cbox_input_mode->addItem("Float", QVariant::fromValue(+as_f32));
m_cbox_input_mode->addItem("Instruction", QVariant::fromValue(+as_inst));
m_cbox_input_mode->setToolTip(tr("String: search the memory for the specified string."
"\nHEX bytes/integer: search the memory for hexadecimal values. Spaces, commas, \"0x\", \"0X\", \"\\x\" ensure separation of bytes but they are not mandatory."
m_cbox_input_mode->addItem(tr("Select search mode(s).."), QVariant::fromValue(+no_mode));
m_cbox_input_mode->addItem(tr("String"), QVariant::fromValue(+as_string));
m_cbox_input_mode->addItem(tr("HEX bytes/integer"), QVariant::fromValue(+as_hex));
m_cbox_input_mode->addItem(tr("Double"), QVariant::fromValue(+as_f64));
m_cbox_input_mode->addItem(tr("Float"), QVariant::fromValue(+as_f32));
m_cbox_input_mode->addItem(tr("Instruction"), QVariant::fromValue(+as_inst));
QString tooltip = tr("String: search the memory for the specified string."
"\nHEX bytes/integer: search the memory for hexadecimal values. Spaces, commas, \"0x\", \"0X\", \"\\x\", \"h\", \"H\" ensure separation of bytes but they are not mandatory."
"\nDouble: reinterpret the string as 64-bit precision floating point value. Values are searched for exact representation, meaning -0 != 0."
"\nFloat: reinterpret the string as 32-bit precision floating point value. Values are searched for exact representation, meaning -0 != 0."
"\nInstruction: search an instruction contains the text of the string."));
"\nInstruction: search an instruction contains the text of the string.");
if (m_size != 0x40000/*SPU_LS_SIZE*/)
{
m_cbox_input_mode->addItem("SPU Instruction", QVariant::fromValue(+as_fake_spu_inst));
tooltip.append(tr("\nSPU Instruction: Search an SPU instruction contains the text of the string. For searching instructions within embedded SPU images.\nTip: SPU floats are commented along forming instructions."));
}
connect(m_cbox_input_mode, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index)
{
if ((1 << index) == no_mode)
{
m_modes = {};
}
else
{
m_modes = search_mode{m_modes | (1 << index)};
}
m_modes_label->setText(qstr(fmt::format("%s.", m_modes)));
});
m_cbox_input_mode->setToolTip(tooltip);
m_modes_label = new QLabel(qstr(fmt::format("%s.", m_modes)));
QHBoxLayout* hbox_panel = new QHBoxLayout();
hbox_panel->addWidget(m_addr_line);
@ -81,9 +152,33 @@ memory_string_searcher::memory_string_searcher(QWidget* parent, std::shared_ptr<
hbox_panel->addWidget(m_chkbox_case_insensitive);
hbox_panel->addWidget(button_search);
setLayout(hbox_panel);
QVBoxLayout* vbox_panel = new QVBoxLayout();
vbox_panel->addLayout(hbox_panel);
vbox_panel->addWidget(m_modes_label);
connect(button_search, &QAbstractButton::clicked, this, &memory_string_searcher::OnSearch);
setLayout(vbox_panel);
connect(button_search, &QAbstractButton::clicked, this, [this]()
{
std::string wstr = m_addr_line->text().toStdString();
if (wstr.empty() || wstr.size() >= 4096u)
{
gui_log.error("String is empty or too long (size=%u)", wstr.size());
return;
}
gui_log.notice("Searching for %s (mode: %s)", wstr, m_modes);
u64 found = 0;
for (int modes = m_modes; modes; modes &= modes - 1)
{
found += OnSearch(wstr, modes & ~(modes - 1));
}
gui_log.success("Search completed (found %u matches)", +found);
});
layout()->setSizeConstraint(QLayout::SetFixedSize);
@ -97,19 +192,8 @@ memory_string_searcher::memory_string_searcher(QWidget* parent, std::shared_ptr<
});
}
void memory_string_searcher::OnSearch()
u64 memory_string_searcher::OnSearch(std::string wstr, int mode)
{
std::string wstr = m_addr_line->text().toStdString();
if (wstr.empty() || wstr.size() >= 4096u)
{
gui_log.error("String is empty or too long (size=%u)", wstr.size());
return;
}
gui_log.notice("Searching for %s", wstr);
const int mode = std::max(m_cbox_input_mode->currentIndex(), 0);
bool case_insensitive = false;
// First characters for case insensitive search
@ -126,6 +210,7 @@ void memory_string_searcher::OnSearch()
{
case as_inst:
case as_string:
case as_fake_spu_inst:
{
case_insensitive = m_chkbox_case_insensitive->isChecked();
@ -141,7 +226,7 @@ void memory_string_searcher::OnSearch()
constexpr std::string_view hex_chars = "0123456789ABCDEFabcdef";
// Split
std::vector<std::string> parts = fmt::split(wstr, {" ", ",", "0x", "0X", "\\x"});
std::vector<std::string> parts = fmt::split(wstr, {" ", ",", "0x", "0X", "\\x", "h", "H"});
// Pad zeroes
for (std::string& part : parts)
@ -164,7 +249,7 @@ void memory_string_searcher::OnSearch()
{
gui_log.error("String '%s' cannot be interpreted as hexadecimal byte string due to unknown character '%c'.",
m_addr_line->text().toStdString(), wstr[pos]);
return;
return 0;
}
std::string dst;
@ -182,13 +267,16 @@ void memory_string_searcher::OnSearch()
}
case as_f64:
{
// Remove trailing 'f' letters
wstr = wstr.substr(0, wstr.find_last_not_of("Ff") + 1);
char* end{};
be_t<f64> value = std::strtod(wstr.data(), &end);
if (end != wstr.data() + wstr.size())
if (wstr.empty() || end != wstr.data() + wstr.size())
{
gui_log.error("String '%s' cannot be interpreted as double.", wstr);
return;
return 0;
}
wstr.resize(sizeof(value));
@ -197,13 +285,15 @@ void memory_string_searcher::OnSearch()
}
case as_f32:
{
wstr = wstr.substr(0, wstr.find_last_not_of("Ff") + 1);
char* end{};
be_t<f32> value = std::strtof(wstr.data(), &end);
if (end != wstr.data() + wstr.size())
if (wstr.empty() || end != wstr.data() + wstr.size())
{
gui_log.error("String '%s' cannot be interpreted as float.", wstr);
return;
return 0;
}
wstr.resize(sizeof(value));
@ -218,7 +308,7 @@ void memory_string_searcher::OnSearch()
atomic_t<u32> avail_addr = 0;
// There's no need for so many threads (except for instructions searching)
const u32 max_threads = utils::aligned_div(utils::get_thread_count(), mode != as_inst ? 2 : 1);
const u32 max_threads = utils::aligned_div(utils::get_thread_count(), mode < as_inst ? 2 : 1);
static constexpr u32 block_size = 0x2000000;
@ -226,12 +316,14 @@ void memory_string_searcher::OnSearch()
const named_thread_group workers("String Searcher "sv, max_threads, [&]()
{
if (mode == as_inst)
if (mode == as_inst || mode == as_fake_spu_inst)
{
auto disasm = m_disasm->copy_type_erased();
disasm->change_mode(cpu_disasm_mode::normal);
const usz limit = std::min(m_size, m_ptr == vm::g_sudo_addr ? 0x4000'0000 : m_size);
SPUDisAsm spu_dis(cpu_disasm_mode::normal, static_cast<const u8*>(m_ptr));
const usz limit = std::min(m_size, m_ptr == vm::g_sudo_addr ? 0xFFFF'0000 : m_size);
while (true)
{
@ -241,7 +333,7 @@ void memory_string_searcher::OnSearch()
{
if (val < limit && val != umax)
{
while (m_ptr == vm::g_sudo_addr && !vm::check_addr(val, vm::page_executable))
while (m_ptr == vm::g_sudo_addr && !vm::check_addr(val, mode == as_inst ? vm::page_executable : 0))
{
// Skip unmapped memory
val = utils::align(val + 1, 0x10000);
@ -274,11 +366,23 @@ void memory_string_searcher::OnSearch()
return;
}
u32 spu_base_pc = 0;
if (mode == as_fake_spu_inst)
{
// Check if we can extend the limits of SPU decoder so it can use the previous 64k block
// For SPU instruction patterns
spu_base_pc = (addr >= 0x10000 && (m_ptr != vm::g_sudo_addr || vm::check_addr(addr - 0x10000, 0))) ? 0x10000 : 0;
// Set base for SPU decoder
spu_dis.change_ptr(static_cast<const u8*>(m_ptr) + addr - spu_base_pc);
}
for (u32 i = 0; i < 0x10000; i += 4)
{
if (disasm->disasm(addr + i))
if (mode == as_fake_spu_inst ? spu_dis.disasm(spu_base_pc + i) : disasm->disasm(addr + i))
{
auto& last = disasm->last_opcode;
auto& last = mode == as_fake_spu_inst ? spu_dis.last_opcode : disasm->last_opcode;
if (case_insensitive)
{
@ -287,7 +391,7 @@ void memory_string_searcher::OnSearch()
if (last.find(wstr) != umax)
{
gui_log.success("Found instruction at 0x%08x: '%s'", addr + i, disasm->last_opcode);
gui_log.success("Found instruction at 0x%08x: '%s'", addr + i, last);
found++;
}
}
@ -437,5 +541,5 @@ void memory_string_searcher::OnSearch()
workers.join();
gui_log.success("Search completed (found %u matches)", +found);
return found;
}

View File

@ -7,9 +7,12 @@
class QLineEdit;
class QCheckBox;
class QComboBox;
class QLabel;
class CPUDisAsm;
enum search_mode : int;
class memory_string_searcher : public QDialog
{
Q_OBJECT
@ -17,17 +20,20 @@ class memory_string_searcher : public QDialog
QLineEdit* m_addr_line = nullptr;
QCheckBox* m_chkbox_case_insensitive = nullptr;
QComboBox* m_cbox_input_mode = nullptr;
QLabel* m_modes_label = nullptr;
std::shared_ptr<CPUDisAsm> m_disasm;
const void* m_ptr;
usz m_size;
search_mode m_modes{};
public:
explicit memory_string_searcher(QWidget* parent, std::shared_ptr<CPUDisAsm> disasm, std::string_view title = {});
private Q_SLOTS:
void OnSearch();
private:
u64 OnSearch(std::string wstr, int mode);
};
// Lifetime management with IDM