[PDB] Emit old fpo data to the PDB file.

r342003 added support for emitting FPO data from the
DEBUG_S_FRAMEDATA subsection of the .debug$S section to the PDB
file.  However, that is not the end of the story.  FPO can end
up in two different destinations in a PDB, each corresponding to
a different FPO data source.

The case handled by r342003 involves copying data from the
DEBUG_S_FRAMEDATA subsection of the .debug$S section to the
"New FPO" stream in the PDB, which is then referred to by the
DBI stream.  The case handled by this patch involves copying
records from the .debug$F section of an object file to the "FPO"
stream (or perhaps more aptly, the "Old FPO" stream) in the PDB
file, which is also referred to by the DBI stream.

The formats are largely similar, and the difference is mostly
only visible in masm generated object files, such as some of the
low-level CRT object files like memcpy.  MASM doesn't appear to
support writing the DEBUG_S_FRAMEDATA subsection, and instead
just writes these records to the .debug$F section.

Although clang-cl does not emit a .debug$F section ever, lld still
needs to support it so we have good debugging for CRT functions.

Differential Revision: https://reviews.llvm.org/D51958

llvm-svn: 342080
This commit is contained in:
Zachary Turner 2018-09-12 21:02:01 +00:00
parent cd95e03cf0
commit a1f85f8bdd
7 changed files with 308 additions and 136 deletions

View File

@ -82,7 +82,11 @@ struct CVIndexMap {
bool IsTypeServerMap = false;
};
class DebugSHandler;
class PDBLinker {
friend DebugSHandler;
public:
PDBLinker(SymbolTable *Symtab)
: Alloc(), Symtab(Symtab), Builder(Alloc), TypeTable(Alloc),
@ -168,6 +172,50 @@ private:
/// Cached to prevent repeated load attempts.
std::map<GUID, std::string> 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<DebugFrameDataSubsectionRef> 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<ulittle32_t *> 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<SectionChunk *> Sections,
@ -785,15 +833,14 @@ static void mergeSymbolRecords(BumpPtrAllocator &Alloc, ObjFile *File,
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<uint8_t> relocateDebugChunk(BumpPtrAllocator &Alloc,
SectionChunk *DebugChunk) {
uint8_t *Buffer = Alloc.Allocate<uint8_t>(DebugChunk->getSize());
assert(DebugChunk->OutputSectionOff == 0 &&
SectionChunk &DebugChunk) {
uint8_t *Buffer = Alloc.Allocate<uint8_t>(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 @@ translateStringTableIndex(uint32_t ObjIndex,
return PdbStrTable.insert(*ExpectedString);
}
void DebugSHandler::handleDebugS(lld::coff::SectionChunk &DebugS) {
DebugSubsectionArray Subsections;
ArrayRef<uint8_t> 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<DebugChecksumsSubsection>(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 @@ void PDBLinker::addObjFile(ObjFile *File) {
return;
}
const CVIndexMap &IndexMap = *IndexMapResult;
ScopedTimer T(SymbolMergingTimer);
// Now do all live .debug$S sections.
DebugStringTableSubsectionRef CVStrTab;
DebugChecksumsSubsectionRef Checksums;
std::vector<ulittle32_t *> StringTableReferences;
std::vector<DebugFrameDataSubsectionRef> 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;
if (DebugChunk->getSectionName() == ".debug$S") {
DSH.handleDebugS(*DebugChunk);
continue;
}
if (DebugChunk->getSectionName() == ".debug$F") {
ArrayRef<uint8_t> RelocatedDebugContents =
relocateDebugChunk(Alloc, DebugChunk);
if (RelocatedDebugContents.empty())
continue;
relocateDebugChunk(Alloc, *DebugChunk);
DebugSubsectionArray Subsections;
FixedStreamArray<object::FpoData> FpoRecords;
BinaryStreamReader Reader(RelocatedDebugContents, support::little);
ExitOnErr(Reader.readArray(Subsections, RelocatedDebugContents.size()));
uint32_t Count = RelocatedDebugContents.size() / sizeof(object::FpoData);
ExitOnErr(Reader.readArray(FpoRecords, Count));
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;
}
// 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;
}
}
// 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);
}
}
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<DebugChecksumsSubsection>(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) {

View File

@ -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;

View File

@ -33,6 +33,7 @@ class MSFBuilder;
}
namespace object {
struct coff_section;
struct FpoData;
}
namespace pdb {
class DbiStream;
@ -69,7 +70,8 @@ public:
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<DbiModuleDescriptorBuilder &> addModuleInfo(StringRef ModuleName);
Error addModuleSourceFile(DbiModuleDescriptorBuilder &Module, StringRef File);
@ -123,7 +125,8 @@ private:
std::vector<std::unique_ptr<DbiModuleDescriptorBuilder>> ModiList;
Optional<codeview::DebugFrameDataSubsection> FrameData;
Optional<codeview::DebugFrameDataSubsection> NewFpoData;
std::vector<object::FpoData> OldFpoData;
StringMap<uint32_t> SourceFileNames;

View File

@ -594,6 +594,8 @@ enum class coff_guard_flags : uint32_t {
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 @@ struct FpoData {
bool useBP() const { return (Attributes >> 10) & 1; }
// cbFrame: frame pointer
int getFP() const { return Attributes >> 14; }
frame_type getFP() const { return static_cast<frame_type>(Attributes >> 14); }
};
} // end namespace object

View File

@ -75,11 +75,15 @@ void DbiStreamBuilder::setPublicsStreamIndex(uint32_t Index) {
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::finalize() {
}
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));
};
}

View File

@ -991,22 +991,56 @@ Error DumpOutputStyle::dumpXme() {
return Error::success();
}
Error DumpOutputStyle::dumpFpo() {
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 "<unknown>";
}
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();
}
std::unique_ptr<MappedBlockStream> OldFpo = File.createIndexedStream(Index);
BinaryStreamReader Reader(*OldFpo);
FixedStreamArray<object::FpoData> 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();
}
Error DumpOutputStyle::dumpNewFpo(PDBFile &File) {
printHeader(P, "New FPO Data");
if (!File.isPdb()) {
printStreamNotValidForObj();
return Error::success();
}
PDBFile &File = getPdb();
if (!File.hasPDBDbiStream()) {
printStreamNotPresent("DBI");
return Error::success();
}
ExitOnError Err("Error dumping 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 @@ Error DumpOutputStyle::dumpFpo() {
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();

View File

@ -86,6 +86,8 @@ private:
Error dumpXmi();
Error dumpXme();
Error dumpFpo();
Error dumpOldFpo(PDBFile &File);
Error dumpNewFpo(PDBFile &File);
Error dumpTpiStream(uint32_t StreamIdx);
Error dumpTypesFromObjectFile();
Error dumpModules();