[llvm] [BOLT] Add support for Linux kernel PCI fixup section (PR #84982)

Maksim Panchenko via llvm-commits llvm-commits at lists.llvm.org
Tue Mar 12 13:49:17 PDT 2024


https://github.com/maksfb created https://github.com/llvm/llvm-project/pull/84982

.pci_fixup section contains a table with entries allowing to invoke a fixup hook whenever a problem is encountered with a PCI device. The hookup code typically points to the start of a function. As we are not relocating functions in the kernel (at least not yet), verify this assumption while reading the table and ignore any functions with a fixup code in the middle.

>From d58e7ee63ab9787b4f52948db7b269b7c4d25485 Mon Sep 17 00:00:00 2001
From: Maksim Panchenko <maks at fb.com>
Date: Tue, 12 Mar 2024 13:04:35 -0700
Subject: [PATCH] [BOLT] Add support for Linux kernel PCI fixup section

.pci_fixup section contains a table with entries allowing to invoke a
fixup hook whenever a problem is encountered with a PCI device. The
hookup code typically points to the start of a function. As we are not
relocating functions in the kernel (at least not yet), verify this
assumption while reading the table and ignore any functions with a fixup
code in the middle.
---
 bolt/lib/Rewrite/LinuxKernelRewriter.cpp | 126 +++++++++++++++++------
 bolt/test/X86/linux-pci-fixup.s          |  41 ++++++++
 2 files changed, 134 insertions(+), 33 deletions(-)
 create mode 100644 bolt/test/X86/linux-pci-fixup.s

diff --git a/bolt/lib/Rewrite/LinuxKernelRewriter.cpp b/bolt/lib/Rewrite/LinuxKernelRewriter.cpp
index 331a61e7c3c2cd..a2bfd45a64e304 100644
--- a/bolt/lib/Rewrite/LinuxKernelRewriter.cpp
+++ b/bolt/lib/Rewrite/LinuxKernelRewriter.cpp
@@ -55,6 +55,11 @@ static cl::opt<bool> DumpParavirtualPatchSites(
     "dump-para-sites", cl::desc("dump Linux kernel paravitual patch sites"),
     cl::init(false), cl::Hidden, cl::cat(BoltCategory));
 
+static cl::opt<bool>
+    DumpPCIFixups("dump-pci-fixups",
+                  cl::desc("dump Linux kernel PCI fixup table"),
+                  cl::init(false), cl::Hidden, cl::cat(BoltCategory));
+
 static cl::opt<bool> DumpStaticCalls("dump-static-calls",
                                      cl::desc("dump Linux kernel static calls"),
                                      cl::init(false), cl::Hidden,
@@ -181,6 +186,10 @@ class LinuxKernelRewriter final : public MetadataRewriter {
   /// Size of bug_entry struct.
   static constexpr size_t BUG_TABLE_ENTRY_SIZE = 12;
 
+  /// .pci_fixup section.
+  ErrorOr<BinarySection &> PCIFixupSection = std::errc::bad_address;
+  static constexpr size_t PCI_FIXUP_ENTRY_SIZE = 16;
+
   /// Insert an LKMarker for a given code pointer \p PC from a non-code section
   /// \p SectionName.
   void insertLKMarker(uint64_t PC, uint64_t SectionOffset,
@@ -190,9 +199,6 @@ class LinuxKernelRewriter final : public MetadataRewriter {
   /// Process linux kernel special sections and their relocations.
   void processLKSections();
 
-  /// Process special linux kernel section, .pci_fixup.
-  void processLKPCIFixup();
-
   /// Process __ksymtab and __ksymtab_gpl.
   void processLKKSymtab(bool IsGPL = false);
 
@@ -226,6 +232,9 @@ class LinuxKernelRewriter final : public MetadataRewriter {
   /// Read alternative instruction info from .altinstructions.
   Error readAltInstructions();
 
+  /// Read .pci_fixup
+  Error readPCIFixupTable();
+
   /// Mark instructions referenced by kernel metadata.
   Error markInstructions();
 
@@ -256,6 +265,9 @@ class LinuxKernelRewriter final : public MetadataRewriter {
     if (Error E = readAltInstructions())
       return E;
 
+    if (Error E = readPCIFixupTable())
+      return E;
+
     return Error::success();
   }
 
@@ -318,41 +330,11 @@ void LinuxKernelRewriter::insertLKMarker(uint64_t PC, uint64_t SectionOffset,
 }
 
 void LinuxKernelRewriter::processLKSections() {
-  processLKPCIFixup();
   processLKKSymtab();
   processLKKSymtab(true);
   processLKSMPLocks();
 }
 
-/// Process .pci_fixup section of Linux Kernel.
-/// This section contains a list of entries for different PCI devices and their
-/// corresponding hook handler (code pointer where the fixup
-/// code resides, usually on x86_64 it is an entry PC relative 32 bit offset).
-/// Documentation is in include/linux/pci.h.
-void LinuxKernelRewriter::processLKPCIFixup() {
-  ErrorOr<BinarySection &> SectionOrError =
-      BC.getUniqueSectionByName(".pci_fixup");
-  if (!SectionOrError)
-    return;
-
-  const uint64_t SectionSize = SectionOrError->getSize();
-  const uint64_t SectionAddress = SectionOrError->getAddress();
-  assert((SectionSize % 16) == 0 && ".pci_fixup size is not a multiple of 16");
-
-  for (uint64_t I = 12; I + 4 <= SectionSize; I += 16) {
-    const uint64_t PC = SectionAddress + I;
-    ErrorOr<uint64_t> Offset = BC.getSignedValueAtAddress(PC, 4);
-    assert(Offset && "cannot read value from .pci_fixup");
-    const int32_t SignedOffset = *Offset;
-    const uint64_t HookupAddress = PC + SignedOffset;
-    BinaryFunction *HookupFunction =
-        BC.getBinaryFunctionAtAddress(HookupAddress);
-    assert(HookupFunction && "expected function for entry in .pci_fixup");
-    BC.addRelocation(PC, HookupFunction->getSymbol(), Relocation::getPC32(), 0,
-                     *Offset);
-  }
-}
-
 /// Process __ksymtab[_gpl] sections of Linux Kernel.
 /// This section lists all the vmlinux symbols that kernel modules can access.
 ///
@@ -1283,6 +1265,84 @@ Error LinuxKernelRewriter::readAltInstructions() {
   return Error::success();
 }
 
+/// When the Linux kernel needs to handle an error associated with a given PCI
+/// device, it uses a table stored in .pci_fixup section to locate a fixup code
+/// specific to the vendor and the problematic device. The section contains a
+/// list of the following structures defined in include/linux/pci.h:
+///
+///   struct pci_fixup {
+///     u16 vendor;     /* Or PCI_ANY_ID */
+///     u16 device;     /* Or PCI_ANY_ID */
+///     u32 class;      /* Or PCI_ANY_ID */
+///     unsigned int class_shift; /* should be 0, 8, 16 */
+///     int hook_offset;
+///   };
+///
+/// Normally, the hook will point to a function start and we don't have to
+/// update the pointer if we are not relocating functions. Hence, while reading
+/// the table we validate this assumption. If a function has a fixup code in the
+/// middle of its body, we issue a warning and ignore it.
+Error LinuxKernelRewriter::readPCIFixupTable() {
+  PCIFixupSection = BC.getUniqueSectionByName(".pci_fixup");
+  if (!PCIFixupSection)
+    return Error::success();
+
+  if (PCIFixupSection->getSize() % PCI_FIXUP_ENTRY_SIZE)
+    return createStringError(errc::executable_format_error,
+                             "PCI fixup table size error");
+
+  const uint64_t Address = PCIFixupSection->getAddress();
+  DataExtractor DE = DataExtractor(PCIFixupSection->getContents(),
+                                   BC.AsmInfo->isLittleEndian(),
+                                   BC.AsmInfo->getCodePointerSize());
+  uint64_t EntryID = 0;
+  DataExtractor::Cursor Cursor(0);
+  while (Cursor && !DE.eof(Cursor)) {
+    const uint16_t Vendor = DE.getU16(Cursor);
+    const uint16_t Device = DE.getU16(Cursor);
+    const uint32_t Class = DE.getU32(Cursor);
+    const uint32_t ClassShift = DE.getU32(Cursor);
+    const uint64_t HookAddress =
+        Address + Cursor.tell() + (int32_t)DE.getU32(Cursor);
+
+    if (!Cursor)
+      return createStringError(errc::executable_format_error,
+                               "out of bounds while reading .pci_fixup: %s",
+                               toString(Cursor.takeError()).c_str());
+
+    ++EntryID;
+
+    if (opts::DumpPCIFixups) {
+      BC.outs() << "PCI fixup entry: " << EntryID << "\n\tVendor       0x"
+                << Twine::utohexstr(Vendor) << "\n\tDevice:      0x"
+                << Twine::utohexstr(Device) << "\n\tClass:       0x"
+                << Twine::utohexstr(Class) << "\n\tClassShift:  0x"
+                << Twine::utohexstr(ClassShift) << "\n\tHookAddress: 0x"
+                << Twine::utohexstr(HookAddress) << '\n';
+    }
+
+    BinaryFunction *BF = BC.getBinaryFunctionContainingAddress(HookAddress);
+    if (!BF && opts::Verbosity) {
+      BC.outs() << "BOLT-INFO: no function matches address 0x"
+                << Twine::utohexstr(HookAddress)
+                << " of hook from .pci_fixup\n";
+    }
+
+    if (!BF || !BC.shouldEmit(*BF))
+      continue;
+
+    if (const uint64_t Offset = HookAddress - BF->getAddress()) {
+      BC.errs() << "BOLT-WARNING: PCI fixup detected in the middle of function "
+                << *BF << " at offset 0x" << Twine::utohexstr(Offset) << '\n';
+      BF->setSimple(false);
+    }
+  }
+
+  BC.outs() << "BOLT-INFO: parsed " << EntryID << " PCI fixup entries\n";
+
+  return Error::success();
+}
+
 } // namespace
 
 std::unique_ptr<MetadataRewriter>
diff --git a/bolt/test/X86/linux-pci-fixup.s b/bolt/test/X86/linux-pci-fixup.s
new file mode 100644
index 00000000000000..a574ba84c4df11
--- /dev/null
+++ b/bolt/test/X86/linux-pci-fixup.s
@@ -0,0 +1,41 @@
+# REQUIRES: system-linux
+
+# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %s -o %t.o
+# RUN: %clang %cflags -nostdlib %t.o -o %t.exe \
+# RUN:   -Wl,--image-base=0xffffffff80000000,--no-dynamic-linker,--no-eh-frame-hdr,--no-pie
+# RUN: llvm-bolt %t.exe --print-normalized -o %t.out |& FileCheck %s
+
+## Check that BOLT correctly parses the Linux kernel .pci_fixup section and
+## verify that PCI fixup hook in the middle of a function is detected.
+
+# CHECK:      BOLT-INFO: Linux kernel binary detected
+# CHECK:      BOLT-WARNING: PCI fixup detected in the middle of function _start
+# CHECK:      BOLT-INFO: parsed 2 PCI fixup entries
+
+  .text
+  .globl _start
+  .type _start, %function
+_start:
+  nop
+.L0:
+  ret
+  .size _start, .-_start
+
+## PCI fixup table.
+  .section .pci_fixup,"a", at progbits
+
+  .short 0x8086     # vendor
+  .short 0xbeef     # device
+  .long 0xffffffff  # class
+  .long 0x0         # class shift
+  .long _start - .  # fixup
+
+  .short 0x8086     # vendor
+  .short 0xbad      # device
+  .long 0xffffffff  # class
+  .long 0x0         # class shift
+  .long .L0 - .     # fixup
+
+## Fake Linux Kernel sections.
+  .section __ksymtab,"a", at progbits
+  .section __ksymtab_gpl,"a", at progbits



More information about the llvm-commits mailing list