//===- bolt/Rewrite/LinuxKernelRewriter.cpp -------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // Support for updating Linux Kernel metadata. // //===----------------------------------------------------------------------===// #include "bolt/Core/BinaryFunction.h" #include "bolt/Rewrite/MetadataRewriter.h" #include "bolt/Rewrite/MetadataRewriters.h" #include "bolt/Utils/CommandLineOpts.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseSet.h" #include "llvm/MC/MCDisassembler/MCDisassembler.h" #include "llvm/Support/BinaryStreamWriter.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/Errc.h" #define DEBUG_TYPE … usingnamespacellvm; usingnamespacebolt; namespace opts { static cl::opt<bool> AltInstHasPadLen("alt-inst-has-padlen", cl::desc("specify that .altinstructions has padlen field"), cl::init(false), cl::Hidden, cl::cat(BoltCategory)); static cl::opt<uint32_t> AltInstFeatureSize("alt-inst-feature-size", cl::desc("size of feature field in .altinstructions"), cl::init(2), cl::Hidden, cl::cat(BoltCategory)); static cl::opt<bool> DumpAltInstructions("dump-alt-instructions", cl::desc("dump Linux alternative instructions info"), cl::init(false), cl::Hidden, cl::cat(BoltCategory)); static cl::opt<bool> DumpExceptions("dump-linux-exceptions", cl::desc("dump Linux kernel exception table"), cl::init(false), cl::Hidden, cl::cat(BoltCategory)); static cl::opt<bool> DumpORC("dump-orc", cl::desc("dump raw ORC unwind information (sorted)"), cl::init(false), cl::Hidden, cl::cat(BoltCategory)); 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> DumpSMPLocks("dump-smp-locks", cl::desc("dump Linux kernel SMP locks"), 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, cl::cat(BoltCategory)); static cl::opt<bool> DumpStaticKeys("dump-static-keys", cl::desc("dump Linux kernel static keys jump table"), cl::init(false), cl::Hidden, cl::cat(BoltCategory)); static cl::opt<bool> LongJumpLabels( "long-jump-labels", cl::desc("always use long jumps/nops for Linux kernel static keys"), cl::init(false), cl::Hidden, cl::cat(BoltCategory)); static cl::opt<bool> PrintORC("print-orc", cl::desc("print ORC unwind information for instructions"), cl::init(true), cl::Hidden, cl::cat(BoltCategory)); } // namespace opts /// Linux Kernel supports stack unwinding using ORC (oops rewind capability). /// ORC state at every IP can be described by the following data structure. struct ORCState { … }; /// Section terminator ORC entry. static ORCState NullORC = …; /// Basic printer for ORC entry. It does not provide the same level of /// information as objtool (for now). inline raw_ostream &operator<<(raw_ostream &OS, const ORCState &E) { … } namespace { class LinuxKernelRewriter final : public MetadataRewriter { … }; void LinuxKernelRewriter::processLKSections() { … } /// Process __ksymtab[_gpl] sections of Linux Kernel. /// This section lists all the vmlinux symbols that kernel modules can access. /// /// All the entries are 4 bytes each and hence we can read them by one by one /// and ignore the ones that are not pointing to the .text section. All pointers /// are PC relative offsets. Always, points to the beginning of the function. void LinuxKernelRewriter::processLKKSymtab(bool IsGPL) { … } /// .smp_locks section contains PC-relative references to instructions with LOCK /// prefix. The prefix can be converted to NOP at boot time on non-SMP systems. Error LinuxKernelRewriter::processSMPLocks() { … } void LinuxKernelRewriter::processInstructionFixups() { … } Error LinuxKernelRewriter::readORCTables() { … } Error LinuxKernelRewriter::processORCPostCFG() { … } Error LinuxKernelRewriter::rewriteORCTables() { … } Error LinuxKernelRewriter::validateORCTables() { … } /// The static call site table is created by objtool and contains entries in the /// following format: /// /// struct static_call_site { /// s32 addr; /// s32 key; /// }; /// Error LinuxKernelRewriter::readStaticCalls() { … } /// The static call table is sorted during boot time in /// static_call_sort_entries(). This makes it possible to update existing /// entries in-place ignoring their relative order. Error LinuxKernelRewriter::rewriteStaticCalls() { … } /// Instructions that access user-space memory can cause page faults. These /// faults will be handled by the kernel and execution will resume at the fixup /// code location if the address was invalid. The kernel uses the exception /// table to match the faulting instruction to its fixup. The table consists of /// the following entries: /// /// struct exception_table_entry { /// int insn; /// int fixup; /// int data; /// }; /// /// More info at: /// https://www.kernel.org/doc/Documentation/x86/exception-tables.txt Error LinuxKernelRewriter::readExceptionTable() { … } /// Depending on the value of CONFIG_BUILDTIME_TABLE_SORT, the kernel expects /// the exception table to be sorted. Hence we have to sort it after code /// reordering. Error LinuxKernelRewriter::rewriteExceptionTable() { … } /// .parainsrtuctions section contains information for patching parvirtual call /// instructions during runtime. The entries in the section are in the form: /// /// struct paravirt_patch_site { /// u8 *instr; /* original instructions */ /// u8 type; /* type of this instruction */ /// u8 len; /* length of original instruction */ /// }; /// /// Note that the structures are aligned at 8-byte boundary. Error LinuxKernelRewriter::readParaInstructions() { … } void LinuxKernelRewriter::skipFunctionsWithAnnotation( StringRef Annotation) const { … } Error LinuxKernelRewriter::rewriteParaInstructions() { … } /// Process __bug_table section. /// This section contains information useful for kernel debugging, mostly /// utilized by WARN()/WARN_ON() macros and deprecated BUG()/BUG_ON(). /// /// Each entry in the section is a struct bug_entry that contains a pointer to /// the ud2 instruction corresponding to the bug, corresponding file name (both /// pointers use PC relative offset addressing), line number, and flags. /// The definition of the struct bug_entry can be found in /// `include/asm-generic/bug.h`. The first entry in the struct is an instruction /// address encoded as a PC-relative offset. In theory, it could be an absolute /// address if CONFIG_GENERIC_BUG_RELATIVE_POINTERS is not set, but in practice /// the kernel code relies on it being a relative offset on x86-64. Error LinuxKernelRewriter::readBugTable() { … } /// find_bug() uses linear search to match an address to an entry in the bug /// table. Hence, there is no need to sort entries when rewriting the table. /// When we need to erase an entry, we set its instruction address to zero. Error LinuxKernelRewriter::rewriteBugTable() { … } /// The kernel can replace certain instruction sequences depending on hardware /// it is running on and features specified during boot time. The information /// about alternative instruction sequences is stored in .altinstructions /// section. The format of entries in this section is defined in /// arch/x86/include/asm/alternative.h: /// /// struct alt_instr { /// s32 instr_offset; /// s32 repl_offset; /// uXX feature; /// u8 instrlen; /// u8 replacementlen; /// u8 padlen; // present in older kernels /// } __packed; /// /// Note that the structure is packed. /// /// Since the size of the "feature" field could be either u16 or u32, and /// "padlen" presence is unknown, we attempt to parse .altinstructions section /// using all possible combinations (four at this time). Since we validate the /// contents of the section and its size, the detection works quite well. /// Still, we leave the user the opportunity to specify these features on the /// command line and skip the guesswork. Error LinuxKernelRewriter::readAltInstructions() { … } Error LinuxKernelRewriter::tryReadAltInstructions(uint32_t AltInstFeatureSize, bool AltInstHasPadLen, bool ParseOnly) { … } void LinuxKernelRewriter::processAltInstructionsPostCFG() { … } /// 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() { … } /// Runtime code modification used by static keys is the most ubiquitous /// self-modifying feature of the Linux kernel. The idea is to eliminate the /// condition check and associated conditional jump on a hot path if that /// condition (based on a boolean value of a static key) does not change often. /// Whenever the condition changes, the kernel runtime modifies all code paths /// associated with that key flipping the code between nop and (unconditional) /// jump. The information about the code is stored in a static key jump table /// and contains the list of entries of the following type from /// include/linux/jump_label.h: // /// struct jump_entry { /// s32 code; /// s32 target; /// long key; // key may be far away from the core kernel under KASLR /// }; /// /// The list does not have to be stored in any sorted way, but it is sorted at /// boot time (or module initialization time) first by "key" and then by "code". /// jump_label_sort_entries() is responsible for sorting the table. /// /// The key in jump_entry structure uses lower two bits of the key address /// (which itself is aligned) to store extra information. We are interested in /// the lower bit which indicates if the key is likely to be set on the code /// path associated with this jump_entry. /// /// static_key_{enable,disable}() functions modify the code based on key and /// jump table entries. /// /// jump_label_update() updates all code entries for a given key. Batch mode is /// used for x86. /// /// The actual patching happens in text_poke_bp_batch() that overrides the first /// byte of the sequence with int3 before proceeding with actual code /// replacement. Error LinuxKernelRewriter::readStaticKeysJumpTable() { … } // Pre-emit pass. Convert dynamic branch instructions into jumps that could be // relaxed. In post-emit pass we will convert those jumps into nops when // necessary. We do the unconditional conversion into jumps so that the jumps // can be relaxed and the optimal size of jump/nop instruction is selected. Error LinuxKernelRewriter::rewriteStaticKeysJumpTable() { … } // Post-emit pass of static keys jump section. Convert jumps to nops. Error LinuxKernelRewriter::updateStaticKeysJumpTablePostEmit() { … } } // namespace std::unique_ptr<MetadataRewriter> llvm::bolt::createLinuxKernelRewriter(BinaryContext &BC) { … }