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:
|
||||
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:
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user