PPU: HACK instruction removed

Breakpoints fixed
This commit is contained in:
Nekotekina 2017-03-22 23:23:47 +03:00
parent ddd6ebc58d
commit 3bfe17a14f
20 changed files with 155 additions and 379 deletions

View file

@ -13,7 +13,8 @@
#include "Emu/Cell/lv2/sys_prx.h"
#include <unordered_set>
#include <map>
#include <set>
#include <algorithm>
namespace vm { using namespace ps3; }
@ -118,6 +119,7 @@ cfg::set_entry g_cfg_load_libs(cfg::root.core, "Load libraries");
extern std::string ppu_get_function_name(const std::string& module, u32 fnid);
extern std::string ppu_get_variable_name(const std::string& module, u32 vnid);
extern void ppu_register_range(u32 addr, u32 size);
extern void ppu_register_function_at(u32 addr, u32 size, ppu_function_t ptr);
extern void ppu_initialize(const ppu_module& info);
extern void ppu_initialize();
@ -125,53 +127,6 @@ extern void sys_initialize_tls(ppu_thread&, u64, u32, u32, u32);
extern u32 g_ps3_sdk_version;
// Function lookup table. Not supposed to grow after emulation start.
std::vector<ppu_function_t> g_ppu_function_cache;
// Function name cache in format %s.%s (module name, function name)
std::vector<std::string> g_ppu_function_names;
// Function NID cache for autopause. Autopause tool should probably be rewritten.
std::vector<u32> g_ppu_fnid_cache;
extern std::string ppu_get_module_function_name(u32 index)
{
if (index < g_ppu_function_names.size())
{
return g_ppu_function_names[index];
}
return fmt::format(".%u", index);
}
extern void ppu_execute_function(ppu_thread& ppu, u32 index)
{
if (index < g_ppu_function_cache.size())
{
// If autopause occures, check_status() will hold the thread until unpaused
if (debug::autopause::pause_function(g_ppu_fnid_cache[index])) ppu.test_state();
if (const auto func = g_ppu_function_cache[index])
{
func(ppu);
LOG_TRACE(HLE, "'%s' finished, r3=0x%llx", ppu_get_module_function_name(index), ppu.gpr[3]);
return;
}
}
fmt::throw_exception("Function not registered (index %u)" HERE, index);
}
extern ppu_function_t ppu_get_function(u32 index)
{
if (index < g_ppu_function_cache.size())
{
return g_ppu_function_cache[index];
}
return nullptr;
}
extern u32 ppu_generate_id(const char* name)
{
// Symbol name suffix
@ -224,6 +179,26 @@ const ppu_static_module* ppu_module_manager::get_module(const std::string& name)
return found != map.end() ? found->second : nullptr;
}
// Global linkage information
struct ppu_linkage_info
{
struct module
{
struct info
{
u32 export_addr = 0;
std::set<u32> imports;
};
// FNID -> (export; [imports...])
std::map<u32, info> functions;
std::map<u32, info> variables;
};
// Module map
std::unordered_map<std::string, module> modules;
};
// Initialize static modules.
static void ppu_initialize_modules()
{
@ -323,13 +298,6 @@ static void ppu_initialize_modules()
&ppu_module_manager::sys_lv2dbg,
};
// Reinitialize function cache
g_ppu_function_cache = ppu_function_manager::get();
g_ppu_function_names.clear();
g_ppu_function_names.resize(g_ppu_function_cache.size());
g_ppu_fnid_cache.clear();
g_ppu_fnid_cache.resize(g_ppu_function_cache.size());
// "Use" all the modules for correct linkage
for (auto& module : registered)
{
@ -338,8 +306,6 @@ static void ppu_initialize_modules()
for (auto& function : module->functions)
{
LOG_TRACE(LOADER, "** 0x%08X: %s", function.first, function.second.name);
g_ppu_function_names.at(function.second.index) = fmt::format("%s.%s", module->name, function.second.name);
g_ppu_fnid_cache.at(function.second.index) = function.first;
}
for (auto& variable : module->variables)
@ -348,152 +314,31 @@ static void ppu_initialize_modules()
variable.second.var->set(0);
}
}
// Initialize double-purpose fake OPD array for HLE functions
const auto& hle_funcs = ppu_function_manager::get();
// Allocate memory for the array (must be called after fixed allocations)
ppu_function_manager::addr = vm::alloc(::size32(hle_funcs) * 8, vm::main);
// Initialize as PPU executable code
ppu_register_range(ppu_function_manager::addr, ::size32(hle_funcs) * 8);
// Fill the array (visible data: self address and function index)
for (u32 addr = ppu_function_manager::addr, index = 0; index < hle_funcs.size(); addr += 8, index++)
{
// Function address = current address, RTOC = BLR instruction for the interpreter
vm::ps3::write32(addr + 0, addr);
vm::ps3::write32(addr + 4, ppu_instructions::BLR());
// Register the HLE function directly
ppu_register_function_at(addr + 0, 4, hle_funcs[index]);
}
// Set memory protection to read-only
vm::page_protect(ppu_function_manager::addr, ::align(::size32(hle_funcs) * 8, 0x1000), 0, 0, vm::page_writable);
}
// Detect import stub at specified address and inject HACK instruction with index immediate.
static bool ppu_patch_import_stub(u32 addr, u32 index)
{
const auto data = vm::_ptr<u32>(addr);
using namespace ppu_instructions;
// Check various patterns:
if (vm::check_addr(addr, 32) &&
(data[0] & 0xffff0000) == LI(r12, 0) &&
(data[1] & 0xffff0000) == ORIS(r12, r12, 0) &&
(data[2] & 0xffff0000) == LWZ(r12, r12, 0) &&
data[3] == STD(r2, r1, 0x28) &&
data[4] == LWZ(r0, r12, 0) &&
data[5] == LWZ(r2, r12, 4) &&
data[6] == MTCTR(r0) &&
data[7] == BCTR())
{
data[0] = STD(r2, r1, 0x28); // Save RTOC
data[1] = HACK(index);
data[2] = BLR();
std::fill(data + 3, data + 8, NOP());
return true;
}
if (vm::check_addr(addr, 12) &&
(data[0] & 0xffff0000) == LI(r0, 0) &&
(data[1] & 0xffff0000) == ORIS(r0, r0, 0) &&
(data[2] & 0xfc000003) == B(0, 0, 0))
{
const auto sub = vm::cptr<u32>::make(addr + 8 + ((s32)data[2] << 6 >> 8 << 2));
if (vm::check_addr(sub.addr(), 60) &&
sub[0x0] == STDU(r1, r1, -0x80) &&
sub[0x1] == STD(r2, r1, 0x70) &&
sub[0x2] == MR(r2, r0) &&
sub[0x3] == MFLR(r0) &&
sub[0x4] == STD(r0, r1, 0x90) &&
sub[0x5] == LWZ(r2, r2, 0) &&
sub[0x6] == LWZ(r0, r2, 0) &&
sub[0x7] == LWZ(r2, r2, 4) &&
sub[0x8] == MTCTR(r0) &&
sub[0x9] == BCTRL() &&
sub[0xa] == LD(r2, r1, 0x70) &&
sub[0xb] == ADDI(r1, r1, 0x80) &&
sub[0xc] == LD(r0, r1, 0x10) &&
sub[0xd] == MTLR(r0) &&
sub[0xe] == BLR())
{
data[0] = HACK(index);
data[1] = BLR();
data[2] = NOP();
return true;
}
}
if (vm::check_addr(addr, 64) &&
data[0x0] == MFLR(r0) &&
data[0x1] == STD(r0, r1, 0x10) &&
data[0x2] == STDU(r1, r1, -0x80) &&
data[0x3] == STD(r2, r1, 0x70) &&
(data[0x4] & 0xffff0000) == LI(r2, 0) &&
(data[0x5] & 0xffff0000) == ORIS(r2, r2, 0) &&
data[0x6] == LWZ(r2, r2, 0) &&
data[0x7] == LWZ(r0, r2, 0) &&
data[0x8] == LWZ(r2, r2, 4) &&
data[0x9] == MTCTR(r0) &&
data[0xa] == BCTRL() &&
data[0xb] == LD(r2, r1, 0x70) &&
data[0xc] == ADDI(r1, r1, 0x80) &&
data[0xd] == LD(r0, r1, 0x10) &&
data[0xe] == MTLR(r0) &&
data[0xf] == BLR())
{
data[0] = HACK(index);
data[1] = BLR();
std::fill(data + 2, data + 16, NOP());
return true;
}
if (vm::check_addr(addr, 60) &&
data[0x0] == MFLR(r0) &&
data[0x1] == STD(r0, r1, 0x10) &&
data[0x2] == STDU(r1, r1, -0x80) &&
data[0x3] == STD(r2, r1, 0x70) &&
(data[0x4] & 0xffff0000) == LIS(r12, 0) &&
(data[0x5] & 0xffff0000) == LWZ(r12, r12, 0) &&
data[0x6] == LWZ(r0, r12, 0) &&
data[0x7] == LWZ(r2, r12, 4) &&
data[0x8] == MTCTR(r0) &&
data[0x9] == BCTRL() &&
data[0xa] == LD(r2, r1, 0x70) &&
data[0xb] == ADDI(r1, r1, 0x80) &&
data[0xc] == LD(r0, r1, 0x10) &&
data[0xd] == MTLR(r0) &&
data[0xe] == BLR())
{
data[0] = HACK(index);
data[1] = BLR();
std::fill(data + 2, data + 15, NOP());
return true;
}
if (vm::check_addr(addr, 56) &&
(data[0x0] & 0xffff0000) == LI(r12, 0) &&
(data[0x1] & 0xffff0000) == ORIS(r12, r12, 0) &&
(data[0x2] & 0xffff0000) == LWZ(r12, r12, 0) &&
data[0x3] == STD(r2, r1, 0x28) &&
data[0x4] == MFLR(r0) &&
data[0x5] == STD(r0, r1, 0x20) &&
data[0x6] == LWZ(r0, r12, 0) &&
data[0x7] == LWZ(r2, r12, 4) &&
data[0x8] == MTCTR(r0) &&
data[0x9] == BCTRL() &&
data[0xa] == LD(r0, r1, 0x20) &&
data[0xb] == MTLR(r0) &&
data[0xc] == LD(r2, r1, 0x28) &&
data[0xd] == BLR())
{
data[0] = HACK(index);
data[1] = BLR();
std::fill(data + 2, data + 14, NOP());
return true;
}
return false;
}
// Global linkage information
struct ppu_linkage_info
{
struct module
{
using info_t = std::unordered_map<u32, std::pair<u32, std::unordered_set<u32>>>;
info_t functions;
info_t variables;
};
// Module -> (NID -> (export; [imports...]))
std::unordered_map<std::string, module> modules;
};
// Link variable
static void ppu_patch_variable_refs(u32 vref, u32 vaddr)
{
@ -605,7 +450,7 @@ static auto ppu_load_exports(const std::shared_ptr<ppu_linkage_info>& link, u32
// Function linkage info
auto& flink = link->modules[module_name].functions[fnid];
if (flink.first)
if (flink.export_addr)
{
LOG_FATAL(LOADER, "Already linked function '%s' in module '%s'", ppu_get_function_name(module_name, fnid), module_name);
}
@ -616,18 +461,32 @@ static auto ppu_load_exports(const std::shared_ptr<ppu_linkage_info>& link, u32
if (_sf && (_sf->flags & MFF_FORCED_HLE))
{
// Inject HACK instruction (TODO: guess function size and analyse B instruction, or reimplement BLR flag for HACK instruction)
const auto code = vm::ptr<u32>::make(vm::read32(faddr));
code[0] = ppu_instructions::HACK(_sf->index);
code[1] = ppu_instructions::BLR();
// Inject a branch to the HLE implementation
const u32 _entry = vm::read32(faddr);
const u32 target = ppu_function_manager::addr + 8 * _sf->index;
if ((target <= _entry && _entry - target <= 0x2000000) || (target > _entry && target - _entry < 0x2000000))
{
// Use relative branch
vm::write32(_entry, ppu_instructions::B(target - _entry));
}
else if (target < 0x2000000)
{
// Use absolute branch if possible
vm::write32(_entry, ppu_instructions::B(target, true));
}
else
{
LOG_FATAL(LOADER, "Failed to patch function at 0x%x (0x%x)", _entry, target);
}
}
else
{
// Set exported function
flink.first = faddr;
flink.export_addr = faddr;
// Fix imports
for (const auto addr : flink.second)
for (const u32 addr : flink.imports)
{
vm::write32(addr, faddr);
//LOG_WARNING(LOADER, "Exported function '%s' in module '%s'", ppu_get_function_name(module_name, fnid), module_name);
@ -649,17 +508,17 @@ static auto ppu_load_exports(const std::shared_ptr<ppu_linkage_info>& link, u32
// Variable linkage info
auto& vlink = link->modules[module_name].variables[vnid];
if (vlink.first)
if (vlink.export_addr)
{
LOG_FATAL(LOADER, "Already linked variable '%s' in module '%s'", ppu_get_variable_name(module_name, vnid), module_name);
}
else
{
// Set exported variable
vlink.first = vaddr;
vlink.export_addr = vaddr;
// Fix imports
for (const auto vref : vlink.second)
for (const auto vref : vlink.imports)
{
ppu_patch_variable_refs(vref, vaddr);
//LOG_WARNING(LOADER, "Exported variable '%s' in module '%s'", ppu_get_variable_name(module_name, vnid), module_name);
@ -705,10 +564,13 @@ static void ppu_load_imports(const std::shared_ptr<ppu_linkage_info>& link, u32
auto& flink = link->modules[module_name].functions[fnid];
// Add new import
flink.second.emplace(faddr);
flink.imports.emplace(faddr);
// Link if available
if (flink.first) vm::write32(faddr, flink.first);
if (flink.export_addr)
{
vm::write32(faddr, flink.export_addr);
}
//LOG_WARNING(LOADER, "Imported function '%s' in module '%s' (0x%x)", ppu_get_function_name(module_name, fnid), module_name, faddr);
}
@ -726,10 +588,13 @@ static void ppu_load_imports(const std::shared_ptr<ppu_linkage_info>& link, u32
auto& vlink = link->modules[module_name].variables[vnid];
// Add new import
vlink.second.emplace(vref);
vlink.imports.emplace(vref);
// Link if available
if (vlink.first) ppu_patch_variable_refs(vref, vlink.first);
if (vlink.export_addr)
{
ppu_patch_variable_refs(vref, vlink.export_addr);
}
//LOG_WARNING(LOADER, "Imported variable '%s' in module '%s' (0x%x)", ppu_get_variable_name(module_name, vnid), module_name, vlink.first);
}
@ -940,8 +805,6 @@ std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::stri
void ppu_load_exec(const ppu_exec_object& elf)
{
ppu_initialize_modules();
if (g_cfg_hook_ppu_funcs)
{
LOG_TODO(LOADER, "'Hook static functions' option deactivated");
@ -996,6 +859,7 @@ void ppu_load_exec(const ppu_exec_object& elf)
}
}
// Load section list, used by the analyser
for (const auto& s : elf.shdrs)
{
LOG_NOTICE(LOADER, "** Section: sh_type=0x%x, addr=0x%llx, size=0x%llx, flags=0x%x", s.sh_type, s.sh_addr, s.sh_size, s.sh_flags);
@ -1009,6 +873,9 @@ void ppu_load_exec(const ppu_exec_object& elf)
}
}
// Initialize HLE modules
ppu_initialize_modules();
// Load other programs
for (auto& prog : elf.progs)
{
@ -1296,55 +1163,31 @@ void ppu_load_exec(const ppu_exec_object& elf)
for (auto& entry : module.second.functions)
{
const u32 fnid = entry.first;
const u32 faddr = entry.second.first;
const u32 faddr = entry.second.export_addr;
if (faddr == 0)
{
const std::string fname = ppu_get_function_name(module.first, fnid);
// Link HLE implementation if available
if (const auto _sf = _sm && _sm->functions.count(fnid) ? &_sm->functions.at(fnid) : nullptr)
{
// Static function
for (auto& import : entry.second.second)
{
const u32 stub = vm::read32(import);
LOG_NOTICE(LOADER, "Linking HLE function '%s' in module '%s' (index %u)", fname, module.first, _sf->index);
if (!ppu_patch_import_stub(stub, _sf->index))
{
LOG_ERROR(LOADER, "Failed to inject code for function '%s' in module '%s' (0x%x)", _sf->name, module.first, stub);
}
else
{
LOG_NOTICE(LOADER, "Injected hack for function '%s' in module '%s' (*0x%x)", _sf->name, module.first, stub);
}
for (const u32 import : entry.second.imports)
{
LOG_TRACE(LOADER, "** Linked at *0x%x (0x%x)", import, vm::read32(import));
vm::write32(import, ppu_function_manager::addr + 8 * _sf->index);
}
}
else
{
// TODO
const u32 index = ::size32(g_ppu_function_cache);
const std::string& fname = ppu_get_function_name(module.first, fnid);
g_ppu_function_cache.emplace_back();
g_ppu_function_names.emplace_back(fmt::format("%s.%s", module.first, fname));
g_ppu_fnid_cache.emplace_back(fnid);
LOG_ERROR(LOADER, "Unknown function '%s' in module '%s'", fname, module.first);
LOG_ERROR(LOADER, "Unknown function '%s' in module '%s' (index %u)", fname, module.first, index);
for (auto& import : entry.second.second)
for (const u32 import : entry.second.imports)
{
if (_sm)
{
const u32 stub = vm::read32(import);
if (!ppu_patch_import_stub(stub, index))
{
LOG_ERROR(LOADER, "Failed to inject code for function '%s' in module '%s' (0x%x)", fname, module.first, stub);
}
else
{
LOG_NOTICE(LOADER, "Injected hack for function '%s' in module '%s' (*0x%x)", fname, module.first, stub);
}
}
LOG_WARNING(LOADER, "** Not linked at *0x%x", import);
LOG_WARNING(LOADER, "** Not linked at *0x%x (0x%x)", import, vm::read32(import));
vm::write32(import, ppu_function_manager::addr);
}
}
}
@ -1353,16 +1196,18 @@ void ppu_load_exec(const ppu_exec_object& elf)
for (auto& entry : module.second.variables)
{
const u32 vnid = entry.first;
const u32 vaddr = entry.second.first;
const u32 vaddr = entry.second.export_addr;
if (vaddr == 0)
{
// Static variable
const std::string vname = ppu_get_variable_name(module.first, vnid);
// Link HLE variable if available
if (const auto _sv = _sm && _sm->variables.count(vnid) ? &_sm->variables.at(vnid) : nullptr)
{
LOG_NOTICE(LOADER, "Linking HLE variable '%s' in module '%s' (*0x%x):", ppu_get_variable_name(module.first, vnid), module.first, _sv->var->addr());
LOG_NOTICE(LOADER, "Linking HLE variable '%s' in module '%s' (*0x%x):", vname, module.first, _sv->var->addr());
for (auto& ref : entry.second.second)
for (const u32 ref : entry.second.imports)
{
ppu_patch_variable_refs(ref, _sv->var->addr());
LOG_NOTICE(LOADER, "** Linked at ref=*0x%x", ref);
@ -1370,9 +1215,9 @@ void ppu_load_exec(const ppu_exec_object& elf)
}
else
{
LOG_ERROR(LOADER, "Unknown variable '%s' in module '%s'", ppu_get_variable_name(module.first, vnid), module.first);
LOG_ERROR(LOADER, "Unknown variable '%s' in module '%s'", vname, module.first);
for (auto& ref : entry.second.second)
for (const u32 ref : entry.second.imports)
{
LOG_WARNING(LOADER, "** Not linked at ref=*0x%x", ref);
}