Skip to content

Add VC Debug support for linking pdb files - #3

Merged
bwrsandman merged 2 commits into
openblack:bw1-decompfrom
bwrsandman:debug
Jul 23, 2026
Merged

Add VC Debug support for linking pdb files#3
bwrsandman merged 2 commits into
openblack:bw1-decompfrom
bwrsandman:debug

Conversation

@bwrsandman

Copy link
Copy Markdown
Member

No description provided.

MSVC 6.0's /Zi writes each TU's types into an external PDB 2.0 type
server (*.o.pdb), referenced from .debug$T by an old LF_TYPESERVER/
LF_TYPESERVER_ST record. PDB 2.0 is a different, older MSF container
than PDB 7.0 ("DS"), so PDBFile/NativeSession can't open it, and its
named type records use old "_ST" (Pascal-name) leaf kinds lld doesn't
recognize -- so --debug PDBs for VC6-era objects previously got no
struct/class/enum type info, only function names from COFF symbols.

Adds a small from-scratch PDB 2.0 MSF + TPI-stream reader
(Pdb2TypeServer.h/.cpp) that extracts the TPI stream and rewrites
_ST leaf kinds to their modern equivalents in place (Pascal-length-
prefixed names to null-terminated; same byte length, no reindexing).
Intra-object TypeIndex references are already 0x1000-based per
record position, matching what TypeIndex::toArrayIndex() expects, so
no remapping is needed there.

Wires this into InputFiles.cpp's LF_TYPESERVER/_ST handling as a
plain generic TpiSource, bypassing TypeServerSource/NativeSession
entirely for the old format. Every failure path (sidecar not found,
unreadable, wrong format) falls back to an empty TpiSource rather
than leaving debugTypesObj null, since PDB.cpp's writeSymbolRecord
dereferences it unconditionally for any object with .debug$S symbol
records.

Chunks.cpp: accept CV_SIGNATURE_C11 for .debug$T specifically, so the
old-format LF_TYPESERVER record isn't rejected before it can be read
(.debug$S stays rejected -- VC6's C11 symbol subsection format isn't
handled here).

PDB.cpp: guard against empty-chunk output sections in
addLinkerModuleSectionSymbol (unrelated crash hit along the way).

llvm-pdbutil: fix a pre-existing build break (TpiStream::getHashValues
was renamed to getHashValuesV80) hit while verifying the output PDB.

Verified against a real BW1 game binary rebuild: --debug link
completes cleanly and the output PDB contains real type names
(enum and struct) round-tripped from VC6 object files.
MSVC 6.0 /Zi writes each .debug$S section using the old CodeView C11
signature: a flat, unwrapped sequence of symbol records (no subsection
framing, unlike modern C13), using old "_ST" (Pascal-name) kinds for
the handful of record types that carry a name. lld previously rejected
these sections outright (unrecognized magic), so --debug PDBs had no
local/global variable names, enum constants, or UDT references -- only
function names from COFF publics and (as of the previous commit) types
from the per-object PDB 2.0 .debug$T type servers.

Adds Pdb2Symbols.h/.cpp, converting the old symbol kinds actually
emitted by real compiled objects in this project (surveyed across all
of them): S_OBJNAME_ST, S_CONSTANT_ST, S_UDT_ST, S_LDATA32_ST,
S_GDATA32_ST, S_REGISTER_ST, S_BPREL32_ST, S_LABEL32_ST, S_LPROC32_ST,
S_GPROC32_ST -> their modern equivalents, and S_COMPILE (which
predates the "_ST" family and has no modern equivalent in LLVM at all)
-> S_SKIP. Objects with more than one function have additional
.debug$S sections -- one per function -- that carry no magic at all,
starting directly with S_GPROC32_ST/S_LPROC32_ST; those are handled
too.

Conversion happens once per chunk at object-load time (InputFiles.cpp)
and is strictly in-place: same total size, same byte offsets, magic
present or absent exactly as given. This section's own COFF
relocations point at byte offsets within these records (e.g.
S_GDATA32_ST's DataOffset/Segment fields), so nothing may shift them --
not even by stripping a magic prefix from the stored copy, which is why
the override still carries whatever magic (or lack thereof) the
original had; callers check isOldCodeViewSymbols/
isBareOldCodeViewSymbols to decide how to treat it instead.

SectionChunk has a static_assert on its size (there can be very many
instances), so the override lives in a small DenseMap on ObjFile
instead of a new SectionChunk field -- ObjFile instances are much
rarer.

Verified against a real BW1 game binary rebuild: the output PDB now
contains real global/local variable names, enum constants, and
compiler-generated labels (e.g. "MPFEConnectionStatus::IsInternetLobby",
"$L17277") round-tripped from VC6 object files, with zero errors or
warnings.
@bwrsandman
bwrsandman merged commit f14e534 into openblack:bw1-decomp Jul 23, 2026
4 checks passed
@bwrsandman
bwrsandman deleted the debug branch July 23, 2026 12:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant