Index: lld/trunk/COFF/PDB.cpp =================================================================== --- lld/trunk/COFF/PDB.cpp +++ lld/trunk/COFF/PDB.cpp @@ -82,7 +82,11 @@ bool IsTypeServerMap = false; }; +class DebugSHandler; + class PDBLinker { + friend DebugSHandler; + public: PDBLinker(SymbolTable *Symtab) : Alloc(), Symtab(Symtab), Builder(Alloc), TypeTable(Alloc), @@ -168,6 +172,50 @@ /// Cached to prevent repeated load attempts. std::map MissingTypeServerPDBs; }; + +class DebugSHandler { + PDBLinker &Linker; + + /// The object file whose .debug$S sections we're processing. + ObjFile &File; + + /// The result of merging type indices. + const CVIndexMap &IndexMap; + + /// The DEBUG_S_STRINGTABLE subsection. These strings are referred to by + /// index from other records in the .debug$S section. All of these strings + /// need to be added to the global PDB string table, and all references to + /// these strings need to have their indices re-written to refer to the + /// global PDB string table. + DebugStringTableSubsectionRef CVStrTab; + + /// The DEBUG_S_FILECHKSMS subsection. As above, these are referred to + /// by other records in the .debug$S section and need to be merged into the + /// PDB. + DebugChecksumsSubsectionRef Checksums; + + /// The DEBUG_S_FRAMEDATA subsection(s). There can be more than one of + /// these and they need not appear in any specific order. However, they + /// contain string table references which need to be re-written, so we + /// collect them all here and re-write them after all subsections have been + /// discovered and processed. + std::vector NewFpoFrames; + + /// Pointers to raw memory that we determine have string table references + /// that need to be re-written. We first process all .debug$S subsections + /// to ensure that we can handle subsections written in any order, building + /// up this list as we go. At the end, we use the string table (which must + /// have been discovered by now else it is an error) to re-write these + /// references. + std::vector StringTableReferences; + +public: + DebugSHandler(PDBLinker &Linker, ObjFile &File, const CVIndexMap &IndexMap) + : Linker(Linker), File(File), IndexMap(IndexMap) {} + + void handleDebugS(lld::coff::SectionChunk &DebugS); + void finish(); +}; } static SectionChunk *findByName(ArrayRef Sections, @@ -785,15 +833,14 @@ cantFail(std::move(EC)); } -// Allocate memory for a .debug$S section and relocate it. +// Allocate memory for a .debug$S / .debug$F section and relocate it. static ArrayRef relocateDebugChunk(BumpPtrAllocator &Alloc, - SectionChunk *DebugChunk) { - uint8_t *Buffer = Alloc.Allocate(DebugChunk->getSize()); - assert(DebugChunk->OutputSectionOff == 0 && + SectionChunk &DebugChunk) { + uint8_t *Buffer = Alloc.Allocate(DebugChunk.getSize()); + assert(DebugChunk.OutputSectionOff == 0 && "debug sections should not be in output sections"); - DebugChunk->writeTo(Buffer); - return consumeDebugMagic(makeArrayRef(Buffer, DebugChunk->getSize()), - ".debug$S"); + DebugChunk.writeTo(Buffer); + return makeArrayRef(Buffer, DebugChunk.getSize()); } static pdb::SectionContrib createSectionContrib(const Chunk *C, uint32_t Modi) { @@ -836,6 +883,114 @@ return PdbStrTable.insert(*ExpectedString); } +void DebugSHandler::handleDebugS(lld::coff::SectionChunk &DebugS) { + DebugSubsectionArray Subsections; + + ArrayRef RelocatedDebugContents = consumeDebugMagic( + relocateDebugChunk(Linker.Alloc, DebugS), DebugS.getSectionName()); + + BinaryStreamReader Reader(RelocatedDebugContents, support::little); + ExitOnErr(Reader.readArray(Subsections, RelocatedDebugContents.size())); + + for (const DebugSubsectionRecord &SS : Subsections) { + switch (SS.kind()) { + case DebugSubsectionKind::StringTable: { + assert(!CVStrTab.valid() && + "Encountered multiple string table subsections!"); + ExitOnErr(CVStrTab.initialize(SS.getRecordData())); + break; + } + case DebugSubsectionKind::FileChecksums: + assert(!Checksums.valid() && + "Encountered multiple checksum subsections!"); + ExitOnErr(Checksums.initialize(SS.getRecordData())); + break; + case DebugSubsectionKind::Lines: + // We can add the relocated line table directly to the PDB without + // modification because the file checksum offsets will stay the same. + File.ModuleDBI->addDebugSubsection(SS); + break; + case DebugSubsectionKind::FrameData: { + // We need to re-write string table indices here, so save off all + // frame data subsections until we've processed the entire list of + // subsections so that we can be sure we have the string table. + DebugFrameDataSubsectionRef FDS; + ExitOnErr(FDS.initialize(SS.getRecordData())); + NewFpoFrames.push_back(std::move(FDS)); + break; + } + case DebugSubsectionKind::Symbols: + if (Config->DebugGHashes) { + mergeSymbolRecords(Linker.Alloc, &File, Linker.Builder.getGsiBuilder(), + IndexMap, Linker.GlobalIDTable, + StringTableReferences, SS.getRecordData()); + } else { + mergeSymbolRecords(Linker.Alloc, &File, Linker.Builder.getGsiBuilder(), + IndexMap, Linker.IDTable, StringTableReferences, + SS.getRecordData()); + } + break; + default: + // FIXME: Process the rest of the subsections. + break; + } + } +} + +void DebugSHandler::finish() { + pdb::DbiStreamBuilder &DbiBuilder = Linker.Builder.getDbiBuilder(); + + // We should have seen all debug subsections across the entire object file now + // which means that if a StringTable subsection and Checksums subsection were + // present, now is the time to handle them. + if (!CVStrTab.valid()) { + if (Checksums.valid()) + fatal(".debug$S sections with a checksums subsection must also contain a " + "string table subsection"); + + if (!StringTableReferences.empty()) + warn("No StringTable subsection was encountered, but there are string " + "table references"); + return; + } + + // Rewrite string table indices in the Fpo Data and symbol records to refer to + // the global PDB string table instead of the object file string table. + for (DebugFrameDataSubsectionRef &FDS : NewFpoFrames) { + const uint32_t *Reloc = FDS.getRelocPtr(); + for (codeview::FrameData FD : FDS) { + FD.RvaStart += *Reloc; + FD.FrameFunc = + translateStringTableIndex(FD.FrameFunc, CVStrTab, Linker.PDBStrTab); + DbiBuilder.addNewFpoData(FD); + } + } + + for (ulittle32_t *Ref : StringTableReferences) + *Ref = translateStringTableIndex(*Ref, CVStrTab, Linker.PDBStrTab); + + // Make a new file checksum table that refers to offsets in the PDB-wide + // string table. Generally the string table subsection appears after the + // checksum table, so we have to do this after looping over all the + // subsections. + auto NewChecksums = make_unique(Linker.PDBStrTab); + for (FileChecksumEntry &FC : Checksums) { + SmallString<128> FileName = + ExitOnErr(CVStrTab.getString(FC.FileNameOffset)); + if (!sys::path::is_absolute(FileName) && !Config->PDBSourcePath.empty()) { + SmallString<128> AbsoluteFileName = Config->PDBSourcePath; + sys::path::append(AbsoluteFileName, FileName); + sys::path::native(AbsoluteFileName); + sys::path::remove_dots(AbsoluteFileName, /*remove_dot_dots=*/true); + FileName = std::move(AbsoluteFileName); + } + ExitOnErr(Linker.Builder.getDbiBuilder().addModuleSourceFile( + *File.ModuleDBI, FileName)); + NewChecksums->addChecksum(FileName, FC.Kind, FC.Checksum); + } + File.ModuleDBI->addDebugSubsection(std::move(NewChecksums)); +} + void PDBLinker::addObjFile(ObjFile *File) { // Add a module descriptor for every object file. We need to put an absolute // path to the object into the PDB. If this is a plain object, we make its @@ -878,122 +1033,38 @@ return; } - const CVIndexMap &IndexMap = *IndexMapResult; - ScopedTimer T(SymbolMergingTimer); - // Now do all live .debug$S sections. - DebugStringTableSubsectionRef CVStrTab; - DebugChecksumsSubsectionRef Checksums; - std::vector StringTableReferences; - std::vector FpoFrames; + DebugSHandler DSH(*this, *File, *IndexMapResult); + // Now do all live .debug$S and .debug$F sections. for (SectionChunk *DebugChunk : File->getDebugChunks()) { - if (!DebugChunk->Live || DebugChunk->getSectionName() != ".debug$S") + if (!DebugChunk->Live || DebugChunk->getSize() == 0) continue; - ArrayRef RelocatedDebugContents = - relocateDebugChunk(Alloc, DebugChunk); - if (RelocatedDebugContents.empty()) + if (DebugChunk->getSectionName() == ".debug$S") { + DSH.handleDebugS(*DebugChunk); continue; - - DebugSubsectionArray Subsections; - BinaryStreamReader Reader(RelocatedDebugContents, support::little); - ExitOnErr(Reader.readArray(Subsections, RelocatedDebugContents.size())); - - for (const DebugSubsectionRecord &SS : Subsections) { - switch (SS.kind()) { - case DebugSubsectionKind::StringTable: { - assert(!CVStrTab.valid() && - "Encountered multiple string table subsections!"); - ExitOnErr(CVStrTab.initialize(SS.getRecordData())); - break; - } - case DebugSubsectionKind::FileChecksums: - assert(!Checksums.valid() && - "Encountered multiple checksum subsections!"); - ExitOnErr(Checksums.initialize(SS.getRecordData())); - break; - case DebugSubsectionKind::Lines: - // We can add the relocated line table directly to the PDB without - // modification because the file checksum offsets will stay the same. - File->ModuleDBI->addDebugSubsection(SS); - break; - case DebugSubsectionKind::FrameData: { - // We need to re-write string table indices here, so save off all - // frame data subsections until we've processed the entire list of - // subsections so that we can be sure we have the string table. - DebugFrameDataSubsectionRef FDS; - ExitOnErr(FDS.initialize(SS.getRecordData())); - FpoFrames.push_back(std::move(FDS)); - break; - } - case DebugSubsectionKind::Symbols: - if (Config->DebugGHashes) { - mergeSymbolRecords(Alloc, File, Builder.getGsiBuilder(), IndexMap, - GlobalIDTable, StringTableReferences, - SS.getRecordData()); - } else { - mergeSymbolRecords(Alloc, File, Builder.getGsiBuilder(), IndexMap, - IDTable, StringTableReferences, - SS.getRecordData()); - } - break; - default: - // FIXME: Process the rest of the subsections. - break; - } } - } - // We should have seen all debug subsections across the entire object file now - // which means that if a StringTable subsection and Checksums subsection were - // present, now is the time to handle them. - if (!CVStrTab.valid()) { - if (Checksums.valid()) - fatal(".debug$S sections with a checksums subsection must also contain a " - "string table subsection"); - - if (!StringTableReferences.empty()) - warn("No StringTable subsection was encountered, but there are string " - "table references"); - return; - } - - // Rewrite string table indices in the Fpo Data and symbol records to refer to - // the global PDB string table instead of the object file string table. - for (DebugFrameDataSubsectionRef &FDS : FpoFrames) { - const uint32_t *Reloc = FDS.getRelocPtr(); - for (codeview::FrameData FD : FDS) { - FD.RvaStart += *Reloc; - FD.FrameFunc = - translateStringTableIndex(FD.FrameFunc, CVStrTab, PDBStrTab); - DbiBuilder.addFrameData(FD); + if (DebugChunk->getSectionName() == ".debug$F") { + ArrayRef RelocatedDebugContents = + relocateDebugChunk(Alloc, *DebugChunk); + + FixedStreamArray FpoRecords; + BinaryStreamReader Reader(RelocatedDebugContents, support::little); + uint32_t Count = RelocatedDebugContents.size() / sizeof(object::FpoData); + ExitOnErr(Reader.readArray(FpoRecords, Count)); + + // These are already relocated and don't refer to the string table, so we + // can just copy it. + for (const object::FpoData &FD : FpoRecords) + DbiBuilder.addOldFpoData(FD); + continue; } } - for (ulittle32_t *Ref : StringTableReferences) - *Ref = translateStringTableIndex(*Ref, CVStrTab, PDBStrTab); - - // Make a new file checksum table that refers to offsets in the PDB-wide - // string table. Generally the string table subsection appears after the - // checksum table, so we have to do this after looping over all the - // subsections. - auto NewChecksums = make_unique(PDBStrTab); - for (FileChecksumEntry &FC : Checksums) { - SmallString<128> FileName = ExitOnErr(CVStrTab.getString(FC.FileNameOffset)); - if (!sys::path::is_absolute(FileName) && - !Config->PDBSourcePath.empty()) { - SmallString<128> AbsoluteFileName = Config->PDBSourcePath; - sys::path::append(AbsoluteFileName, FileName); - sys::path::native(AbsoluteFileName); - sys::path::remove_dots(AbsoluteFileName, /*remove_dot_dots=*/true); - FileName = std::move(AbsoluteFileName); - } - ExitOnErr(Builder.getDbiBuilder().addModuleSourceFile(*File->ModuleDBI, - FileName)); - NewChecksums->addChecksum(FileName, FC.Kind, FC.Checksum); - } - File->ModuleDBI->addDebugSubsection(std::move(NewChecksums)); + // Do any post-processing now that all .debug$S sections have been processed. + DSH.finish(); } static PublicSym32 createPublic(Defined *Def) { Index: lld/trunk/test/COFF/pdb-debug-f.s =================================================================== --- lld/trunk/test/COFF/pdb-debug-f.s +++ lld/trunk/test/COFF/pdb-debug-f.s @@ -0,0 +1,27 @@ +# RUN: llvm-mc -triple=i386-pc-win32 -filetype=obj -o %t.obj %s +# RUN: lld-link /subsystem:console /debug /nodefaultlib /entry:foo /out:%t.exe /pdb:%t.pdb %t.obj +# RUN: llvm-pdbutil dump -fpo %t.pdb | FileCheck %s + +# CHECK: Old FPO Data +# CHECK-NEXT: ============================================================ +# CHECK-NEXT: RVA | Code | Locals | Params | Prolog | Saved Regs | Use BP | Has SEH | Frame Type +# CHECK-NEXT: 00001002 | 1 | 2 | 3 | 4 | 0 | false | false | FPO + +.text +_foo: +ret + +.global _foo + +.section .debug$F,"dr" + .long _foo@IMGREL+2 + .long 1 # cbProc + .long 2 # cdwLocals; + .short 3 # cdwParams; + .short 4 # flags + # cbProlog : 8; + # cbRegs : 3; + # fHasSEH : 1; + # fUseBP : 1; + # reserved : 1; + # cbFrame : 2; \ No newline at end of file Index: llvm/trunk/include/llvm/DebugInfo/PDB/Native/DbiStreamBuilder.h =================================================================== --- llvm/trunk/include/llvm/DebugInfo/PDB/Native/DbiStreamBuilder.h +++ llvm/trunk/include/llvm/DebugInfo/PDB/Native/DbiStreamBuilder.h @@ -33,6 +33,7 @@ } namespace object { struct coff_section; +struct FpoData; } namespace pdb { class DbiStream; @@ -69,7 +70,8 @@ void setGlobalsStreamIndex(uint32_t Index); void setPublicsStreamIndex(uint32_t Index); void setSymbolRecordStreamIndex(uint32_t Index); - void addFrameData(const codeview::FrameData &FD); + void addNewFpoData(const codeview::FrameData &FD); + void addOldFpoData(const object::FpoData &Fpo); Expected addModuleInfo(StringRef ModuleName); Error addModuleSourceFile(DbiModuleDescriptorBuilder &Module, StringRef File); @@ -123,7 +125,8 @@ std::vector> ModiList; - Optional FrameData; + Optional NewFpoData; + std::vector OldFpoData; StringMap SourceFileNames; Index: llvm/trunk/include/llvm/Object/COFF.h =================================================================== --- llvm/trunk/include/llvm/Object/COFF.h +++ llvm/trunk/include/llvm/Object/COFF.h @@ -594,6 +594,8 @@ FidTableHasFlags = 0x10000000, // Indicates that fid tables are 5 bytes }; +enum class frame_type : uint16_t { Fpo = 0, Trap = 1, Tss = 2, NonFpo = 3 }; + struct coff_load_config_code_integrity { support::ulittle16_t Flags; support::ulittle16_t Catalog; @@ -1228,7 +1230,7 @@ bool useBP() const { return (Attributes >> 10) & 1; } // cbFrame: frame pointer - int getFP() const { return Attributes >> 14; } + frame_type getFP() const { return static_cast(Attributes >> 14); } }; } // end namespace object Index: llvm/trunk/lib/DebugInfo/PDB/Native/DbiStreamBuilder.cpp =================================================================== --- llvm/trunk/lib/DebugInfo/PDB/Native/DbiStreamBuilder.cpp +++ llvm/trunk/lib/DebugInfo/PDB/Native/DbiStreamBuilder.cpp @@ -75,11 +75,15 @@ PublicsStreamIndex = Index; } -void DbiStreamBuilder::addFrameData(const codeview::FrameData &FD) { - if (!FrameData.hasValue()) - FrameData.emplace(false); +void DbiStreamBuilder::addNewFpoData(const codeview::FrameData &FD) { + if (!NewFpoData.hasValue()) + NewFpoData.emplace(false); - FrameData->addFrameData(FD); + NewFpoData->addFrameData(FD); +} + +void DbiStreamBuilder::addOldFpoData(const object::FpoData &FD) { + OldFpoData.push_back(FD); } Error DbiStreamBuilder::addDbgStream(pdb::DbgHeaderType Type, @@ -286,13 +290,23 @@ } Error DbiStreamBuilder::finalizeMsfLayout() { - if (FrameData.hasValue()) { + if (NewFpoData.hasValue()) { DbgStreams[(int)DbgHeaderType::NewFPO].emplace(); DbgStreams[(int)DbgHeaderType::NewFPO]->Size = - FrameData->calculateSerializedSize(); + NewFpoData->calculateSerializedSize(); DbgStreams[(int)DbgHeaderType::NewFPO]->WriteFn = [this](BinaryStreamWriter &Writer) { - return FrameData->commit(Writer); + return NewFpoData->commit(Writer); + }; + } + + if (!OldFpoData.empty()) { + DbgStreams[(int)DbgHeaderType::FPO].emplace(); + DbgStreams[(int)DbgHeaderType::FPO]->Size = + sizeof(object::FpoData) * OldFpoData.size(); + DbgStreams[(int)DbgHeaderType::FPO]->WriteFn = + [this](BinaryStreamWriter &Writer) { + return Writer.writeArray(makeArrayRef(OldFpoData)); }; } Index: llvm/trunk/tools/llvm-pdbutil/DumpOutputStyle.h =================================================================== --- llvm/trunk/tools/llvm-pdbutil/DumpOutputStyle.h +++ llvm/trunk/tools/llvm-pdbutil/DumpOutputStyle.h @@ -86,6 +86,8 @@ Error dumpXmi(); Error dumpXme(); Error dumpFpo(); + Error dumpOldFpo(PDBFile &File); + Error dumpNewFpo(PDBFile &File); Error dumpTpiStream(uint32_t StreamIdx); Error dumpTypesFromObjectFile(); Error dumpModules(); Index: llvm/trunk/tools/llvm-pdbutil/DumpOutputStyle.cpp =================================================================== --- llvm/trunk/tools/llvm-pdbutil/DumpOutputStyle.cpp +++ llvm/trunk/tools/llvm-pdbutil/DumpOutputStyle.cpp @@ -991,22 +991,56 @@ return Error::success(); } -Error DumpOutputStyle::dumpFpo() { - printHeader(P, "New FPO Data"); +std::string formatFrameType(object::frame_type FT) { + switch (FT) { + case object::frame_type::Fpo: + return "FPO"; + case object::frame_type::NonFpo: + return "Non-FPO"; + case object::frame_type::Trap: + return "Trap"; + case object::frame_type::Tss: + return "TSS"; + } + return ""; +} - if (!File.isPdb()) { - printStreamNotValidForObj(); +Error DumpOutputStyle::dumpOldFpo(PDBFile &File) { + printHeader(P, "Old FPO Data"); + + ExitOnError Err("Error dumping old fpo data:"); + auto &Dbi = Err(File.getPDBDbiStream()); + + uint32_t Index = Dbi.getDebugStreamIndex(DbgHeaderType::FPO); + if (Index == kInvalidStreamIndex) { + printStreamNotPresent("FPO"); return Error::success(); } - PDBFile &File = getPdb(); - if (!File.hasPDBDbiStream()) { - printStreamNotPresent("DBI"); - return Error::success(); + std::unique_ptr OldFpo = File.createIndexedStream(Index); + BinaryStreamReader Reader(*OldFpo); + FixedStreamArray Records; + Err(Reader.readArray(Records, + Reader.bytesRemaining() / sizeof(object::FpoData))); + + P.printLine(" RVA | Code | Locals | Params | Prolog | Saved Regs | Use " + "BP | Has SEH | Frame Type"); + + for (const object::FpoData &FD : Records) { + P.formatLine("{0:X-8} | {1,4} | {2,6} | {3,6} | {4,6} | {5,10} | {6,6} | " + "{7,7} | {8,9}", + uint32_t(FD.Offset), uint32_t(FD.Size), uint32_t(FD.NumLocals), + uint32_t(FD.NumParams), FD.getPrologSize(), + FD.getNumSavedRegs(), FD.useBP(), FD.hasSEH(), + formatFrameType(FD.getFP())); } + return Error::success(); +} - ExitOnError Err("Error dumping fpo data:"); +Error DumpOutputStyle::dumpNewFpo(PDBFile &File) { + printHeader(P, "New FPO Data"); + ExitOnError Err("Error dumping new fpo data:"); auto &Dbi = Err(File.getPDBDbiStream()); uint32_t Index = Dbi.getDebugStreamIndex(DbgHeaderType::NewFPO); @@ -1043,6 +1077,25 @@ return Error::success(); } +Error DumpOutputStyle::dumpFpo() { + if (!File.isPdb()) { + printStreamNotValidForObj(); + return Error::success(); + } + + PDBFile &File = getPdb(); + if (!File.hasPDBDbiStream()) { + printStreamNotPresent("DBI"); + return Error::success(); + } + + if (auto EC = dumpOldFpo(File)) + return EC; + if (auto EC = dumpNewFpo(File)) + return EC; + return Error::success(); +} + Error DumpOutputStyle::dumpStringTableFromPdb() { AutoIndent Indent(P); auto IS = getPdb().getStringTable();