Files
Xenia-Canary/src/xenia/cpu/ppc/ppc_scanner.cc
2020-04-07 16:09:41 -05:00

370 lines
12 KiB
C++

/**
******************************************************************************
* 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 <algorithm>
#include <map>
#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<uint32_t>(function->address());
uint32_t end_address = static_cast<uint32_t>(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<uint32_t>(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<BlockInfo> PPCScanner::FindBlocks(GuestFunction* function) {
Memory* memory = frontend_->memory();
std::map<uint32_t, BlockInfo> 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<uint32_t>(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<BlockInfo> 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