Checked against two PSP binaries that shipped with intact symbol tables,
which turned up several constructs the format's usual description doesn't
mention:
- Template arguments are written literally inside the length-prefixed name
("39CList<Q38hlScreen5Brwsr13CContentsUnit>"), not with a "__PT" prefix,
and they nest. Function templates put theirs in the base name instead,
followed by the return type.
- A family of "@"-decorated symbols for things with no C++ name: thunks
("@12@__dt__3SonFv"), string literals, function-local statics and their
guard variables. Plus __vt__/__RTTI__/__sinit_, printed in the same style
as the Itanium special names.
- Types are now built as a split declarator, so a pointer to a function
comes out as "int (*)(int)" rather than "int (int) *".
Also stop the lenient pass from turning plain C names with a "__" in them
into nonsense - "I3dClut__FlushCache" became "I3dClut(long, ...)". It now
requires a class qualifier, which costs nothing: over ~10000 symbols the
lenient pass rescued none and only produced those false positives.
Symbol map names go from 128 to 256 characters, since a demangled name
keeps its parameters and templates make short work of 128.
docs/CodeWarriorMangling.md describes the format, marking the parts that
are inferred from cfront rather than attested in a real binary.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SF5eS5QDNexLksRDeDZvwY
Older PSP binaries weren't built with GCC, so the Itanium demangler doesn't
help with them. Add two more, tried in turn by DemangleSymbolName():
- Metrowerks CodeWarrior, a descendant of the AT&T cfront scheme
("getDistance__6KzUtilFP7st_unitP7st_unit"). Handles Q<n> qualified names,
the cfront type codes including T/N back-references, cv-qualifiers, and the
operator/ctor/dtor name codes.
- SN Systems SNC/ProDG ("__0f5DstdIbad_castEwhatvK"), which encodes name
component lengths as letters. Reverse engineered from a small sample, so
the parts that are guesses are marked as such - they don't affect the name.
Both are rougher than the Itanium one: they aim for a correctly qualified name
plus a plausible parameter list, and print "..." for a parameter they can't
decode rather than throwing the name away. Results come back as a
DemangledSymbol with the name, parameters, return type and qualifiers kept
separate, in case a caller wants more than the printed string.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFV5DUTc9ZYAKgsCMZGwX8
C++ homebrew has an unreadable symbol table -
everything is _ZN10PxRenderer7DrawImmE... - which makes the disassembly and
symbol list nearly useless. Add an Itanium C++ ABI demangler and run ELF
symbols through it on load, in both ElfReader::LoadSymbols (unstripped EXECs,
which is what a CMake pspdev EBOOT actually contains) and the companion-ELF
path.
The demangling standard is called Itanium for historical reasons - it
was defined for Itanium but ended up being almost universally
applicable.
Written from scratch rather than using __cxa_demangle, which doesn't exist on
MSVC/UWP, or vendoring LLVM's demangler, whose license doesn't fit. Anything
unrecognized (arbitrary constant expressions, decltype) aborts the parse and
the caller gets the original mangled name back, so a caller never sees a
half-parsed result. Recursion is depth-capped since the input comes from a
file we didn't write.
Checked against c++filt as an oracle: of 1089 mangled symbols in a real C++
homebrew EBOOT, one differs; of 55189 from libstdc++/libLLVM/cc1plus, 22
differ and 413 are declined. Fuzzed with 220k mutated and random inputs under
ASan/UBSan.
Also adds a right-click menu to the ImDebugger symbol list.
Note that SymbolMap stores names in char[128], so the longest STL names get
truncated in the UI. Still far more readable than the mangled form.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3DbkJ8ShYiXU7q5Tv1LZu