[BOLT] Add support for Linux kernel PCI fixup section (#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.
This commit is contained in:
Maksim Panchenko
2024-03-12 15:52:27 -07:00
committed by GitHub
parent 47625e47db
commit fd32e744a5
2 changed files with 134 additions and 33 deletions

View File

@@ -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 @@ public:
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>

View File

@@ -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",@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",@progbits
.section __ksymtab_gpl,"a",@progbits