/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2013 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/cpu/ppc/ppc_scanner.h" #include #include #include "xenia/base/logging.h" #include "xenia/base/memory.h" #include "xenia/base/profiling.h" #include "xenia/cpu/ppc/ppc_decode_data.h" #include "xenia/cpu/ppc/ppc_frontend.h" #include "xenia/cpu/ppc/ppc_opcode_info.h" #include "xenia/cpu/processor.h" #if 0 #define LOGPPC(fmt, ...) XELOGCORE('p', fmt, ##__VA_ARGS__) #else #define LOGPPC(fmt, ...) \ do { \ } while (false) #endif namespace xe { namespace cpu { namespace ppc { PPCScanner::PPCScanner(PPCFrontend* frontend) : frontend_(frontend) {} PPCScanner::~PPCScanner() {} bool PPCScanner::IsRestGprLr(uint32_t address) { auto function = frontend_->processor()->QueryFunction(address); return function && function->behavior() == Function::Behavior::kEpilogReturn; } bool PPCScanner::Scan(GuestFunction* function, FunctionDebugInfo* debug_info) { // This is a simple basic block analyizer. It walks the start address to the // end address looking for branches. Each span of instructions between // branches is considered a basic block. When the last blr (that has no // branches to after it) is found the function is considered ended. If this // is before the expected end address then the function address range is // split up and the second half is treated as another function. Memory* memory = frontend_->memory(); LOGPPC("Analyzing function {:08X}...", function->address()); // For debug info, only if needed. uint32_t address_reference_count = 0; uint32_t instruction_result_count = 0; uint32_t start_address = static_cast(function->address()); uint32_t end_address = static_cast(function->end_address()); uint32_t address = start_address; uint32_t furthest_target = start_address; size_t blocks_found = 0; bool in_block = false; bool starts_with_mfspr_lr = false; while (true) { uint32_t code = xe::load_and_swap(memory->TranslateVirtual(address)); // If we fetched 0 assume that we somehow hit one of the awesome // 'no really we meant to end after that bl' functions. if (!code) { LOGPPC("function end {:08X} (0x00000000 read)", address); // Don't include the 0's. address -= 4; break; } auto opcode = LookupOpcode(code); PPCDecodeData d; d.address = address; d.code = code; // TODO(benvanik): switch on instruction metadata. ++address_reference_count; ++instruction_result_count; // Check if the function starts with a mfspr lr, as that's a good indication // of whether or not this is a normal function with a prolog/epilog. // Some valid leaf functions won't have this, but most will. if (address == start_address && opcode == PPCOpcode::mfspr && (((d.XFX.SPR() & 0x1F) << 5) | ((d.XFX.SPR() >> 5) & 0x1F)) == 8) { starts_with_mfspr_lr = true; } if (!in_block) { in_block = true; blocks_found++; } bool ends_fn = false; bool ends_block = false; if (opcode == PPCOpcode::kInvalid) { // Invalid instruction. // We can just ignore it because there's (very little)/no chance it'll // affect flow control. LOGPPC("Invalid instruction at {:08X}: {:08X}", address, code); } else if (code == 0x4E800020) { // blr -- unconditional branch to LR. // This is generally a return. if (furthest_target > address) { // Remaining targets within function, not end. LOGPPC("ignoring blr {:08X} (branch to {:08X})", address, furthest_target); } else { // Function end point. LOGPPC("function end {:08X}", address); ends_fn = true; } ends_block = true; } else if (code == 0x4E800420) { // bctr -- unconditional branch to CTR. // This is generally a jump to a function pointer (non-return). // This is almost always a jump table. // TODO(benvanik): decode jump tables. if (furthest_target > address) { // Remaining targets within function, not end. LOGPPC("ignoring bctr {:08X} (branch to {:08X})", address, furthest_target); } else { // Function end point. LOGPPC("function end {:08X}", address); ends_fn = true; } ends_block = true; } else if (opcode == PPCOpcode::bx) { // b/ba/bl/bla uint32_t target = d.I.ADDR(); if (d.I.LK()) { LOGPPC("bl {:08X} -> {:08X}", address, target); // Queue call target if needed. // GetOrInsertFunction(target); } else { LOGPPC("b {:08X} -> {:08X}", address, target); // If the target is back into the function and there's no further target // we are at the end of a function. // (Indirect branches may still go beyond, but no way of knowing). if (target >= start_address && target < address && furthest_target <= address) { LOGPPC("function end {:08X} (back b)", address); ends_fn = true; } // If the target is not a branch and it goes to before the current // address it's definitely a tail call. if (!ends_fn && target < start_address && furthest_target <= address) { LOGPPC("function end {:08X} (back b before addr)", address); ends_fn = true; } // If the target is a __restgprlr_* method it's the end of a function. // Note that sometimes functions stick this in a basic block *inside* // of the function somewhere, so ensure we don't have any branches over // it. if (!ends_fn && furthest_target <= address && IsRestGprLr(target)) { LOGPPC("function end {:08X} (__restgprlr_*)", address); ends_fn = true; } // Heuristic: if there's an unconditional branch in the first block of // the function it's likely a thunk. // Ex: // li r3, 0 // b KeBugCheck // This check may hit on functions that jump over data code, so only // trigger this check in leaf functions (no mfspr lr/prolog). if (!ends_fn && !starts_with_mfspr_lr && blocks_found == 1) { LOGPPC("HEURISTIC: ending at simple leaf thunk {:08X}", address); ends_fn = true; } // Heuristic: if this is an unconditional branch at the end of the // function (nothing jumps over us) and we are jumping forward there's // a good chance it's a tail call. // This may not be true if the code is jumping over data/etc. // TODO(benvanik): figure out how to do this reliably. This check as is // is too aggressive and turns a lot of valid branches into tail calls. // It seems like a lot of functions end up with some prologue bit then // jump deep inside only to jump back towards the top soon after. May // need something more complex than just a simple 1-pass system to // detect these, unless more signals can be found. /* if (!ends_fn && target > addr && furthest_target < addr) { LOGPPC("HEURISTIC: ending at tail call branch %.8X", addr); ends_fn = true; } */ if (!ends_fn && !IsRestGprLr(target)) { furthest_target = std::max(furthest_target, target); // TODO(benvanik): perhaps queue up for a speculative check? I think // we are running over tail-call functions here that branch to // somewhere else. // GetOrInsertFunction(target); } } ends_block = true; } else if (opcode == PPCOpcode::bcx) { // bc/bca/bcl/bcla uint32_t target = d.B.ADDR(); if (d.B.LK()) { LOGPPC("bcl {:08X} -> {:08X}", address, target); // Queue call target if needed. // TODO(benvanik): see if this is correct - not sure anyone makes // function calls with bcl. // GetOrInsertFunction(target); } else { LOGPPC("bc {:08X} -> {:08X}", address, target); // TODO(benvanik): GetOrInsertFunction? it's likely a BB if (!IsRestGprLr(target)) { furthest_target = std::max(furthest_target, target); } } ends_block = true; } else if (opcode == PPCOpcode::bclrx) { // bclr/bclrl if (d.XL.LK()) { LOGPPC("bclrl {:08X}", address); } else { LOGPPC("bclr {:08X}", address); } ends_block = true; } else if (opcode == PPCOpcode::bcctrx) { // bcctr/bcctrl if (d.XL.LK()) { LOGPPC("bcctrl {:08X}", address); } else { LOGPPC("bcctr {:08X}", address); } ends_block = true; } if (ends_block) { in_block = false; } if (ends_fn) { break; } address += 4; if (end_address && address > end_address) { // Hmm.... LOGPPC("Ran over function bounds! {:08X}-{:08X}", start_address, end_address); break; } } if (end_address && address + 4 < end_address) { // Ran under the expected value - since we probably got the initial bounds // from someplace valid (like method hints) this may indicate an error. // It's also possible that we guessed in hole-filling and there's another // function below this one. LOGPPC("Function ran under: {:08X}-{:08X} ended at {:08X}", start_address, end_address, address + 4); } function->set_end_address(address); // If there's spare bits at the end, split the function. // TODO(benvanik): splitting? // TODO(benvanik): find and record stack information // - look for __savegprlr_* and __restgprlr_* // - if present, flag function as needing a stack // - record prolog/epilog lengths/stack size/etc if (debug_info) { debug_info->set_address_reference_count(address_reference_count); debug_info->set_instruction_result_count(instruction_result_count); } LOGPPC("Finished analyzing {:08X}", start_address); return true; } std::vector PPCScanner::FindBlocks(GuestFunction* function) { Memory* memory = frontend_->memory(); std::map block_map; uint32_t start_address = function->address(); uint32_t end_address = function->end_address(); bool in_block = false; uint32_t block_start = 0; for (uint32_t address = start_address; address <= end_address; address += 4) { uint32_t code = xe::load_and_swap(memory->TranslateVirtual(address)); if (!code) { continue; } auto opcode = xe::cpu::ppc::LookupOpcode(code); if (!in_block) { in_block = true; block_start = address; } bool ends_block = false; if (code == 0x4E800020) { // blr -- unconditional branch to LR. ends_block = true; } else if (code == 0x4E800420) { // bctr -- unconditional branch to CTR. // This is almost always a jump table. // TODO(benvanik): decode jump tables. ends_block = true; } else if (opcode == PPCOpcode::bx) { // b/ba/bl/bla // uint32_t target = // (uint32_t)XEEXTS26(i.I.LI << 2) + (i.I.AA ? 0 : (int32_t)address); ends_block = true; } else if (opcode == PPCOpcode::bcx) { // bc/bca/bcl/bcla // uint32_t target = // (uint32_t)XEEXTS16(i.B.BD << 2) + (i.B.AA ? 0 : (int32_t)address); ends_block = true; } else if (opcode == PPCOpcode::bclrx) { // bclr/bclrl ends_block = true; } else if (opcode == PPCOpcode::bcctrx) { // bcctr/bcctrl ends_block = true; } if (ends_block) { in_block = false; block_map[block_start] = { block_start, address, }; } } if (in_block) { block_map[block_start] = { block_start, end_address, }; } std::vector blocks; for (auto it = block_map.begin(); it != block_map.end(); ++it) { blocks.push_back(it->second); } return blocks; } } // namespace ppc } // namespace cpu } // namespace xe