[LLD][COFF] Add support for /FUNCTIONPADMIN command-line option
Initial patch by Stefan Reinalter.
Fixes PR36775
Differential Revision: https://reviews.llvm.org/D49366
llvm-svn: 354716
diff --git a/lld/COFF/Chunks.cpp b/lld/COFF/Chunks.cpp
index 8c73bda..4f12ecb 100644
--- a/lld/COFF/Chunks.cpp
+++ b/lld/COFF/Chunks.cpp
@@ -594,6 +594,40 @@
return A;
}
+ArrayRef<uint8_t> SectionChunk::consumeDebugMagic() {
+ assert(isCodeView());
+ return consumeDebugMagic(getContents(), SectionName);
+}
+
+ArrayRef<uint8_t> SectionChunk::consumeDebugMagic(ArrayRef<uint8_t> Data,
+ StringRef SectionName) {
+ if (Data.empty())
+ return {};
+
+ // First 4 bytes are section magic.
+ if (Data.size() < 4)
+ fatal("the section is too short: " + SectionName);
+
+ if (!SectionName.startswith(".debug$"))
+ fatal("invalid section: " + SectionName);
+
+ unsigned Magic = support::endian::read32le(Data.data());
+ unsigned ExpectedMagic = SectionName == ".debug$H"
+ ? DEBUG_HASHES_SECTION_MAGIC
+ : DEBUG_SECTION_MAGIC;
+ if (Magic != ExpectedMagic)
+ fatal("section: " + SectionName + " has an invalid magic: " + Twine(Magic));
+ return Data.slice(4);
+}
+
+SectionChunk *SectionChunk::findByName(ArrayRef<SectionChunk *> Sections,
+ StringRef Name) {
+ for (SectionChunk *C : Sections)
+ if (C->getSectionName() == Name)
+ return C;
+ return nullptr;
+}
+
void SectionChunk::replace(SectionChunk *Other) {
Alignment = std::max(Alignment, Other->Alignment);
Other->Repl = Repl;
diff --git a/lld/COFF/Chunks.h b/lld/COFF/Chunks.h
index 954c753..8939853 100644
--- a/lld/COFF/Chunks.h
+++ b/lld/COFF/Chunks.h
@@ -108,6 +108,8 @@
// The alignment of this chunk. The writer uses the value.
uint32_t Alignment = 1;
+ virtual bool isHotPatchable() const { return false; }
+
protected:
Chunk(Kind K = OtherKind) : ChunkKind(K) {}
const Kind ChunkKind;
@@ -205,6 +207,16 @@
// The section ID this chunk belongs to in its Obj.
uint32_t getSectionNumber() const;
+ ArrayRef<uint8_t> consumeDebugMagic();
+
+ static ArrayRef<uint8_t> consumeDebugMagic(ArrayRef<uint8_t> Data,
+ StringRef SectionName);
+
+ static SectionChunk *findByName(ArrayRef<SectionChunk *> Sections,
+ StringRef Name);
+
+ bool isHotPatchable() const override { return File->HotPatchable; }
+
// A pointer pointing to a replacement for this chunk.
// Initially it points to "this" object. If this chunk is merged
// with other chunk by ICF, it points to another chunk,
@@ -321,6 +333,8 @@
size_t getSize() const override { return sizeof(ImportThunkX86); }
void writeTo(uint8_t *Buf) const override;
+ bool isHotPatchable() const override { return true; }
+
private:
Defined *ImpSymbol;
};
@@ -332,6 +346,8 @@
void getBaserels(std::vector<Baserel> *Res) override;
void writeTo(uint8_t *Buf) const override;
+ bool isHotPatchable() const override { return true; }
+
private:
Defined *ImpSymbol;
};
@@ -343,6 +359,8 @@
void getBaserels(std::vector<Baserel> *Res) override;
void writeTo(uint8_t *Buf) const override;
+ bool isHotPatchable() const override { return true; }
+
private:
Defined *ImpSymbol;
};
@@ -353,6 +371,8 @@
size_t getSize() const override { return sizeof(ImportThunkARM64); }
void writeTo(uint8_t *Buf) const override;
+ bool isHotPatchable() const override { return true; }
+
private:
Defined *ImpSymbol;
};
diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h
index 31e6afa..fefec40 100644
--- a/lld/COFF/Config.h
+++ b/lld/COFF/Config.h
@@ -185,6 +185,7 @@
uint32_t MajorOSVersion = 6;
uint32_t MinorOSVersion = 0;
uint32_t Timestamp = 0;
+ uint32_t FunctionPadMin = 0;
bool DynamicBase = true;
bool AllowBind = true;
bool NxCompat = true;
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index 1de196c..6266ed6 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -1418,6 +1418,10 @@
}
Config->Wordsize = Config->is64() ? 8 : 4;
+ // Handle /functionpadmin
+ for (auto *Arg : Args.filtered(OPT_functionpadmin, OPT_functionpadmin_opt))
+ parseFunctionPadMin(Arg, Config->Machine);
+
// Input files can be Windows resource files (.res files). We use
// WindowsResource to convert resource files to a regular COFF file,
// then link the resulting file normally.
diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h
index 2c2f11e..51a21db 100644
--- a/lld/COFF/Driver.h
+++ b/lld/COFF/Driver.h
@@ -158,6 +158,9 @@
void parseSection(StringRef);
void parseAligncomm(StringRef);
+// Parses a string in the form of "[:<integer>]"
+void parseFunctionPadMin(llvm::opt::Arg *A, llvm::COFF::MachineTypes Machine);
+
// Parses a string in the form of "EMBED[,=<integer>]|NO".
void parseManifest(StringRef Arg);
diff --git a/lld/COFF/DriverUtils.cpp b/lld/COFF/DriverUtils.cpp
index c022f93..b790c1b 100644
--- a/lld/COFF/DriverUtils.cpp
+++ b/lld/COFF/DriverUtils.cpp
@@ -249,6 +249,27 @@
Config->AlignComm[Name] = std::max(Config->AlignComm[Name], 1 << V);
}
+// Parses /functionpadmin option argument.
+void parseFunctionPadMin(llvm::opt::Arg *A, llvm::COFF::MachineTypes Machine) {
+ StringRef Arg = A->getNumValues() ? A->getValue() : "";
+ if (!Arg.empty()) {
+ // Optional padding in bytes is given.
+ if (Arg.getAsInteger(0, Config->FunctionPadMin))
+ error("/functionpadmin: invalid argument: " + Arg);
+ return;
+ }
+ // No optional argument given.
+ // Set default padding based on machine, similar to link.exe.
+ // There is no default padding for ARM platforms.
+ if (Machine == I386) {
+ Config->FunctionPadMin = 5;
+ } else if (Machine == AMD64) {
+ Config->FunctionPadMin = 6;
+ } else {
+ error("/functionpadmin: invalid argument for this machine: " + Arg);
+ }
+}
+
// Parses a string in the form of "EMBED[,=<integer>]|NO".
// Results are directly written to Config.
void parseManifest(StringRef Arg) {
diff --git a/lld/COFF/InputFiles.cpp b/lld/COFF/InputFiles.cpp
index ae68cbe..b0fbaef 100644
--- a/lld/COFF/InputFiles.cpp
+++ b/lld/COFF/InputFiles.cpp
@@ -19,6 +19,9 @@
#include "llvm/ADT/Triple.h"
#include "llvm/ADT/Twine.h"
#include "llvm/BinaryFormat/COFF.h"
+#include "llvm/DebugInfo/CodeView/DebugSubsectionRecord.h"
+#include "llvm/DebugInfo/CodeView/SymbolDeserializer.h"
+#include "llvm/DebugInfo/CodeView/SymbolRecord.h"
#include "llvm/Object/Binary.h"
#include "llvm/Object/COFF.h"
#include "llvm/Support/Casting.h"
@@ -34,6 +37,7 @@
using namespace llvm;
using namespace llvm::COFF;
+using namespace llvm::codeview;
using namespace llvm::object;
using namespace llvm::support::endian;
@@ -124,6 +128,7 @@
// Read section and symbol tables.
initializeChunks();
initializeSymbols();
+ initializeFlags();
}
const coff_section* ObjFile::getSection(uint32_t I) {
@@ -598,6 +603,59 @@
return IMAGE_FILE_MACHINE_UNKNOWN;
}
+ArrayRef<uint8_t> ObjFile::getDebugSection(StringRef SecName) {
+ if (SectionChunk *Sec = SectionChunk::findByName(DebugChunks, SecName))
+ return Sec->consumeDebugMagic();
+ return {};
+}
+
+// OBJ files systematically store critical informations in a .debug$S stream,
+// even if the TU was compiled with no debug info. At least two records are
+// always there. S_OBJNAME stores a 32-bit signature, which is loaded into the
+// PCHSignature member. S_COMPILE3 stores compile-time cmd-line flags. This is
+// currently used to initialize the HotPatchable member.
+void ObjFile::initializeFlags() {
+ ArrayRef<uint8_t> Data = getDebugSection(".debug$S");
+ if (Data.empty())
+ return;
+
+ DebugSubsectionArray Subsections;
+
+ BinaryStreamReader Reader(Data, support::little);
+ ExitOnError ExitOnErr;
+ ExitOnErr(Reader.readArray(Subsections, Data.size()));
+
+ for (const DebugSubsectionRecord &SS : Subsections) {
+ if (SS.kind() != DebugSubsectionKind::Symbols)
+ continue;
+
+ unsigned Offset = 0;
+
+ // Only parse the first two records. We are only looking for S_OBJNAME
+ // and S_COMPILE3, and they usually appear at the beginning of the
+ // stream.
+ for (unsigned I = 0; I < 2; ++I) {
+ Expected<CVSymbol> Sym = readSymbolFromStream(SS.getRecordData(), Offset);
+ if (!Sym) {
+ consumeError(Sym.takeError());
+ return;
+ }
+ if (Sym->kind() == SymbolKind::S_COMPILE3) {
+ auto CS =
+ cantFail(SymbolDeserializer::deserializeAs<Compile3Sym>(Sym.get()));
+ HotPatchable =
+ (CS.Flags & CompileSym3Flags::HotPatch) != CompileSym3Flags::None;
+ }
+ if (Sym->kind() == SymbolKind::S_OBJNAME) {
+ auto ObjName = cantFail(SymbolDeserializer::deserializeAs<ObjNameSym>(
+ Sym.get()));
+ PCHSignature = ObjName.Signature;
+ }
+ Offset += Sym->length();
+ }
+ }
+}
+
StringRef ltrim1(StringRef S, const char *Chars) {
if (!S.empty() && strchr(Chars, S[0]))
return S.substr(1);
diff --git a/lld/COFF/InputFiles.h b/lld/COFF/InputFiles.h
index f65f34d..78a9501 100644
--- a/lld/COFF/InputFiles.h
+++ b/lld/COFF/InputFiles.h
@@ -117,6 +117,8 @@
ArrayRef<SectionChunk *> getGuardLJmpChunks() { return GuardLJmpChunks; }
ArrayRef<Symbol *> getSymbols() { return Symbols; }
+ ArrayRef<uint8_t> getDebugSection(StringRef SecName);
+
// Returns a Symbol object for the SymbolIndex'th symbol in the
// underlying object file.
Symbol *getSymbol(uint32_t SymbolIndex) {
@@ -156,6 +158,9 @@
// precompiled object. Any difference indicates out-of-date objects.
llvm::Optional<uint32_t> PCHSignature;
+ // Tells whether this file was compiled with /hotpatch
+ bool HotPatchable = false;
+
private:
const coff_section* getSection(uint32_t I);
const coff_section *getSection(COFFSymbolRef Sym) {
@@ -164,6 +169,7 @@
void initializeChunks();
void initializeSymbols();
+ void initializeFlags();
SectionChunk *
readSection(uint32_t SectionNumber,
diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td
index ce0a087..f83e445 100644
--- a/lld/COFF/Options.td
+++ b/lld/COFF/Options.td
@@ -32,6 +32,8 @@
def export : P<"export", "Export a function">;
// No help text because /failifmismatch is not intended to be used by the user.
def failifmismatch : P<"failifmismatch", "">;
+def functionpadmin : F<"functionpadmin">;
+def functionpadmin_opt : P<"functionpadmin", "Prepares an image for hotpatching">;
def guard : P<"guard", "Control flow guard">;
def heap : P<"heap", "Size of the heap">;
def ignore : P<"ignore", "Specify warning codes to ignore">;
@@ -178,7 +180,6 @@
def _no : F<name#":no">;
}
-def functionpadmin : F<"functionpadmin">;
def ignoreidl : F<"ignoreidl">;
def nologo : F<"nologo">;
def throwingnew : F<"throwingnew">;
diff --git a/lld/COFF/PDB.cpp b/lld/COFF/PDB.cpp
index 4070ad4..8a9c5fe 100644
--- a/lld/COFF/PDB.cpp
+++ b/lld/COFF/PDB.cpp
@@ -308,30 +308,6 @@
FileName = std::move(AbsoluteFileName);
}
-static SectionChunk *findByName(ArrayRef<SectionChunk *> Sections,
- StringRef Name) {
- for (SectionChunk *C : Sections)
- if (C->getSectionName() == Name)
- return C;
- return nullptr;
-}
-
-static ArrayRef<uint8_t> consumeDebugMagic(ArrayRef<uint8_t> Data,
- StringRef SecName) {
- // First 4 bytes are section magic.
- if (Data.size() < 4)
- fatal(SecName + " too short");
- if (support::endian::read32le(Data.data()) != COFF::DEBUG_SECTION_MAGIC)
- fatal(SecName + " has an invalid magic");
- return Data.slice(4);
-}
-
-static ArrayRef<uint8_t> getDebugSection(ObjFile *File, StringRef SecName) {
- if (SectionChunk *Sec = findByName(File->getDebugChunks(), SecName))
- return consumeDebugMagic(Sec->getContents(), SecName);
- return {};
-}
-
// A COFF .debug$H section is currently a clang extension. This function checks
// if a .debug$H section is in a format that we expect / understand, so that we
// can ignore any sections which are coincidentally also named .debug$H but do
@@ -349,7 +325,8 @@
}
static Optional<ArrayRef<uint8_t>> getDebugH(ObjFile *File) {
- SectionChunk *Sec = findByName(File->getDebugChunks(), ".debug$H");
+ SectionChunk *Sec =
+ SectionChunk::findByName(File->getDebugChunks(), ".debug$H");
if (!Sec)
return llvm::None;
ArrayRef<uint8_t> Contents = Sec->getContents();
@@ -381,51 +358,17 @@
});
}
-// OBJs usually start their symbol stream with a S_OBJNAME record. This record
-// also contains the signature/key of the current PCH session. The signature
-// must be same for all objects which depend on the precompiled object.
-// Recompiling the precompiled headers will generate a new PCH key and thus
-// invalidate all the dependent objects.
-static uint32_t extractPCHSignature(ObjFile *File) {
- auto DbgIt = find_if(File->getDebugChunks(), [](SectionChunk *C) {
- return C->getSectionName() == ".debug$S";
- });
- if (!DbgIt)
- return 0;
-
- ArrayRef<uint8_t> Contents =
- consumeDebugMagic((*DbgIt)->getContents(), ".debug$S");
- DebugSubsectionArray Subsections;
- BinaryStreamReader Reader(Contents, support::little);
- ExitOnErr(Reader.readArray(Subsections, Contents.size()));
-
- for (const DebugSubsectionRecord &SS : Subsections) {
- if (SS.kind() != DebugSubsectionKind::Symbols)
- continue;
-
- // If it's there, the S_OBJNAME record shall come first in the stream.
- Expected<CVSymbol> Sym = readSymbolFromStream(SS.getRecordData(), 0);
- if (!Sym) {
- consumeError(Sym.takeError());
- continue;
- }
- if (auto ObjName = SymbolDeserializer::deserializeAs<ObjNameSym>(Sym.get()))
- return ObjName->Signature;
- }
- return 0;
-}
-
Expected<const CVIndexMap &>
PDBLinker::mergeDebugT(ObjFile *File, CVIndexMap *ObjectIndexMap) {
ScopedTimer T(TypeMergingTimer);
bool IsPrecompiledHeader = false;
- ArrayRef<uint8_t> Data = getDebugSection(File, ".debug$T");
+ ArrayRef<uint8_t> Data = File->getDebugSection(".debug$T");
if (Data.empty()) {
// Try again, Microsoft precompiled headers use .debug$P instead of
// .debug$T
- Data = getDebugSection(File, ".debug$P");
+ Data = File->getDebugSection(".debug$P");
IsPrecompiledHeader = true;
}
if (Data.empty())
@@ -434,7 +377,7 @@
// Precompiled headers objects need to save the index map for further
// reference by other objects which use the precompiled headers.
if (IsPrecompiledHeader) {
- uint32_t PCHSignature = extractPCHSignature(File);
+ uint32_t PCHSignature = File->PCHSignature.getValueOr(0);
if (PCHSignature == 0)
fatal("No signature found for the precompiled headers OBJ (" +
File->getName() + ")");
@@ -1176,7 +1119,7 @@
void DebugSHandler::handleDebugS(lld::coff::SectionChunk &DebugS) {
DebugSubsectionArray Subsections;
- ArrayRef<uint8_t> RelocatedDebugContents = consumeDebugMagic(
+ ArrayRef<uint8_t> RelocatedDebugContents = SectionChunk::consumeDebugMagic(
relocateDebugChunk(Linker.Alloc, DebugS), DebugS.getSectionName());
BinaryStreamReader Reader(RelocatedDebugContents, support::little);
@@ -1676,7 +1619,7 @@
}
ArrayRef<uint8_t> Contents =
- consumeDebugMagic(DbgC->getContents(), ".debug$S");
+ SectionChunk::consumeDebugMagic(DbgC->getContents(), ".debug$S");
DebugSubsectionArray Subsections;
BinaryStreamReader Reader(Contents, support::little);
ExitOnErr(Reader.readArray(Subsections, Contents.size()));
diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp
index d883a47..d054490 100644
--- a/lld/COFF/Writer.cpp
+++ b/lld/COFF/Writer.cpp
@@ -1117,7 +1117,18 @@
addBaserels();
uint64_t RawSize = 0, VirtualSize = 0;
Sec->Header.VirtualAddress = RVA;
+
+ // If /FUNCTIONPADMIN is used, functions are padded in order to create a
+ // hotpatchable image.
+ const bool IsCodeSection =
+ (Sec->Header.Characteristics & IMAGE_SCN_CNT_CODE) &&
+ (Sec->Header.Characteristics & IMAGE_SCN_MEM_READ) &&
+ (Sec->Header.Characteristics & IMAGE_SCN_MEM_EXECUTE);
+ uint32_t Padding = IsCodeSection ? Config->FunctionPadMin : 0;
+
for (Chunk *C : Sec->Chunks) {
+ if (Padding && C->isHotPatchable())
+ VirtualSize += Padding;
VirtualSize = alignTo(VirtualSize, C->Alignment);
C->setRVA(RVA + VirtualSize);
C->OutputSectionOff = VirtualSize;