mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-31 18:12:35 +01:00
b9aad3310e
PowerPC.h at this point is pretty much a general glob of stuff, and it's unfortunate, since it means pulling in a lot of unrelated header dependencies and a bunch of other things that don't need to be seen by things that just want to read memory. Breaking this out into its own header keeps all the MMU-related stuff together and also limits the amount of header dependencies being included (the primary motivation for this being the former reason).
501 lines
15 KiB
C++
501 lines
15 KiB
C++
// Copyright 2008 Dolphin Emulator Project
|
|
// Licensed under GPLv2+
|
|
// Refer to the license.txt file included.
|
|
|
|
#include "Core/PowerPC/PPCSymbolDB.h"
|
|
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/File.h"
|
|
#include "Common/Logging/Log.h"
|
|
#include "Common/StringUtil.h"
|
|
#include "Core/PowerPC/MMU.h"
|
|
#include "Core/PowerPC/PPCAnalyst.h"
|
|
#include "Core/PowerPC/PowerPC.h"
|
|
#include "Core/PowerPC/SignatureDB/SignatureDB.h"
|
|
|
|
PPCSymbolDB g_symbolDB;
|
|
|
|
PPCSymbolDB::PPCSymbolDB() : debugger{&PowerPC::debug_interface}
|
|
{
|
|
}
|
|
|
|
PPCSymbolDB::~PPCSymbolDB() = default;
|
|
|
|
// Adds the function to the list, unless it's already there
|
|
Symbol* PPCSymbolDB::AddFunction(u32 start_addr)
|
|
{
|
|
// It's already in the list
|
|
if (functions.find(start_addr) != functions.end())
|
|
return nullptr;
|
|
|
|
Symbol symbol;
|
|
if (!PPCAnalyst::AnalyzeFunction(start_addr, symbol))
|
|
return nullptr;
|
|
|
|
functions[start_addr] = std::move(symbol);
|
|
Symbol* ptr = &functions[start_addr];
|
|
ptr->type = Symbol::Type::Function;
|
|
checksumToFunction[ptr->hash].insert(ptr);
|
|
return ptr;
|
|
}
|
|
|
|
void PPCSymbolDB::AddKnownSymbol(u32 startAddr, u32 size, const std::string& name,
|
|
Symbol::Type type)
|
|
{
|
|
auto iter = functions.find(startAddr);
|
|
if (iter != functions.end())
|
|
{
|
|
// already got it, let's just update name, checksum & size to be sure.
|
|
Symbol* tempfunc = &iter->second;
|
|
tempfunc->Rename(name);
|
|
tempfunc->hash = HashSignatureDB::ComputeCodeChecksum(startAddr, startAddr + size - 4);
|
|
tempfunc->type = type;
|
|
tempfunc->size = size;
|
|
}
|
|
else
|
|
{
|
|
// new symbol. run analyze.
|
|
Symbol tf;
|
|
tf.Rename(name);
|
|
tf.type = type;
|
|
tf.address = startAddr;
|
|
if (tf.type == Symbol::Type::Function)
|
|
{
|
|
PPCAnalyst::AnalyzeFunction(startAddr, tf, size);
|
|
// Do not truncate symbol when a size is expected
|
|
if (size != 0 && tf.size != size)
|
|
{
|
|
WARN_LOG(SYMBOLS, "Analysed symbol (%s) size mismatch, %u expected but %u computed",
|
|
name.c_str(), size, tf.size);
|
|
tf.size = size;
|
|
}
|
|
checksumToFunction[tf.hash].insert(&functions[startAddr]);
|
|
}
|
|
else
|
|
{
|
|
tf.size = size;
|
|
}
|
|
functions[startAddr] = tf;
|
|
}
|
|
}
|
|
|
|
Symbol* PPCSymbolDB::GetSymbolFromAddr(u32 addr)
|
|
{
|
|
auto it = functions.lower_bound(addr);
|
|
if (it == functions.end())
|
|
return nullptr;
|
|
|
|
// If the address is exactly the start address of a symbol, we're done.
|
|
if (it->second.address == addr)
|
|
return &it->second;
|
|
|
|
// Otherwise, check whether the address is within the bounds of a symbol.
|
|
if (it != functions.begin())
|
|
--it;
|
|
if (addr >= it->second.address && addr < it->second.address + it->second.size)
|
|
return &it->second;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::string PPCSymbolDB::GetDescription(u32 addr)
|
|
{
|
|
Symbol* symbol = GetSymbolFromAddr(addr);
|
|
if (symbol)
|
|
return symbol->name;
|
|
else
|
|
return " --- ";
|
|
}
|
|
|
|
void PPCSymbolDB::FillInCallers()
|
|
{
|
|
for (auto& p : functions)
|
|
{
|
|
p.second.callers.clear();
|
|
}
|
|
|
|
for (auto& entry : functions)
|
|
{
|
|
Symbol& f = entry.second;
|
|
for (const SCall& call : f.calls)
|
|
{
|
|
const SCall new_call(entry.first, call.callAddress);
|
|
const u32 function_address = call.function;
|
|
|
|
auto func_iter = functions.find(function_address);
|
|
if (func_iter != functions.end())
|
|
{
|
|
Symbol& called_function = func_iter->second;
|
|
called_function.callers.push_back(new_call);
|
|
}
|
|
else
|
|
{
|
|
// LOG(SYMBOLS, "FillInCallers tries to fill data in an unknown function 0x%08x.",
|
|
// FunctionAddress);
|
|
// TODO - analyze the function instead.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PPCSymbolDB::PrintCalls(u32 funcAddr) const
|
|
{
|
|
const auto iter = functions.find(funcAddr);
|
|
if (iter == functions.end())
|
|
{
|
|
WARN_LOG(SYMBOLS, "Symbol does not exist");
|
|
return;
|
|
}
|
|
|
|
const Symbol& f = iter->second;
|
|
DEBUG_LOG(SYMBOLS, "The function %s at %08x calls:", f.name.c_str(), f.address);
|
|
for (const SCall& call : f.calls)
|
|
{
|
|
const auto n = functions.find(call.function);
|
|
if (n != functions.end())
|
|
{
|
|
DEBUG_LOG(SYMBOLS, "* %08x : %s", call.callAddress, n->second.name.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void PPCSymbolDB::PrintCallers(u32 funcAddr) const
|
|
{
|
|
const auto iter = functions.find(funcAddr);
|
|
if (iter == functions.end())
|
|
return;
|
|
|
|
const Symbol& f = iter->second;
|
|
DEBUG_LOG(SYMBOLS, "The function %s at %08x is called by:", f.name.c_str(), f.address);
|
|
for (const SCall& caller : f.callers)
|
|
{
|
|
const auto n = functions.find(caller.function);
|
|
if (n != functions.end())
|
|
{
|
|
DEBUG_LOG(SYMBOLS, "* %08x : %s", caller.callAddress, n->second.name.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void PPCSymbolDB::LogFunctionCall(u32 addr)
|
|
{
|
|
auto iter = functions.find(addr);
|
|
if (iter == functions.end())
|
|
return;
|
|
|
|
Symbol& f = iter->second;
|
|
f.numCalls++;
|
|
}
|
|
|
|
// The use case for handling bad map files is when you have a game with a map file on the disc,
|
|
// but you can't tell whether that map file is for the particular release version used in that game,
|
|
// or when you know that the map file is not for that build, but perhaps half the functions in the
|
|
// map file are still at the correct locations. Which are both common situations. It will load any
|
|
// function names and addresses that have a BLR before the start and at the end, but ignore any that
|
|
// don't, and then tell you how many were good and how many it ignored. That way you either find out
|
|
// it is all good and use it, find out it is partly good and use the good part, or find out that
|
|
// only
|
|
// a handful of functions lined up by coincidence and then you can clear the symbols. In the future
|
|
// I
|
|
// want to make it smarter, so it checks that there are no BLRs in the middle of the function
|
|
// (by checking the code length), and also make it cope with added functions in the middle or work
|
|
// based on the order of the functions and their approximate length. Currently that process has to
|
|
// be
|
|
// done manually and is very tedious.
|
|
// The use case for separate handling of map files that aren't bad is that you usually want to also
|
|
// load names that aren't functions(if included in the map file) without them being rejected as
|
|
// invalid.
|
|
// You can see discussion about these kinds of issues here :
|
|
// https://forums.oculus.com/viewtopic.php?f=42&t=11241&start=580
|
|
// https://m2k2.taigaforum.com/post/metroid_prime_hacking_help_25.html#metroid_prime_hacking_help_25
|
|
|
|
// This one can load both leftover map files on game discs (like Zelda), and mapfiles
|
|
// produced by SaveSymbolMap below.
|
|
// bad=true means carefully load map files that might not be from exactly the right version
|
|
bool PPCSymbolDB::LoadMap(const std::string& filename, bool bad)
|
|
{
|
|
File::IOFile f(filename, "r");
|
|
if (!f)
|
|
return false;
|
|
|
|
// Two columns are used by Super Smash Bros. Brawl Korean map file
|
|
// Three columns are commonly used
|
|
// Four columns are used in American Mensa Academy map files and perhaps other games
|
|
int column_count = 0;
|
|
int good_count = 0;
|
|
int bad_count = 0;
|
|
|
|
char line[512];
|
|
std::string section_name;
|
|
while (fgets(line, 512, f.GetHandle()))
|
|
{
|
|
size_t length = strlen(line);
|
|
if (length < 4)
|
|
continue;
|
|
|
|
if (length == 34 && strcmp(line, " address Size address offset\n") == 0)
|
|
{
|
|
column_count = 4;
|
|
continue;
|
|
}
|
|
|
|
char temp[256];
|
|
sscanf(line, "%255s", temp);
|
|
|
|
if (strcmp(temp, "UNUSED") == 0)
|
|
continue;
|
|
|
|
// Support CodeWarrior and Dolphin map
|
|
if (StringEndsWith(line, " section layout\n") || strcmp(temp, ".text") == 0 ||
|
|
strcmp(temp, ".init") == 0)
|
|
{
|
|
section_name = temp;
|
|
continue;
|
|
}
|
|
|
|
// Skip four columns' header.
|
|
//
|
|
// Four columns example:
|
|
//
|
|
// .text section layout
|
|
// Starting Virtual
|
|
// address Size address
|
|
// -----------------------
|
|
if (strcmp(temp, "Starting") == 0)
|
|
continue;
|
|
if (strcmp(temp, "address") == 0)
|
|
continue;
|
|
if (strcmp(temp, "-----------------------") == 0)
|
|
continue;
|
|
|
|
// Skip link map.
|
|
//
|
|
// Link map example:
|
|
//
|
|
// Link map of __start
|
|
// 1] __start(func, weak) found in os.a __start.c
|
|
// 2] __init_registers(func, local) found in os.a __start.c
|
|
// 3] _stack_addr found as linker generated symbol
|
|
// ...
|
|
// 10] EXILock(func, global) found in exi.a EXIBios.c
|
|
if (StringEndsWith(temp, "]"))
|
|
continue;
|
|
|
|
// TODO - Handle/Write a parser for:
|
|
// - Memory map
|
|
// - Link map
|
|
// - Linker generated symbols
|
|
if (section_name.empty())
|
|
continue;
|
|
|
|
// Detect two columns with three columns fallback
|
|
if (column_count == 0)
|
|
{
|
|
const std::string stripped_line = StripSpaces(line);
|
|
if (std::count(stripped_line.begin(), stripped_line.end(), ' ') == 1)
|
|
column_count = 2;
|
|
else
|
|
column_count = 3;
|
|
}
|
|
|
|
u32 address, vaddress, size, offset, alignment;
|
|
char name[512], container[512];
|
|
if (column_count == 4)
|
|
{
|
|
// sometimes there is no alignment value, and sometimes it is because it is an entry of
|
|
// something else
|
|
if (length > 37 && line[37] == ' ')
|
|
{
|
|
alignment = 0;
|
|
sscanf(line, "%08x %08x %08x %08x %511s", &address, &size, &vaddress, &offset, name);
|
|
char* s = strstr(line, "(entry of ");
|
|
if (s)
|
|
{
|
|
sscanf(s + 10, "%511s", container);
|
|
char* s2 = (strchr(container, ')'));
|
|
if (s2 && container[0] != '.')
|
|
{
|
|
s2[0] = '\0';
|
|
strcat(container, "::");
|
|
strcat(container, name);
|
|
strcpy(name, container);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sscanf(line, "%08x %08x %08x %08x %i %511s", &address, &size, &vaddress, &offset,
|
|
&alignment, name);
|
|
}
|
|
}
|
|
else if (column_count == 3)
|
|
{
|
|
// some entries in the table have a function name followed by " (entry of " followed by a
|
|
// container name, followed by ")"
|
|
// instead of a space followed by a number followed by a space followed by a name
|
|
if (length > 27 && line[27] != ' ' && strstr(line, "(entry of "))
|
|
{
|
|
alignment = 0;
|
|
sscanf(line, "%08x %08x %08x %511s", &address, &size, &vaddress, name);
|
|
char* s = strstr(line, "(entry of ");
|
|
if (s)
|
|
{
|
|
sscanf(s + 10, "%511s", container);
|
|
char* s2 = (strchr(container, ')'));
|
|
if (s2 && container[0] != '.')
|
|
{
|
|
s2[0] = '\0';
|
|
strcat(container, "::");
|
|
strcat(container, name);
|
|
strcpy(name, container);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sscanf(line, "%08x %08x %08x %i %511s", &address, &size, &vaddress, &alignment, name);
|
|
}
|
|
}
|
|
else if (column_count == 2)
|
|
{
|
|
sscanf(line, "%08x %511s", &address, name);
|
|
vaddress = address;
|
|
size = 0;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
const char* namepos = strstr(line, name);
|
|
if (namepos != nullptr) // would be odd if not :P
|
|
strcpy(name, namepos);
|
|
name[strlen(name) - 1] = 0;
|
|
if (name[strlen(name) - 1] == '\r')
|
|
name[strlen(name) - 1] = 0;
|
|
|
|
// Check if this is a valid entry.
|
|
if (strlen(name) > 0)
|
|
{
|
|
// Can't compute the checksum if not in RAM
|
|
bool good = !bad && PowerPC::HostIsInstructionRAMAddress(vaddress) &&
|
|
PowerPC::HostIsInstructionRAMAddress(vaddress + size - 4);
|
|
if (!good)
|
|
{
|
|
// check for BLR before function
|
|
PowerPC::TryReadInstResult read_result = PowerPC::TryReadInstruction(vaddress - 4);
|
|
if (read_result.valid && read_result.hex == 0x4e800020)
|
|
{
|
|
// check for BLR at end of function
|
|
read_result = PowerPC::TryReadInstruction(vaddress + size - 4);
|
|
good = read_result.valid && read_result.hex == 0x4e800020;
|
|
}
|
|
}
|
|
if (good)
|
|
{
|
|
++good_count;
|
|
if (section_name == ".text" || section_name == ".init")
|
|
AddKnownSymbol(vaddress, size, name, Symbol::Type::Function);
|
|
else
|
|
AddKnownSymbol(vaddress, size, name, Symbol::Type::Data);
|
|
}
|
|
else
|
|
{
|
|
++bad_count;
|
|
}
|
|
}
|
|
}
|
|
|
|
Index();
|
|
NOTICE_LOG(SYMBOLS, "%d symbols loaded, %d symbols ignored.", good_count, bad_count);
|
|
return true;
|
|
}
|
|
|
|
// Save symbol map similar to CodeWarrior's map file
|
|
bool PPCSymbolDB::SaveSymbolMap(const std::string& filename) const
|
|
{
|
|
File::IOFile f(filename, "w");
|
|
if (!f)
|
|
return false;
|
|
|
|
std::vector<const Symbol*> function_symbols;
|
|
std::vector<const Symbol*> data_symbols;
|
|
|
|
for (const auto& function : functions)
|
|
{
|
|
const Symbol& symbol = function.second;
|
|
if (symbol.type == Symbol::Type::Function)
|
|
function_symbols.push_back(&symbol);
|
|
else
|
|
data_symbols.push_back(&symbol);
|
|
}
|
|
|
|
// Write .text section
|
|
fprintf(f.GetHandle(), ".text section layout\n");
|
|
for (const auto& symbol : function_symbols)
|
|
{
|
|
// Write symbol address, size, virtual address, alignment, name
|
|
fprintf(f.GetHandle(), "%08x %08x %08x %i %s\n", symbol->address, symbol->size, symbol->address,
|
|
0, symbol->name.c_str());
|
|
}
|
|
|
|
// Write .data section
|
|
fprintf(f.GetHandle(), "\n.data section layout\n");
|
|
for (const auto& symbol : data_symbols)
|
|
{
|
|
// Write symbol address, size, virtual address, alignment, name
|
|
fprintf(f.GetHandle(), "%08x %08x %08x %i %s\n", symbol->address, symbol->size, symbol->address,
|
|
0, symbol->name.c_str());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Save code map (won't work if Core is running)
|
|
//
|
|
// Notes:
|
|
// - Dolphin doesn't load back code maps
|
|
// - It's a custom code map format
|
|
bool PPCSymbolDB::SaveCodeMap(const std::string& filename) const
|
|
{
|
|
constexpr int SYMBOL_NAME_LIMIT = 30;
|
|
File::IOFile f(filename, "w");
|
|
if (!f)
|
|
return false;
|
|
|
|
// Write ".text" at the top
|
|
fprintf(f.GetHandle(), ".text\n");
|
|
|
|
u32 next_address = 0;
|
|
for (const auto& function : functions)
|
|
{
|
|
const Symbol& symbol = function.second;
|
|
|
|
// Skip functions which are inside bigger functions
|
|
if (symbol.address + symbol.size <= next_address)
|
|
{
|
|
// At least write the symbol name and address
|
|
fprintf(f.GetHandle(), "// %08x beginning of %s\n", symbol.address, symbol.name.c_str());
|
|
continue;
|
|
}
|
|
|
|
// Write the symbol full name
|
|
fprintf(f.GetHandle(), "\n%s:\n", symbol.name.c_str());
|
|
next_address = symbol.address + symbol.size;
|
|
|
|
// Write the code
|
|
for (u32 address = symbol.address; address < next_address; address += 4)
|
|
{
|
|
const std::string disasm = debugger->Disassemble(address);
|
|
fprintf(f.GetHandle(), "%08x %-*.*s %s\n", address, SYMBOL_NAME_LIMIT, SYMBOL_NAME_LIMIT,
|
|
symbol.name.c_str(), disasm.c_str());
|
|
}
|
|
}
|
|
return true;
|
|
}
|