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

View File

@ -2,6 +2,7 @@
#include "Emu/Memory/vm.h" #include "Emu/Memory/vm.h"
#include "Emu/Memory/vm_reservation.h" #include "Emu/Memory/vm_reservation.h"
#include "Emu/CPU/CPUDisAsm.h" #include "Emu/CPU/CPUDisAsm.h"
#include "Emu/Cell/SPUDisAsm.h"
#include "Emu/IdManager.h" #include "Emu/IdManager.h"
#include "Utilities/Thread.h" #include "Utilities/Thread.h"
@ -23,15 +24,59 @@
LOG_CHANNEL(gui_log, "GUI"); LOG_CHANNEL(gui_log, "GUI");
enum : int constexpr auto qstr = QString::fromStdString;
enum search_mode : int
{ {
as_string, no_mode = 1,
as_hex, as_string = 2,
as_f64, as_hex = 4,
as_f32, as_f64 = 8,
as_inst, 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) memory_string_searcher::memory_string_searcher(QWidget* parent, std::shared_ptr<CPUDisAsm> disasm, std::string_view title)
: QDialog(parent) : QDialog(parent)
, m_disasm(std::move(disasm)) , 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.")); "\nWarning: this may reduce performance of the search."));
m_cbox_input_mode = new QComboBox(this); m_cbox_input_mode = new QComboBox(this);
m_cbox_input_mode->addItem("String", QVariant::fromValue(+as_string)); m_cbox_input_mode->addItem(tr("Select search mode(s).."), QVariant::fromValue(+no_mode));
m_cbox_input_mode->addItem("HEX bytes/integer", QVariant::fromValue(+as_hex)); m_cbox_input_mode->addItem(tr("String"), QVariant::fromValue(+as_string));
m_cbox_input_mode->addItem("Double", QVariant::fromValue(+as_f64)); m_cbox_input_mode->addItem(tr("HEX bytes/integer"), QVariant::fromValue(+as_hex));
m_cbox_input_mode->addItem("Float", QVariant::fromValue(+as_f32)); m_cbox_input_mode->addItem(tr("Double"), QVariant::fromValue(+as_f64));
m_cbox_input_mode->addItem("Instruction", QVariant::fromValue(+as_inst)); m_cbox_input_mode->addItem(tr("Float"), QVariant::fromValue(+as_f32));
m_cbox_input_mode->setToolTip(tr("String: search the memory for the specified string." m_cbox_input_mode->addItem(tr("Instruction"), QVariant::fromValue(+as_inst));
"\nHEX bytes/integer: search the memory for hexadecimal values. Spaces, commas, \"0x\", \"0X\", \"\\x\" ensure separation of bytes but they are not mandatory."
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." "\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." "\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(); QHBoxLayout* hbox_panel = new QHBoxLayout();
hbox_panel->addWidget(m_addr_line); 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(m_chkbox_case_insensitive);
hbox_panel->addWidget(button_search); 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); 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; bool case_insensitive = false;
// First characters for case insensitive search // First characters for case insensitive search
@ -126,6 +210,7 @@ void memory_string_searcher::OnSearch()
{ {
case as_inst: case as_inst:
case as_string: case as_string:
case as_fake_spu_inst:
{ {
case_insensitive = m_chkbox_case_insensitive->isChecked(); case_insensitive = m_chkbox_case_insensitive->isChecked();
@ -141,7 +226,7 @@ void memory_string_searcher::OnSearch()
constexpr std::string_view hex_chars = "0123456789ABCDEFabcdef"; constexpr std::string_view hex_chars = "0123456789ABCDEFabcdef";
// Split // 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 // Pad zeroes
for (std::string& part : parts) 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'.", gui_log.error("String '%s' cannot be interpreted as hexadecimal byte string due to unknown character '%c'.",
m_addr_line->text().toStdString(), wstr[pos]); m_addr_line->text().toStdString(), wstr[pos]);
return; return 0;
} }
std::string dst; std::string dst;
@ -182,13 +267,16 @@ void memory_string_searcher::OnSearch()
} }
case as_f64: case as_f64:
{ {
// Remove trailing 'f' letters
wstr = wstr.substr(0, wstr.find_last_not_of("Ff") + 1);
char* end{}; char* end{};
be_t<f64> value = std::strtod(wstr.data(), &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); gui_log.error("String '%s' cannot be interpreted as double.", wstr);
return; return 0;
} }
wstr.resize(sizeof(value)); wstr.resize(sizeof(value));
@ -197,13 +285,15 @@ void memory_string_searcher::OnSearch()
} }
case as_f32: case as_f32:
{ {
wstr = wstr.substr(0, wstr.find_last_not_of("Ff") + 1);
char* end{}; char* end{};
be_t<f32> value = std::strtof(wstr.data(), &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); gui_log.error("String '%s' cannot be interpreted as float.", wstr);
return; return 0;
} }
wstr.resize(sizeof(value)); wstr.resize(sizeof(value));
@ -218,7 +308,7 @@ void memory_string_searcher::OnSearch()
atomic_t<u32> avail_addr = 0; atomic_t<u32> avail_addr = 0;
// There's no need for so many threads (except for instructions searching) // 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; 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, [&]() 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(); auto disasm = m_disasm->copy_type_erased();
disasm->change_mode(cpu_disasm_mode::normal); 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) while (true)
{ {
@ -241,7 +333,7 @@ void memory_string_searcher::OnSearch()
{ {
if (val < limit && val != umax) 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 // Skip unmapped memory
val = utils::align(val + 1, 0x10000); val = utils::align(val + 1, 0x10000);
@ -274,11 +366,23 @@ void memory_string_searcher::OnSearch()
return; 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) 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) if (case_insensitive)
{ {
@ -287,7 +391,7 @@ void memory_string_searcher::OnSearch()
if (last.find(wstr) != umax) 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++; found++;
} }
} }
@ -437,5 +541,5 @@ void memory_string_searcher::OnSearch()
workers.join(); workers.join();
gui_log.success("Search completed (found %u matches)", +found); return found;
} }

View File

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