Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion clang/lib/CodeGen/BackendUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,8 @@ static void addKCFIPass(const Triple &TargetTriple, const LangOptions &LangOpts,
PassBuilder &PB) {
// If the back-end supports KCFI operand bundle lowering, skip KCFIPass.
if (TargetTriple.getArch() == llvm::Triple::x86_64 ||
TargetTriple.isAArch64(64) || TargetTriple.isRISCV())
TargetTriple.isAArch64(64) || TargetTriple.isRISCV() ||
TargetTriple.isARM() || TargetTriple.isThumb())
return;

// Ensure we lower KCFI operand bundles with -O0.
Expand Down
435 changes: 435 additions & 0 deletions llvm/lib/Target/ARM/ARMAsmPrinter.cpp

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions llvm/lib/Target/ARM/ARMAsmPrinter.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,20 @@ class LLVM_LIBRARY_VISIBILITY ARMAsmPrinter : public AsmPrinter {
void LowerPATCHABLE_FUNCTION_EXIT(const MachineInstr &MI);
void LowerPATCHABLE_TAIL_CALL(const MachineInstr &MI);

// KCFI check lowering
void LowerKCFI_CHECK(const MachineInstr &MI);

private:
void EmitSled(const MachineInstr &MI, SledKind Kind);

// KCFI check emission helpers
void EmitKCFI_CHECK_ARM32(Register AddrReg, int64_t Type,
const MachineInstr &Call, int64_t PrefixNops);
void EmitKCFI_CHECK_Thumb2(Register AddrReg, int64_t Type,
const MachineInstr &Call, int64_t PrefixNops);
void EmitKCFI_CHECK_Thumb1(Register AddrReg, int64_t Type,
const MachineInstr &Call, int64_t PrefixNops);

// Helpers for emitStartOfAsmFile() and emitEndOfAsmFile()
void emitAttributes();

Expand Down
2 changes: 2 additions & 0 deletions llvm/lib/Target/ARM/ARMExpandPseudoInsts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2301,6 +2301,8 @@ bool ARMExpandPseudo::ExpandMI(MachineBasicBlock &MBB,
for (unsigned i = 2, e = MBBI->getNumOperands(); i != e; ++i)
NewMI->addOperand(MBBI->getOperand(i));

NewMI->setCFIType(*MBB.getParent(), MI.getCFIType());

// Update call info and delete the pseudo instruction TCRETURN.
if (MI.isCandidateForAdditionalCallInfo())
MI.getMF()->moveAdditionalCallInfo(&MI, &*NewMI);
Expand Down
69 changes: 69 additions & 0 deletions llvm/lib/Target/ARM/ARMISelLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2848,13 +2848,17 @@ ARMTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
if (isTailCall) {
MF.getFrameInfo().setHasTailCall();
SDValue Ret = DAG.getNode(ARMISD::TC_RETURN, dl, MVT::Other, Ops);
if (CLI.CFIType)
Ret.getNode()->setCFIType(CLI.CFIType->getZExtValue());
DAG.addNoMergeSiteInfo(Ret.getNode(), CLI.NoMerge);
DAG.addCallSiteInfo(Ret.getNode(), std::move(CSInfo));
return Ret;
}

// Returns a chain and a flag for retval copy to use.
Chain = DAG.getNode(CallOpc, dl, {MVT::Other, MVT::Glue}, Ops);
if (CLI.CFIType)
Chain.getNode()->setCFIType(CLI.CFIType->getZExtValue());
DAG.addNoMergeSiteInfo(Chain.getNode(), CLI.NoMerge);
InGlue = Chain.getValue(1);
DAG.addCallSiteInfo(Chain.getNode(), std::move(CSInfo));
Expand Down Expand Up @@ -12007,6 +12011,71 @@ static void genTPLoopBody(MachineBasicBlock *TpLoopBody,
.add(predOps(ARMCC::AL));
}

bool ARMTargetLowering::supportKCFIBundles() const {
// KCFI is supported in all ARM/Thumb modes
return true;
}

MachineInstr *
ARMTargetLowering::EmitKCFICheck(MachineBasicBlock &MBB,
MachineBasicBlock::instr_iterator &MBBI,
const TargetInstrInfo *TII) const {
assert(MBBI->isCall() && MBBI->getCFIType() &&
"Invalid call instruction for a KCFI check");

MachineOperand *TargetOp = nullptr;
switch (MBBI->getOpcode()) {
// ARM mode opcodes
case ARM::BLX:
case ARM::BLX_pred:
case ARM::BLX_noip:
case ARM::BLX_pred_noip:
case ARM::BX_CALL:
TargetOp = &MBBI->getOperand(0);
break;
case ARM::TCRETURNri:
case ARM::TCRETURNrinotr12:
case ARM::TAILJMPr:
case ARM::TAILJMPr4:
TargetOp = &MBBI->getOperand(0);
break;
// Thumb mode opcodes (Thumb1 and Thumb2)
// Note: Most Thumb call instructions have predicate operands before the
// target register Format: tBLXr pred, predreg, target_register, ...
case ARM::tBLXr: // Thumb1/Thumb2: BLX register (requires V5T)
case ARM::tBLXr_noip: // Thumb1/Thumb2: BLX register, no IP clobber
case ARM::tBX_CALL: // Thumb1 only: BX call (push LR, BX)
TargetOp = &MBBI->getOperand(2);
break;
// Tail call instructions don't have predicates, target is operand 0
case ARM::tTAILJMPr: // Thumb1/Thumb2: Tail call via register
TargetOp = &MBBI->getOperand(0);
break;
default:
llvm_unreachable("Unexpected CFI call opcode");
}

assert(TargetOp && TargetOp->isReg() && "Invalid target operand");
TargetOp->setIsRenamable(false);

// Select the appropriate KCFI_CHECK variant based on the instruction set
unsigned KCFICheckOpcode;
if (Subtarget->isThumb()) {
if (Subtarget->isThumb2()) {
KCFICheckOpcode = ARM::KCFI_CHECK_Thumb2;
} else {
KCFICheckOpcode = ARM::KCFI_CHECK_Thumb1;
}
} else {
KCFICheckOpcode = ARM::KCFI_CHECK_ARM;
}

return BuildMI(MBB, MBBI, MBBI->getDebugLoc(), TII->get(KCFICheckOpcode))
.addReg(TargetOp->getReg())
.addImm(MBBI->getCFIType())
.getInstr();
}

MachineBasicBlock *
ARMTargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
MachineBasicBlock *BB) const {
Expand Down
6 changes: 6 additions & 0 deletions llvm/lib/Target/ARM/ARMISelLowering.h
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,12 @@ class VectorType;
void AdjustInstrPostInstrSelection(MachineInstr &MI,
SDNode *Node) const override;

bool supportKCFIBundles() const override;

MachineInstr *EmitKCFICheck(MachineBasicBlock &MBB,
MachineBasicBlock::instr_iterator &MBBI,
const TargetInstrInfo *TII) const override;

SDValue PerformCMOVCombine(SDNode *N, SelectionDAG &DAG) const;
SDValue PerformBRCONDCombine(SDNode *N, SelectionDAG &DAG) const;
SDValue PerformCMOVToBFICombine(SDNode *N, SelectionDAG &DAG) const;
Expand Down
30 changes: 30 additions & 0 deletions llvm/lib/Target/ARM/ARMInstrInfo.td
Original file line number Diff line number Diff line change
Expand Up @@ -6535,6 +6535,36 @@ def CMP_SWAP_64 : PseudoInst<(outs GPRPair:$Rd, GPRPair:$addr_temp_out),

def : Pat<(atomic_fence (timm), 0), (MEMBARRIER)>;

//===----------------------------------------------------------------------===//
// KCFI check pseudo-instruction.
//===----------------------------------------------------------------------===//
// KCFI_CHECK pseudo-instruction for Kernel Control-Flow Integrity.
// Expands to a sequence that verifies the function pointer's type hash.
// Different sizes for different architectures due to different expansions.

def KCFI_CHECK_ARM
: PseudoInst<(outs), (ins GPR:$ptr, i32imm:$type), NoItinerary, []>,
Sched<[]>,
Requires<[IsARM]> {
let Size = 28; // 7 instructions (bic, ldr, 4x eor, beq, udf)
}

def KCFI_CHECK_Thumb2
: PseudoInst<(outs), (ins GPR:$ptr, i32imm:$type), NoItinerary, []>,
Sched<[]>,
Requires<[IsThumb2]> {
let Size =
32; // worst-case 9 instructions (push, bic, ldr, 4x eor, pop, beq.w, udf)
}

def KCFI_CHECK_Thumb1
: PseudoInst<(outs), (ins GPR:$ptr, i32imm:$type), NoItinerary, []>,
Sched<[]>,
Requires<[IsThumb1Only]> {
let Size = 50; // worst-case 25 instructions (pushes, bic helper, type
// building, cmp, pops)
}

//===----------------------------------------------------------------------===//
// Instructions used for emitting unwind opcodes on Windows.
//===----------------------------------------------------------------------===//
Expand Down
12 changes: 10 additions & 2 deletions llvm/lib/Target/ARM/ARMTargetMachine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void LLVMInitializeARMTarget() {
initializeMVELaneInterleavingPass(Registry);
initializeARMFixCortexA57AES1742098Pass(Registry);
initializeARMDAGToDAGISelLegacyPass(Registry);
initializeKCFIPass(Registry);
}

static std::unique_ptr<TargetLoweringObjectFile> createTLOF(const Triple &TT) {
Expand Down Expand Up @@ -487,6 +488,9 @@ void ARMPassConfig::addPreSched2() {
// proper scheduling.
addPass(createARMExpandPseudoPass());

// Emit KCFI checks for indirect calls.
addPass(createKCFIPass());

if (getOptLevel() != CodeGenOptLevel::None) {
// When optimising for size, always run the Thumb2SizeReduction pass before
// IfConversion. Otherwise, check whether IT blocks are restricted
Expand Down Expand Up @@ -517,9 +521,12 @@ void ARMPassConfig::addPreSched2() {
void ARMPassConfig::addPreEmitPass() {
addPass(createThumb2SizeReductionPass());

// Constant island pass work on unbundled instructions.
// Unpack bundles for:
// - Thumb2: Constant island pass requires unbundled instructions
// - KCFI: KCFI_CHECK pseudo instructions need to be unbundled for AsmPrinter
addPass(createUnpackMachineBundles([](const MachineFunction &MF) {
return MF.getSubtarget<ARMSubtarget>().isThumb2();
return MF.getSubtarget<ARMSubtarget>().isThumb2() ||
MF.getFunction().getParent()->getModuleFlag("kcfi");
}));

// Don't optimize barriers or block placement at -O0.
Expand All @@ -530,6 +537,7 @@ void ARMPassConfig::addPreEmitPass() {
}

void ARMPassConfig::addPreEmitPass2() {

// Inserts fixup instructions before unsafe AES operations. Instructions may
// be inserted at the start of blocks and at within blocks so this pass has to
// come before those below.
Expand Down
1 change: 1 addition & 0 deletions llvm/test/CodeGen/ARM/O3-pipeline.ll
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@
; CHECK-NEXT: ARM Execution Domain Fix
; CHECK-NEXT: BreakFalseDeps
; CHECK-NEXT: ARM pseudo instruction expansion pass
; CHECK-NEXT: Insert KCFI indirect call checks
; CHECK-NEXT: Thumb2 instruction size reduce pass
; CHECK-NEXT: MachineDominator Tree Construction
; CHECK-NEXT: Machine Natural Loop Construction
Expand Down
138 changes: 138 additions & 0 deletions llvm/test/CodeGen/ARM/kcfi-arm.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
; RUN: llc -mtriple=armv7-linux-gnueabi -verify-machineinstrs < %s | FileCheck %s --check-prefix=ASM
; RUN: llc -mtriple=armv7-linux-gnueabi -verify-machineinstrs -stop-after=finalize-isel < %s | FileCheck %s --check-prefixes=MIR,ISEL
; RUN: llc -mtriple=armv7-linux-gnueabi -verify-machineinstrs -stop-after=kcfi < %s | FileCheck %s --check-prefixes=MIR,KCFI

; MIR checks for all functions (grouped here to prevent update_llc_test_checks.py from removing them)

; MIR-LABEL: name: f1
; MIR: body:

; ISEL: BLX %0, csr_aapcs,{{.*}} cfi-type 12345678

; KCFI: BUNDLE{{.*}} {
; KCFI-NEXT: KCFI_CHECK_ARM $r0, 12345678
; KCFI-NEXT: BLX killed $r0, csr_aapcs,{{.*}}
; KCFI-NEXT: }

; MIR-LABEL: name: f2
; MIR: body:

; ISEL: TCRETURNri %0, 0, csr_aapcs, implicit $sp, cfi-type 12345678

; KCFI: BUNDLE{{.*}} {
; KCFI-NEXT: KCFI_CHECK_ARM $r0, 12345678
; KCFI-NEXT: TAILJMPr killed $r0, csr_aapcs, implicit $sp, implicit $sp
; KCFI-NEXT: }

; ASM: .long 12345678
define void @f1(ptr noundef %x) !kcfi_type !1 {
; ASM-LABEL: f1:
; ASM: @ %bb.0:
; ASM-NEXT: .save {r11, lr}
; ASM-NEXT: push {r11, lr}
; ASM-NEXT: bic r12, r0, #1
; ASM-NEXT: ldr r12, [r12, #-4]
; ASM-NEXT: eor r12, r12, #78
; ASM-NEXT: eor r12, r12, #24832
; ASM-NEXT: eor r12, r12, #12320768
; ASM-NEXT: eors r12, r12, #0
; ASM-NEXT: beq .Ltmp0
; ASM-NEXT: udf #33760
; ASM-NEXT: .Ltmp0:
; ASM-NEXT: blx r0
; ASM-NEXT: pop {r11, pc}

call void %x() [ "kcfi"(i32 12345678) ]
ret void
}

; Test with tail call
define void @f2(ptr noundef %x) !kcfi_type !1 {
; ASM-LABEL: f2:
; ASM: @ %bb.0:
; ASM-NEXT: bic r12, r0, #1
; ASM-NEXT: ldr r12, [r12, #-4]
; ASM-NEXT: eor r12, r12, #78
; ASM-NEXT: eor r12, r12, #24832
; ASM-NEXT: eor r12, r12, #12320768
; ASM-NEXT: eors r12, r12, #0
; ASM-NEXT: beq .Ltmp1
; ASM-NEXT: udf #33760
; ASM-NEXT: .Ltmp1:
; ASM-NEXT: bx r0

tail call void %x() [ "kcfi"(i32 12345678) ]
ret void
}

; Test r3 spill/reload when target is r12 and r3 is a call argument.
; With 5+ arguments (target + 4 args), r0-r3 are all used for arguments,
; forcing r3 to be spilled when we need it as scratch register.
define void @f3_r3_spill(ptr noundef %target, i32 %a, i32 %b, i32 %c, i32 %d) !kcfi_type !1 {
; ASM-LABEL: f3_r3_spill:
; ASM: @ %bb.0:
; ASM-NEXT: .save {r11, lr}
; ASM-NEXT: push {r11, lr}
; ASM-NEXT: mov lr, r3
; ASM-NEXT: ldr r3, [sp, #8]
; ASM-NEXT: mov r12, r0
; ASM-NEXT: mov r0, r1
; ASM-NEXT: mov r1, r2
; ASM-NEXT: mov r2, lr
; ASM-NEXT: stmdb sp!, {r3}
; ASM-NEXT: bic r3, r12, #1
; ASM-NEXT: ldr r3, [r3, #-4]
; ASM-NEXT: eor r3, r3, #78
; ASM-NEXT: eor r3, r3, #24832
; ASM-NEXT: eor r3, r3, #12320768
; ASM-NEXT: eors r3, r3, #0
; ASM-NEXT: ldm sp!, {r3}
; ASM-NEXT: beq .Ltmp2
; ASM-NEXT: udf #33772
; ASM-NEXT: .Ltmp2:
; ASM-NEXT: blx r12
; ASM-NEXT: pop {r11, pc}
; Arguments: r0=%target, r1=%a, r2=%b, r3=%c, [sp]=%d
; Call needs: r0=%a, r1=%b, r2=%c, r3=%d, target in r12
; Compiler shuffles arguments into place, saving r3 (c) in lr, loading d from stack
; r3 is live as 4th argument, so push it before KCFI check
; Restore r3 immediately after comparison, before branch
call void %target(i32 %a, i32 %b, i32 %c, i32 %d) [ "kcfi"(i32 12345678) ]
ret void
}

; Test with 3 arguments - r3 not live, target in r12, so r3 used as scratch without spilling
define void @f4_r3_unused(ptr noundef %target, i32 %a, i32 %b) !kcfi_type !1 {
; ASM-LABEL: f4_r3_unused:
; ASM: @ %bb.0:
; ASM-NEXT: .save {r11, lr}
; ASM-NEXT: push {r11, lr}
; ASM-NEXT: mov r3, r0
; ASM-NEXT: mov r0, r1
; ASM-NEXT: mov r1, r2
; ASM-NEXT: bic r12, r3, #1
; ASM-NEXT: ldr r12, [r12, #-4]
; ASM-NEXT: eor r12, r12, #78
; ASM-NEXT: eor r12, r12, #24832
; ASM-NEXT: eor r12, r12, #12320768
; ASM-NEXT: eors r12, r12, #0
; ASM-NEXT: beq .Ltmp3
; ASM-NEXT: udf #33763
; ASM-NEXT: .Ltmp3:
; ASM-NEXT: blx r3
; ASM-NEXT: pop {r11, pc}
; Only 3 arguments total, so r3 is not used as call argument
; Compiler puts target→r3, a→r0, b→r1
; r3 is the target, so we use r12 as scratch (no spill needed)
call void %target(i32 %a, i32 %b) [ "kcfi"(i32 12345678) ]
ret void
}

!llvm.module.flags = !{!0}
!0 = !{i32 4, !"kcfi", i32 1}
!1 = !{i32 12345678}
;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
; ISEL: {{.*}}
; KCFI: {{.*}}
; MIR: {{.*}}
Loading