Files
ppsspp/Core/Debugger/SymbolMap.cpp
T
Henrik RydgårdandClaude Opus 5 05f5668dfe Save symbols outside any module to a per-game file, and only save real names
Module symbols are keyed by module+crc so they're shared by every game that
loads that module. But symbols the user attaches to addresses that aren't in
any module - the heap, the stack, scratchpad, a hardware register, typically
after a memory.search - describe one game's own memory layout and are worthless
to any other game. Those now go to PSP/SYSTEM/SYMBOLS/<gameID>_syms.ppsym.

They're module index 0 ("absolute"), which already round-trips through the
existing per-module code: GetModuleRelativeAddr/GetModuleAbsoluteAddr are
identity for it, so the file format is unchanged, just with absolute addresses.
SaveModuleSymbols only needed to stop requiring a ModuleEntry. Auto-load/save
hang off CPU_Init/CPU_Shutdown rather than module load/unload, gated on the same
bAutoSaveLoadSymbols setting - and deliberately not on SYSPROP_HAS_DEBUGGER,
which only the Windows port reports true for, so LoadSymbolsIfSupported next to
it does nothing at all on headless. hle.game.saveSymbols/loadSymbols expose it.

Four things found while doing it:

- Symbols outside any module were being dropped on the floor. AddFunction/
  AddData/AddLabel take moduleIndex -1 as "work it out", pass it to
  GetModuleIndex(), and store whatever comes back - but that returns -1 when no
  module contains the address, and -1 is never an active module, so the symbol
  never reached the active maps: invisible to every lookup and to any save.
  hle.data.add had spotted this and normalized -1 to 0 locally; nothing else
  did, so e.g. hle.func.add outside a module silently did nothing. Fixed
  centrally in a new ResolveModuleIndex() the three of them share.
  (This only became reachable with the GetModuleIndex() fix in 29a38af37e -
  before that it returned a wrong-but-valid module index instead.)

- The saved files were almost entirely noise. Every function the analyzer finds
  is named z_un_<addr> and every import stub zz_<name>, both regenerated from
  scratch on each load. One real module wrote 13KB - 443 unnamed functions and
  64 stub names - for the four names a human had actually chosen. Worse, on the
  next run those were loaded back as authoritative and would beat the module's
  own symbols to the address. Now only names that aren't regenerated get saved,
  and a module with none writes no file at all (and removes a stale one, so
  deleting a symbol sticks). That module's file went 13020 -> 81 bytes.

- LoadModuleSymbols trusted the addresses in the file. It's meant to be
  hand-edited and can outlive the build it came from, so relative addresses past
  the end of the module are now skipped with a warning instead of landing at
  nonsense addresses.

- AddFunction and AddData both erased the map entry they were updating and then
  read back through the now-dangling iterator to refresh the active copy. Only
  latent: the refresh is guarded on the active copy's module matching the new
  one, which is false exactly when the erase happens. Re-point the iterator at
  the entry's new home instead, so it can't rot if that guard ever changes.
  AddLabel already did the equivalent correctly, via a local copy.

Filename sanitizing goes through SanitizeString with a new FileName restriction
rather than being open-coded; unlike the existing restrictions it substitutes
'_' instead of dropping, so two module names can't collapse onto one file.

Verified end to end on headless with cpu_alu.prx: named a function inside the
module and data/functions in scratchpad and the heap, let it exit, checked both
files, rebooted and confirmed all of it came back at the right addresses.
Unit tests 51/51, pspautotests 314/314.

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

1511 lines
44 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"
#include "Core/Util/PathUtil.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;
}
static const char *DataTypeName(DataType type) {
switch (type) {
case DATATYPE_BYTE: return "byte";
case DATATYPE_HALFWORD: return "halfword";
case DATATYPE_WORD: return "word";
case DATATYPE_ASCII: return "ascii";
default: return "byte";
}
}
static bool DataTypeFromName(const char *s, DataType *out) {
if (!strcmp(s, "byte")) *out = DATATYPE_BYTE;
else if (!strcmp(s, "halfword")) *out = DATATYPE_HALFWORD;
else if (!strcmp(s, "word")) *out = DATATYPE_WORD;
else if (!strcmp(s, "ascii")) *out = DATATYPE_ASCII;
else return false;
return true;
}
// Returns a pointer to the start of the (count+1)th whitespace-separated token in line, or to
// the trailing '\0' if there aren't that many - used instead of sscanf's %s/%[^\n] for the
// trailing name field below, since scanf's "match one or more characters" conversions fail
// (rather than matching an empty string) when a data/label entry legitimately has no name,
// which would otherwise silently drop the whole line instead of just leaving the name blank.
static const char *SkipTokens(const char *line, int count) {
const char *p = line;
for (int i = 0; i < count; i++) {
while (*p == ' ' || *p == '\t') p++;
while (*p && *p != ' ' && *p != '\t') p++;
}
while (*p == ' ' || *p == '\t') p++;
return p;
}
// Module names and disc IDs come straight from game/homebrew data (an ELF's module-info string,
// PARAM.SFO), so they have to go through SanitizeString before ending up in a filename.
static std::string SymbolFileStem(const std::string &name, const char *fallback) {
std::string stem = SanitizeString(name, StringRestriction::FileName);
return stem.empty() ? fallback : stem;
}
Path SymbolMap::GetModuleSymbolsPath(const std::string &moduleName, u32 crc) {
// Deliberately keyed by module name + crc, NOT by game - the exact same module (e.g. a
// kernel/driver module, or a homebrew's own statically-linked library) commonly gets loaded
// by many different games/homebrew, and symbols for it are equally valid for all of them.
return GetSysDirectory(DIRECTORY_SYSTEM) / "SYMBOLS" / StringFromFormat("%s_%08x.ppsym", SymbolFileStem(moduleName, "module").c_str(), crc);
}
Path SymbolMap::GetGameSymbolsPath(const std::string &gameID) {
// The opposite trade-off from GetModuleSymbolsPath: symbols that aren't inside any module are
// addresses in this game's own RAM layout, so they're worth nothing to any other game.
// The "_syms" suffix can't be mistaken for a module file, which always ends in _<8 hex digits>.
return GetSysDirectory(DIRECTORY_SYSTEM) / "SYMBOLS" / StringFromFormat("%s_syms.ppsym", SymbolFileStem(gameID, "unknown").c_str());
}
// Names that loading the module produces again by itself, so there's nothing to preserve:
// - "zz_*" is an import stub, named from the stub table on every load - either "zz_<funcName>"
// or "zz_<moduleName>_<nid>" when the NID isn't known (see KernelImportModuleFuncs).
// LoadSymbolMap skips these on the way in for the same reason.
// - "z_un_<8 hex digits>" is what MIPSAnalyst::ScanForFunctions calls every function it finds,
// i.e. a placeholder for "there's a function here, we don't know what it is".
// Writing them out would bury the handful of names a human actually chose (in one real module:
// four, among five hundred of these), and on the next run they'd be loaded back as authoritative
// and beat the module's own symbols to the address.
static bool IsRegeneratedSymbolName(const char *name) {
if (!name)
return true;
if (startsWith(name, "zz_"))
return true;
if (!startsWith(name, "z_un_"))
return false;
const char *p = name + 5;
for (int i = 0; i < 8; i++, p++) {
if (!isxdigit((unsigned char)*p))
return false;
}
return *p == '\0';
}
u32 SymbolMap::GetModuleCrc(int moduleIndex) const {
for (const auto &module : modules) {
if (module.index == moduleIndex)
return module.crc;
}
return 0;
}
// File format is a simple, human-editable text format - deliberately not the denser gzipped
// .map format LoadSymbolMap/SaveSymbolMap use, since these files are meant to be hand-tweaked
// (e.g. after manually naming a function) and diffed/version-controlled if the user wants to.
//
// .ppsym 1
// crc <hex, 0 if unknown>
// # game <gameID> <gameTitle> -- informational only, see SaveModuleSymbols
// F <relAddr hex> <size hex> <name> -- function
// D <relAddr hex> <size hex> <type> <name> -- data (type: byte/halfword/word/ascii)
// L <relAddr hex> <name> -- bare label (not a function or data start)
//
// moduleIndex 0 means "symbols not inside any module" - addresses the user attached to RAM
// directly (heap, stack, scratchpad, hardware registers). Those have no module to be relative to,
// so the addresses are simply absolute; everything else about the format is the same. They're
// per-game rather than per-module, hence GetGameSymbolsPath instead of GetModuleSymbolsPath.
bool SymbolMap::SaveModuleSymbols(int moduleIndex, const Path &filename, const std::string &gameID, const std::string &gameTitle) const {
u32 crc = 0;
if (moduleIndex != 0) {
bool found = false;
for (const auto &module : modules) {
if (module.index == moduleIndex) {
crc = module.crc;
found = true;
break;
}
}
if (!found)
return false;
}
// Built up first so we can tell whether anything survived the filtering below. Most modules
// contribute nothing a human chose, and writing a header-only file for each of them would
// bury the few that matter.
Buffer body;
int count = 0;
for (const auto &[key, e] : functions) {
if (key.first != moduleIndex)
continue;
// Only functions someone actually named are worth keeping - the rest are rediscovered
// (with the same boundaries) by the scan on every load. See IsRegeneratedSymbolName.
const char *name = GetLabelNameRel(e.start, moduleIndex);
if (IsRegeneratedSymbolName(name))
continue;
body.Printf("F %08x %08x %s\n", e.start, e.size, name);
count++;
}
for (const auto &[key, e] : data) {
if (key.first != moduleIndex)
continue;
const char *name = GetLabelNameRel(e.start, moduleIndex);
body.Printf("D %08x %08x %s %s\n", e.start, e.size, DataTypeName(e.type), name ? name : "");
count++;
}
for (const auto &[key, e] : labels) {
if (key.first != moduleIndex)
continue;
// Functions/data already saved their own (function/data-start) label above - only save
// the remainder here, labels that aren't at a function or data start.
if (functions.find(key) != functions.end() || data.find(key) != data.end())
continue;
if (IsRegeneratedSymbolName(e.name))
continue;
body.Printf("L %08x %s\n", e.addr, e.name);
count++;
}
if (count == 0) {
// Nothing worth keeping. Remove any previous file rather than leaving one behind that
// would restore symbols the user has since deleted.
if (File::Exists(filename))
File::Delete(filename);
return true;
}
File::CreateFullPath(filename.NavigateUp());
FILE *f = File::OpenCFile(filename, "w");
if (!f)
return false;
fprintf(f, ".ppsym 1\n");
fprintf(f, "crc %08x\n", crc);
// This file may be shared between multiple games that all load this module - this comment
// just records who saved it most recently, purely for a human's benefit (e.g. to recognize
// where a set of names came from); it's never read back by LoadModuleSymbols.
fprintf(f, "# game %s %s\n", gameID.empty() ? "?" : gameID.c_str(),
SanitizeString(gameTitle, StringRestriction::NoLineBreaksOrSpecials).c_str());
std::string text;
body.TakeAll(&text);
fwrite(text.data(), 1, text.size(), f);
fclose(f);
return true;
}
bool SymbolMap::LoadModuleSymbols(int moduleIndex, const Path &filename) {
if (!IsModuleActive(moduleIndex))
return false;
FILE *f = File::OpenCFile(filename, "r");
if (!f)
return false;
u32 currentCrc = 0;
// 0 means "don't range check" - either module 0 (absolute addresses, no range to speak of) or
// a module we somehow have no entry for.
u32 moduleSize = 0;
for (const auto &module : modules) {
if (module.index == moduleIndex) {
currentCrc = module.crc;
moduleSize = module.size;
break;
}
}
// A file can outlive the build of the module it was saved from (that's what the crc warning
// below is for), and it's meant to be hand-editable, so don't trust the addresses in it to
// land inside the module - a symbol placed outside would show up at a nonsense address.
auto inRange = [moduleSize](u32 relAddr) {
return moduleSize == 0 || relAddr < moduleSize;
};
int skipped = 0;
char line[512];
while (fgets(line, sizeof(line), f)) {
size_t len = strlen(line);
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
line[--len] = '\0';
if (line[0] == '\0' || line[0] == '#' || startsWith(line, ".ppsym"))
continue;
u32 addr, size;
char field[192];
if (startsWith(line, "crc ")) {
u32 fileCrc = 0;
if (sscanf(line + 4, "%x", &fileCrc) == 1 && currentCrc != 0 && fileCrc != 0 && fileCrc != currentCrc) {
WARN_LOG(Log::Loader, "LoadModuleSymbols: crc mismatch for '%s' (file %08x, loaded %08x) - symbols may not match this build of the module", filename.c_str(), fileCrc, currentCrc);
}
} else if (line[0] == 'F' && sscanf(line, "F %x %x", &addr, &size) == 2) {
if (!inRange(addr)) {
skipped++;
continue;
}
const u32 absAddr = GetModuleAbsoluteAddr(addr, moduleIndex);
const char *name = SkipTokens(line, 3);
if (!name[0]) {
// Shouldn't happen from our own writer, but the file is hand-editable. Register
// the function under the scan's usual placeholder rather than an empty name, and
// don't let it displace a name the module's own symbols may supply.
const std::string placeholder = StringFromFormat("z_un_%08x", absAddr);
AddFunction(placeholder.c_str(), absAddr, size, moduleIndex, false);
} else {
AddFunction(name, absAddr, size, moduleIndex, true);
}
} else if (line[0] == 'D' && sscanf(line, "D %x %x %191s", &addr, &size, field) == 3) {
if (!inRange(addr)) {
skipped++;
continue;
}
DataType type;
if (!DataTypeFromName(field, &type))
type = DATATYPE_BYTE;
u32 absAddr = GetModuleAbsoluteAddr(addr, moduleIndex);
AddData(absAddr, size, type, moduleIndex);
const char *name = SkipTokens(line, 4);
if (name[0])
AddLabel(name, absAddr, moduleIndex, true);
} else if (line[0] == 'L' && sscanf(line, "L %x", &addr) == 1) {
if (!inRange(addr)) {
skipped++;
continue;
}
const char *name = SkipTokens(line, 2);
if (name[0])
AddLabel(name, GetModuleAbsoluteAddr(addr, moduleIndex), moduleIndex, true);
}
}
fclose(f);
if (skipped > 0) {
WARN_LOG(Log::Loader, "LoadModuleSymbols: skipped %d symbol(s) in '%s' that fall outside the module (%08x bytes) - stale file?", skipped, filename.c_str(), moduleSize);
}
SortSymbols();
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 symbolMask) {
u32 functionAddress = INVALID_ADDRESS;
u32 dataAddress = INVALID_ADDRESS;
if (symbolMask & 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 (symbolMask & 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 symbolMask) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
const auto functionEntry = (symbolMask & ST_FUNCTION) ? activeFunctions.upper_bound(address) : activeFunctions.end();
const auto dataEntry = (symbolMask & 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(u32 address) {
u32 funcStart = GetFunctionStart(address);
const char *labelName = nullptr;
if (funcStart != INVALID_ADDRESS) {
labelName = GetLabelName(funcStart);
} else {
u32 dataStart = GetDataStart(address);
if (dataStart != INVALID_ADDRESS) {
labelName = GetLabelName(dataStart);
}
}
if (labelName) {
return std::string(labelName);
}
char descriptionTemp[32];
snprintf(descriptionTemp, sizeof(descriptionTemp), "(%08x)", address);
return descriptionTemp;
}
std::vector<SymbolEntry> SymbolMap::GetAllActiveSymbols(SymbolType symbolMask) {
if (activeNeedUpdate_)
UpdateActiveSymbols();
std::vector<SymbolEntry> result;
if (symbolMask & ST_FUNCTION) {
for (auto &[key, func] : activeFunctions) {
SymbolEntry entry;
entry.address = key;
entry.size = GetFunctionSize(entry.address);
const char* name = GetLabelName(entry.address);
if (name)
entry.name = name;
result.push_back(entry);
}
}
if (symbolMask & ST_DATA) {
for (auto &[key, data] : activeData) {
SymbolEntry entry;
entry.address = key;
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, u32 crc) {
for (auto &module : modules) {
if (equals(module.name, name)) {
// A name match alone isn't proof it's really the same module reloading - some
// module names are generic enough to collide between unrelated binaries. If both
// sides know their crc and they disagree, treat this as a different module instead
// of falling through to reactivate (and thus reusing/polluting) the old one's
// symbol table; crc == 0 on either side means "unknown" and we fall back to
// matching by name alone, same as before crc existed.
if (module.crc != 0 && crc != 0 && module.crc != crc)
continue;
// Just reactivate that one.
module.start = address;
module.size = size;
if (crc != 0)
module.crc = crc;
activeModuleEnds.emplace(module.start + module.size, module);
activeNeedUpdate_ = true;
return;
}
}
ModuleEntry mod;
truncate_cpy(mod.name, name);
mod.start = address;
mod.size = size;
mod.crc = crc;
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 {
// activeModuleEnds is keyed by each active module's END address, so upper_bound() finds
// the first module whose end is > address. That alone doesn't prove address falls inside
// it though - address could just as well be sitting in the gap before that module's start
// (e.g. between two active modules, or before the very first one) - so start must be
// checked too, or addresses in such a gap get silently misattributed to the wrong module.
auto iter = activeModuleEnds.upper_bound(address);
if (iter == activeModuleEnds.end())
return -1;
if (address < iter->second.start)
return -1;
return iter->second.index;
}
int SymbolMap::ResolveModuleIndex(u32 address, int moduleIndex) {
if (moduleIndex == -1) {
// -1 from a caller means "work it out from the address".
moduleIndex = GetModuleIndex(address);
if (moduleIndex < 0) {
// Not inside any loaded module - the heap, the stack, scratchpad, a hardware
// register. That's module 0, "absolute address", not an error. Leaving it at -1
// would file the symbol under a module index that is never active, so it would
// never reach the active maps: invisible to every lookup and lost on save.
moduleIndex = 0;
}
}
if (moduleIndex == 0)
sawUnknownModule = true;
return moduleIndex;
}
int SymbolMap::GetModuleIndexByName(const std::string &name) const {
// Prefer a currently active module if the name is ambiguous (e.g. two distinct modules
// that happen to share a name - see AddModule's crc handling).
for (const auto &[key, module] : activeModuleEnds) {
if (name == module.name)
return module.index;
}
// Not active (or never was) - fall back to the most recently added entry with that name.
int found = -1;
for (const auto &module : modules) {
if (name == module.name)
found = module.index;
}
return found;
}
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, bool updateName) {
moduleIndex = ResolveModuleIndex(address, moduleIndex);
// 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);
// Re-point at the entry's new home: erase() invalidated the old iterator, and the
// refresh below still reads through it.
existing = functions.insert_or_assign(symbolKey, func).first;
}
// 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, updateName);
}
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.
if (activeModuleEnds.empty() || (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, bool updateName) {
moduleIndex = ResolveModuleIndex(address, moduleIndex);
// 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()) {
// By default we leave an existing label's name alone, rather than overwriting it (see
// updateName's doc comment in the header).
bool nameChanged = false;
if (updateName && !equals(existing->second.name, name)) {
truncate_cpy(existing->second.name, name);
nameChanged = true;
}
// 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 if (nameChanged) {
// Module/address didn't change, but the name did - activeLabels still needs a
// refresh, since it holds a separate flattened (and const-valued) copy, not a
// reference into labels.
auto active = activeLabels.find(address);
if (active != activeLabels.end()) {
activeLabels.erase(active);
activeLabels.emplace(address, existing->second);
}
}
} 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) {
moduleIndex = ResolveModuleIndex(address, moduleIndex);
// 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);
// Re-point at the entry's new home: erase() invalidated the old iterator, and the
// refresh below still reads through it.
existing = data.insert_or_assign(symbolKey, entry).first;
}
// 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