[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:
@@ -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>
|
||||
|
||||
41
bolt/test/X86/linux-pci-fixup.s
Normal file
41
bolt/test/X86/linux-pci-fixup.s
Normal 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
|
||||
Reference in New Issue
Block a user