[llvm] [BOLT] Support instrumentation hook via DT_FINI_ARRAY (PR #67348)

Job Noorman via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 25 09:54:05 PDT 2023


https://github.com/mtvec created https://github.com/llvm/llvm-project/pull/67348

BOLT currently hooks its its instrumentation finalization function via `DT_FINI`. However, this method of calling finalization routines is not supported anymore on newer ABIs like RISC-V. `DT_FINI_ARRAY` is preferred there.

This patch adds support for hooking into `DT_FINI_ARRAY` instead if the binary does not have a `DT_FINI` entry. If it does, `DT_FINI` takes precedence so this patch should not change how the currently supported instrumentation targets behave.

`DT_FINI_ARRAY` points to an array in memory of `DT_FINI_ARRAYSZ` bytes. It consists of pointer-length entries that contain the addresses of finalization functions. However, the addresses are only filled-in by the dynamic linker at load time using relative relocations. This makes hooking via `DT_FINI_ARRAY` a bit more complicated than via `DT_FINI`.

The implementation works as follows:
- While scanning the binary: find the section where `DT_FINI_ARRAY` points to, read its first dynamic relocation and use its addend to find the address of the fini function we will use to hook;
- While writing the output file: overwrite the addend of the dynamic relocation with the address of the runtime library's fini function.

Updating the dynamic relocation required a bit of boiler plate: since dynamic relocations are stored in a `std::multiset` which doesn't support getting mutable references to its items, functions were added to `BinarySection` to take an existing relocation and insert a new one.

Note on testing: I can currently only test this on RISC-V, but its instrumentation support hasn't been upstreamed yet as it depends on this patch which I would like to review separately. I cannot get this patch to work on x86, even when forcing the linker to omit `DT_FINI`. The reason is that, even though `DT_FINI_ARRAY` exists and has valid entries, the functions do not show up in the symbols table which triggers some asserts later in BOLT. I would propose to not add tests right now but wait until RISC-V instrumentation is upstreamed which will automatically test this patch.

>From dbe2993e1359eaa707f8d5942a254b646812bf56 Mon Sep 17 00:00:00 2001
From: Job Noorman <jnoorman at igalia.com>
Date: Mon, 25 Sep 2023 17:54:53 +0200
Subject: [PATCH] [BOLT] Support instrumentation hook via DT_FINI_ARRAY

BOLT currently hooks its its instrumentation finalization function via
`DT_FINI`. However, this method of calling finalization routines is not
supported anymore on newer ABIs like RISC-V. `DT_FINI_ARRAY` is
preferred there.

This patch adds support for hooking into `DT_FINI_ARRAY` instead if the
binary does not have a `DT_FINI` entry. If it does, `DT_FINI` takes
precedence so this patch should not change how the currently supported
instrumentation targets behave.

`DT_FINI_ARRAY` points to an array in memory of `DT_FINI_ARRAYSZ` bytes.
It consists of pointer-length entries that contain the addresses of
finalization functions. However, the addresses are only filled-in by the
dynamic linker at load time using relative relocations. This makes
hooking via `DT_FINI_ARRAY` a bit more complicated than via `DT_FINI`.

The implementation works as follows:
- While scanning the binary: find the section where `DT_FINI_ARRAY`
  points to, read its first dynamic relocation and use its addend to
  find the address of the fini function we will use to hook;
- While writing the output file: overwrite the addend of the dynamic
  relocation with the address of the runtime library's fini function.

Updating the dynamic relocation required a bit of boiler plate: since
dynamic relocations are stored in a `std::multiset` which doesn't
support getting mutable references to its items, functions were added to
`BinarySection` to take an existing relocation and insert a new one.

Note on testing: I can currently only test this on RISC-V, but its
instrumentation support hasn't been upstreamed yet as it depends on this
patch which I would like to review separately. I cannot get this patch
to work on x86, even when forcing the linker to omit `DT_FINI`. The
reason is that, even though `DT_FINI_ARRAY` exists and has valid
entries, the functions do not show up in the symbols table which
triggers some asserts later in BOLT. I would propose to not add tests
right now but wait until RISC-V instrumentation is upstreamed which will
automatically test this patch.
---
 bolt/include/bolt/Core/BinaryContext.h        |  9 +++
 bolt/include/bolt/Core/BinarySection.h        | 20 +++++-
 bolt/include/bolt/Rewrite/RewriteInstance.h   |  9 +++
 bolt/lib/Rewrite/RewriteInstance.cpp          | 67 ++++++++++++++++++-
 .../InstrumentationRuntimeLibrary.cpp         |  6 --
 5 files changed, 102 insertions(+), 9 deletions(-)

diff --git a/bolt/include/bolt/Core/BinaryContext.h b/bolt/include/bolt/Core/BinaryContext.h
index ef57ff3541dc8c9..caba1e7a4ab3915 100644
--- a/bolt/include/bolt/Core/BinaryContext.h
+++ b/bolt/include/bolt/Core/BinaryContext.h
@@ -680,6 +680,15 @@ class BinaryContext {
   /// the execution of the binary is completed.
   std::optional<uint64_t> FiniFunctionAddress;
 
+  /// DT_FINI_ARRAY. Only used when DT_FINI is not set.
+  std::optional<uint64_t> FiniArrayAddress;
+
+  /// DT_FINI_ARRAYSZ. Only used when DT_FINI is not set.
+  std::optional<uint64_t> FiniArraySize;
+
+  /// Section where DT_FINI_ARRAY points to. Only used when DT_FINI is not set.
+  BinarySection *FiniArraySection{nullptr};
+
   /// Page alignment used for code layout.
   uint64_t PageAlign{HugePageSize};
 
diff --git a/bolt/include/bolt/Core/BinarySection.h b/bolt/include/bolt/Core/BinarySection.h
index 326d088d1f0465d..92ab6ea0d38e14e 100644
--- a/bolt/include/bolt/Core/BinarySection.h
+++ b/bolt/include/bolt/Core/BinarySection.h
@@ -375,8 +375,12 @@ class BinarySection {
   /// Add a dynamic relocation at the given /p Offset.
   void addDynamicRelocation(uint64_t Offset, MCSymbol *Symbol, uint64_t Type,
                             uint64_t Addend, uint64_t Value = 0) {
-    assert(Offset < getSize() && "offset not within section bounds");
-    DynamicRelocations.emplace(Relocation{Offset, Symbol, Type, Addend, Value});
+    addDynamicRelocation(Relocation{Offset, Symbol, Type, Addend, Value});
+  }
+
+  void addDynamicRelocation(const Relocation &Reloc) {
+    assert(Reloc.Offset < getSize() && "offset not within section bounds");
+    DynamicRelocations.emplace(Reloc);
   }
 
   /// Add relocation against the original contents of this section.
@@ -410,6 +414,18 @@ class BinarySection {
     return Itr != DynamicRelocations.end() ? &*Itr : nullptr;
   }
 
+  std::optional<Relocation> takeDynamicRelocationAt(uint64_t Offset) {
+    Relocation Key{Offset, 0, 0, 0, 0};
+    auto Itr = DynamicRelocations.find(Key);
+
+    if (Itr == DynamicRelocations.end())
+      return std::nullopt;
+
+    Relocation Reloc = *Itr;
+    DynamicRelocations.erase(Itr);
+    return Reloc;
+  }
+
   uint64_t hash(const BinaryData &BD) const {
     std::map<const BinaryData *, uint64_t> Cache;
     return hash(BD, Cache);
diff --git a/bolt/include/bolt/Rewrite/RewriteInstance.h b/bolt/include/bolt/Rewrite/RewriteInstance.h
index 1dacdc944f9775b..a002d9d88098bc6 100644
--- a/bolt/include/bolt/Rewrite/RewriteInstance.h
+++ b/bolt/include/bolt/Rewrite/RewriteInstance.h
@@ -95,6 +95,15 @@ class RewriteInstance {
   /// from meta data in the file.
   void discoverFileObjects();
 
+  /// Check whether we should use DT_FINI or DT_FINI_ARRAY for instrumentation.
+  /// DT_FINI is preferred; DT_FINI_ARRAY is only used when no DT_FINI entry was
+  /// found.
+  Error discoverFiniAddress();
+
+  /// If DT_FINI_ARRAY is used for instrumentation, update the relocation of its
+  /// first entry to point to the instrumentation library's fini address.
+  void updateFiniReloc();
+
   /// Create and initialize metadata rewriters for this instance.
   void initializeMetadataManager();
 
diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp
index 85c0092b505f73b..fe9e0981f6c9443 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -700,6 +700,10 @@ Error RewriteInstance::run() {
   adjustCommandLineOptions();
   discoverFileObjects();
 
+  if (opts::Instrument)
+    if (Error E = discoverFiniAddress())
+      return E;
+
   preprocessProfileData();
 
   // Skip disassembling if we have a translation table and we are running an
@@ -1276,6 +1280,60 @@ void RewriteInstance::discoverFileObjects() {
   registerFragments();
 }
 
+Error RewriteInstance::discoverFiniAddress() {
+  // If FiniFunctionAddress is already set, we got if from DT_FINI. We use
+  // DT_FINI instead of DT_FINI_ARRAY if it's available.
+  if (BC->FiniFunctionAddress)
+    return Error::success();
+
+  if (BC->IsStaticExecutable)
+    return Error::success();
+
+  if (!BC->FiniArrayAddress || !BC->FiniArraySize) {
+    return createStringError(
+        std::errc::not_supported,
+        "Instrumentation needs either DT_FINI or DT_FINI_ARRAY");
+  }
+
+  if (*BC->FiniArraySize < BC->AsmInfo->getCodePointerSize()) {
+    return createStringError(std::errc::not_supported,
+                             "Need at least 1 DT_FINI_ARRAY slot");
+  }
+
+  ErrorOr<BinarySection &> FiniArraySection =
+      BC->getSectionForAddress(*BC->FiniArrayAddress);
+  if (auto EC = FiniArraySection.getError())
+    return errorCodeToError(EC);
+
+  BC->FiniArraySection = &*FiniArraySection;
+  const Relocation *FiniFunctionReloc =
+      FiniArraySection->getDynamicRelocationAt(0);
+
+  if (!FiniFunctionReloc) {
+    return createStringError(std::errc::not_supported,
+                             "No relocation for first DT_FINI_ARRAY slot");
+  }
+
+  BC->FiniFunctionAddress = FiniFunctionReloc->Addend;
+  return Error::success();
+}
+
+void RewriteInstance::updateFiniReloc() {
+  if (!BC->FiniArraySection)
+    return;
+
+  const RuntimeLibrary *RT = BC->getRuntimeLibrary();
+  if (!RT)
+    return;
+
+  std::optional<Relocation> FiniFunctionReloc =
+      BC->FiniArraySection->takeDynamicRelocationAt(0);
+  assert(FiniFunctionReloc && "First DT_FINI_ARRAY relocation removed");
+
+  FiniFunctionReloc->Addend = RT->getRuntimeFiniAddress();
+  BC->FiniArraySection->addDynamicRelocation(*FiniFunctionReloc);
+}
+
 void RewriteInstance::registerFragments() {
   if (!BC->HasSplitFunctions)
     return;
@@ -5008,7 +5066,7 @@ void RewriteInstance::patchELFDynamic(ELFObjectFile<ELFT> *File) {
         }
       }
       RuntimeLibrary *RtLibrary = BC->getRuntimeLibrary();
-      if (RtLibrary && Dyn.getTag() == ELF::DT_FINI) {
+      if (RtLibrary && Dyn.getTag() == ELF::DT_FINI && !BC->FiniArraySection) {
         if (uint64_t Addr = RtLibrary->getRuntimeFiniAddress())
           NewDE.d_un.d_ptr = Addr;
       }
@@ -5091,6 +5149,12 @@ Error RewriteInstance::readELFDynamic(ELFObjectFile<ELFT> *File) {
     case ELF::DT_FINI:
       BC->FiniFunctionAddress = Dyn.getPtr();
       break;
+    case ELF::DT_FINI_ARRAY:
+      BC->FiniArrayAddress = Dyn.getPtr();
+      break;
+    case ELF::DT_FINI_ARRAYSZ:
+      BC->FiniArraySize = Dyn.getPtr();
+      break;
     case ELF::DT_RELA:
       DynamicRelocationsAddress = Dyn.getPtr();
       break;
@@ -5339,6 +5403,7 @@ void RewriteInstance::rewriteFile() {
 
   // Copy non-allocatable sections once allocatable part is finished.
   rewriteNoteSections();
+  updateFiniReloc();
 
   if (BC->HasRelocations) {
     patchELFAllocatableRelaSections();
diff --git a/bolt/lib/RuntimeLibs/InstrumentationRuntimeLibrary.cpp b/bolt/lib/RuntimeLibs/InstrumentationRuntimeLibrary.cpp
index cc36406543f3995..3d0830eb4f6b1e9 100644
--- a/bolt/lib/RuntimeLibs/InstrumentationRuntimeLibrary.cpp
+++ b/bolt/lib/RuntimeLibs/InstrumentationRuntimeLibrary.cpp
@@ -57,12 +57,6 @@ void InstrumentationRuntimeLibrary::adjustCommandLineOptions(
               "the input binary\n";
     exit(1);
   }
-  if (!BC.FiniFunctionAddress && !BC.IsStaticExecutable) {
-    errs() << "BOLT-ERROR: input binary lacks DT_FINI entry in the dynamic "
-              "section but instrumentation currently relies on patching "
-              "DT_FINI to write the profile\n";
-    exit(1);
-  }
 
   if ((opts::InstrumentationWaitForks || opts::InstrumentationSleepTime) &&
       opts::InstrumentationFileAppendPID) {



More information about the llvm-commits mailing list