Index: COFF/Writer.cpp =================================================================== --- COFF/Writer.cpp +++ COFF/Writer.cpp @@ -28,6 +28,7 @@ #include "llvm/Support/Parallel.h" #include "llvm/Support/Path.h" #include "llvm/Support/RandomNumberGenerator.h" +#include "llvm/Support/xxhash.h" #include #include #include @@ -71,11 +72,18 @@ uint64_t Offs = OS->getFileOff() + (Record->getRVA() - OS->getRVA()); D->PointerToRawData = Offs; + TimeDateStamps.push_back(&D->TimeDateStamp); ++D; } } + void setTimeDateStamp(uint32_t TimeDateStamp) { + for (support::ulittle32_t *TDS : TimeDateStamps) + *TDS = TimeDateStamp; + } + private: + mutable std::vector TimeDateStamps; const std::vector &Records; }; @@ -157,7 +165,7 @@ RVATableChunk *GuardFidsTable = nullptr; RVATableChunk *SEHTable = nullptr; - Chunk *DebugDirectory = nullptr; + DebugDirectoryChunk *DebugDirectory = nullptr; std::vector DebugRecords; CVDebugRecordChunk *BuildId = nullptr; Optional PreviousBuildId; @@ -1026,22 +1034,50 @@ } void Writer::writeBuildId() { - // If we're not writing a build id (e.g. because /debug is not specified), - // then just return; - if (!Config->Debug) - return; + // There are two important parts to the build ID. + // 1) If building with debug info, the COFF debug directory contains a + // timestamp as well as a Guid and Age of the PDB. + // 2) In all cases, the PE COFF file header also contains a timestamp. + // For reproducibility, instead of a timestamp we want to use a hash of the + // binary, however when building with debug info the hash needs to take into + // account the debug info, since it's possible to add blank lines to a file + // which causes the debug info to change but not the generated code. + // + // To handle this, we first set the Guid and Age in the debug directory (but + // only if we're doing a debug build). Then, we hash the binary (thus causing + // the hash to change if only the debug info changes, since the Age will be + // different). Finally, we write that hash into the debug directory (if + // present) as well as the COFF file header (always). + if (Config->Debug) { + assert(BuildId && "BuildId is not set!"); + if (PreviousBuildId.hasValue()) { + *BuildId->BuildId = *PreviousBuildId; + BuildId->BuildId->PDB70.Age = BuildId->BuildId->PDB70.Age + 1; + } else { + BuildId->BuildId->Signature.CVSignature = OMF::Signature::PDB70; + BuildId->BuildId->PDB70.Age = 1; + llvm::getRandomBytes(BuildId->BuildId->PDB70.Signature, 16); + } + } - assert(BuildId && "BuildId is not set!"); + // At this point the only fields in the COFF file which remain unset are the + // "timestamp" in the COFF file header, and the ones in the coff debug + // directory. Now we can hash the file and write that hash to the various + // timestamp fields in the file. + StringRef OutputFileData( + reinterpret_cast(Buffer->getBufferStart()), + Buffer->getBufferSize()); - if (PreviousBuildId.hasValue()) { - *BuildId->BuildId = *PreviousBuildId; - BuildId->BuildId->PDB70.Age = BuildId->BuildId->PDB70.Age + 1; - return; - } + uint32_t Hash = static_cast(xxHash64(OutputFileData)); + + if (DebugDirectory) + DebugDirectory->setTimeDateStamp(Hash); - BuildId->BuildId->Signature.CVSignature = OMF::Signature::PDB70; - BuildId->BuildId->PDB70.Age = 1; - llvm::getRandomBytes(BuildId->BuildId->PDB70.Signature, 16); + uint8_t *Buf = Buffer->getBufferStart(); + Buf += DOSStubSize + sizeof(PEMagic); + object::coff_file_header *CoffHeader = + reinterpret_cast(Buf); + CoffHeader->TimeDateStamp = Hash; } // Sort .pdata section contents according to PE/COFF spec 5.5.