mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-03-13 07:14:49 +00:00
PS3 String Searcher: Implement instruction searching in embedded SPU images
This commit is contained in:
parent
0e20acdf55
commit
edcc2a9e0d
@ -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:
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user