Files
ppsspp/Core/Debugger/SymbolMap.h
Henrik RydgårdandClaude Opus 5 daa18fc25a ImDebugger: fix stale symbol list after a game is reloaded
The disasm window cached the flattened symbol list and only rebuilt it when one
of three menu items said so. Nothing marked it dirty when a game booted or
exited, and a new SymbolMap is allocated per boot, so the list kept showing the
previous game's functions.

Give SymbolMap a version counter that every mutator bumps, and let the window
compare against it instead. The counter is process-wide rather than per-map, so
a fresh map can't hand out a version a cached copy already holds.

Also re-find the selected symbol by address after a rebuild (the index means
something else afterwards), and drop the unused symbol cache members in
ImMemWindow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SfY7iFJEjmRXf1XGrTs4MF
2026-08-27 10:43:08 +02:00

264 lines
11 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/.
#pragma once
#include <vector>
#include <map>
#include <string>
#include "Common/CommonTypes.h"
#include "Common/File/Path.h"
// Next plans for the symbol map
//
// Symbol maps should auto-load and auto-save. They should be per module with section offsets, and the ID
// should be the module name plus the hash of the code and data sections. This way
// symbols will not misapply to the wrong modules.
// We already have some support for module-specific symbols, but it's just not set up right, as far as I can tell.
enum SymbolType {
ST_NONE = 0,
ST_FUNCTION = 1,
ST_DATA = 2,
ST_ALL = 3,
};
struct SymbolInfo {
SymbolType type;
u32 address;
u32 size;
u32 moduleAddress;
};
struct SymbolEntry {
std::string name;
u32 address;
u32 size;
};
struct LoadedModuleInfo {
std::string name;
u32 address;
u32 size;
bool active;
};
enum DataType {
DATATYPE_NONE, DATATYPE_BYTE, DATATYPE_HALFWORD, DATATYPE_WORD, DATATYPE_ASCII
};
struct LabelDefinition;
#ifdef _WIN32
struct HWND__;
typedef struct HWND__ *HWND;
#endif
// SymbolMap keeps two parallel sets of tables for functions/labels/data:
// - The "master" tables (functions/labels/data below) are permanent: keyed by
// (moduleIndex, addressRelativeToModuleStart), they retain every symbol ever seen for
// every module ever loaded this session, even after a module unloads. This is what makes
// it safe for two unrelated modules to occupy the same address range at different points
// in a session (common - modules load/unload constantly) without losing or corrupting
// each other's symbols.
// - The "active" tables (activeFunctions/activeLabels/activeData) are a derived, read-only
// cache: just the master entries belonging to currently-loaded modules, flattened to plain
// absolute addresses. Nearly every query (GetFunctionStart, GetSymbolType, ...) reads only
// from these, so lookups are a single cheap map access with no per-call module resolution,
// and a stale/unloaded module's symbols can never shadow whatever's actually live right now.
// The active tables are rebuilt lazily by UpdateActiveSymbols() - AddModule/UnloadModule just
// set activeNeedUpdate_, and the rebuild happens on the next query.
//
// Module index 0 is reserved to mean "unknown module" and is always treated as active - it
// exists for backward compatibility with old flat (non-per-module) symbol files, and for
// symbols added without an explicit module.
class SymbolMap {
public:
SymbolMap();
// Changes every time anything in the map changes, so a UI that caches a flattened copy of
// it (see GetAllActiveSymbols) can tell that its copy went stale without being explicitly
// told. Values are unique across SymbolMap instances, not just within one - a new map is
// created for every game boot (see PSP_Init), so a per-instance counter starting over at
// zero would let a fresh map's version compare equal to a cached one from the last game.
uint32_t Version() const { return version_; }
void Clear();
void SortSymbols();
bool LoadSymbolMap(const Path &filename);
bool SaveSymbolMap(const Path &filename) const;
bool LoadNocashSym(const Path &filename);
bool SaveNocashSym(const Path &filename) const;
// Save/load just one module's symbols, e.g. for the WebSocket debugger's
// hle.module.saveSymbols/loadSymbols, or the auto-save/load hooks around module load/unload
// in Core/HLE/sceKernelModule.cpp (gated on g_Config.bAutoSaveLoadSymbols). Addresses inside
// the file are relative to the module's load address (like the master tables above), so a
// saved file stays valid however the module ends up positioned on a later run.
// The file is keyed by module name + crc, not by game - see GetModuleSymbolsPath - so it's
// deliberately shared by every game that happens to load the exact same module (common for
// kernel/driver modules, or a homebrew's own libraries). gameID/gameTitle are only recorded
// as an informational "last saved by" comment for a human reading the file; they don't
// affect the file path or matching.
// LoadModuleSymbols requires moduleIndex to currently be an active module (so relative
// addresses can be resolved), and overwrites any existing names for symbols it touches -
// the file is assumed to be the authoritative, possibly hand-edited version.
// Pass moduleIndex 0 for the symbols that aren't inside any module - see GetGameSymbolsPath.
// Only symbols a human chose are saved; if there are none, no file is written and any previous
// one is removed, so an unnamed module doesn't leave a file behind.
bool SaveModuleSymbols(int moduleIndex, const Path &filename, const std::string &gameID, const std::string &gameTitle) const;
bool LoadModuleSymbols(int moduleIndex, const Path &filename);
// Standard per-module symbol file path: <memstick>/PSP/SYSTEM/SYMBOLS/<moduleName>_<crc>.ppsym
static Path GetModuleSymbolsPath(const std::string &moduleName, u32 crc);
// Symbols that aren't inside any module (module index 0) are absolute addresses the user - or
// a loaded map file - attached to RAM directly: heap, stack, scratchpad, hardware registers.
// Those describe one game's own memory layout and are worthless to any other game, so unlike
// module symbols they're keyed by game rather than shared:
// <memstick>/PSP/SYSTEM/SYMBOLS/<gameID>_syms.ppsym
static Path GetGameSymbolsPath(const std::string &gameID);
// 0 if moduleIndex isn't known (never seen, not just inactive).
u32 GetModuleCrc(int moduleIndex) const;
SymbolType GetSymbolType(u32 address);
bool GetSymbolInfo(SymbolInfo *info, u32 address, SymbolType symmask = ST_FUNCTION);
u32 GetNextSymbolAddress(u32 address, SymbolType symmask);
std::string GetDescription(u32 address);
std::vector<SymbolEntry> GetAllActiveSymbols(SymbolType symmask);
#ifdef _WIN32
void FillSymbolListBox(HWND listbox, SymbolType symType);
#endif
void GetLabels(std::vector<LabelDefinition> &dest);
// crc is optional (0 = unknown) and lets a reload of the same-named module be told apart
// from a different binary that just happens to share a name (common with generic module
// names) - see AddModule's implementation comment. Passing 0 falls back to matching by
// name alone, same as before crc existed.
void AddModule(const char *name, u32 address, u32 size, u32 crc = 0);
void UnloadModule(u32 address, u32 size);
u32 GetModuleRelativeAddr(u32 address, int moduleIndex = -1) const;
u32 GetModuleAbsoluteAddr(u32 relative, int moduleIndex) const;
// Index of the currently *active* module containing address, or -1 if none does (including
// if address falls in a gap between active modules' ranges).
int GetModuleIndex(u32 address) const;
// Prefers a currently active module if the name is ambiguous; otherwise the most recently
// added module entry with that name (which may be inactive). -1 if never seen.
int GetModuleIndexByName(const std::string &name) const;
// Turns the moduleIndex an AddFunction/AddData/AddLabel caller passed into one that's safe to
// store: -1 means "work it out from the address", and an address in no module is module 0
// ("absolute"), never -1. See the implementation for why that distinction matters.
int ResolveModuleIndex(u32 address, int moduleIndex);
bool IsModuleActive(int moduleIndex);
std::vector<LoadedModuleInfo> getAllModules() const;
// updateName controls whether an existing label at this address gets its name overwritten -
// see AddLabel.
void AddFunction(const char* name, u32 address, u32 size, int moduleIndex = -1, bool updateName = false);
u32 GetFunctionStart(u32 address);
int GetFunctionNum(u32 address);
u32 GetFunctionSize(u32 startAddress);
u32 GetFunctionModuleAddress(u32 startAddress);
bool SetFunctionSize(u32 startAddress, u32 newSize);
bool RemoveFunction(u32 startAddress, bool removeName);
// Search for the first address their may be a function after address.
// Only valid for currently loaded modules. Not guaranteed there will be a function.
u32 FindPossibleFunctionAtAfter(u32 address);
// By default, an existing label's name is left alone (first writer wins) - this protects a
// deliberately-assigned name from being clobbered by a later, lower-confidence automatic
// detection pass. Pass updateName=true to override this for a source that should take
// priority, e.g. restoring symbols explicitly saved for this module.
void AddLabel(const char* name, u32 address, int moduleIndex = -1, bool updateName = false);
std::string GetLabelString(u32 address);
void SetLabelName(const char* name, u32 address);
bool GetLabelValue(const char* name, u32& dest);
void AddData(u32 address, u32 size, DataType type, int moduleIndex = -1);
u32 GetDataStart(u32 address);
u32 GetDataSize(u32 startAddress);
u32 GetDataModuleAddress(u32 startAddress);
DataType GetDataType(u32 startAddress);
bool RemoveData(u32 startAddress, bool removeName);
static const u32 INVALID_ADDRESS = (u32)-1;
void UpdateActiveSymbols();
private:
// Call from anything that adds, removes, renames or moves a symbol or module.
void Bump();
void AssignFunctionIndices();
const char *GetLabelName(u32 address);
const char *GetLabelNameRel(u32 relAddress, int moduleIndex) const;
struct FunctionEntry {
u32 start;
u32 size;
int index;
int module;
};
struct LabelEntry {
u32 addr;
int module;
char name[128];
};
struct DataEntry {
DataType type;
u32 start;
u32 size;
int module;
};
struct ModuleEntry {
// Note: this index is +1, 0 matches any for backwards-compat.
int index;
u32 start;
u32 size;
char name[128];
// 0 = unknown. See AddModule.
u32 crc = 0;
};
// These are flattened, read-only copies of the actual data in active modules only.
std::map<u32, const FunctionEntry> activeFunctions;
std::map<u32, const LabelEntry> activeLabels;
std::map<u32, const DataEntry> activeData;
bool activeNeedUpdate_ = false;
// This is indexed by the end address of the module.
std::map<u32, const ModuleEntry> activeModuleEnds;
// Module ID, index
typedef std::pair<int, u32> SymbolKey;
// These are indexed by the module id and relative address in the module.
std::map<SymbolKey, FunctionEntry> functions;
std::map<SymbolKey, LabelEntry> labels;
std::map<SymbolKey, DataEntry> data;
std::vector<ModuleEntry> modules;
uint32_t version_ = 0;
bool sawUnknownModule = false;
};
extern SymbolMap *g_symbolMap;