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