diff --git a/lld/COFF/CMakeLists.txt b/lld/COFF/CMakeLists.txt index 2bbadf75bfa1e..e72f6063eae45 100644 --- a/lld/COFF/CMakeLists.txt +++ b/lld/COFF/CMakeLists.txt @@ -17,6 +17,8 @@ add_lld_library(lldCOFF MapFile.cpp MarkLive.cpp MinGW.cpp + Pdb2Symbols.cpp + Pdb2TypeServer.cpp PDB.cpp SymbolTable.cpp Symbols.cpp diff --git a/lld/COFF/Chunks.cpp b/lld/COFF/Chunks.cpp index ec0fdf0b67b38..31ab082eaec20 100644 --- a/lld/COFF/Chunks.cpp +++ b/lld/COFF/Chunks.cpp @@ -702,6 +702,9 @@ StringRef SectionChunk::getDebugName() const { } ArrayRef SectionChunk::getContents() const { + if (ArrayRef override = file->getDebugSOverride(this); + !override.empty()) + return override; ArrayRef a; cantFail(file->getCOFFObj()->getSectionContents(header, a)); return a; @@ -729,6 +732,26 @@ ArrayRef SectionChunk::consumeDebugMagic(ArrayRef data, ? DEBUG_HASHES_SECTION_MAGIC : DEBUG_SECTION_MAGIC; if (magic != expectedMagic) { + // MSVC 6.0 /Zi emits .debug$T and .debug$S with the old CodeView "C11" + // signature (CV_SIGNATURE_C11 = 2) instead of C13 (4). + // + // For .debug$T, the record that follows is a normal CVType -- an old + // LF_TYPESERVER / LF_TYPESERVER_ST pointing at a PDB 2.0 type server -- + // which the type-server path in ObjFile::initializeDependencies() + // already handles. + // + // For .debug$S, ObjFile::readSection() already rewrote this chunk's old + // "_ST" symbol kinds to modern equivalents in place (see Pdb2Symbols.h) + // and installed the result via setDebugSOverride -- what getContents() + // returns here IS that converted buffer, just still C11-tagged since + // the rewrite doesn't touch the magic. Old .debug$S has no subsection + // framing (unlike modern C13), so callers must not treat the stripped + // bytes as a subsection array -- see the callers in PDB.cpp, which check + // isOldCodeViewSymbols() before parsing. + constexpr uint32_t CV_SIGNATURE_C11 = 2; + if ((sectionName == ".debug$T" || sectionName == ".debug$S") && + magic == CV_SIGNATURE_C11) + return data.slice(4); warn("ignoring section " + sectionName + " with unrecognized magic 0x" + utohexstr(magic)); return {}; diff --git a/lld/COFF/InputFiles.cpp b/lld/COFF/InputFiles.cpp index 025ca6396e052..7c5e7e688f08b 100644 --- a/lld/COFF/InputFiles.cpp +++ b/lld/COFF/InputFiles.cpp @@ -7,6 +7,8 @@ //===----------------------------------------------------------------------===// #include "InputFiles.h" +#include "Pdb2Symbols.h" +#include "Pdb2TypeServer.h" #include "COFFLinkerContext.h" #include "Chunks.h" #include "Config.h" @@ -319,8 +321,27 @@ SectionChunk *ObjFile::readSection(uint32_t sectionNumber, // CodeView sections are stored to a different vector because they are not // linked in the regular manner. - if (c->isCodeView()) + if (c->isCodeView()) { debugChunks.push_back(c); + // MSVC 6.0 /Zi emits .debug$S with the old CV_SIGNATURE_C11 format (a + // flat symbol stream using old "_ST" kinds; see Pdb2Symbols.h) -- and an + // object with more than one function has additional .debug$S sections, + // one per function, that carry no magic at all (a "bare" continuation + // starting directly with S_GPROC32_ST/S_LPROC32_ST). Convert either kind + // in place now, once, rather than at each of the several places that + // later read this chunk's contents. + if (name == ".debug$S" && + (isOldCodeViewSymbols(c->getContents()) || + isBareOldCodeViewSymbols(c->getContents()))) { + if (Expected> converted = + convertOldCodeViewSymbols(c->getContents(), bAlloc())) + setDebugSOverride(c, *converted); + else + Warn(symtab.ctx) << "failed to convert VC6 .debug$S in " + << toString(this) << ": " + << toString(converted.takeError()); + } + } else if (name == ".gfids$y") guardFidChunks.push_back(c); else if (name == ".giats$y") @@ -866,6 +887,11 @@ void ObjFile::initializeFlags() { } } +// Forward declaration; defined further down, needed by initializeDependencies +// to resolve a type-server path relative to this OBJ / the output file. +static std::optional +findPdbPath(StringRef pdbPath, ObjFile *dependentFile, StringRef outputPath); + // Depending on the compilation flags, OBJs can refer to external files, // necessary to merge this OBJ into the final PDB. We currently support two // types of external files: Precomp/PCH OBJs, when compiling with /Yc and /Yu. @@ -922,23 +948,81 @@ void ObjFile::initializeDependencies() { } // Handle older LF_TYPESERVER (0x1501) and LF_TYPESERVER_ST (0x0016) formats. - // These use a 4-byte CRC signature instead of a 16-byte GUID. Parse manually - // and create a TypeServer2Record with a zeroed GUID so that GUID matching is - // skipped in UseTypeServerSource::getTypeServerSource(). + // These are MSVC 6.0 /Zi objects pointing at a PDB 2.0 ("JG") type server, + // an older MSF container that PDBFile/NativeSession cannot open (hence no + // TypeServerSource/UseTypeServerSource route here, unlike the modern + // LF_TYPESERVER2 case above). Resolve the path ourselves, read the file + // directly, and hand the reindexed+fixed-up type records to this object as + // if they were a plain inline .debug$T -- see Pdb2TypeServer.h. if (firstType->kind() == LF_TYPESERVER || firstType->kind() == LF_TYPESERVER_ST) { + // Every failure path below falls back to a plain, empty TpiSource + // (mirroring the "no data" case above) rather than leaving + // debugTypesObj null: this object's .debug$S may still have real symbol + // records even though its type-server sidecar couldn't be found/opened/ + // parsed (a renamed build tree, a missing .o.pdb, an unrecognized + // format), and PDBLinker::writeSymbolRecord dereferences debugTypesObj + // unconditionally for every symbol record it processes -- leaving it + // null here would crash the whole link on what should just be "this + // object's symbols get remapped against no extra local types." ArrayRef content = firstType->content(); - // Record body: [4 bytes CRC sig][4 bytes age][null-terminated name] - if (content.size() >= 9) { - uint32_t age = support::endian::read32le(content.data() + 4); - StringRef name(reinterpret_cast(content.data() + 8)); - TypeServer2Record ts(TypeRecordKind::TypeServer2); - ts.Age = age; - ts.Name = name; - // Guid is zero-initialized; GUID matching skipped for old-style records. - debugTypesObj = makeUseTypeServerSource(ctx, this, ts); - enqueuePdbFile(ts.getName(), this); + // Record body: [4 bytes CRC sig][4 bytes age][name]. The old "_ST" form + // (MSVC 6.0 /Zi) stores the name length-prefixed (1 byte length + chars); + // the non-ST form stores it null-terminated. + if (content.size() < 9) { + debugTypesObj = makeTpiSource(ctx, this); + return; } + StringRef name; + if (firstType->kind() == LF_TYPESERVER_ST) { + uint8_t len = content[8]; + if (9 + static_cast(len) <= content.size()) + name = StringRef(reinterpret_cast(content.data() + 9), + len); + } else { + name = StringRef(reinterpret_cast(content.data() + 8)); + } + if (name.empty()) { + debugTypesObj = makeTpiSource(ctx, this); + return; + } + + std::optional path = + findPdbPath(name.str(), this, symtab.ctx.config.outputFile); + if (!path) { + Warn(ctx) << "VC6 type server not found: " << name; + debugTypesObj = makeTpiSource(ctx, this); + return; + } + ErrorOr> mbOrErr = + MemoryBuffer::getFile(*path, /*IsText=*/false, + /*RequiresNullTerminator=*/false); + if (!mbOrErr) { + Warn(ctx) << "failed to open VC6 type server " << *path << ": " + << mbOrErr.getError().message(); + debugTypesObj = makeTpiSource(ctx, this); + return; + } + MemoryBufferRef mbRef = (*mbOrErr)->getMemBufferRef(); + if (!isPdb2TypeServer(mbRef)) { + Warn(ctx) << *path << " is not a recognized PDB 2.0 type server"; + debugTypesObj = makeTpiSource(ctx, this); + return; + } + Expected> typesOrErr = + readPdb2TypeServerTypes(mbRef, bAlloc()); + if (!typesOrErr) { + Warn(ctx) << "failed to read VC6 type server " << *path << ": " + << toString(typesOrErr.takeError()); + debugTypesObj = makeTpiSource(ctx, this); + return; + } + // Keep the backing MemoryBuffer alive for the life of the link; the types + // above were copied out into bAlloc(), but nothing else needs `mbOrErr`. + ctx.driver.takeBuffer(std::move(*mbOrErr)); + + debugTypes = *typesOrErr; + debugTypesObj = makeTpiSource(ctx, this); return; } diff --git a/lld/COFF/InputFiles.h b/lld/COFF/InputFiles.h index fd2e409ada30f..f536e75233273 100644 --- a/lld/COFF/InputFiles.h +++ b/lld/COFF/InputFiles.h @@ -144,6 +144,15 @@ class ObjFile : public InputFile { MachineTypes getMachineType() const override; ArrayRef getChunks() { return chunks; } ArrayRef getDebugChunks() { return debugChunks; } + + // See debugSOverrides below. + void setDebugSOverride(const SectionChunk *sc, ArrayRef data) { + debugSOverrides[sc] = data; + } + ArrayRef getDebugSOverride(const SectionChunk *sc) const { + auto it = debugSOverrides.find(sc); + return it == debugSOverrides.end() ? ArrayRef() : it->second; + } ArrayRef getSXDataChunks() { return sxDataChunks; } ArrayRef getGuardFidChunks() { return guardFidChunks; } ArrayRef getGuardIATChunks() { return guardIATChunks; } @@ -289,6 +298,16 @@ class ObjFile : public InputFile { // CodeView debug info sections. std::vector debugChunks; + // Overrides the contents SectionChunk::getContents() would otherwise + // derive from the raw COFF section, for VC6 .debug$S chunks whose old + // "_ST" symbol kinds have been rewritten to modern equivalents (see + // Pdb2Symbols.h). Same total size as the original, so this chunk's COFF + // relocations remain valid without adjustment. Keyed by ObjFile rather + // than added as a SectionChunk field: SectionChunk has a static_assert on + // its size (there can be very many of them), while ObjFile instances are + // comparatively rare. + llvm::DenseMap> debugSOverrides; + // Chunks containing symbol table indices of exception handlers. Only used for // 32-bit x86. std::vector sxDataChunks; diff --git a/lld/COFF/PDB.cpp b/lld/COFF/PDB.cpp index 21475033b0ae8..9f135c41ea01e 100644 --- a/lld/COFF/PDB.cpp +++ b/lld/COFF/PDB.cpp @@ -12,6 +12,7 @@ #include "Config.h" #include "DebugTypes.h" #include "Driver.h" +#include "Pdb2Symbols.h" #include "SymbolTable.h" #include "Symbols.h" #include "TypeMerger.h" @@ -657,23 +658,25 @@ Error PDBLinker::writeAllModuleSymbolRecords(ObjFile *file, continue; ArrayRef sectionContents = debugChunk->getContents(); - auto contents = - SectionChunk::consumeDebugMagic(sectionContents, ".debug$S"); - DebugSubsectionArray subsections; - BinaryStreamReader reader(contents, llvm::endianness::little); - exitOnErr(reader.readArray(subsections, contents.size())); + // Whether this chunk got an old-format override at load time (see + // Pdb2Symbols.h) is the signal to use here, not re-inspecting + // sectionContents: conversion rewrites the bare case's leading kind + // value (what isBareOldCodeViewSymbols keys on) from old to modern, so + // checking it again post-conversion would always come back false. The + // magic bytes are never touched by conversion, so isOldCodeViewSymbols + // remains valid to tell whether there's a magic to strip. + bool oldFormat = !file->getDebugSOverride(debugChunk).empty(); + bool magicPrefixed = isOldCodeViewSymbols(sectionContents); + ArrayRef contents = + magicPrefixed ? SectionChunk::consumeDebugMagic(sectionContents, + ".debug$S") + : sectionContents; uint32_t nextRelocIndex = 0; - for (const DebugSubsectionRecord &ss : subsections) { - if (ss.kind() != DebugSubsectionKind::Symbols) - continue; - + auto processSymbols = [&](ArrayRef symsBuffer) -> Error { uint32_t moduleSymStart = writer.getOffset(); scopes.clear(); storage.clear(); - ArrayRef symsBuffer; - BinaryStreamRef sr = ss.getRecordData(); - cantFail(sr.readBytes(0, sr.getLength(), symsBuffer)); auto ec = forEachCodeViewRecord( symsBuffer, [&](CVSymbol sym) -> llvm::Error { // Track the current scope. Only update records in the postmerge @@ -704,7 +707,31 @@ Error PDBLinker::writeAllModuleSymbolRecords(ObjFile *file, // at once. // TODO: Consider buffering symbols for the entire object file to reduce // overhead even further. - if (Error e = writer.writeBytes(storage)) + return writer.writeBytes(storage); + }; + + // Old (VC6 CV_SIGNATURE_C11, or bare per-function continuation) .debug$S + // has no subsection framing: the whole thing (already converted from + // "_ST" kinds; see Pdb2Symbols.h) IS one flat symbol stream. Modern + // .debug$S wraps symbol data in a Kind=Symbols subsection alongside + // others (StringTable, Lines, ...). + if (oldFormat) { + if (Error e = processSymbols(contents)) + return e; + continue; + } + + DebugSubsectionArray subsections; + BinaryStreamReader reader(contents, llvm::endianness::little); + exitOnErr(reader.readArray(subsections, contents.size())); + + for (const DebugSubsectionRecord &ss : subsections) { + if (ss.kind() != DebugSubsectionKind::Symbols) + continue; + ArrayRef symsBuffer; + BinaryStreamRef sr = ss.getRecordData(); + cantFail(sr.readBytes(0, sr.getLength(), symsBuffer)); + if (Error e = processSymbols(symsBuffer)) return e; } } @@ -759,17 +786,41 @@ translateStringTableIndex(COFFLinkerContext &ctx, uint32_t objIndex, void DebugSHandler::handleDebugS(SectionChunk *debugChunk) { // Note that we are processing the *unrelocated* section contents. They will // be relocated later during PDB writing. - ArrayRef contents = debugChunk->getContents(); - contents = SectionChunk::consumeDebugMagic(contents, ".debug$S"); - DebugSubsectionArray subsections; - BinaryStreamReader reader(contents, llvm::endianness::little); + ArrayRef rawContents = debugChunk->getContents(); + // Whether this chunk got an old-format override at load time (see + // Pdb2Symbols.h) is the signal to use here, not re-inspecting + // rawContents: conversion rewrites the bare case's leading kind value + // from old to modern, so checking it again post-conversion would always + // come back false. The magic bytes are never touched by conversion, so + // isOldCodeViewSymbols remains valid to tell whether there's a magic to + // strip. + bool oldFormat = !file.getDebugSOverride(debugChunk).empty(); + bool magicPrefixed = isOldCodeViewSymbols(rawContents); + ArrayRef contents = + magicPrefixed ? SectionChunk::consumeDebugMagic(rawContents, ".debug$S") + : rawContents; ExitOnError exitOnErr; - exitOnErr(reader.readArray(subsections, contents.size())); debugChunk->sortRelocations(); // Reset the relocation index, since this is a new section. nextRelocIndex = 0; + // Old (VC6 CV_SIGNATURE_C11, or bare per-function continuation) .debug$S + // has no subsection framing: the whole thing (already converted from + // "_ST" kinds; see Pdb2Symbols.h) IS one flat symbol stream, and this + // format has no StringTable/FileChecksums/Lines/etc subsections to look + // for. + if (oldFormat) { + linker.analyzeSymbolSubsection( + debugChunk, moduleStreamSize, nextRelocIndex, stringTableFixups, + BinaryStreamRef(contents, llvm::endianness::little)); + return; + } + + DebugSubsectionArray subsections; + BinaryStreamReader reader(contents, llvm::endianness::little); + exitOnErr(reader.readArray(subsections, contents.size())); + for (const DebugSubsectionRecord &ss : subsections) { // Ignore subsections with the 'ignore' bit. Some versions of the Visual C++ // runtime have subsections with this bit set. @@ -1522,6 +1573,11 @@ static void addLinkerModuleSectionSymbol(pdb::DbiModuleDescriptorBuilder &mod, // Output COFF groups for individual chunks of this section. for (PartialSection *sec : os.contribSections) { + // A contributing section can end up with no chunks (e.g. after section + // merging such as .idata folded into .rdata); it has nothing to emit and + // would otherwise dereference an empty chunk list below. + if (sec->chunks.empty()) + continue; addLinkerModuleCoffGroup(sec, mod, os); } } @@ -1771,6 +1827,16 @@ static bool findLineTable(const SectionChunk *c, uint32_t addr, for (SectionChunk *dbgC : c->file->getDebugChunks()) { if (dbgC->getSectionName() != ".debug$S") continue; + // Old (VC6 CV_SIGNATURE_C11, or bare per-function continuation) + // .debug$S is a flat symbol stream with no StringTable/FileChecksums/ + // Lines subsections to look for (see Pdb2Symbols.h) -- nothing here + // applies to it. Whether this chunk got an old-format override at load + // time is the signal (see the comment in handleDebugS): conversion + // rewrites the bare case's leading kind value, so re-checking + // isBareOldCodeViewSymbols against the (already-converted) contents + // here would always come back false. + if (!dbgC->file->getDebugSOverride(dbgC).empty()) + continue; // Build a mapping of SECREL relocations in dbgC that refer to `c`. DenseMap secrels; diff --git a/lld/COFF/Pdb2Symbols.cpp b/lld/COFF/Pdb2Symbols.cpp new file mode 100644 index 0000000000000..2a91a93cca0a2 --- /dev/null +++ b/lld/COFF/Pdb2Symbols.cpp @@ -0,0 +1,231 @@ +//===- Pdb2Symbols.cpp -----------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// See Pdb2Symbols.h. A real MSVC 6.0 .debug$S section (surveyed across every +// matched object in this project) uses exactly these old symbol kinds: +// +// S_OBJNAME_ST (0x0009) [sig:4][name] -> S_OBJNAME (0x1101) +// S_CONSTANT_ST (0x1002) [type:4][value: numeric-leaf][name] -> S_CONSTANT (0x1107) +// S_UDT_ST (0x1003) [type:4][name] -> S_UDT (0x1108) +// S_LDATA32_ST (0x1007) [type:4][offset:4][segment:2][name] -> S_LDATA32 (0x110c) +// S_GDATA32_ST (0x1008) [type:4][offset:4][segment:2][name] -> S_GDATA32 (0x110d) +// S_REGISTER_ST (0x1001) [index:4][register:2][name] -> S_REGISTER (0x1106) +// S_BPREL32_ST (0x1006) [offset:4][type:4][name] -> S_BPREL32 (0x110b) +// S_LABEL32_ST (0x0209) [codeoffset:4][segment:2][flags:1][name] -> S_LABEL32 (0x1105) +// S_LPROC32_ST (0x100a) [8 x u32][segment:2][flags:1][name] -> S_LPROC32 (0x110f) +// S_GPROC32_ST (0x100b) same shape as S_LPROC32_ST -> S_GPROC32 (0x1110) +// S_END (0x0006) -- already the same value in old and new numbering +// (no name, no conversion needed) +// S_COMPILE (0x0001) -- no modern equivalent exists in LLVM at all (not +// even an "_ST" sibling; this predates that +// family). Carries only compiler-version text, +// nothing structural. Replaced with S_SKIP rather +// than guessed at. +// +// Shapes verified against llvm::codeview::SymbolRecordMapping:: +// visitKnownRecord for ObjNameSym/ConstantSym/UDTSym/DataSym/RegisterSym/ +// BPRelativeSym/LabelSym/ProcSym in SymbolRecordMapping.cpp, and cross- +// checked against real record bytes (struct.unpack, not by hand -- an +// earlier by-hand count of ProcSym's fields looked wrong at first and +// wasn't). + +#include "Pdb2Symbols.h" + +#include "llvm/DebugInfo/CodeView/CodeView.h" +#include "llvm/DebugInfo/CodeView/RecordSerialization.h" +#include "llvm/Support/Endian.h" + +using namespace llvm; +using namespace llvm::codeview; +using namespace llvm::support; + +namespace lld::coff { + +static constexpr uint32_t cvSignatureC11 = 2; + +bool isOldCodeViewSymbols(ArrayRef sectionContents) { + return sectionContents.size() >= 4 && + support::endian::read32le(sectionContents.data()) == cvSignatureC11; +} + +bool isBareOldCodeViewSymbols(ArrayRef sectionContents) { + if (sectionContents.size() < 4) + return false; + uint16_t length = support::endian::read16le(sectionContents.data()); + uint16_t kind = support::endian::read16le(sectionContents.data() + 2); + return (kind == S_GPROC32_ST || kind == S_LPROC32_ST) && + size_t(2 + length) <= sectionContents.size(); +} + +// Same numeric-leaf-encoding table as Pdb2TypeServer.cpp's numericLeafSize; +// duplicated rather than shared since it's the only overlap between the two +// files and it's small. +static bool numericLeafSize(uint16_t firstWord, size_t &size) { + if (firstWord < LF_NUMERIC) { + size = 2; + return true; + } + switch (firstWord) { + case LF_CHAR: + size = 3; + return true; + case LF_SHORT: + case LF_USHORT: + size = 4; + return true; + case LF_LONG: + case LF_ULONG: + size = 6; + return true; + case LF_REAL32: + size = 6; + return true; + case LF_REAL64: + size = 10; + return true; + case LF_REAL80: + size = 12; + return true; + case LF_REAL128: + size = 18; + return true; + case LF_QUADWORD: + case LF_UQUADWORD: + size = 10; + return true; + default: + return false; + } +} + +// Converts a Pascal-style (length-prefixed) name at `nameOffset` to +// null-terminated, in place. Both encodings occupy exactly N+1 bytes for an +// N-character name, so this never changes the record's length. Returns +// false (record left untouched) if the shape doesn't match. +static bool fixupPascalNameDirectAt(MutableArrayRef rec, + size_t nameOffset) { + if (nameOffset >= rec.size()) + return false; + uint8_t nameLen = rec[nameOffset]; + if (nameOffset + 1 + nameLen > rec.size()) + return false; + uint8_t *name = rec.data() + nameOffset; + for (uint8_t i = 0; i < nameLen; ++i) + name[i] = name[i + 1]; + name[nameLen] = 0; + return true; +} + +// As above, but `prefixSize` is followed by a numeric-leaf-encoded value +// (S_CONSTANT's Value) before the name. +static bool fixupPascalNameAfterLeafAt(MutableArrayRef rec, + size_t prefixSize) { + if (rec.size() < prefixSize + 3) + return false; + size_t numericFieldSize; + if (!numericLeafSize(support::endian::read16le(rec.data() + prefixSize), + numericFieldSize)) + return false; + return fixupPascalNameDirectAt(rec, prefixSize + numericFieldSize); +} + +// Rewrites one old-kind symbol record to its modern equivalent in place. +// Only commits the kind rewrite if the name fixup succeeds, for the same +// reason as Pdb2TypeServer.cpp's stFixupFor loop: a kind tag that doesn't +// match its actual (still Pascal-encoded) name is worse than leaving the +// record as an unrecognized old kind. +static void convertOneSymbol(MutableArrayRef rec) { + uint16_t kind = support::endian::read16le(rec.data() + 2); + switch (kind) { + case S_OBJNAME_ST: // [len:2][kind:2][sig:4][name] + if (fixupPascalNameDirectAt(rec, /*len+kind+sig=*/4 + 4)) + support::endian::write16le(rec.data() + 2, uint16_t(S_OBJNAME)); + break; + case S_CONSTANT_ST: // [len:2][kind:2][type:4][value-leaf][name] + if (fixupPascalNameAfterLeafAt(rec, /*len+kind+type=*/4 + 4)) + support::endian::write16le(rec.data() + 2, uint16_t(S_CONSTANT)); + break; + case S_UDT_ST: // [len:2][kind:2][type:4][name] + if (fixupPascalNameDirectAt(rec, /*len+kind+type=*/4 + 4)) + support::endian::write16le(rec.data() + 2, uint16_t(S_UDT)); + break; + case S_LDATA32_ST: // [len:2][kind:2][type:4][offset:4][segment:2][name] + if (fixupPascalNameDirectAt(rec, /*len+kind+type+offset+segment=*/ + 4 + 4 + 4 + 2)) + support::endian::write16le(rec.data() + 2, uint16_t(S_LDATA32)); + break; + case S_GDATA32_ST: // same shape as S_LDATA32_ST + if (fixupPascalNameDirectAt(rec, /*len+kind+type+offset+segment=*/ + 4 + 4 + 4 + 2)) + support::endian::write16le(rec.data() + 2, uint16_t(S_GDATA32)); + break; + case S_REGISTER_ST: // [len:2][kind:2][index:4][register:2][name] + if (fixupPascalNameDirectAt(rec, /*len+kind+index+register=*/4 + 4 + 2)) + support::endian::write16le(rec.data() + 2, uint16_t(S_REGISTER)); + break; + case S_BPREL32_ST: // [len:2][kind:2][offset:4][type:4][name] + if (fixupPascalNameDirectAt(rec, /*len+kind+offset+type=*/4 + 4 + 4)) + support::endian::write16le(rec.data() + 2, uint16_t(S_BPREL32)); + break; + case S_LABEL32_ST: // [len:2][kind:2][codeoffset:4][segment:2][flags:1][name] + if (fixupPascalNameDirectAt(rec, /*len+kind+codeoffset+segment+flags=*/ + 4 + 4 + 2 + 1)) + support::endian::write16le(rec.data() + 2, uint16_t(S_LABEL32)); + break; + case S_LPROC32_ST: // [len:2][kind:2][8 x u32][segment:2][flags:1][name] -- + // the 8 dwords are Parent,End,Next,CodeSize,DbgStart, + // DbgEnd,FunctionType,CodeOffset + if (fixupPascalNameDirectAt(rec, /*len+kind+8u32+segment+flags=*/ + 4 + 8 * 4 + 2 + 1)) + support::endian::write16le(rec.data() + 2, uint16_t(S_LPROC32)); + break; + case S_GPROC32_ST: // same shape as S_LPROC32_ST + if (fixupPascalNameDirectAt(rec, /*len+kind+8u32+segment+flags=*/ + 4 + 8 * 4 + 2 + 1)) + support::endian::write16le(rec.data() + 2, uint16_t(S_GPROC32)); + break; + case S_COMPILE: // no modern shape exists to convert to; just skip it + support::endian::write16le(rec.data() + 2, uint16_t(S_SKIP)); + break; + default: + break; // unrecognized old kind; leave as-is, downstream treats as opaque + } +} + +Expected> +convertOldCodeViewSymbols(ArrayRef sectionContents, + BumpPtrAllocator &alloc) { + bool hasMagic = isOldCodeViewSymbols(sectionContents); + assert(hasMagic || isBareOldCodeViewSymbols(sectionContents)); + + // Same total size and layout as the input (magic present or absent, + // exactly as given -- unchanged either way): this section's COFF + // relocations point at byte offsets counted from the section's start + // (e.g. S_GDATA32_ST's DataOffset/Segment fields), so nothing here may + // shift those offsets, including by stripping a magic that was there. + size_t bodyStart = hasMagic ? 4 : 0; + uint8_t *out = alloc.Allocate(sectionContents.size()); + memcpy(out, sectionContents.data(), sectionContents.size()); + MutableArrayRef body(out + bodyStart, + sectionContents.size() - bodyStart); + + size_t pos = 0; + while (pos + sizeof(RecordPrefix) <= body.size()) { + uint16_t length = support::endian::read16le(body.data() + pos); + if (pos + 2 + length > body.size()) + return createStringError("VC6 .debug$S: symbol record overruns " + "section at offset " + + Twine(pos)); + convertOneSymbol(body.slice(pos, 2 + length)); + pos += 2 + length; + } + + return ArrayRef(out, sectionContents.size()); +} + +} // namespace lld::coff diff --git a/lld/COFF/Pdb2Symbols.h b/lld/COFF/Pdb2Symbols.h new file mode 100644 index 0000000000000..2f1b8298552fa --- /dev/null +++ b/lld/COFF/Pdb2Symbols.h @@ -0,0 +1,64 @@ +//===- Pdb2Symbols.h --------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// MSVC 6.0 `/Zi` writes each .debug$S section using the old CodeView C11 +// signature (same CV_SIGNATURE_C11 = 2 as .debug$T, see Pdb2TypeServer.h): +// a flat, unwrapped sequence of symbol records (no subsection framing, unlike +// modern C13), using old "_ST" (Pascal-name) symbol kinds for the handful of +// record types that carry a name. +// +// An object with more than one function has additional .debug$S sections +// (one per function) that carry no magic at all -- they start directly with +// an S_GPROC32_ST/S_LPROC32_ST record. isBareOldCodeViewSymbols detects +// those. +// +// convertOldCodeViewSymbols rewrites the old-kind records to modern +// equivalents in place. The returned buffer is the same total size and +// layout as the input -- magic present or absent, exactly as given -- since +// this section's existing COFF relocations point at byte offsets counted +// from the section's start (e.g. S_GDATA32_ST's DataOffset/Segment fields); +// nothing here may shift those offsets, including by adding or stripping a +// magic prefix. Callers check isOldCodeViewSymbols/isBareOldCodeViewSymbols +// on whatever SectionChunk::getContents() returns (the override, once one +// is set) to decide whether to treat it as a flat symbol stream instead of +// a subsection array -- see the callers in PDB.cpp. + +#ifndef LLD_COFF_PDB2SYMBOLS_H +#define LLD_COFF_PDB2SYMBOLS_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/Error.h" + +namespace lld::coff { + +// Returns true if `sectionContents` (the raw, unstripped .debug$S section +// bytes) starts with the old CV_SIGNATURE_C11 magic. +bool isOldCodeViewSymbols(llvm::ArrayRef sectionContents); + +// Returns true if `sectionContents` has no magic at all but looks like a +// per-function continuation .debug$S section: starts directly with an +// S_GPROC32_ST/S_LPROC32_ST record whose declared length doesn't overrun +// the buffer. +bool isBareOldCodeViewSymbols(llvm::ArrayRef sectionContents); + +// Converts an old-format .debug$S section's known "_ST" symbol kinds to +// their modern (null-terminated-name) equivalents, and strips the leading +// magic if `sectionContents` has one (per isOldCodeViewSymbols) -- the +// returned buffer is always just the flat symbol stream, whether or not the +// input had a magic prefix. Suitable as a drop-in replacement for the +// section's contents, and every consumer's signal that this chunk is +// old-format and already converted, via ObjFile::getDebugSOverride +// returning non-empty for it. +llvm::Expected> +convertOldCodeViewSymbols(llvm::ArrayRef sectionContents, + llvm::BumpPtrAllocator &alloc); + +} // namespace lld::coff + +#endif diff --git a/lld/COFF/Pdb2TypeServer.cpp b/lld/COFF/Pdb2TypeServer.cpp new file mode 100644 index 0000000000000..ae32554c89848 --- /dev/null +++ b/lld/COFF/Pdb2TypeServer.cpp @@ -0,0 +1,753 @@ +//===- Pdb2TypeServer.cpp -------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// See Pdb2TypeServer.h for the format background. Layout notes (all +// little-endian), reverse-engineered against real VC6 /Zi *.o.pdb files: +// +// SuperBlock (fixed offsets from file start): +// 0x00 44-byte text signature "Microsoft C/C++ program database 2.00\r\n" +// followed by 0x1a 'J' 'G' 0x00 0x00. +// 0x2C u32 pageSize (observed 0x1000) +// 0x30 u16 startPage (unused here) +// 0x32 u16 filePages (unused here) +// 0x34 u32 rootSize (byte size of the root/stream-directory +// stream) +// 0x38 u32 reserved +// 0x3C u16[ceil(rootSize/pageSize)] page numbers holding the root stream +// +// Root stream ("stream directory"), once reassembled from its pages: +// u32 numStreams +// numStreams * { u32 size; u32 reserved; } -- per-stream byte size +// then, for every stream with size != 0 (in stream-index order), that +// stream's page numbers as u16s, ceil(size/pageSize) of them. +// +// Stream #2 is the TPI (types) stream. Its own header is a fixed 0x38 (56) +// bytes; empirically: +// 0x00 u32 version (observed 19961031) +// 0x04 u32 typeIndexBegin (observed 56 -- NOT the modern 0x1000) +// 0x08 u32 typeIndexEnd +// 0x10 u32 typeRecordBytes (byte length of the record array that +// follows the header; verified to exactly +// match "TPI stream size - 56" on samples) +// Bytes [0x38, 0x38+typeRecordBytes) are then a plain, tightly-packed +// CVTypeArray -- [u16 length][u16 leaf][...body...] repeated, length not +// including itself. Leaf kinds without an embedded name (LF_MODIFIER, +// LF_POINTER, LF_ARGLIST, LF_PROCEDURE, LF_FIELDLIST itself, ...) use +// ordinary *modern* (0x1000+) kind values and field layouts. Leaf kinds that +// DO carry a name (LF_STRUCTURE/CLASS/UNION, LF_MEMBER, and siblings) use +// the old "_ST" kind values instead (e.g. LF_STRUCTURE_ST = 0x1005, not +// modern LF_STRUCTURE = 0x1505) with an otherwise-identical field layout, +// except the trailing name is Pascal-style length-prefixed rather than +// null-terminated. See stFixupFor() below for the kinds this reader +// recognizes and rewrites to modern form. +// Stream #3 (conventionally DBI/symbols) is empty in every sample, matching +// "type server PDBs do not contain symbols" (see DebugTypes.cpp). + +#include "Pdb2TypeServer.h" + +#include "llvm/DebugInfo/CodeView/CVRecord.h" +#include "llvm/DebugInfo/CodeView/CodeView.h" +#include "llvm/DebugInfo/CodeView/RecordSerialization.h" +#include "llvm/DebugInfo/CodeView/TypeIndex.h" +#include "llvm/Support/Endian.h" + +using namespace llvm; +using namespace llvm::codeview; +using namespace llvm::support; + +namespace lld::coff { + +static const char pdb2Magic[] = "Microsoft C/C++ program database 2.00\r\n"; +static constexpr size_t pdb2MagicLen = sizeof(pdb2Magic) - 1; // no NUL +static constexpr size_t tpiStreamIndex = 2; +static constexpr size_t pdb2TpiHeaderSize = 0x38; + +static Error err(const Twine &msg) { + return createStringError("VC6 PDB 2.0 type server: " + msg.str()); +} + +bool isPdb2TypeServer(MemoryBufferRef mb) { + StringRef buf = mb.getBuffer(); + return buf.size() > pdb2MagicLen && + buf.substr(0, pdb2MagicLen) == StringRef(pdb2Magic, pdb2MagicLen); +} + +namespace { +struct Pdb2File { + ArrayRef data; + uint32_t pageSize; + + ArrayRef page(uint32_t p) const { + uint64_t off = uint64_t(p) * pageSize; + if (off + pageSize > data.size()) + return {}; + return data.slice(off, pageSize); + } + + // Concatenate `count` pages starting at `pages[0..count)`, then trim to + // `byteSize`. Fails loudly (rather than silently dropping the page and + // shifting every later page's bytes left by one page width) if any page + // index is out of range -- this is the first parsing surface touching raw + // untrusted bytes off disk, not output from a trusted compiler run. + Expected> readStream(ArrayRef pages, + uint32_t byteSize) const { + std::vector out; + out.reserve(byteSize); + for (uint16_t p : pages) { + ArrayRef pg = page(p); + if (pg.empty()) + return err("page index " + Twine(p) + " is out of range"); + out.insert(out.end(), pg.begin(), pg.end()); + } + if (out.size() > byteSize) + out.resize(byteSize); + return out; + } +}; +} // namespace + +// Parses the SuperBlock + stream directory and returns the raw bytes of +// stream `tpiStreamIndex` (the TPI stream), still including its own 0x38-byte +// header. +static Expected> readTpiStreamRaw(MemoryBufferRef mb) { + ArrayRef data( + reinterpret_cast(mb.getBufferStart()), + mb.getBufferSize()); + if (data.size() < 0x40) + return err("file too short for a SuperBlock"); + + uint32_t pageSize = support::endian::read32le(data.data() + 0x2C); + uint32_t rootSize = support::endian::read32le(data.data() + 0x34); + if (pageSize == 0 || pageSize > (1u << 20)) + return err("implausible page size " + Twine(pageSize)); + + Pdb2File pdb{data, pageSize}; + + uint32_t rootPageCount = (rootSize + pageSize - 1) / pageSize; + if (0x3C + uint64_t(rootPageCount) * 2 > data.size()) + return err("stream directory page list runs off the end of the file"); + ArrayRef rootPages( + reinterpret_cast(data.data() + 0x3C), + rootPageCount); + + Expected> rootOrErr = pdb.readStream(rootPages, rootSize); + if (!rootOrErr) + return rootOrErr.takeError(); + std::vector &root = *rootOrErr; + if (root.size() < 4) + return err("stream directory too short"); + + uint32_t numStreams = support::endian::read32le(root.data()); + // Per-stream: u32 size, u32 reserved. + if (4 + uint64_t(numStreams) * 8 > root.size()) + return err("stream directory truncated (sizes table)"); + + std::vector sizes(numStreams); + for (uint32_t i = 0; i < numStreams; ++i) + sizes[i] = support::endian::read32le(root.data() + 4 + i * 8); + + if (tpiStreamIndex >= numStreams) + return err("file has no TPI stream (index " + Twine(tpiStreamIndex) + + " >= " + Twine(numStreams) + " streams)"); + + // Page numbers for all streams are concatenated after the sizes table, in + // stream-index order, skipping any stream whose size is 0. + size_t p = 4 + size_t(numStreams) * 8; + ArrayRef tpiPages; + for (uint32_t i = 0; i < numStreams; ++i) { + uint32_t pages = (sizes[i] + pageSize - 1) / pageSize; + if (p + pages * 2 > root.size()) + return err("stream directory truncated (page list)"); + if (i == tpiStreamIndex) + tpiPages = ArrayRef( + reinterpret_cast(root.data() + p), + pages); + p += pages * 2; + } + + return pdb.readStream(tpiPages, sizes[tpiStreamIndex]); +} + +// Converts a record's embedded name from Pascal-style (length-prefixed) to +// null-terminated, in place, given the fixed byte size of whatever +// non-name fields precede it (`prefixSize`, counted from the start of the +// record including its [len][kind] header). Both encodings occupy exactly +// N+1 bytes for an N-character name, so this never changes the record's +// length or shifts any other byte. Returns false (record left untouched) if +// the shape doesn't match what we know how to parse -- callers should treat +// that as "name stays Pascal-encoded", not fatal. +static bool fixupPascalNameDirectAt(MutableArrayRef rec, + size_t nameOffset) { + if (nameOffset >= rec.size()) + return false; + uint8_t nameLen = rec[nameOffset]; + if (nameOffset + 1 + nameLen > rec.size()) + return false; // malformed / not actually a Pascal name here + // Shift the N name chars left by one (over the length byte), then null- + // terminate in what used to be the last name byte's slot. + uint8_t *name = rec.data() + nameOffset; + for (uint8_t i = 0; i < nameLen; ++i) + name[i] = name[i + 1]; + name[nameLen] = 0; + return true; +} + +// CodeView numeric-leaf encoding: values < LF_NUMERIC (0x8000) are a plain +// u16; values >= 0x8000 are a 2-byte discriminator tag followed by the value +// itself. Mirrors llvm::codeview::consume(BinaryStreamReader&, APSInt&) in +// RecordSerialization.cpp -- which is itself the authority here, since it's +// the same fixed set that consumer accepts (member offsets / enum values / +// array sizes are always integral, so LF_REAL*/LF_COMPLEX* etc never appear +// in this position and neither llvm nor this reader need to handle them). +// Returns false (uncommon/unsupported encoding) if `firstWord` isn't one of +// these tags and isn't a plain u16. +static bool numericLeafSize(uint16_t firstWord, size_t &size) { + if (firstWord < LF_NUMERIC) { + size = 2; // plain u16 numeric leaf + return true; + } + switch (firstWord) { + case LF_CHAR: // u16 discriminator + i8 + size = 3; + return true; + case LF_SHORT: // u16 discriminator + i16 + case LF_USHORT: // u16 discriminator + u16 + size = 4; + return true; + case LF_LONG: // u16 discriminator + i32 + case LF_ULONG: // u16 discriminator + u32 + size = 6; + return true; + case LF_REAL32: // u16 discriminator + 4-byte float + size = 6; + return true; + case LF_REAL64: // u16 discriminator + 8-byte double + size = 10; + return true; + case LF_REAL80: // u16 discriminator + 10-byte extended + size = 12; + return true; + case LF_REAL128: // u16 discriminator + 16-byte float128 + size = 18; + return true; + case LF_QUADWORD: // u16 discriminator + i64 + case LF_UQUADWORD: // u16 discriminator + u64 + size = 10; + return true; + default: + return false; // uncommon numeric-leaf encoding; leave name as-is + } +} + +// As above, but `prefixSize` is followed by a CodeView numeric-leaf-encoded +// value (e.g. a member offset or enum value) before the Pascal name, whose +// own byte width must be determined first. +static bool fixupPascalNameAt(MutableArrayRef rec, + size_t prefixSize) { + if (rec.size() < prefixSize + 3) + return false; + size_t p = prefixSize; + uint16_t firstWord = support::endian::read16le(rec.data() + p); + size_t numericFieldSize; + if (!numericLeafSize(firstWord, numericFieldSize)) + return false; + p += numericFieldSize; + return fixupPascalNameDirectAt(rec, p); +} + +// LF_MEMBER: [len:2][kind:2][attr:2][type:4][offset-numeric-leaf][name]... +static bool fixupMemberName(MutableArrayRef rec) { + return fixupPascalNameAt(rec, /*len+kind+attr+type=*/4 + 2 + 4); +} + +// LF_FIELDLIST member sub-records have no [len:2] of their own (unlike +// top-level records): they're packed back-to-back as [kind:2][...], each +// individually padded up to a multiple of 4 with an LF_PAD1..3 filler byte, +// and the whole sequence's extent is implied by the *outer* FIELDLIST +// record's declared length. See fixupFieldListMembers, which computes each +// member's numeric-field offset (shape-dependent -- e.g. 8 for LF_MEMBER's +// [kind:2][attr:2][type:4], 4 for LF_ENUMERATE's [kind:2][attrs:2]) and +// calls fixupPascalNameAt directly rather than going through a per-kind +// wrapper like the top-level fixups below. + +// LF_STRUCTURE/LF_CLASS (identical shape up to the name; verified against +// llvm's TypeRecordMapping::visitKnownRecord(ClassRecord&) in +// TypeRecordMapping.cpp, which is the ground truth for what the ghash-based +// hashTypeRecord()/TypeDeserializer path expects on read): +// [len:2][kind:2][count:2][properties:2][fieldlist:4][derived:4][vshape:4] +// [size-numeric-leaf][name]... +static bool fixupStructureLikeName(MutableArrayRef rec) { + return fixupPascalNameAt(rec, /*len+kind+count+props+3 TypeIndices=*/ + 4 + 2 + 2 + 4 + 4 + 4); +} + +// LF_UNION: unlike LF_STRUCTURE/LF_CLASS, has no DerivedFrom or VShape +// TypeIndex field (TypeRecordMapping::visitKnownRecord(UnionRecord&): +// MemberCount, Options, FieldList, SizeOf, Name -- no DerivationList/ +// VTableShape). Do not merge this with fixupStructureLikeName; the prefix +// is 8 bytes shorter. +// [len:2][kind:2][count:2][properties:2][fieldlist:4][size-numeric-leaf][name]... +static bool fixupUnionName(MutableArrayRef rec) { + return fixupPascalNameAt(rec, /*len+kind+count+props+fieldlist=*/ + 4 + 2 + 2 + 4); +} + +// LF_ARRAY: [len:2][kind:2][elementType:4][indexType:4][size-numeric-leaf] +// [name]... Usually anonymous (empty Pascal name: a 0x00 length byte) since +// arrays are typically embedded as another record's type field rather than +// named in their own right, but the shape (and thus the fixup) is the same +// either way. +static bool fixupArrayLikeName(MutableArrayRef rec) { + return fixupPascalNameAt(rec, /*len+kind+elementType+indexType=*/ + 4 + 4 + 4); +} + +// LF_ENUM: [len:2][kind:2][count:2][properties:2][underlyingType:4] +// [fieldlist:4][name]... Per TypeRecordMapping::visitKnownRecord(EnumRecord&): +// MemberCount, Options, UnderlyingType, FieldList, then straight into the +// name -- unlike LF_STRUCTURE/CLASS/UNION/LF_ARRAY, there is no SizeOf/ +// encoded-integer field (an enum's size is implicit from UnderlyingType), so +// the name is NOT preceded by a numeric leaf. Use fixupPascalNameDirectAt +// here, not fixupPascalNameAt. +static bool fixupEnumName(MutableArrayRef rec) { + return fixupPascalNameDirectAt( + rec, /*len+kind+count+props+underlying+fieldlist=*/ + 4 + 2 + 2 + 4 + 4); +} + +// LF_ENUMERATE_ST only ever appears as a nested field-list member (see +// fixupFieldListMembers, which handles it directly via fixupPascalNameAt +// with the nested prefix size); this top-level-shaped wrapper exists only so +// stFixupFor's entry for it has a valid, safe fixName, in case it were ever +// (incorrectly) encountered as a top-level record's own kind. +static bool fixupEnumeratorNameTopLevel(MutableArrayRef rec) { + return fixupPascalNameAt(rec, /*len+kind+attrs=*/4 + 2); +} + +// The following three are, like LF_MEMBER/LF_ENUMERATE, nested-only (no +// top-level form): fixupFieldListMembers dispatches them directly via +// fixupPascalNameDirectAt (no numeric-leaf value field precedes their name, +// unlike LF_MEMBER/LF_ENUMERATE), so these top-level-shaped wrappers exist +// only for a valid, safe stFixupFor::fixName entry. +// +// LF_STMEMBER: [kind:2][attrs:2][type:4][name]... (static data member: a +// plain TypeIndex, no offset -- statics don't live at a fixed in-object +// offset). +static bool fixupStaticMemberNameTopLevel(MutableArrayRef rec) { + return fixupPascalNameDirectAt(rec, /*len+kind+attrs+type=*/4 + 2 + 4); +} + +// LF_METHOD (overload set): [kind:2][count:2][methodList:4][name]... +static bool fixupMethodNameTopLevel(MutableArrayRef rec) { + return fixupPascalNameDirectAt(rec, /*len+kind+count+methodList=*/4 + 2 + 4); +} + +// LF_NESTTYPE: [kind:2][padding:2][type:4][name]... +static bool fixupNestedTypeNameTopLevel(MutableArrayRef rec) { + return fixupPascalNameDirectAt(rec, /*len+kind+padding+type=*/4 + 2 + 4); +} + +// LF_ONEMETHOD: [kind:2][attrs:2][type:4][vftableOffset:4 -- ONLY present if +// attrs' MethodKind sub-field says this method introduces a virtual +// function][name]... The MethodKind occupies bits 2-4 of attrs +// (MethodOptions::MethodKindMask, shifted right by 2); MethodKind:: +// IntroducingVirtual or MethodKind::PureIntroducingVirtual means the 4-byte +// VFTableOffset field is present, any other kind means it's omitted -- see +// MemberAttributes::getMethodKind() / OneMethodRecord::isIntroducingVirtual() +// in TypeRecord.h. Unlike the other nested kinds, this one has a genuinely +// conditional prefix length, so it isn't expressible as a single fixed +// offset -- computed directly rather than via nestedShapeFor/NestedShape. +static bool isIntroducingVirtualMethodKind(uint16_t attrs) { + auto methodKind = MethodKind( + (attrs & uint16_t(MethodOptions::MethodKindMask)) >> 2); + return methodKind == MethodKind::IntroducingVirtual || + methodKind == MethodKind::PureIntroducingVirtual; +} +static size_t oneMethodPrefixSize(uint16_t attrs, size_t base) { + return base + (isIntroducingVirtualMethodKind(attrs) ? 4 : 0); +} +// Top-level-shaped wrapper for stFixupFor::fixName (see comment on the +// STMEMBER/METHOD/NESTTYPE wrappers above); LF_ONEMETHOD_ST normally only +// appears nested, handled directly in fixupFieldListMembers. +static bool fixupOneMethodNameTopLevel(MutableArrayRef rec) { + if (rec.size() < 6) + return false; + uint16_t attrs = support::endian::read16le(rec.data() + 4); // len+kind+attrs + return fixupPascalNameDirectAt(rec, oneMethodPrefixSize(attrs, 4 + 2 + 4)); +} + +// Three more nested-only field-list member kinds, assumed to share the same +// [kind:2][word:2][type:4][name]... shape as LF_STMEMBER/LF_METHOD/LF_NESTTYPE +// above (no numeric leaf): friend-function declarations, "extended" +// nested-type declarations (adds access attrs where LF_NESTTYPE has only +// padding), and member-modify records (multiple-inheritance override +// bookkeeping). None of these three have a TYPE_RECORD/MEMBER_RECORD entry +// in CodeViewTypes.def, so unlike LF_STMEMBER/LF_METHOD/LF_NESTTYPE/ +// LF_ONEMETHOD there's no TypeRecordMapping.cpp shape to verify against -- +// this is cvinfo.h-by-analogy, unverified (same caveat as fixupAliasName +// below). Same top-level-shaped-wrapper caveat as the others -- real +// occurrences are nested, handled via nestedShapeFor/fixupFieldListMembers. +static bool fixupFriendFcnNameTopLevel(MutableArrayRef rec) { + return fixupPascalNameDirectAt(rec, /*len+kind+pad+type=*/4 + 2 + 4); +} +static bool fixupNestTypeExNameTopLevel(MutableArrayRef rec) { + return fixupPascalNameDirectAt(rec, /*len+kind+attrs+type=*/4 + 2 + 4); +} +static bool fixupMemberModifyNameTopLevel(MutableArrayRef rec) { + return fixupPascalNameDirectAt(rec, /*len+kind+pad+type=*/4 + 2 + 4); +} + +// LF_ALIAS (typedef): [len:2][kind:2][utype:4][name]... -- genuinely +// top-level only (a typedef isn't a field-list member). No modern +// TypeRecord/deserializer class exists for this in llvm (only the raw +// CV_TYPE kind enum value), so downstream consumers see it as an opaque +// record; that's fine, all we need is a well-formed length/kind/name. +static bool fixupAliasName(MutableArrayRef rec) { + return fixupPascalNameDirectAt(rec, /*len+kind+utype=*/4 + 4); +} + +// LF_DEFARG (default argument expression, e.g. `void F(int x = 5)`): +// [len:2][kind:2][type:4][expr]... `expr` is Pascal/null-terminated source +// text (not a symbol name), but the fixup is byte-identical. Top-level only +// (referenced by an LF_ARGLIST/method's argument list, not a field member). +// Like LF_ALIAS below, LF_DEFARG has no TYPE_RECORD entry in +// CodeViewTypes.def and thus no TypeRecordMapping.cpp shape to check +// against -- this is old cvinfo.h-by-memory reasoning, unverified against +// llvm ground truth. +static bool fixupDefArgName(MutableArrayRef rec) { + return fixupPascalNameDirectAt(rec, /*len+kind+type=*/4 + 4); +} + +// LF_DIMARRAY (multi-dimensional array, rare -- Fortran-oriented, unlikely +// in VC6 C/C++ but cheap to support): [len:2][kind:2][elementType:4] +// [dimInfo:4][name]... Top-level only. Also no TypeRecordMapping.cpp entry +// to check against -- same unverified-against-llvm-ground-truth caveat as +// LF_ALIAS/LF_DEFARG. +static bool fixupDimArrayName(MutableArrayRef rec) { + return fixupPascalNameDirectAt(rec, /*len+kind+elementType+dimInfo=*/ + 4 + 4 + 4); +} + +// LF_PRECOMP (precompiled-header type-range reference): [len:2][kind:2] +// [start:4][count:4][signature:4][name]... Matches llvm's PrecompRecord +// (StartTypeIndex/TypesCount/Signature/PrecompFilePath). Top-level only. +// May be unused if bw1-decomp's objects don't use /Yc /Yu PCH, but handled +// for completeness rather than left as a silent gap. +static bool fixupPrecompName(MutableArrayRef rec) { + return fixupPascalNameDirectAt(rec, /*len+kind+start+count+signature=*/ + 4 + 4 + 4 + 4); +} + +// Old "_ST" (Pascal-name) leaf kinds whose non-name field layout is +// otherwise identical to a validated modern counterpart: the modern kind +// they should be rewritten to (so the rest of LLVM's CodeView tooling +// recognizes them), paired with the name-fixup shape to apply. +struct StStructureFixup { + TypeLeafKind modernKind; + bool (*fixName)(MutableArrayRef); +}; +static std::optional stFixupFor(uint16_t leaf) { + switch (leaf) { + case LF_STRUCTURE_ST: + return StStructureFixup{LF_STRUCTURE, fixupStructureLikeName}; + case LF_CLASS_ST: + return StStructureFixup{LF_CLASS, fixupStructureLikeName}; + case LF_UNION_ST: + return StStructureFixup{LF_UNION, fixupUnionName}; + case LF_MEMBER_ST: + return StStructureFixup{LF_MEMBER, fixupMemberName}; + case LF_ARRAY_ST: + return StStructureFixup{LF_ARRAY, fixupArrayLikeName}; + case LF_ENUM_ST: + return StStructureFixup{LF_ENUM, fixupEnumName}; + case LF_ENUMERATE_ST: // normally only nested; see fixName comment + return StStructureFixup{LF_ENUMERATE, fixupEnumeratorNameTopLevel}; + case LF_STMEMBER_ST: // normally only nested; see fixName comment + return StStructureFixup{LF_STMEMBER, fixupStaticMemberNameTopLevel}; + case LF_METHOD_ST: // normally only nested; see fixName comment + return StStructureFixup{LF_METHOD, fixupMethodNameTopLevel}; + case LF_NESTTYPE_ST: // normally only nested; see fixName comment + return StStructureFixup{LF_NESTTYPE, fixupNestedTypeNameTopLevel}; + case LF_ONEMETHOD_ST: // normally only nested; see fixName comment + return StStructureFixup{LF_ONEMETHOD, fixupOneMethodNameTopLevel}; + case LF_FRIENDFCN_ST: // normally only nested; see fixName comment + return StStructureFixup{LF_FRIENDFCN, fixupFriendFcnNameTopLevel}; + case LF_NESTTYPEEX_ST: // normally only nested; see fixName comment + return StStructureFixup{LF_NESTTYPEEX, fixupNestTypeExNameTopLevel}; + case LF_MEMBERMODIFY_ST: // normally only nested; see fixName comment + return StStructureFixup{LF_MEMBERMODIFY, fixupMemberModifyNameTopLevel}; + case LF_ALIAS_ST: // typedef; top-level only + return StStructureFixup{LF_ALIAS, fixupAliasName}; + case LF_DEFARG_ST: // default argument expression; top-level only + return StStructureFixup{LF_DEFARG, fixupDefArgName}; + case LF_DIMARRAY_ST: // top-level only + return StStructureFixup{LF_DIMARRAY, fixupDimArrayName}; + case LF_PRECOMP_ST: // top-level only + return StStructureFixup{LF_PRECOMP, fixupPrecompName}; + // LF_MANAGED_ST: .NET/managed-code metadata reference. Never emitted by + // VC6 compiling native C/C++ (no /clr in this era), so deliberately left + // unhandled rather than guessed at -- not a gap for this project's actual + // inputs. + default: + return std::nullopt; + } +} + +// Walks a decoded LF_FIELDLIST's content (immediately after its [len][kind] +// header) member-by-member -- mirroring llvm's own handleFieldList() +// size/padding logic -- and rewrites each recognized "_ST" member's kind to +// modern in place, fixing up its Pascal name in the same pass, so the rest +// of LLVM's CodeView tooling recognizes it. Stops (leaving the remainder +// untouched) at the first member kind we don't know the shape of. + +// Describes where a nested member's name-area begins (byte offset from the +// member's own [kind:2]) and whether a CodeView numeric-leaf-encoded value +// (whose own byte width must be resolved first) precedes the Pascal name +// there, for each nested member kind this function knows how to size. +struct NestedShape { + size_t prefixSize; + bool hasNumericLeaf; +}; +static std::optional nestedShapeFor(TypeLeafKind modernKind) { + switch (modernKind) { + case LF_MEMBER: + return NestedShape{8, true}; // [kind:2][attrs:2][type:4] + numeric-leaf + case LF_ENUMERATE: + return NestedShape{4, true}; // [kind:2][attrs:2] + numeric-leaf + case LF_STMEMBER: + return NestedShape{8, false}; // [kind:2][attrs:2][type:4], no leaf + case LF_METHOD: + return NestedShape{8, false}; // [kind:2][count:2][methodList:4], no leaf + case LF_NESTTYPE: + return NestedShape{8, false}; // [kind:2][padding:2][type:4], no leaf + case LF_FRIENDFCN: + return NestedShape{8, false}; // [kind:2][padding:2][type:4], no leaf + case LF_NESTTYPEEX: + return NestedShape{8, false}; // [kind:2][attrs:2][type:4], no leaf + case LF_MEMBERMODIFY: + return NestedShape{8, false}; // [kind:2][padding:2][type:4], no leaf + default: + return std::nullopt; + } +} + +// LF_BCLASS/LF_VBCLASS/LF_IVBCLASS/LF_VFUNCTAB/LF_INDEX have no embedded +// name in EITHER old or new CodeView, so there's no "_ST" sibling for them +// (they don't appear in stFixupFor) and no Pascal-name fixup is needed -- +// they're already byte-identical to their modern form. But the field-list +// walker still needs their size to skip over them and keep going, mirroring +// llvm's own handleBaseClass/handleVirtualBaseClass/handleVFPtr/ +// handleListContinuation in TypeIndexDiscovery.cpp. Without this, the walker +// would treat any class with a base class (i.e. almost any class using +// inheritance), a virtual base, or a vtable pointer as "unknown member kind" +// and bail immediately, leaving the rest of the field list un-fixed-up -- +// this was the actual cause of real corruption downstream (a Pascal-encoded +// name misread as null-terminated by later tooling), not any of the "_ST" +// leaf kinds. +static std::optional fixedFieldListMemberSize(uint16_t kind, + ArrayRef data) { + switch (TypeLeafKind(kind)) { + case LF_BCLASS: + // [kind:2][attrs:2][type:4][offset-numeric-leaf] + if (data.size() < 10) + return std::nullopt; + { + size_t n; + if (!numericLeafSize(support::endian::read16le(data.data() + 8), n)) + return std::nullopt; + return 8 + n; + } + case LF_VBCLASS: + case LF_IVBCLASS: { + // [kind:2][attrs:2][baseType:4][vbptrType:4][vbpOffset-leaf][vbOffset-leaf] + if (data.size() < 14) + return std::nullopt; + size_t n1; + if (!numericLeafSize(support::endian::read16le(data.data() + 12), n1)) + return std::nullopt; + size_t after1 = 12 + n1; + if (data.size() < after1 + 2) + return std::nullopt; + size_t n2; + if (!numericLeafSize(support::endian::read16le(data.data() + after1), n2)) + return std::nullopt; + return after1 + n2; + } + case LF_VFUNCTAB: + // [kind:2][padding:2][type:4] + return data.size() >= 8 ? std::optional(8) : std::nullopt; + case LF_INDEX: + // [kind:2][padding:2][type:4] (points at a continuation LF_FIELDLIST) + return data.size() >= 8 ? std::optional(8) : std::nullopt; + default: + return std::nullopt; + } +} + +// Note: hitting one unrecognized member kind stops conversion for every +// member *after* it too, not just that one -- a single unexpected kind +// partway through a class definition leaves the remaining, otherwise +// convertible members still Pascal-encoded. Not corruption (consistent with +// this project's "leave broken, don't fake it" philosophy), but the blast +// radius is bigger than "one member" if it ever happens. +static void fixupFieldListMembers(MutableArrayRef content) { + size_t p = 0; + while (p + 2 <= content.size()) { + uint16_t kind = support::endian::read16le(content.data() + p); + + if (std::optional size = + fixedFieldListMemberSize(kind, content.slice(p))) { + p += *size; + if (p < content.size() && content[p] >= LF_PAD0) + p += content[p] & 0x0F; + continue; + } + + std::optional fx = stFixupFor(kind); + if (!fx) + return; // unknown/unhandled nested member kind; stop here + + // LF_ONEMETHOD has a genuinely conditional prefix length (see + // oneMethodPrefixSize), not expressible via the fixed-offset NestedShape + // table below, so it's handled as its own case. + if (fx->modernKind == LF_ONEMETHOD) { + if (p + 8 > content.size()) + return; + uint16_t attrs = support::endian::read16le(content.data() + p + 2); + // [kind:2][attrs:2][type:4] = 8-byte fixed prefix, then optionally a + // 4-byte VFTableOffset, then the name -- see oneMethodPrefixSize. + size_t nameOff = oneMethodPrefixSize(attrs, p + 8); + if (nameOff >= content.size()) + return; + uint8_t nameLen = content[nameOff]; + if (nameOff + 1 + nameLen > content.size()) + return; + size_t memberLen = (nameOff + 1 + nameLen) - p; + + support::endian::write16le(content.data() + p, uint16_t(fx->modernKind)); + fixupPascalNameDirectAt(content.slice(p, memberLen), + nameOff - p); + + p += memberLen; + if (p < content.size() && content[p] >= LF_PAD0) + p += content[p] & 0x0F; + continue; + } + + std::optional shape = nestedShapeFor(fx->modernKind); + if (!shape) + return; // unknown/unhandled nested member shape; stop here + + size_t q = p + shape->prefixSize; + if (shape->hasNumericLeaf) { + // Recompute this member's length the same way handleDataMember() / + // handleEnumerator() do: prefix + encoded-integer + C-string name. + if (q + 2 > content.size()) + return; + uint16_t firstWord = support::endian::read16le(content.data() + q); + size_t numericFieldSize; + if (!numericLeafSize(firstWord, numericFieldSize)) + return; // uncommon numeric-leaf encoding; stop here + q += numericFieldSize; + } + if (q >= content.size()) + return; + uint8_t nameLen = content[q]; + if (q + 1 + nameLen > content.size()) + return; + size_t memberLen = (q + 1 + nameLen) - p; + + support::endian::write16le(content.data() + p, uint16_t(fx->modernKind)); + if (shape->hasNumericLeaf) + fixupPascalNameAt(content.slice(p, memberLen), shape->prefixSize); + else + fixupPascalNameDirectAt(content.slice(p, memberLen), shape->prefixSize); + + p += memberLen; + if (p < content.size() && content[p] >= LF_PAD0) + p += content[p] & 0x0F; // skip LF_PAD1..3 filler, same as handleFieldList + } +} + +Expected> readPdb2TypeServerTypes(MemoryBufferRef mb, + BumpPtrAllocator &alloc) { + Expected> tpiOrErr = readTpiStreamRaw(mb); + if (!tpiOrErr) + return tpiOrErr.takeError(); + std::vector &tpi = *tpiOrErr; + + if (tpi.empty()) { + // A genuinely empty TPI stream means this TU defines no local types of + // its own (observed for real /Zi objects, e.g. one that only uses types + // already known from elsewhere) -- valid and common, not an error. + return ArrayRef(); + } + if (tpi.size() < pdb2TpiHeaderSize) + return err("TPI stream shorter than its own header"); + + uint32_t typeIndexBegin = support::endian::read32le(tpi.data() + 4); + uint32_t typeIndexEnd = support::endian::read32le(tpi.data() + 8); + uint32_t typeRecordBytes = support::endian::read32le(tpi.data() + 16); + if (typeIndexBegin == 0 || typeIndexBegin > TypeIndex::FirstNonSimpleIndex) + return err("implausible TypeIndexBegin " + Twine(typeIndexBegin)); + if (typeIndexEnd < typeIndexBegin) + return err("TypeIndexEnd " + Twine(typeIndexEnd) + + " precedes TypeIndexBegin " + Twine(typeIndexBegin)); + if (pdb2TpiHeaderSize + uint64_t(typeRecordBytes) > tpi.size()) + return err("type record area runs past the end of the TPI stream"); + + // Copy just the record bytes out into an LLD-owned, mutable buffer -- this + // becomes the object's new `debugTypes`. + uint8_t *buf = alloc.Allocate(typeRecordBytes); + memcpy(buf, tpi.data() + pdb2TpiHeaderSize, typeRecordBytes); + MutableArrayRef out(buf, typeRecordBytes); + + // Embedded TypeIndex references inside this stream's records are already + // encoded exactly the way a modern object's local .debug$T would encode + // them: `0x1000 + N` where N is the 0-based sequential position of the + // referenced record within this same stream -- e.g. record position 1's + // LF_POINTER has `referent = 0x1000` (position 0); position 3's LF_POINTER + // has `referent = 0x1002` (position 2); matching what + // TypeIndex::toArrayIndex() (`value - 0x1000`) expects. No rewriting of + // TypeIndex values is needed. typeIndexBegin/typeIndexEnd (see + // readTpiStreamRaw) are bookkeeping internal to the PDB 2.0 container's + // own record layout, not a numbering embedded references use. The only + // transformation needed is rewriting old "_ST" (Pascal-name) leaf kinds to + // their modern equivalents. + size_t pos = 0; + while (pos < out.size()) { + if (pos + sizeof(RecordPrefix) > out.size()) + return err("type record truncated at offset " + Twine(pos)); + uint16_t length = support::endian::read16le(out.data() + pos); + if (pos + 2 + length > out.size()) + return err("type record overruns stream at offset " + Twine(pos)); + MutableArrayRef rec = out.slice(pos, 2 + length); + + // Old "_ST" (Pascal-name) records must be rewritten to their modern + // kind so the rest of LLVM's CodeView tooling recognizes them. Only + // commit the kind rewrite if the name fixup actually succeeds -- fixName + // can fail if this record's shape doesn't match what we assumed (e.g. an + // untested/speculative kind with a wrong byte-offset guess); if we wrote + // the modern kind regardless, the record would end up with a modern tag + // but a still-Pascal-encoded name, which is silently self-inconsistent + // rather than caught (the same corruption class the LF_UNION_ST and + // LF_ENUM_ST bugs produced, just with no code path left to notice it). + std::optional stFixup = + stFixupFor(support::endian::read16le(rec.data() + 2)); + if (stFixup && stFixup->fixName(rec)) + support::endian::write16le(rec.data() + 2, uint16_t(stFixup->modernKind)); + if (CVType(rec).kind() == LF_FIELDLIST) + fixupFieldListMembers(rec.drop_front(sizeof(RecordPrefix))); + + pos += 2 + length; + } + + return ArrayRef(out); +} + +} // namespace lld::coff diff --git a/lld/COFF/Pdb2TypeServer.h b/lld/COFF/Pdb2TypeServer.h new file mode 100644 index 0000000000000..42203e826d3fe --- /dev/null +++ b/lld/COFF/Pdb2TypeServer.h @@ -0,0 +1,53 @@ +//===- Pdb2TypeServer.h ---------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// MSVC 6.0 `/Zi` writes each translation unit's types into an external +// PDB 2.0 ("JG") type server (a `*.o.pdb` referenced from the object's +// `.debug$T` by an old LF_TYPESERVER/LF_TYPESERVER_ST record). PDB 2.0 is a +// different, older MSF container than the PDB 7.0 ("DS") format the rest of +// LLVM's PDB/MSF libraries assume, so it cannot be opened via PDBFile / +// NativeSession; this file implements a small from-scratch reader for it. +// +// Intra-object TypeIndex references inside the TPI stream are already +// encoded the way a modern object's local .debug$T would encode them +// (`0x1000 + N`, N = the referenced record's 0-based position in the +// stream), so no reindexing is needed -- see the comment on +// readPdb2TypeServerTypes's implementation for the byte-level evidence. The +// one real fix-up needed: named leaf kinds (LF_STRUCTURE, LF_MEMBER, +// LF_ENUM, and siblings) use old "_ST" kind values with a Pascal-style +// (length-prefixed) name instead of the modern kind with a null-terminated +// name; see stFixupFor() in the .cpp for the full set handled. + +#ifndef LLD_COFF_PDB2TYPESERVER_H +#define LLD_COFF_PDB2TYPESERVER_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/MemoryBufferRef.h" + +namespace lld::coff { + +// Returns true if `data` looks like a PDB 2.0 ("JG") MSF file, i.e. starts +// with the "Microsoft C/C++ program database 2.00" signature. +bool isPdb2TypeServer(llvm::MemoryBufferRef mb); + +// Reads the TPI stream out of a VC6 /Zi PDB 2.0 type server and returns the +// upgraded type-record bytes: named leaf kinds rewritten from old "_ST" +// (Pascal-name) kind values to their modern (null-terminated-name) +// equivalents. TypeIndex references are left untouched -- see the file +// comment above for why no reindexing is needed. The returned bytes are +// allocated out of `alloc` and are a drop-in replacement for what a normal +// object's `.debug$T` content (post consumeDebugMagic) would contain -- i.e. +// suitable for `ObjFile::debugTypes` + `makeTpiSource()`. +llvm::Expected> +readPdb2TypeServerTypes(llvm::MemoryBufferRef mb, llvm::BumpPtrAllocator &alloc); + +} // namespace lld::coff + +#endif diff --git a/llvm/tools/llvm-pdbutil/DumpOutputStyle.cpp b/llvm/tools/llvm-pdbutil/DumpOutputStyle.cpp index b2362ecb75703..34a47b8dd515a 100644 --- a/llvm/tools/llvm-pdbutil/DumpOutputStyle.cpp +++ b/llvm/tools/llvm-pdbutil/DumpOutputStyle.cpp @@ -1225,7 +1225,7 @@ static void dumpPartialTypeStream(LinePrinter &Printer, NumDigits(TypeIndex::FirstNonSimpleIndex + Stream.getNumTypeRecords()); MinimalTypeDumpVisitor V(Printer, Width + 2, Bytes, Extras, Types, RefTracker, - Stream.getNumHashBuckets(), Stream.getHashValues(), + Stream.getNumHashBuckets(), Stream.getHashValuesV80(), &Stream); if (opts::dump::DumpTypeDependents) { @@ -1375,7 +1375,7 @@ Error DumpOutputStyle::dumpTpiStream(uint32_t StreamIdx) { if (DumpTypes || !Indices.empty()) { if (Indices.empty()) dumpFullTypeStream(P, Types, MaybeTracker, Stream.getNumTypeRecords(), - Stream.getNumHashBuckets(), Stream.getHashValues(), + Stream.getNumHashBuckets(), Stream.getHashValuesV80(), &Stream, DumpBytes, DumpExtras); else { std::vector TiList(Indices.begin(), Indices.end());