Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PS3 String Searcher: Implement instruction searching in embedded SPU images (as well as SPU floats) #11082

Merged
merged 2 commits into from Oct 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 8 additions & 4 deletions rpcs3/Emu/CPU/CPUDisAsm.h
Expand Up @@ -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;
Expand Down Expand Up @@ -64,10 +64,14 @@ class CPUDisAsm
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:
Expand Down
8 changes: 8 additions & 0 deletions rpcs3/Emu/Cell/PPUDisAsm.cpp
Expand Up @@ -356,7 +356,15 @@ void comment_constant(std::string& last_opcode, u64 value, bool print_float = fa

if (std::isfinite(float_val))
{
const usz old_size = last_opcode.size();

fmt::append(last_opcode, " (%.6gf)", float_val);

if (usz pos = last_opcode.find_first_of('.', old_size); pos == umax)
{
// No decimal point has been inserted, force insertion
last_opcode.insert(last_opcode.size() - 2, ".0"sv);
}
}
else
{
Expand Down
188 changes: 146 additions & 42 deletions rpcs3/rpcs3qt/memory_string_searcher.cpp
Expand Up @@ -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"
Expand All @@ -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))
Expand Down Expand Up @@ -64,26 +109,76 @@ 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);
hbox_panel->addWidget(m_cbox_input_mode);
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);

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

connect(button_search, &QAbstractButton::clicked, this, &memory_string_searcher::OnSearch);
gui_log.success("Search completed (found %u matches)", +found);
});

layout()->setSizeConstraint(QLayout::SetFixedSize);

Expand All @@ -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
Expand All @@ -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();

Expand All @@ -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)
Expand All @@ -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;
Expand All @@ -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));
Expand All @@ -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));
Expand All @@ -218,20 +308,22 @@ 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;

vm::reader_lock rlock;

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)
{
Expand All @@ -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);
Expand Down Expand Up @@ -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)
{
Expand All @@ -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++;
}
}
Expand Down Expand Up @@ -437,5 +541,5 @@ void memory_string_searcher::OnSearch()

workers.join();

gui_log.success("Search completed (found %u matches)", +found);
return found;
}