PPU Analyser: TOC detection

Improved TOC detection logic
Added "PPU Debug" option
This commit is contained in:
Nekotekina 2017-04-08 23:58:00 +03:00
parent fb9b09d004
commit dc3d38c255
4 changed files with 171 additions and 38 deletions

View file

@ -341,23 +341,32 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
// Function analysis workload
std::vector<std::reference_wrapper<ppu_function>> func_queue;
// Known references (within segs, addr and value alignment = 4)
std::set<u32> addr_heap{entry};
// Register new function
auto add_func = [&](u32 addr, u32 toc, u32 origin) -> ppu_function&
auto add_func = [&](u32 addr, u32 toc, u32 caller) -> ppu_function&
{
ppu_function& func = funcs[addr];
if (caller)
{
// Register caller
func.callers.emplace(caller);
}
if (func.addr)
{
// Update TOC (TODO: this doesn't work well, must update TOC recursively)
if (func.toc == 0 || toc == -1)
if (toc && func.toc && func.toc != -1 && func.toc != toc)
{
func.toc = toc;
}
else if (toc && func.toc != -1 && func.toc != toc)
{
//LOG_WARNING(PPU, "Function 0x%x: TOC mismatch (0x%x vs 0x%x)", addr, toc, func.toc);
func.toc = -1;
}
else if (toc && func.toc == 0)
{
// Must then update TOC recursively
func.toc = toc;
func_queue.emplace_back(func);
}
return func;
}
@ -365,7 +374,7 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
func_queue.emplace_back(func);
func.addr = addr;
func.toc = toc;
LOG_TRACE(PPU, "Function 0x%x added (toc=0x%x, origin=0x%x)", addr, toc, origin);
LOG_TRACE(PPU, "Function 0x%x added (toc=0x%x)", addr, toc);
return func;
};
@ -386,7 +395,7 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
{
// New function
LOG_TRACE(PPU, "OPD*: [0x%x] 0x%x (TOC=0x%x)", ptr, ptr[0], ptr[1]);
add_func(*ptr, toc, ptr.addr());
add_func(*ptr, addr_heap.count(ptr.addr()) ? toc : 0, 0);
ptr++;
}
}
@ -407,6 +416,29 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
return end;
};
// Find references indiscriminately
for (const auto& seg : segs)
{
for (vm::cptr<u32> ptr = vm::cast(seg.first); ptr.addr() < seg.first + seg.second; ptr++)
{
const u32 value = *ptr;
if (value % 4)
{
continue;
}
for (const auto& _seg : segs)
{
if (value >= _seg.first && value < _seg.first + _seg.second)
{
addr_heap.emplace(value);
break;
}
}
}
}
// Find OPD section
for (const auto& sec : secs)
{
@ -462,7 +494,7 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
LOG_TRACE(PPU, "OPD: [0x%x] 0x%x (TOC=0x%x)", ptr, addr, toc);
TOCs.emplace(toc);
auto& func = add_func(addr, toc, ptr.addr());
auto& func = add_func(addr, addr_heap.count(ptr.addr()) ? toc : 0, 0);
func.attr += ppu_attr::known_addr;
}
}
@ -479,6 +511,15 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
add_toc(lib_toc);
}
// Clean TOCs
for (auto&& pair : funcs)
{
if (pair.second.toc == -1)
{
pair.second.toc = 0;
}
}
// Find .eh_frame section
for (const auto& sec : secs)
{
@ -581,7 +622,7 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
continue;
}
//auto& func = add_func(addr, 0, ptr.addr());
//auto& func = add_func(addr, 0, 0);
//func.attr += ppu_attr::known_addr;
//func.attr += ppu_attr::known_size;
//func.size = size;
@ -594,6 +635,30 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
{
ppu_function& func = func_queue[i];
// Fixup TOCs
if (func.toc && func.toc != -1)
{
for (u32 addr : func.callers)
{
ppu_function& caller = funcs[addr];
if (!caller.toc)
{
add_func(addr, func.toc - caller.trampoline, 0);
}
}
for (u32 addr : func.calls)
{
ppu_function& callee = funcs[addr];
if (!callee.toc)
{
add_func(addr, func.toc + func.trampoline, 0);
}
}
}
if (func.blocks.empty())
{
// Special function analysis
@ -618,7 +683,7 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
if (target >= start && target < end)
{
auto& new_func = add_func(target, 0, func.addr);
auto& new_func = add_func(target, func.toc, func.addr);
if (new_func.blocks.empty())
{
@ -630,7 +695,7 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
func.blocks.emplace(func.addr, func.size);
func.attr += new_func.attr & ppu_attr::no_return;
func.calls.emplace(target);
func.trampoline = target;
func.trampoline = 0;
continue;
}
}
@ -646,7 +711,7 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
if (target >= start && target < end)
{
auto& new_func = add_func(target, 0, func.addr);
auto& new_func = add_func(target, func.toc, func.addr);
if (new_func.blocks.empty())
{
@ -658,7 +723,7 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
func.blocks.emplace(func.addr, func.size);
func.attr += new_func.attr & ppu_attr::no_return;
func.calls.emplace(target);
func.trampoline = target;
func.trampoline = 0;
continue;
}
}
@ -670,15 +735,31 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
(ptr[3] & 0xfc000001) == B({}, {}))
{
// Trampoline with TOC
const u32 new_toc = func.toc && func.toc != -1 ? func.toc + (ptr[1] << 16) + s16(ptr[2]) : 0;
const u32 toc_add = (ptr[1] << 16) + s16(ptr[2]);
const u32 target = (ptr[3] & 0x2 ? 0 : (ptr + 3).addr()) + ppu_opcode_t{ptr[3]}.bt24;
if (target >= start && target < end)
{
add_toc(new_toc);
auto& new_func = add_func(target, 0, func.addr);
if (func.toc && func.toc != -1 && new_func.toc == 0)
{
const u32 toc = func.toc + toc_add;
add_toc(toc);
add_func(new_func.addr, toc, 0);
}
else if (new_func.toc && new_func.toc != -1 && func.toc == 0)
{
const u32 toc = new_func.toc - toc_add;
add_toc(toc);
add_func(func.addr, toc, 0);
}
else if (new_func.toc - func.toc != toc_add)
{
//func.toc = -1;
//new_func.toc = -1;
}
if (new_func.blocks.empty())
{
func_queue.emplace_back(func);
@ -689,7 +770,7 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
func.blocks.emplace(func.addr, func.size);
func.attr += new_func.attr & ppu_attr::no_return;
func.calls.emplace(target);
func.trampoline = target;
func.trampoline = toc_add;
continue;
}
}
@ -813,19 +894,8 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
// Get limit
const u32 func_end2 = _next == funcs.end() ? func_end : std::min<u32>(_next->first, func_end);
// Find more block entries
for (const auto& seg : segs)
{
for (vm::cptr<u32> ptr = vm::cast(seg.first); ptr.addr() < seg.first + seg.second; ptr++)
{
const u32 value = *ptr;
if (value % 4 == 0 && value >= func.addr && value < func_end2)
{
add_block(value);
}
}
}
// Set more block entries
std::for_each(addr_heap.lower_bound(func.addr), addr_heap.lower_bound(func_end2), add_block);
}
const bool was_empty = block_queue.empty();
@ -857,7 +927,7 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
}
const bool is_call = op.lk && target != iaddr;
const auto pfunc = is_call ? &add_func(target, 0, func.addr) : nullptr;
const auto pfunc = is_call ? &add_func(target, 0, 0) : nullptr;
if (pfunc && pfunc->blocks.empty())
{
@ -879,7 +949,7 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
else if (is_call || target < func.addr || target >= func_end)
{
// Add function call (including obvious tail call)
add_func(target, 0, func.addr);
add_func(target, 0, 0);
}
else
{
@ -1025,7 +1095,7 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
if (target < func.addr || target >= func.addr + func.size)
{
func.calls.emplace(target);
add_func(target, 0, func.addr);
add_func(target, func.toc ? func.toc + func.trampoline : 0, func.addr);
}
}
}
@ -1137,6 +1207,20 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
}
}
}
// Fill TOCs for trivial case
if (TOCs.size() == 1)
{
lib_toc = *TOCs.begin();
for (auto&& pair : funcs)
{
if (pair.second.toc == 0)
{
pair.second.toc = lib_toc;
}
}
}
// Convert map to vector (destructive)
std::vector<ppu_function> result;

View file

@ -33,6 +33,7 @@ struct ppu_function
std::map<u32, u32> blocks; // Basic blocks: addr -> size
std::set<u32> calls; // Set of called functions
std::set<u32> callers;
};
// PPU Module Information

View file

@ -346,6 +346,7 @@ static void ppu_initialize_modules()
// Register the HLE function directly
ppu_register_function_at(addr + 0, 4, hle_funcs[index]);
ppu_register_function_at(addr + 4, 4, nullptr);
}
// Set memory protection to read-only

View file

@ -96,6 +96,8 @@ cfg::map_entry<ppu_decoder_type> g_cfg_ppu_decoder(cfg::root.core, "PPU Decoder"
{ "Recompiler (LLVM)", ppu_decoder_type::llvm },
});
cfg::bool_entry g_cfg_ppu_debug(cfg::root.core, "PPU Debug");
cfg::bool_entry g_cfg_llvm_logs(cfg::root.core, "Save LLVM logs");
cfg::string_entry g_cfg_llvm_cpu(cfg::root.core, "Use LLVM CPU");
@ -133,6 +135,38 @@ static bool ppu_fallback(ppu_thread& ppu, ppu_opcode_t op)
}
ppu_ref(ppu.cia) = ppu_cache(ppu.cia);
if (g_cfg_ppu_debug)
{
LOG_ERROR(PPU, "Unregistered instruction: 0x%08x", op.opcode);
}
return false;
}
static std::unordered_map<u32, u32> s_ppu_toc;
static bool ppu_check_toc(ppu_thread& ppu, ppu_opcode_t op)
{
// Compare TOC with expected value
const auto found = s_ppu_toc.find(ppu.cia);
if (ppu.gpr[2] != found->second)
{
LOG_ERROR(PPU, "Unexpected TOC (0x%x, expected 0x%x)", ppu.gpr[2], found->second);
if (!ppu.state.test_and_set(cpu_flag::dbg_pause) && ppu.check_state())
{
return false;
}
}
// Fallback to the interpreter function
if (reinterpret_cast<decltype(&ppu_interpreter::UNK)>(std::uintptr_t{ppu_cache(ppu.cia)})(ppu, op))
{
ppu.cia += 4;
}
return false;
}
@ -168,7 +202,11 @@ extern void ppu_register_function_at(u32 addr, u32 size, ppu_function_t ptr)
if (!size)
{
LOG_ERROR(PPU, "ppu_register_function_at(0x%x): empty range", addr);
if (g_cfg_ppu_debug)
{
LOG_ERROR(PPU, "ppu_register_function_at(0x%x): empty range", addr);
}
return;
}
@ -875,7 +913,16 @@ extern void ppu_initialize(const ppu_module& info)
{
for (const auto& func : info.funcs)
{
ppu_register_function_at(func.addr, func.size, nullptr);
for (auto& block : func.blocks)
{
ppu_register_function_at(block.first, block.second, nullptr);
}
if (g_cfg_ppu_debug && func.size && func.toc != -1)
{
s_ppu_toc.emplace(func.addr, func.toc);
ppu_ref(func.addr) = ::narrow<u32>(reinterpret_cast<std::uintptr_t>(&ppu_check_toc));
}
}
return;