Files
ppsspp/Core/Debugger/SymbolMap.cpp
T
Henrik RydgårdandClaude Opus 5 ee8aaff6d2 Add SymbolMap unit tests, fix absolute symbols vanishing with no module loaded
SymbolMap had no coverage. It stores symbols relative to a module so they
survive that module being unloaded and reloaded elsewhere, and only symbols
belonging to a loaded module count as active - that indirection is where the
surprises are, so the tests concentrate on module lifetime and the shared label
table.

Writing them turned up a real bug. UpdateActiveSymbols() bailed out early when
activeModuleEnds was empty, as a "tiny optimization" for startup and shutdown,
having already cleared the active maps. But symbols with module index 0 are
absolute by design - they belong to no module, which is how you label a heap or
stack address - and the loops it skipped are exactly what keeps those alive.
So an absolute symbol disappeared as soon as the last module was unloaded, and
didn't exist at all before the first one was loaded. Dropping activeModuleEnds
from the early-out condition fixes it; the symbol-count half still gives the
intended fast path when there's nothing to do.

Tests cover function and data lookup by containing address, SetFunctionSize,
RemoveFunction/RemoveData, symbols surviving an unload/reload at a different
address, absolute (module 0) symbols, GetSymbolInfo/GetDescription, and Clear.
Two of them pin down behaviour that catches people out rather than asserting
it's right: AddLabel deliberately won't overwrite an existing label, and because
functions and data share one label table, renaming or removing via one affects
the other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-17 14:50:57 +02:00

1182 lines
31 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
// These functions tends to be slow in debug mode.
// Comment this out if debugging the symbol map itself.
#if defined(_MSC_VER) && defined(_DEBUG)
#pragma optimize("gty", on)
#endif
#include "ppsspp_config.h"
#ifdef _WIN32
#include "Common/CommonWindows.h"
#include <WindowsX.h>
#else
#include <unistd.h>
#endif
#include <algorithm>
#include <memory>
#include <string_view>
#include <unordered_map>
#include "zlib.h"
#include "ext/armips/Core/Assembler.h"
#include "Common/CommonTypes.h"
#include "Common/Log.h"
#include "Common/File/FileUtil.h"
#include "Common/StringUtils.h"
#include "Common/Buffer.h"
#include "Core/MemMap.h"
#include "Core/Config.h"
#include "Core/Debugger/SymbolMap.h"
SymbolMap *g_symbolMap;
void SymbolMap::SortSymbols() {
AssignFunctionIndices();
}
void SymbolMap::Clear() {
functions.clear();
labels.clear();
data.clear();
activeFunctions.clear();
activeLabels.clear();
activeData.clear();
activeModuleEnds.clear();
modules.clear();
activeNeedUpdate_ = false;
}
bool SymbolMap::LoadSymbolMap(const Path &filename) {
Clear();
// TODO(scoped): Use gzdopen instead.
#if defined(_WIN32) && defined(UNICODE)
gzFile f = gzopen_w(filename.ToWString().c_str(), "r");
#else
gzFile f = gzopen(filename.c_str(), "r");
#endif
if (f == Z_NULL)
return false;
//char temp[256];
//fgets(temp,255,f); //.text section layout
//fgets(temp,255,f); // Starting Virtual
//fgets(temp,255,f); // address Size address
//fgets(temp,255,f); // -----------------------
bool started = false;
bool hasModules = false;
while (!gzeof(f)) {
char line[512], temp[256] = {0};
char *p = gzgets(f, line, 512);
if (p == NULL)
break;
// Chop any newlines off.
for (size_t i = strlen(line) - 1; i > 0; i--) {
if (line[i] == '\r' || line[i] == '\n') {
line[i] = '\0';
}
}
if (strlen(line) < 4 || sscanf(line, "%255s", temp) != 1)
continue;
if (strcmp(temp,"UNUSED")==0) continue;
if (strcmp(temp,".text")==0) {started=true;continue;};
if (strcmp(temp,".init")==0) {started=true;continue;};
if (strcmp(temp,"Starting")==0) continue;
if (strcmp(temp,"extab")==0) continue;
if (strcmp(temp,".ctors")==0) break;
if (strcmp(temp,".dtors")==0) break;
if (strcmp(temp,".rodata")==0) continue;
if (strcmp(temp,".data")==0) continue;
if (strcmp(temp,".sbss")==0) continue;
if (strcmp(temp,".sdata")==0) continue;
if (strcmp(temp,".sdata2")==0) continue;
if (strcmp(temp,"address")==0) continue;
if (strcmp(temp,"-----------------------")==0) continue;
if (strcmp(temp,".sbss2")==0) break;
if (temp[1]==']') continue;
if (!started) continue;
u32 address = -1, size = 0, vaddress = -1;
int moduleIndex = 0;
int typeInt = ST_NONE;
SymbolType type;
char name[128] = {0};
if (sscanf(line, ".module %x %08x %08x %127c", (unsigned int *)&moduleIndex, &address, &size, name) >= 3) {
// Found a module definition.
ModuleEntry mod;
mod.index = moduleIndex;
strcpy(mod.name, name);
mod.start = address;
mod.size = size;
modules.push_back(mod);
hasModules = true;
continue;
}
const int matched = sscanf(line, "%08x %08x %x %i %127c", &address, &size, &vaddress, &typeInt, name);
if (matched < 1)
continue;
type = (SymbolType) typeInt;
if (!hasModules) {
if (!Memory::IsValidAddress(vaddress)) {
ERROR_LOG(Log::Loader, "Invalid address in symbol file: %08x (%s)", vaddress, name);
continue;
}
} else {
// The 3rd field is now used for the module index.
moduleIndex = vaddress;
vaddress = GetModuleAbsoluteAddr(address, moduleIndex);
if (!Memory::IsValidAddress(vaddress)) {
ERROR_LOG(Log::Loader, "Invalid address in symbol file: %08x (%s)", vaddress, name);
continue;
}
}
if (type == ST_DATA && size == 0)
size = 4;
// Ignore syscalls, will be recognized from stubs.
// Note: it's still useful to save these for grepping and importing into other tools.
if (strncmp(name, "zz_sce", 6) == 0)
continue;
// Also ignore unresolved imports, which will similarly be replaced.
if (strncmp(name, "zz_[UNK", 7) == 0)
continue;
if (!strcmp(name, ".text") || !strcmp(name, ".init") || strlen(name) <= 1) {
// Ignored
} else {
// Seems legit
switch (type) {
case ST_FUNCTION:
AddFunction(name, vaddress, size, moduleIndex);
break;
case ST_DATA:
AddData(vaddress,size,DATATYPE_BYTE, moduleIndex);
if (name[0] != 0)
AddLabel(name, vaddress, moduleIndex);
break;
case ST_NONE:
case ST_ALL:
// Shouldn't be possible.
break;
}
}
}
gzclose(f);
activeNeedUpdate_ = true;
SortSymbols();
return started;
}
bool SymbolMap::SaveSymbolMap(const Path &filename) const {
// Don't bother writing a blank file.
if (!File::Exists(filename) && functions.empty() && data.empty()) {
return true;
}
Buffer buf;
buf.Printf(".text\n");
for (const auto &module : modules) {
buf.Printf(".module %x %08x %08x %s\n", module.index, module.start, module.size, module.name);
}
for (const auto &[key, e] : functions) {
buf.Printf("%08x %08x %x %i %s\n", e.start, e.size, e.module, ST_FUNCTION, GetLabelNameRel(e.start, e.module));
}
for (const auto &[key, e] : data) {
buf.Printf("%08x %08x %x %i %s\n", e.start, e.size, e.module, ST_DATA, GetLabelNameRel(e.start, e.module));
}
std::string data;
buf.TakeAll(&data);
FILE *file = File::OpenCFile(filename, "wb");
if (file == nullptr) {
return false;
}
if (g_Config.bCompressSymbols) {
uInt out_size = 4096;
Bytef *out_data = (Bytef *)std::malloc(out_size);
if (out_data == nullptr) {
fclose(file);
return false;
}
z_stream strm;
strm.zalloc = nullptr;
strm.zfree = nullptr;
strm.opaque = nullptr;
if (deflateInit2(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
std::free(out_data);
fclose(file);
return false;
}
strm.next_in = (Bytef *)data.data();
strm.avail_in = (u32)data.size();
strm.next_out = out_data;
strm.avail_out = out_size;
int flush = Z_NO_FLUSH;
for (;;) {
int status = deflate(&strm, flush);
switch (status) {
case Z_OK:
case Z_STREAM_END:
if (strm.avail_out != out_size) {
fwrite(out_data, 1, out_size - strm.avail_out, file);
}
break;
case Z_BUF_ERROR:
{
std::free(out_data);
uInt new_out_size = 2 * out_size;
if (new_out_size < out_size) {
deflateEnd(&strm);
fclose(file);
return false;
}
out_size = new_out_size;
out_data = (Bytef *)std::malloc(out_size);
if (out_data == nullptr) {
deflateEnd(&strm);
fclose(file);
return false;
}
}
break;
default:
deflateEnd(&strm);
std::free(out_data);
fclose(file);
return false;
}
if (status == Z_STREAM_END) {
break;
}
if (strm.avail_in == 0) {
flush = Z_FINISH;
}
strm.next_out = out_data;
strm.avail_out = out_size;
}
deflateEnd(&strm);
std::free(out_data);
} else {
// Just plain write it.
fwrite(data.data(), 1, data.size(), file);
}
fclose(file);
return true;
}
bool SymbolMap::LoadNocashSym(const Path &filename) {
FILE *f = File::OpenCFile(filename, "r");
if (!f)
return false;
while (!feof(f)) {
char line[256], value[256] = {0};
char *p = fgets(line, 256, f);
if (p == NULL)
break;
u32 address;
if (sscanf(line, "%08X %255s", &address, value) != 2)
continue;
if (address == 0 && strcmp(value, "0") == 0)
continue;
if (value[0] == '.') {
// data directives
char* s = strchr(value, ':');
if (s != NULL) {
*s = 0;
u32 size = 0;
if (sscanf(s + 1, "%04X", &size) != 1)
continue;
if (strcasecmp(value, ".byt") == 0) {
AddData(address, size, DATATYPE_BYTE, 0);
} else if (strcasecmp(value, ".wrd") == 0) {
AddData(address, size, DATATYPE_HALFWORD, 0);
} else if (strcasecmp(value, ".dbl") == 0) {
AddData(address, size, DATATYPE_WORD, 0);
} else if (strcasecmp(value, ".asc") == 0) {
AddData(address, size, DATATYPE_ASCII, 0);
}
}
} else { // labels
unsigned int size = 1;
char *separator = strchr(value, ',');
if (separator != NULL) {
*separator = '\0';
sscanf(separator + 1, "%08X", &size);
}
if (size != 1) {
AddFunction(value, address, size, 0);
} else {
AddLabel(value, address, 0);
}
}
}
fclose(f);
return true;
}
bool SymbolMap::SaveNocashSym(const Path &filename) const {
// Don't bother writing a blank file.
if (!File::Exists(filename) && functions.empty() && data.empty()) {
return false;
}
FILE *f = File::OpenCFile(filename, "w");
if (!f)
return false;
// only write functions, the rest isn't really interesting
for (auto it = functions.begin(), end = functions.end(); it != end; ++it) {
const FunctionEntry& e = it->second;
fprintf(f, "%08X %s,%04X\n", GetModuleAbsoluteAddr(e.start,e.module), GetLabelNameRel(e.start, e.module), e.size);
}
fclose(f);
return true;
}
SymbolType SymbolMap::GetSymbolType(u32 address) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
if (activeFunctions.find(address) != activeFunctions.end())
return ST_FUNCTION;
if (activeData.find(address) != activeData.end())
return ST_DATA;
return ST_NONE;
}
bool SymbolMap::GetSymbolInfo(SymbolInfo *info, u32 address, SymbolType symmask) {
u32 functionAddress = INVALID_ADDRESS;
u32 dataAddress = INVALID_ADDRESS;
if (symmask & ST_FUNCTION) {
functionAddress = GetFunctionStart(address);
// If both are found, we always return the function, so just do that early.
if (functionAddress != INVALID_ADDRESS) {
if (info != NULL) {
info->type = ST_FUNCTION;
info->address = functionAddress;
info->size = GetFunctionSize(functionAddress);
info->moduleAddress = GetFunctionModuleAddress(functionAddress);
}
return true;
}
}
if (symmask & ST_DATA) {
dataAddress = GetDataStart(address);
if (dataAddress != INVALID_ADDRESS) {
if (info != NULL) {
info->type = ST_DATA;
info->address = dataAddress;
info->size = GetDataSize(dataAddress);
info->moduleAddress = GetDataModuleAddress(dataAddress);
}
return true;
}
}
return false;
}
u32 SymbolMap::GetNextSymbolAddress(u32 address, SymbolType symmask) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
const auto functionEntry = symmask & ST_FUNCTION ? activeFunctions.upper_bound(address) : activeFunctions.end();
const auto dataEntry = symmask & ST_DATA ? activeData.upper_bound(address) : activeData.end();
if (functionEntry == activeFunctions.end() && dataEntry == activeData.end())
return INVALID_ADDRESS;
u32 funcAddress = (functionEntry != activeFunctions.end()) ? functionEntry->first : 0xFFFFFFFF;
u32 dataAddress = (dataEntry != activeData.end()) ? dataEntry->first : 0xFFFFFFFF;
if (funcAddress <= dataAddress)
return funcAddress;
else
return dataAddress;
}
std::string SymbolMap::GetDescription(unsigned int address) {
std::string labelName;
u32 funcStart = GetFunctionStart(address);
if (funcStart != INVALID_ADDRESS) {
labelName = GetLabelName(funcStart);
} else {
u32 dataStart = GetDataStart(address);
if (dataStart != INVALID_ADDRESS)
labelName = GetLabelName(dataStart);
}
if (!labelName.empty())
return labelName;
char descriptionTemp[32];
snprintf(descriptionTemp, sizeof(descriptionTemp), "(%08x)", address);
return descriptionTemp;
}
std::vector<SymbolEntry> SymbolMap::GetAllActiveSymbols(SymbolType symmask) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
std::vector<SymbolEntry> result;
if (symmask & ST_FUNCTION) {
for (auto it = activeFunctions.begin(); it != activeFunctions.end(); it++) {
SymbolEntry entry;
entry.address = it->first;
entry.size = GetFunctionSize(entry.address);
const char* name = GetLabelName(entry.address);
if (name)
entry.name = name;
result.push_back(entry);
}
}
if (symmask & ST_DATA) {
for (auto it = activeData.begin(); it != activeData.end(); it++) {
SymbolEntry entry;
entry.address = it->first;
entry.size = GetDataSize(entry.address);
const char* name = GetLabelName(entry.address);
if (name)
entry.name = name;
result.push_back(entry);
}
}
return result;
}
void SymbolMap::AddModule(const char *name, u32 address, u32 size) {
for (auto &module : modules) {
if (equals(module.name, name)) {
// Just reactivate that one.
module.start = address;
module.size = size;
activeModuleEnds.emplace(module.start + module.size, module );
activeNeedUpdate_ = true;
return;
}
}
ModuleEntry mod;
truncate_cpy(mod.name, name);
mod.start = address;
mod.size = size;
mod.index = (int)modules.size() + 1;
modules.push_back(mod);
activeModuleEnds.emplace(mod.start + mod.size, mod);
activeNeedUpdate_ = true;
}
void SymbolMap::UnloadModule(u32 address, u32 size) {
activeModuleEnds.erase(address + size);
activeNeedUpdate_ = true;
}
u32 SymbolMap::GetModuleRelativeAddr(u32 address, int moduleIndex) const {
if (moduleIndex == -1) {
moduleIndex = GetModuleIndex(address);
}
for (const auto &module : modules) {
if (module.index == moduleIndex) {
return address - module.start;
}
}
return address;
}
u32 SymbolMap::GetModuleAbsoluteAddr(u32 relative, int moduleIndex) const {
for (const auto &module : modules) {
if (module.index == moduleIndex) {
return module.start + relative;
}
}
return relative;
}
int SymbolMap::GetModuleIndex(u32 address) const {
auto iter = activeModuleEnds.upper_bound(address);
if (iter == activeModuleEnds.end())
return -1;
return iter->second.index;
}
bool SymbolMap::IsModuleActive(int moduleIndex) {
if (moduleIndex == 0) {
return true;
}
for (const auto &module : activeModuleEnds) {
if (module.second.index == moduleIndex) {
return true;
}
}
return false;
}
std::vector<LoadedModuleInfo> SymbolMap::getAllModules() const {
std::vector<LoadedModuleInfo> result;
for (const auto &module : modules) {
LoadedModuleInfo m;
m.name = module.name;
m.address = module.start;
m.size = module.size;
u32 key = module.start + module.size;
m.active = activeModuleEnds.find(key) != activeModuleEnds.end();
result.push_back(m);
}
return result;
}
void SymbolMap::AddFunction(const char* name, u32 address, u32 size, int moduleIndex) {
if (moduleIndex == -1) {
moduleIndex = GetModuleIndex(address);
} else if (moduleIndex == 0) {
sawUnknownModule = true;
}
// Is there an existing one?
u32 relAddress = GetModuleRelativeAddr(address, moduleIndex);
auto symbolKey = std::make_pair(moduleIndex, relAddress);
auto existing = functions.find(symbolKey);
if (sawUnknownModule && existing == functions.end()) {
// Fall back: maybe it's got moduleIndex = 0.
existing = functions.find(std::make_pair(0, address));
}
if (existing != functions.end()) {
existing->second.size = size;
if (existing->second.module != moduleIndex) {
FunctionEntry func = existing->second;
func.start = relAddress;
func.module = moduleIndex;
functions.erase(existing);
functions[symbolKey] = func;
}
// Refresh the active item if it exists.
auto active = activeFunctions.find(address);
if (active != activeFunctions.end() && active->second.module == moduleIndex) {
activeFunctions.erase(active);
activeFunctions.emplace(address, existing->second);
}
} else {
FunctionEntry func;
func.start = relAddress;
func.size = size;
func.index = (int)functions.size();
func.module = moduleIndex;
functions[symbolKey] = func;
if (IsModuleActive(moduleIndex)) {
activeFunctions.emplace(address, func);
}
}
AddLabel(name, address, moduleIndex);
}
u32 SymbolMap::GetFunctionStart(u32 address) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
auto it = activeFunctions.upper_bound(address);
if (it == activeFunctions.end()) {
// check last element
auto rit = activeFunctions.rbegin();
if (rit != activeFunctions.rend()) {
u32 start = rit->first;
u32 size = rit->second.size;
if (start <= address && start+size > address)
return start;
}
// otherwise there's no function that contains this address
return INVALID_ADDRESS;
}
if (it != activeFunctions.begin()) {
it--;
u32 start = it->first;
u32 size = it->second.size;
if (start <= address && start+size > address)
return start;
}
return INVALID_ADDRESS;
}
u32 SymbolMap::FindPossibleFunctionAtAfter(u32 address) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
auto it = activeFunctions.lower_bound(address);
if (it == activeFunctions.end()) {
return (u32)-1;
}
return it->first;
}
u32 SymbolMap::GetFunctionSize(u32 startAddress) {
if (activeNeedUpdate_) {
// This is common, from the jit. Direct lookup is faster than updating active symbols.
auto mod = activeModuleEnds.lower_bound(startAddress);
std::pair<int, u32> funcKey;
if (mod == activeModuleEnds.end()) {
// Could still be mod 0, backwards compatibility.
if (!sawUnknownModule)
return INVALID_ADDRESS;
funcKey.first = 0;
funcKey.second = startAddress;
} else {
if (mod->second.start > startAddress)
return INVALID_ADDRESS;
funcKey.first = mod->second.index;
funcKey.second = startAddress - mod->second.start;
}
auto func = functions.find(funcKey);
if (func == functions.end())
return INVALID_ADDRESS;
return func->second.size;
}
auto it = activeFunctions.find(startAddress);
if (it == activeFunctions.end())
return INVALID_ADDRESS;
return it->second.size;
}
u32 SymbolMap::GetFunctionModuleAddress(u32 startAddress) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
auto it = activeFunctions.find(startAddress);
if (it == activeFunctions.end())
return INVALID_ADDRESS;
return GetModuleAbsoluteAddr(0, it->second.module);
}
int SymbolMap::GetFunctionNum(u32 address) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
u32 start = GetFunctionStart(address);
if (start == INVALID_ADDRESS)
return INVALID_ADDRESS;
auto it = activeFunctions.find(start);
if (it == activeFunctions.end())
return INVALID_ADDRESS;
return it->second.index;
}
void SymbolMap::AssignFunctionIndices() {
int index = 0;
for (auto mod = activeModuleEnds.begin(), modend = activeModuleEnds.end(); mod != modend; ++mod) {
int moduleIndex = mod->second.index;
auto begin = functions.lower_bound(std::make_pair(moduleIndex, 0));
auto end = functions.upper_bound(std::make_pair(moduleIndex, 0xFFFFFFFF));
for (auto it = begin; it != end; ++it) {
it->second.index = index++;
}
}
}
// Copies functions, labels and data to the active set depending on which modules are "active".
void SymbolMap::UpdateActiveSymbols() {
activeFunctions.clear();
activeLabels.clear();
activeData.clear();
// On startup and shutdown, we can skip the rest. Tiny optimization.
// Note: deliberately not skipping when only activeModuleEnds is empty. Symbols with module
// index 0 are absolute - they belong to no module by design (a label put on a heap or stack
// address, say) - and the loops below handle that case fine with no modules loaded. Bailing
// out here left activeData/activeLabels/activeFunctions cleared, so those symbols vanished
// whenever the last module was unloaded, and didn't exist before the first one was loaded.
if (functions.empty() && labels.empty() && data.empty()) {
return;
}
std::unordered_map<int, u32> activeModuleIndexes;
for (auto it = activeModuleEnds.begin(), end = activeModuleEnds.end(); it != end; ++it) {
activeModuleIndexes[it->second.index] = it->second.start;
}
for (auto it = functions.begin(), end = functions.end(); it != end; ++it) {
const auto mod = activeModuleIndexes.find(it->second.module);
if (it->second.module == 0) {
activeFunctions.emplace(it->second.start, it->second);
} else if (mod != activeModuleIndexes.end()) {
activeFunctions.emplace(mod->second + it->second.start, it->second);
}
}
for (auto it = labels.begin(), end = labels.end(); it != end; ++it) {
const auto mod = activeModuleIndexes.find(it->second.module);
if (it->second.module == 0) {
activeLabels.emplace(it->second.addr, it->second);
} else if (mod != activeModuleIndexes.end()) {
activeLabels.emplace(mod->second + it->second.addr, it->second);
}
}
for (auto it = data.begin(), end = data.end(); it != end; ++it) {
const auto mod = activeModuleIndexes.find(it->second.module);
if (it->second.module == 0) {
activeData.emplace(it->second.start, it->second);
} else if (mod != activeModuleIndexes.end()) {
activeData.emplace(mod->second + it->second.start, it->second);
}
}
AssignFunctionIndices();
activeNeedUpdate_ = false;
}
bool SymbolMap::SetFunctionSize(u32 startAddress, u32 newSize) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
auto funcInfo = activeFunctions.find(startAddress);
if (funcInfo != activeFunctions.end()) {
auto symbolKey = std::make_pair(funcInfo->second.module, funcInfo->second.start);
auto func = functions.find(symbolKey);
if (func != functions.end()) {
func->second.size = newSize;
activeFunctions.erase(funcInfo);
activeFunctions.emplace(startAddress, func->second);
}
}
// TODO: check for overlaps
return true;
}
bool SymbolMap::RemoveFunction(u32 startAddress, bool removeName) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
auto it = activeFunctions.find(startAddress);
if (it == activeFunctions.end())
return false;
auto symbolKey = std::make_pair(it->second.module, it->second.start);
auto it2 = functions.find(symbolKey);
if (it2 != functions.end()) {
functions.erase(it2);
}
activeFunctions.erase(it);
if (removeName) {
auto labelIt = activeLabels.find(startAddress);
if (labelIt != activeLabels.end()) {
symbolKey = std::make_pair(labelIt->second.module, labelIt->second.addr);
auto labelIt2 = labels.find(symbolKey);
if (labelIt2 != labels.end()) {
labels.erase(labelIt2);
}
activeLabels.erase(labelIt);
}
}
return true;
}
void SymbolMap::AddLabel(const char* name, u32 address, int moduleIndex) {
if (moduleIndex == -1) {
moduleIndex = GetModuleIndex(address);
} else if (moduleIndex == 0) {
sawUnknownModule = true;
}
// Is there an existing one?
u32 relAddress = GetModuleRelativeAddr(address, moduleIndex);
auto symbolKey = std::make_pair(moduleIndex, relAddress);
auto existing = labels.find(symbolKey);
if (sawUnknownModule && existing == labels.end()) {
// Fall back: maybe it's got moduleIndex = 0.
existing = labels.find(std::make_pair(0, address));
}
if (existing != labels.end()) {
// We leave an existing label alone, rather than overwriting.
// But we'll still upgrade it to the correct module / relative address.
if (existing->second.module != moduleIndex) {
LabelEntry label = existing->second;
label.addr = relAddress;
label.module = moduleIndex;
labels.erase(existing);
labels[symbolKey] = label;
// Refresh the active item if it exists.
auto active = activeLabels.find(address);
if (active != activeLabels.end() && active->second.module == moduleIndex) {
activeLabels.erase(active);
activeLabels.emplace(address, label);
}
}
} else {
LabelEntry label;
label.addr = relAddress;
label.module = moduleIndex;
truncate_cpy(label.name, name);
labels[symbolKey] = label;
if (IsModuleActive(moduleIndex)) {
activeLabels.emplace(address, label);
}
}
}
void SymbolMap::SetLabelName(const char* name, u32 address) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
auto labelInfo = activeLabels.find(address);
if (labelInfo == activeLabels.end()) {
AddLabel(name, address);
} else {
auto symbolKey = std::make_pair(labelInfo->second.module, labelInfo->second.addr);
auto label = labels.find(symbolKey);
if (label != labels.end()) {
truncate_cpy(label->second.name, name);
label->second.name[127] = 0;
// Refresh the active item if it exists.
auto active = activeLabels.find(address);
if (active != activeLabels.end() && active->second.module == label->second.module) {
activeLabels.erase(active);
activeLabels.emplace(address, label->second);
}
}
}
}
const char *SymbolMap::GetLabelName(u32 address) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
auto it = activeLabels.find(address);
if (it == activeLabels.end())
return NULL;
return it->second.name;
}
const char *SymbolMap::GetLabelNameRel(u32 relAddress, int moduleIndex) const {
auto it = labels.find(std::make_pair(moduleIndex, relAddress));
if (it == labels.end())
return NULL;
return it->second.name;
}
std::string SymbolMap::GetLabelString(u32 address) {
const char *label = GetLabelName(address);
if (label == NULL)
return "";
return label;
}
bool SymbolMap::GetLabelValue(const char* name, u32& dest) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
for (const auto &[key, label] : activeLabels) {
if (equalsNoCase(name, label.name)) {
dest = key;
return true;
}
}
return false;
}
void SymbolMap::AddData(u32 address, u32 size, DataType type, int moduleIndex) {
if (moduleIndex == -1) {
moduleIndex = GetModuleIndex(address);
} else if (moduleIndex == 0) {
sawUnknownModule = true;
}
// Is there an existing one?
u32 relAddress = GetModuleRelativeAddr(address, moduleIndex);
auto symbolKey = std::make_pair(moduleIndex, relAddress);
auto existing = data.find(symbolKey);
if (sawUnknownModule && existing == data.end()) {
// Fall back: maybe it's got moduleIndex = 0.
existing = data.find(std::make_pair(0, address));
}
if (existing != data.end()) {
existing->second.size = size;
existing->second.type = type;
if (existing->second.module != moduleIndex) {
DataEntry entry = existing->second;
entry.module = moduleIndex;
entry.start = relAddress;
data.erase(existing);
data[symbolKey] = entry;
}
// Refresh the active item if it exists.
auto active = activeData.find(address);
if (active != activeData.end() && active->second.module == moduleIndex) {
activeData.erase(active);
activeData.emplace(address, existing->second);
}
} else {
DataEntry entry;
entry.start = relAddress;
entry.size = size;
entry.type = type;
entry.module = moduleIndex;
data[symbolKey] = entry;
if (IsModuleActive(moduleIndex)) {
activeData.emplace(address, entry);
}
}
}
u32 SymbolMap::GetDataStart(u32 address) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
auto it = activeData.upper_bound(address);
if (it == activeData.end()) {
// check last element
auto rit = activeData.rbegin();
if (rit != activeData.rend()) {
u32 start = rit->first;
u32 size = rit->second.size;
if (start <= address && start + size > address) {
return start;
}
}
// otherwise there's no data that contains this address
return INVALID_ADDRESS;
}
if (it != activeData.begin()) {
it--;
u32 start = it->first;
u32 size = it->second.size;
if (start <= address && start + size > address) {
return start;
}
}
return INVALID_ADDRESS;
}
u32 SymbolMap::GetDataSize(u32 startAddress) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
auto it = activeData.find(startAddress);
if (it == activeData.end())
return INVALID_ADDRESS;
return it->second.size;
}
u32 SymbolMap::GetDataModuleAddress(u32 startAddress) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
auto it = activeData.find(startAddress);
if (it == activeData.end())
return INVALID_ADDRESS;
return GetModuleAbsoluteAddr(0, it->second.module);
}
DataType SymbolMap::GetDataType(u32 startAddress) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
auto it = activeData.find(startAddress);
if (it == activeData.end())
return DATATYPE_NONE;
return it->second.type;
}
bool SymbolMap::RemoveData(u32 startAddress, bool removeName) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
auto it = activeData.find(startAddress);
if (it == activeData.end())
return false;
auto symbolKey = std::make_pair(it->second.module, it->second.start);
auto it2 = data.find(symbolKey);
if (it2 != data.end()) {
data.erase(it2);
}
activeData.erase(it);
if (removeName) {
auto labelIt = activeLabels.find(startAddress);
if (labelIt != activeLabels.end()) {
symbolKey = std::make_pair(labelIt->second.module, labelIt->second.addr);
auto labelIt2 = labels.find(symbolKey);
if (labelIt2 != labels.end()) {
labels.erase(labelIt2);
}
activeLabels.erase(labelIt);
}
}
return true;
}
// Transforms the labels to lowercase when returning. Why?
void SymbolMap::GetLabels(std::vector<LabelDefinition> &dest) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
for (const auto &[key, label] : activeLabels) {
LabelDefinition entry;
entry.value = key;
std::string name = label.name;
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
entry.name = Identifier(name);
dest.push_back(entry);
}
}
#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
struct DefaultSymbol {
u32 address;
const char* name;
};
static const DefaultSymbol defaultSymbols[]= {
{ 0x08800000, "User memory" },
{ 0x08804000, "Default load address" },
{ 0x04000000, "VRAM" },
{ 0x88000000, "Kernel memory" },
{ 0x00010000, "Scratchpad" },
};
void SymbolMap::FillSymbolListBox(HWND listbox,SymbolType symType) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
wchar_t temp[256];
SendMessage(listbox, WM_SETREDRAW, FALSE, 0);
ListBox_ResetContent(listbox);
switch (symType) {
case ST_FUNCTION:
{
SendMessage(listbox, LB_INITSTORAGE, (WPARAM)activeFunctions.size(), (LPARAM)activeFunctions.size() * 30);
for (const auto &[key, function] : activeFunctions) {
const char* name = GetLabelName(key);
if (name != NULL)
wsprintf(temp, L"%S", name);
else
wsprintf(temp, L"0x%08X", key);
int index = ListBox_AddString(listbox, temp);
ListBox_SetItemData(listbox, index, key);
}
}
break;
case ST_DATA:
{
size_t count = ARRAYSIZE(defaultSymbols)+activeData.size();
SendMessage(listbox, LB_INITSTORAGE, (WPARAM)count, (LPARAM)count * 30);
for (int i = 0; i < ARRAYSIZE(defaultSymbols); i++) {
wsprintf(temp, L"0x%08X (%S)", defaultSymbols[i].address, defaultSymbols[i].name);
int index = ListBox_AddString(listbox,temp);
ListBox_SetItemData(listbox,index,defaultSymbols[i].address);
}
for (auto it = activeData.begin(), end = activeData.end(); it != end; ++it) {
const char* name = GetLabelName(it->first);
if (name != NULL)
wsprintf(temp, L"%S", name);
else
wsprintf(temp, L"0x%08X", it->first);
int index = ListBox_AddString(listbox,temp);
ListBox_SetItemData(listbox,index,it->first);
}
}
break;
case ST_NONE:
case ST_ALL:
break;
}
SendMessage(listbox, WM_SETREDRAW, TRUE, 0);
RedrawWindow(listbox, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
}
#endif