
In 'asm goto' statements ('callbr' in LLVM IR), you can specify one or more labels / basic blocks in the containing function which the assembly code might jump to. If you're also compiling with branch target enforcement via BTI, then previously listing a basic block as a possible jump destination of an asm goto would cause a BTI instruction to be placed at the start of the block, in case the assembly code used an _indirect_ branch instruction (i.e. to a destination address read from a register) to jump to that location. Now it doesn't do that any more: branches to destination labels from the assembly code are assumed to be direct branches (to a relative offset encoded in the instruction), which don't require a BTI at their destination. This change was proposed in https://discourse.llvm.org/t/85845 and there seemed to be no disagreement. The rationale is: 1. it brings clang's handling of asm goto in Arm and AArch64 in line with gcc's, which didn't generate BTIs at the target labels in the first place. 2. it improves performance in the Linux kernel, which uses a lot of 'asm goto' in which the assembly language just contains a NOP, and the label's address is saved elsewhere to let the kernel self-modify at run time to swap between the original NOP and a direct branch to the label. This allows hot code paths to be instrumented for debugging, at only the cost of a NOP when the instrumentation is turned off, instead of the larger cost of an indirect branch. In this situation a BTI is unnecessary (if the branch happens it's direct), and since the code paths are hot, also a noticeable performance hit. Implementation: `SelectionDAGBuilder::visitCallBr` is the place where 'asm goto' target labels are handled. It calls `setIsInlineAsmBrIndirectTarget()` on each target `MachineBasicBlock`. Previously it also called `setMachineBlockAddressTaken()`, which made `hasAddressTaken()` return true, which caused a BTI to be added in the Arm backends. Now `visitCallBr` doesn't call `setMachineBlockAddressTaken()` any more on asm goto targets, but `hasAddressTaken()` also checks the flag set by `setIsInlineAsmBrIndirectTarget()`. So call sites that were using `hasAddressTaken()` don't need to be modified. But the Arm backends don't call `hasAddressTaken()` any more: instead they test two more specific query functions that cover all the reasons `hasAddressTaken()` might have returned true _except_ being an asm goto target. Testing: The new test `AArch64/callbr-asm-label-bti.ll` is testing the actual change, where it expects not to see a `bti` instruction after `[[LABEL]]`. The rest of the test changes are all churn, due to the flags on basic blocks changing. Actual output code hasn't changed in any of the existing tests, only comments and diagnostics. Further work: `RISCVIndirectBranchTracking.cpp` and `X86IndirectBranchTracking.cpp` also call `hasAddressTaken()` in a way that might benefit from using the same more specific check I've put in `ARMBranchTargets.cpp` and `AArch64BranchTargets.cpp`. But I'm not sure of that, so in this commit I've only changed the Arm backends, and left those alone.
128 lines
4.8 KiB
C++
128 lines
4.8 KiB
C++
//===-- ARMBranchTargets.cpp -- Harden code using v8.1-M BTI extension -----==//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This pass inserts BTI instructions at the start of every function and basic
|
|
// block which could be indirectly called. The hardware will (when enabled)
|
|
// trap when an indirect branch or call instruction targets an instruction
|
|
// which is not a valid BTI instruction. This is intended to guard against
|
|
// control-flow hijacking attacks.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "ARM.h"
|
|
#include "ARMInstrInfo.h"
|
|
#include "ARMMachineFunctionInfo.h"
|
|
#include "llvm/CodeGen/MachineFunctionPass.h"
|
|
#include "llvm/CodeGen/MachineInstrBuilder.h"
|
|
#include "llvm/CodeGen/MachineJumpTableInfo.h"
|
|
#include "llvm/CodeGen/MachineModuleInfo.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
using namespace llvm;
|
|
|
|
#define DEBUG_TYPE "arm-branch-targets"
|
|
#define ARM_BRANCH_TARGETS_NAME "ARM Branch Targets"
|
|
|
|
namespace {
|
|
class ARMBranchTargets : public MachineFunctionPass {
|
|
public:
|
|
static char ID;
|
|
ARMBranchTargets() : MachineFunctionPass(ID) {}
|
|
void getAnalysisUsage(AnalysisUsage &AU) const override;
|
|
bool runOnMachineFunction(MachineFunction &MF) override;
|
|
StringRef getPassName() const override { return ARM_BRANCH_TARGETS_NAME; }
|
|
|
|
private:
|
|
void addBTI(const ARMInstrInfo &TII, MachineBasicBlock &MBB, bool IsFirstBB);
|
|
};
|
|
} // end anonymous namespace
|
|
|
|
char ARMBranchTargets::ID = 0;
|
|
|
|
INITIALIZE_PASS(ARMBranchTargets, "arm-branch-targets", ARM_BRANCH_TARGETS_NAME,
|
|
false, false)
|
|
|
|
void ARMBranchTargets::getAnalysisUsage(AnalysisUsage &AU) const {
|
|
AU.setPreservesCFG();
|
|
MachineFunctionPass::getAnalysisUsage(AU);
|
|
}
|
|
|
|
FunctionPass *llvm::createARMBranchTargetsPass() {
|
|
return new ARMBranchTargets();
|
|
}
|
|
|
|
bool ARMBranchTargets::runOnMachineFunction(MachineFunction &MF) {
|
|
if (!MF.getInfo<ARMFunctionInfo>()->branchTargetEnforcement())
|
|
return false;
|
|
|
|
LLVM_DEBUG(dbgs() << "********** ARM Branch Targets **********\n"
|
|
<< "********** Function: " << MF.getName() << '\n');
|
|
const ARMInstrInfo &TII =
|
|
*static_cast<const ARMInstrInfo *>(MF.getSubtarget().getInstrInfo());
|
|
|
|
bool MadeChange = false;
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
bool IsFirstBB = &MBB == &MF.front();
|
|
|
|
// Every function can potentially be called indirectly (even if it has
|
|
// static linkage, due to linker-generated veneers).
|
|
// If the block itself is address-taken, or is an exception landing pad, it
|
|
// could be indirectly branched to.
|
|
// Jump tables only emit indirect jumps (JUMPTABLE_ADDRS) in ARM or Thumb1
|
|
// modes. These modes do not support PACBTI. As a result, BTI instructions
|
|
// are not added in the destination blocks.
|
|
|
|
if (IsFirstBB || MBB.isMachineBlockAddressTaken() ||
|
|
MBB.isIRBlockAddressTaken() || MBB.isEHPad()) {
|
|
addBTI(TII, MBB, IsFirstBB);
|
|
MadeChange = true;
|
|
}
|
|
}
|
|
|
|
return MadeChange;
|
|
}
|
|
|
|
/// Insert a BTI/PACBTI instruction into a given basic block \c MBB. If
|
|
/// \c IsFirstBB is true (meaning that this is the first BB in a function) try
|
|
/// to find a PAC instruction and replace it with PACBTI. Otherwise just insert
|
|
/// a BTI instruction.
|
|
/// The point of insertion is in the beginning of the BB, immediately after meta
|
|
/// instructions (such labels in exception handling landing pads).
|
|
void ARMBranchTargets::addBTI(const ARMInstrInfo &TII, MachineBasicBlock &MBB,
|
|
bool IsFirstBB) {
|
|
// Which instruction to insert: BTI or PACBTI
|
|
unsigned OpCode = ARM::t2BTI;
|
|
unsigned MIFlags = 0;
|
|
|
|
// Skip meta instructions, including EH labels
|
|
auto MBBI = llvm::find_if_not(MBB.instrs(), [](const MachineInstr &MI) {
|
|
return MI.isMetaInstruction();
|
|
});
|
|
|
|
// If this is the first BB in a function, check if it starts with a PAC
|
|
// instruction and in that case remove the PAC instruction.
|
|
if (IsFirstBB) {
|
|
if (MBBI != MBB.instr_end() && MBBI->getOpcode() == ARM::t2PAC) {
|
|
LLVM_DEBUG(dbgs() << "Removing a 'PAC' instr from BB '" << MBB.getName()
|
|
<< "' to replace with PACBTI\n");
|
|
OpCode = ARM::t2PACBTI;
|
|
MIFlags = MachineInstr::FrameSetup;
|
|
auto NextMBBI = std::next(MBBI);
|
|
MBBI->eraseFromParent();
|
|
MBBI = NextMBBI;
|
|
}
|
|
}
|
|
|
|
LLVM_DEBUG(dbgs() << "Inserting a '"
|
|
<< (OpCode == ARM::t2BTI ? "BTI" : "PACBTI")
|
|
<< "' instr into BB '" << MBB.getName() << "'\n");
|
|
// Finally, insert a new instruction (either PAC or PACBTI)
|
|
BuildMI(MBB, MBBI, MBB.findDebugLoc(MBBI), TII.get(OpCode))
|
|
.setMIFlags(MIFlags);
|
|
}
|