
* Sort ORC entries in the internal table. Older Linux kernels did not sort them in the file (only during boot time). * Add an option to dump sorted ORC tables (--dump-orc). * Associate entries in the internal ORC table with a BinaryFunction even when we are not changing the function. * If the function doesn't have ORC entry at the start, propagate ORC state from a previous entry. Reviewed By: Amir Differential Revision: https://reviews.llvm.org/D155767
632 lines
22 KiB
C++
632 lines
22 KiB
C++
//===- 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/Support/CommandLine.h"
|
|
#include "llvm/Support/Errc.h"
|
|
|
|
using namespace llvm;
|
|
using namespace bolt;
|
|
|
|
namespace opts {
|
|
|
|
static cl::opt<bool>
|
|
PrintORC("print-orc",
|
|
cl::desc("print ORC unwind information for instructions"),
|
|
cl::init(true), 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));
|
|
|
|
} // 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 {
|
|
int16_t SPOffset;
|
|
int16_t BPOffset;
|
|
int16_t Info;
|
|
|
|
bool operator==(const ORCState &Other) const {
|
|
return SPOffset == Other.SPOffset && BPOffset == Other.BPOffset &&
|
|
Info == Other.Info;
|
|
}
|
|
|
|
bool operator!=(const ORCState &Other) const { return !(*this == Other); }
|
|
};
|
|
|
|
/// 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) {
|
|
if (opts::PrintORC)
|
|
OS << format("{sp: %d, bp: %d, info: 0x%x}", E.SPOffset, E.BPOffset,
|
|
E.Info);
|
|
return OS;
|
|
}
|
|
|
|
namespace {
|
|
|
|
/// Section terminator ORC entry.
|
|
static ORCState NullORC = {0, 0, 0};
|
|
|
|
class LinuxKernelRewriter final : public MetadataRewriter {
|
|
/// Linux Kernel special sections point to a specific instruction in many
|
|
/// cases. Unlike SDTMarkerInfo, these markers can come from different
|
|
/// sections.
|
|
struct LKInstructionMarkerInfo {
|
|
uint64_t SectionOffset;
|
|
int32_t PCRelativeOffset;
|
|
bool IsPCRelative;
|
|
StringRef SectionName;
|
|
};
|
|
|
|
/// Map linux kernel program locations/instructions to their pointers in
|
|
/// special linux kernel sections
|
|
std::unordered_map<uint64_t, std::vector<LKInstructionMarkerInfo>> LKMarkers;
|
|
|
|
/// Linux ORC sections.
|
|
ErrorOr<BinarySection &> ORCUnwindSection = std::errc::bad_address;
|
|
ErrorOr<BinarySection &> ORCUnwindIPSection = std::errc::bad_address;
|
|
|
|
/// Size of entries in ORC sections.
|
|
static constexpr size_t ORC_UNWIND_ENTRY_SIZE = 6;
|
|
static constexpr size_t ORC_UNWIND_IP_ENTRY_SIZE = 4;
|
|
|
|
struct ORCListEntry {
|
|
uint64_t IP; /// Instruction address.
|
|
BinaryFunction *BF; /// Binary function corresponding to the entry.
|
|
ORCState ORC; /// Stack unwind info in ORC format.
|
|
|
|
bool operator<(const ORCListEntry &Other) const {
|
|
if (IP < Other.IP)
|
|
return 1;
|
|
if (IP > Other.IP)
|
|
return 0;
|
|
return ORC == NullORC;
|
|
}
|
|
};
|
|
|
|
using ORCListType = std::vector<ORCListEntry>;
|
|
ORCListType ORCEntries;
|
|
|
|
/// 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,
|
|
int32_t PCRelativeOffset, bool IsPCRelative,
|
|
StringRef SectionName);
|
|
|
|
/// Process linux kernel special sections and their relocations.
|
|
void processLKSections();
|
|
|
|
/// Process special linux kernel section, __ex_table.
|
|
void processLKExTable();
|
|
|
|
/// Process special linux kernel section, .pci_fixup.
|
|
void processLKPCIFixup();
|
|
|
|
/// Process __ksymtab and __ksymtab_gpl.
|
|
void processLKKSymtab(bool IsGPL = false);
|
|
|
|
/// Process special linux kernel section, __bug_table.
|
|
void processLKBugTable();
|
|
|
|
/// Process special linux kernel section, .smp_locks.
|
|
void processLKSMPLocks();
|
|
|
|
/// Update LKMarkers' locations for the output binary.
|
|
void updateLKMarkers();
|
|
|
|
/// Read ORC unwind information and annotate instructions.
|
|
Error readORCTables();
|
|
|
|
/// Update ORC for functions once CFG is constructed.
|
|
Error processORCPostCFG();
|
|
|
|
/// Update ORC data in the binary.
|
|
Error rewriteORCTables();
|
|
|
|
/// Mark instructions referenced by kernel metadata.
|
|
Error markInstructions();
|
|
|
|
public:
|
|
LinuxKernelRewriter(BinaryContext &BC)
|
|
: MetadataRewriter("linux-kernel-rewriter", BC) {}
|
|
|
|
Error preCFGInitializer() override {
|
|
processLKSections();
|
|
if (Error E = markInstructions())
|
|
return E;
|
|
|
|
if (Error E = readORCTables())
|
|
return E;
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
Error postCFGInitializer() override {
|
|
if (Error E = processORCPostCFG())
|
|
return E;
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
Error postEmitFinalizer() override {
|
|
updateLKMarkers();
|
|
|
|
if (Error E = rewriteORCTables())
|
|
return E;
|
|
|
|
return Error::success();
|
|
}
|
|
};
|
|
|
|
Error LinuxKernelRewriter::markInstructions() {
|
|
for (const uint64_t PC : llvm::make_first_range(LKMarkers)) {
|
|
BinaryFunction *BF = BC.getBinaryFunctionContainingAddress(PC);
|
|
|
|
if (!BF || !BC.shouldEmit(*BF))
|
|
continue;
|
|
|
|
const uint64_t Offset = PC - BF->getAddress();
|
|
MCInst *Inst = BF->getInstructionAtOffset(Offset);
|
|
if (!Inst)
|
|
return createStringError(errc::executable_format_error,
|
|
"no instruction matches kernel marker offset");
|
|
|
|
BC.MIB->setOffset(*Inst, static_cast<uint32_t>(Offset));
|
|
|
|
BF->setHasSDTMarker(true);
|
|
}
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
void LinuxKernelRewriter::insertLKMarker(uint64_t PC, uint64_t SectionOffset,
|
|
int32_t PCRelativeOffset,
|
|
bool IsPCRelative,
|
|
StringRef SectionName) {
|
|
LKMarkers[PC].emplace_back(LKInstructionMarkerInfo{
|
|
SectionOffset, PCRelativeOffset, IsPCRelative, SectionName});
|
|
}
|
|
|
|
void LinuxKernelRewriter::processLKSections() {
|
|
assert(opts::LinuxKernelMode &&
|
|
"process Linux Kernel special sections and their relocations only in "
|
|
"linux kernel mode.\n");
|
|
|
|
processLKExTable();
|
|
processLKPCIFixup();
|
|
processLKKSymtab();
|
|
processLKKSymtab(true);
|
|
processLKBugTable();
|
|
processLKSMPLocks();
|
|
}
|
|
|
|
/// Process __ex_table section of Linux Kernel.
|
|
/// This section contains information regarding kernel level exception
|
|
/// handling (https://www.kernel.org/doc/html/latest/x86/exception-tables.html).
|
|
/// More documentation is in arch/x86/include/asm/extable.h.
|
|
///
|
|
/// The section is the list of the following structures:
|
|
///
|
|
/// struct exception_table_entry {
|
|
/// int insn;
|
|
/// int fixup;
|
|
/// int handler;
|
|
/// };
|
|
///
|
|
void LinuxKernelRewriter::processLKExTable() {
|
|
ErrorOr<BinarySection &> SectionOrError =
|
|
BC.getUniqueSectionByName("__ex_table");
|
|
if (!SectionOrError)
|
|
return;
|
|
|
|
const uint64_t SectionSize = SectionOrError->getSize();
|
|
const uint64_t SectionAddress = SectionOrError->getAddress();
|
|
assert((SectionSize % 12) == 0 &&
|
|
"The size of the __ex_table section should be a multiple of 12");
|
|
for (uint64_t I = 0; I < SectionSize; I += 4) {
|
|
const uint64_t EntryAddress = SectionAddress + I;
|
|
ErrorOr<uint64_t> Offset = BC.getSignedValueAtAddress(EntryAddress, 4);
|
|
assert(Offset && "failed reading PC-relative offset for __ex_table");
|
|
int32_t SignedOffset = *Offset;
|
|
const uint64_t RefAddress = EntryAddress + SignedOffset;
|
|
|
|
BinaryFunction *ContainingBF =
|
|
BC.getBinaryFunctionContainingAddress(RefAddress);
|
|
if (!ContainingBF)
|
|
continue;
|
|
|
|
MCSymbol *ReferencedSymbol = ContainingBF->getSymbol();
|
|
const uint64_t FunctionOffset = RefAddress - ContainingBF->getAddress();
|
|
switch (I % 12) {
|
|
default:
|
|
llvm_unreachable("bad alignment of __ex_table");
|
|
break;
|
|
case 0:
|
|
// insn
|
|
insertLKMarker(RefAddress, I, SignedOffset, true, "__ex_table");
|
|
break;
|
|
case 4:
|
|
// fixup
|
|
if (FunctionOffset)
|
|
ReferencedSymbol = ContainingBF->addEntryPointAtOffset(FunctionOffset);
|
|
BC.addRelocation(EntryAddress, ReferencedSymbol, Relocation::getPC32(), 0,
|
|
*Offset);
|
|
break;
|
|
case 8:
|
|
// handler
|
|
assert(!FunctionOffset &&
|
|
"__ex_table handler entry should point to function start");
|
|
BC.addRelocation(EntryAddress, ReferencedSymbol, Relocation::getPC32(), 0,
|
|
*Offset);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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");
|
|
assert(SectionOrError &&
|
|
".pci_fixup section not found in Linux Kernel binary");
|
|
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.
|
|
///
|
|
/// 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) {
|
|
StringRef SectionName = "__ksymtab";
|
|
if (IsGPL)
|
|
SectionName = "__ksymtab_gpl";
|
|
ErrorOr<BinarySection &> SectionOrError =
|
|
BC.getUniqueSectionByName(SectionName);
|
|
assert(SectionOrError &&
|
|
"__ksymtab[_gpl] section not found in Linux Kernel binary");
|
|
const uint64_t SectionSize = SectionOrError->getSize();
|
|
const uint64_t SectionAddress = SectionOrError->getAddress();
|
|
assert((SectionSize % 4) == 0 &&
|
|
"The size of the __ksymtab[_gpl] section should be a multiple of 4");
|
|
|
|
for (uint64_t I = 0; I < SectionSize; I += 4) {
|
|
const uint64_t EntryAddress = SectionAddress + I;
|
|
ErrorOr<uint64_t> Offset = BC.getSignedValueAtAddress(EntryAddress, 4);
|
|
assert(Offset && "Reading valid PC-relative offset for a ksymtab entry");
|
|
const int32_t SignedOffset = *Offset;
|
|
const uint64_t RefAddress = EntryAddress + SignedOffset;
|
|
BinaryFunction *BF = BC.getBinaryFunctionAtAddress(RefAddress);
|
|
if (!BF)
|
|
continue;
|
|
|
|
BC.addRelocation(EntryAddress, BF->getSymbol(), Relocation::getPC32(), 0,
|
|
*Offset);
|
|
}
|
|
}
|
|
|
|
/// Process __bug_table section.
|
|
/// This section contains information useful for kernel debugging.
|
|
/// 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`
|
|
void LinuxKernelRewriter::processLKBugTable() {
|
|
ErrorOr<BinarySection &> SectionOrError =
|
|
BC.getUniqueSectionByName("__bug_table");
|
|
if (!SectionOrError)
|
|
return;
|
|
|
|
const uint64_t SectionSize = SectionOrError->getSize();
|
|
const uint64_t SectionAddress = SectionOrError->getAddress();
|
|
assert((SectionSize % 12) == 0 &&
|
|
"The size of the __bug_table section should be a multiple of 12");
|
|
for (uint64_t I = 0; I < SectionSize; I += 12) {
|
|
const uint64_t EntryAddress = SectionAddress + I;
|
|
ErrorOr<uint64_t> Offset = BC.getSignedValueAtAddress(EntryAddress, 4);
|
|
assert(Offset &&
|
|
"Reading valid PC-relative offset for a __bug_table entry");
|
|
const int32_t SignedOffset = *Offset;
|
|
const uint64_t RefAddress = EntryAddress + SignedOffset;
|
|
assert(BC.getBinaryFunctionContainingAddress(RefAddress) &&
|
|
"__bug_table entries should point to a function");
|
|
|
|
insertLKMarker(RefAddress, I, SignedOffset, true, "__bug_table");
|
|
}
|
|
}
|
|
|
|
/// .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.
|
|
void LinuxKernelRewriter::processLKSMPLocks() {
|
|
ErrorOr<BinarySection &> SectionOrError =
|
|
BC.getUniqueSectionByName(".smp_locks");
|
|
if (!SectionOrError)
|
|
return;
|
|
|
|
uint64_t SectionSize = SectionOrError->getSize();
|
|
const uint64_t SectionAddress = SectionOrError->getAddress();
|
|
assert((SectionSize % 4) == 0 &&
|
|
"The size of the .smp_locks section should be a multiple of 4");
|
|
|
|
for (uint64_t I = 0; I < SectionSize; I += 4) {
|
|
const uint64_t EntryAddress = SectionAddress + I;
|
|
ErrorOr<uint64_t> Offset = BC.getSignedValueAtAddress(EntryAddress, 4);
|
|
assert(Offset && "Reading valid PC-relative offset for a .smp_locks entry");
|
|
int32_t SignedOffset = *Offset;
|
|
uint64_t RefAddress = EntryAddress + SignedOffset;
|
|
|
|
BinaryFunction *ContainingBF =
|
|
BC.getBinaryFunctionContainingAddress(RefAddress);
|
|
if (!ContainingBF)
|
|
continue;
|
|
|
|
insertLKMarker(RefAddress, I, SignedOffset, true, ".smp_locks");
|
|
}
|
|
}
|
|
|
|
void LinuxKernelRewriter::updateLKMarkers() {
|
|
if (LKMarkers.size() == 0)
|
|
return;
|
|
|
|
std::unordered_map<std::string, uint64_t> PatchCounts;
|
|
for (std::pair<const uint64_t, std::vector<LKInstructionMarkerInfo>>
|
|
&LKMarkerInfoKV : LKMarkers) {
|
|
const uint64_t OriginalAddress = LKMarkerInfoKV.first;
|
|
const BinaryFunction *BF =
|
|
BC.getBinaryFunctionContainingAddress(OriginalAddress, false, true);
|
|
if (!BF)
|
|
continue;
|
|
|
|
uint64_t NewAddress = BF->translateInputToOutputAddress(OriginalAddress);
|
|
if (NewAddress == 0)
|
|
continue;
|
|
|
|
// Apply base address.
|
|
if (OriginalAddress >= 0xffffffff00000000 && NewAddress < 0xffffffff)
|
|
NewAddress = NewAddress + 0xffffffff00000000;
|
|
|
|
if (OriginalAddress == NewAddress)
|
|
continue;
|
|
|
|
for (LKInstructionMarkerInfo &LKMarkerInfo : LKMarkerInfoKV.second) {
|
|
StringRef SectionName = LKMarkerInfo.SectionName;
|
|
SimpleBinaryPatcher *LKPatcher;
|
|
ErrorOr<BinarySection &> BSec = BC.getUniqueSectionByName(SectionName);
|
|
assert(BSec && "missing section info for kernel section");
|
|
if (!BSec->getPatcher())
|
|
BSec->registerPatcher(std::make_unique<SimpleBinaryPatcher>());
|
|
LKPatcher = static_cast<SimpleBinaryPatcher *>(BSec->getPatcher());
|
|
PatchCounts[std::string(SectionName)]++;
|
|
if (LKMarkerInfo.IsPCRelative)
|
|
LKPatcher->addLE32Patch(LKMarkerInfo.SectionOffset,
|
|
NewAddress - OriginalAddress +
|
|
LKMarkerInfo.PCRelativeOffset);
|
|
else
|
|
LKPatcher->addLE64Patch(LKMarkerInfo.SectionOffset, NewAddress);
|
|
}
|
|
}
|
|
outs() << "BOLT-INFO: patching linux kernel sections. Total patches per "
|
|
"section are as follows:\n";
|
|
for (const std::pair<const std::string, uint64_t> &KV : PatchCounts)
|
|
outs() << " Section: " << KV.first << ", patch-counts: " << KV.second
|
|
<< '\n';
|
|
}
|
|
|
|
Error LinuxKernelRewriter::readORCTables() {
|
|
// NOTE: we should ignore relocations for orc tables as the tables are sorted
|
|
// post-link time and relocations are not updated.
|
|
ORCUnwindSection = BC.getUniqueSectionByName(".orc_unwind");
|
|
ORCUnwindIPSection = BC.getUniqueSectionByName(".orc_unwind_ip");
|
|
|
|
if (!ORCUnwindSection && !ORCUnwindIPSection)
|
|
return Error::success();
|
|
|
|
if (!ORCUnwindSection || !ORCUnwindIPSection)
|
|
return createStringError(errc::executable_format_error,
|
|
"missing ORC section");
|
|
|
|
const uint64_t NumEntries =
|
|
ORCUnwindIPSection->getSize() / ORC_UNWIND_IP_ENTRY_SIZE;
|
|
if (ORCUnwindSection->getSize() != NumEntries * ORC_UNWIND_ENTRY_SIZE ||
|
|
ORCUnwindIPSection->getSize() != NumEntries * ORC_UNWIND_IP_ENTRY_SIZE)
|
|
return createStringError(errc::executable_format_error,
|
|
"ORC entries number mismatch detected");
|
|
|
|
const uint64_t IPSectionAddress = ORCUnwindIPSection->getAddress();
|
|
DataExtractor OrcDE = DataExtractor(ORCUnwindSection->getContents(),
|
|
BC.AsmInfo->isLittleEndian(),
|
|
BC.AsmInfo->getCodePointerSize());
|
|
DataExtractor IPDE = DataExtractor(ORCUnwindIPSection->getContents(),
|
|
BC.AsmInfo->isLittleEndian(),
|
|
BC.AsmInfo->getCodePointerSize());
|
|
DataExtractor::Cursor ORCCursor(0);
|
|
DataExtractor::Cursor IPCursor(0);
|
|
uint64_t PrevIP = 0;
|
|
for (uint32_t Index = 0; Index < NumEntries; ++Index) {
|
|
const uint64_t IP =
|
|
IPSectionAddress + IPCursor.tell() + (int32_t)IPDE.getU32(IPCursor);
|
|
|
|
// Consume the status of the cursor.
|
|
if (!IPCursor)
|
|
return createStringError(errc::executable_format_error,
|
|
"out of bounds while reading ORC IP table");
|
|
|
|
if (IP < PrevIP && opts::Verbosity)
|
|
errs() << "BOLT-WARNING: out of order IP 0x" << Twine::utohexstr(IP)
|
|
<< " detected while reading ORC\n";
|
|
|
|
PrevIP = IP;
|
|
|
|
// Store all entries, includes those we are not going to update as the
|
|
// tables need to be sorted globally before being written out.
|
|
ORCEntries.push_back(ORCListEntry());
|
|
ORCListEntry &Entry = ORCEntries.back();
|
|
|
|
Entry.IP = IP;
|
|
Entry.ORC.SPOffset = (int16_t)OrcDE.getU16(ORCCursor);
|
|
Entry.ORC.BPOffset = (int16_t)OrcDE.getU16(ORCCursor);
|
|
Entry.ORC.Info = (int16_t)OrcDE.getU16(ORCCursor);
|
|
|
|
// Consume the status of the cursor.
|
|
if (!ORCCursor)
|
|
return createStringError(errc::executable_format_error,
|
|
"out of bounds while reading ORC");
|
|
|
|
BinaryFunction *&BF = Entry.BF;
|
|
BF = BC.getBinaryFunctionContainingAddress(IP, /*CheckPastEnd*/ true);
|
|
|
|
// If the entry immediately pointing past the end of the function is not
|
|
// the terminator entry, then it does not belong to this function.
|
|
if (BF && BF->getAddress() + BF->getSize() == IP && Entry.ORC != NullORC)
|
|
BF = 0;
|
|
|
|
// If terminator entry points to the start of the function, then it belongs
|
|
// to a different function that contains the previous IP.
|
|
if (BF && BF->getAddress() == IP && Entry.ORC == NullORC)
|
|
BF = BC.getBinaryFunctionContainingAddress(IP - 1);
|
|
|
|
if (!BF) {
|
|
if (opts::Verbosity)
|
|
errs() << "BOLT-WARNING: no binary function found matching ORC 0x"
|
|
<< Twine::utohexstr(IP) << ": " << Entry.ORC << '\n';
|
|
continue;
|
|
}
|
|
|
|
if (Entry.ORC == NullORC)
|
|
continue;
|
|
|
|
BF->setHasORC(true);
|
|
|
|
if (!BF->hasInstructions())
|
|
continue;
|
|
|
|
MCInst *Inst = BF->getInstructionAtOffset(IP - BF->getAddress());
|
|
if (!Inst)
|
|
return createStringError(
|
|
errc::executable_format_error,
|
|
"no instruction at address 0x%" PRIx64 " in .orc_unwind_ip", IP);
|
|
|
|
// Some addresses will have two entries associated with them. The first
|
|
// one being a "weak" section terminator. Since we ignore the terminator,
|
|
// we should only assign one entry per instruction.
|
|
if (BC.MIB->hasAnnotation(*Inst, "ORC"))
|
|
return createStringError(
|
|
errc::executable_format_error,
|
|
"duplicate non-terminal ORC IP 0x%" PRIx64 " in .orc_unwind_ip", IP);
|
|
|
|
BC.MIB->addAnnotation(*Inst, "ORC", Entry.ORC);
|
|
}
|
|
|
|
// Older kernels could contain unsorted tables in the file as the tables were
|
|
// sorted during boot time.
|
|
llvm::sort(ORCEntries);
|
|
|
|
if (opts::DumpORC) {
|
|
outs() << "BOLT-INFO: ORC unwind information:\n";
|
|
for (const ORCListEntry &E : ORCEntries) {
|
|
outs() << "0x" << Twine::utohexstr(E.IP) << ": " << E.ORC;
|
|
if (E.BF)
|
|
outs() << ": " << *E.BF;
|
|
outs() << '\n';
|
|
}
|
|
}
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
Error LinuxKernelRewriter::processORCPostCFG() {
|
|
// Propagate ORC to the rest of the function. We can annotate every
|
|
// instruction in every function, but to minimize the overhead, we annotate
|
|
// the first instruction in every basic block to reflect the state at the
|
|
// entry. This way, the ORC state can be calculated based on annotations
|
|
// regardless of the basic block layout. Note that if we insert/delete
|
|
// instructions, we must take care to attach ORC info to the new/deleted ones.
|
|
for (BinaryFunction &BF : llvm::make_second_range(BC.getBinaryFunctions())) {
|
|
|
|
std::optional<ORCState> CurrentState;
|
|
for (BinaryBasicBlock &BB : BF) {
|
|
for (MCInst &Inst : BB) {
|
|
ErrorOr<ORCState> State =
|
|
BC.MIB->tryGetAnnotationAs<ORCState>(Inst, "ORC");
|
|
|
|
if (State) {
|
|
CurrentState = *State;
|
|
continue;
|
|
}
|
|
|
|
// In case there was no ORC entry that matched the function start
|
|
// address, we need to propagate ORC state from the previous entry.
|
|
if (!CurrentState) {
|
|
auto It =
|
|
llvm::partition_point(ORCEntries, [&](const ORCListEntry &E) {
|
|
return E.IP < BF.getAddress();
|
|
});
|
|
if (It != ORCEntries.begin())
|
|
It = std::prev(It);
|
|
|
|
if (It->ORC == NullORC && BF.hasORC())
|
|
errs() << "BOLT-WARNING: ORC unwind info excludes prologue for "
|
|
<< BF << '\n';
|
|
|
|
CurrentState = It->ORC;
|
|
if (It->ORC != NullORC)
|
|
BF.setHasORC(true);
|
|
}
|
|
|
|
// While printing ORC, attach info to every instruction for convenience.
|
|
if (opts::PrintORC || &Inst == &BB.front())
|
|
BC.MIB->addAnnotation(Inst, "ORC", *CurrentState);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
Error LinuxKernelRewriter::rewriteORCTables() {
|
|
// TODO:
|
|
return Error::success();
|
|
}
|
|
} // namespace
|
|
|
|
std::unique_ptr<MetadataRewriter>
|
|
llvm::bolt::createLinuxKernelRewriter(BinaryContext &BC) {
|
|
return std::make_unique<LinuxKernelRewriter>(BC);
|
|
}
|