From 0fbb17458a01a6b388fc67661ffb92969503e977 Mon Sep 17 00:00:00 2001 From: Ties Stuij Date: Mon, 6 Dec 2021 11:00:10 +0000 Subject: [PATCH] [ARM] Implement setjmp BTI placement for PACBTI-M This patch intends to guard indirect branches performed by longjmp by inserting BTI instructions after calls to setjmp. Calls with 'returns-twice' are lowered to a new pseudo-instruction named t2CALL_BTI that is later expanded to a bundle of {tBL,t2BTI}. This patch is part of a series that adds support for the PACBTI-M extension of the Armv8.1-M architecture, as detailed here: https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/armv8-1-m-pointer-authentication-and-branch-target-identification-extension The PACBTI-M specification can be found in the Armv8-M Architecture Reference Manual: https://developer.arm.com/documentation/ddi0553/latest The following people contributed to this patch: - Alexandros Lamprineas - Ties Stuij Reviewed By: labrinea Differential Revision: https://reviews.llvm.org/D112427 --- clang/docs/ClangCommandLineReference.rst | 5 ++ clang/include/clang/Driver/Options.td | 5 ++ clang/lib/Driver/ToolChains/Arch/ARM.cpp | 2 + clang/test/Driver/arm-bti-return-twice.c | 7 +++ llvm/lib/Target/ARM/ARM.td | 5 ++ llvm/lib/Target/ARM/ARMExpandPseudoInsts.cpp | 16 +++++ llvm/lib/Target/ARM/ARMISelLowering.cpp | 11 +++- llvm/lib/Target/ARM/ARMISelLowering.h | 1 + llvm/lib/Target/ARM/ARMInstrThumb2.td | 7 +++ llvm/lib/Target/ARM/ARMSubtarget.h | 6 ++ llvm/test/CodeGen/ARM/setjmp-bti-basic.ll | 50 +++++++++++++++ llvm/test/CodeGen/ARM/setjmp-bti-outliner.ll | 92 ++++++++++++++++++++++++++++ 12 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 clang/test/Driver/arm-bti-return-twice.c create mode 100644 llvm/test/CodeGen/ARM/setjmp-bti-basic.ll create mode 100644 llvm/test/CodeGen/ARM/setjmp-bti-outliner.ll diff --git a/clang/docs/ClangCommandLineReference.rst b/clang/docs/ClangCommandLineReference.rst index 8d4ffa6..9780700 100644 --- a/clang/docs/ClangCommandLineReference.rst +++ b/clang/docs/ClangCommandLineReference.rst @@ -3260,6 +3260,11 @@ Thread pointer access method (AArch32/AArch64 only) Allow memory accesses to be unaligned (AArch32/AArch64 only) +.. option:: -mno-bti-at-return-twice + +Do not add a BTI instruction after a setjmp or other return-twice construct (Arm +only) + Hexagon ------- .. option:: -mieee-rnd-near diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 4e6dd20..7c257e4 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -3338,6 +3338,11 @@ def mno_fix_cortex_a53_835769 : Flag<["-"], "mno-fix-cortex-a53-835769">, def mmark_bti_property : Flag<["-"], "mmark-bti-property">, Group, HelpText<"Add .note.gnu.property with BTI to assembly files (AArch64 only)">; +def mno_bti_at_return_twice : Flag<["-"], "mno-bti-at-return-twice">, + Group, + HelpText<"Do not add a BTI instruction after a setjmp or other" + " return-twice construct (Arm only)">; + foreach i = {1-31} in def ffixed_x#i : Flag<["-"], "ffixed-x"#i>, Group, HelpText<"Reserve the x"#i#" register (AArch64/RISC-V only)">; diff --git a/clang/lib/Driver/ToolChains/Arch/ARM.cpp b/clang/lib/Driver/ToolChains/Arch/ARM.cpp index 8d5c64d..e03bed0a 100644 --- a/clang/lib/Driver/ToolChains/Arch/ARM.cpp +++ b/clang/lib/Driver/ToolChains/Arch/ARM.cpp @@ -875,6 +875,8 @@ fp16_fml_fallthrough: } } + if (Args.getLastArg(options::OPT_mno_bti_at_return_twice)) + Features.push_back("+no-bti-at-return-twice"); } std::string arm::getARMArch(StringRef Arch, const llvm::Triple &Triple) { diff --git a/clang/test/Driver/arm-bti-return-twice.c b/clang/test/Driver/arm-bti-return-twice.c new file mode 100644 index 0000000..c5cd385 --- /dev/null +++ b/clang/test/Driver/arm-bti-return-twice.c @@ -0,0 +1,7 @@ +// RUN: %clang -target arm-arm-none-eabi -march=armv8-m.main -mbranch-protection=bti \ +// RUN: -mno-bti-at-return-twice -### %s 2>&1 | FileCheck %s --check-prefix=FEAT +// RUN: %clang -target arm-arm-none-eabi -march=armv8-m.main -mbranch-protection=bti \ +// RUN: -### %s 2>&1 | FileCheck %s --check-prefix=NOFEAT + +// FEAT: "+no-bti-at-return-twice" +// NOFEAT-NOT: "+no-bti-at-return-twice" diff --git a/llvm/lib/Target/ARM/ARM.td b/llvm/lib/Target/ARM/ARM.td index e03dd59..8173fe4 100644 --- a/llvm/lib/Target/ARM/ARM.td +++ b/llvm/lib/Target/ARM/ARM.td @@ -446,6 +446,11 @@ def FeaturePACBTI : SubtargetFeature<"pacbti", "HasPACBTI", "true", "Enable Pointer Authentication and Branch " "Target Identification">; +def FeatureNoBTIAtReturnTwice : SubtargetFeature<"no-bti-at-return-twice", + "NoBTIAtReturnTwice", "true", + "Don't place a BTI instruction " + "after a return-twice">; + //===----------------------------------------------------------------------===// // ARM architecture class // diff --git a/llvm/lib/Target/ARM/ARMExpandPseudoInsts.cpp b/llvm/lib/Target/ARM/ARMExpandPseudoInsts.cpp index 7a35f25..d6b1444 100644 --- a/llvm/lib/Target/ARM/ARMExpandPseudoInsts.cpp +++ b/llvm/lib/Target/ARM/ARMExpandPseudoInsts.cpp @@ -3073,6 +3073,22 @@ bool ARMExpandPseudo::ExpandMI(MachineBasicBlock &MBB, MI.eraseFromParent(); return true; } + case ARM::t2CALL_BTI: { + MachineFunction &MF = *MI.getMF(); + MachineInstrBuilder MIB = + BuildMI(MF, MI.getDebugLoc(), TII->get(ARM::tBL)); + MIB.cloneMemRefs(MI); + for (unsigned i = 0; i < MI.getNumOperands(); ++i) + MIB.add(MI.getOperand(i)); + if (MI.isCandidateForCallSiteEntry()) + MF.moveCallSiteInfo(&MI, MIB.getInstr()); + MIBundleBuilder Bundler(MBB, MI); + Bundler.append(MIB); + Bundler.append(BuildMI(MF, MI.getDebugLoc(), TII->get(ARM::t2BTI))); + finalizeBundle(MBB, Bundler.begin(), Bundler.end()); + MI.eraseFromParent(); + return true; + } case ARM::LOADDUAL: case ARM::STOREDUAL: { Register PairReg = MI.getOperand(0).getReg(); diff --git a/llvm/lib/Target/ARM/ARMISelLowering.cpp b/llvm/lib/Target/ARM/ARMISelLowering.cpp index 9a4e642..add5b1c 100644 --- a/llvm/lib/Target/ARM/ARMISelLowering.cpp +++ b/llvm/lib/Target/ARM/ARMISelLowering.cpp @@ -1658,6 +1658,7 @@ const char *ARMTargetLowering::getTargetNodeName(unsigned Opcode) const { MAKE_CASE(ARMISD::CALL_PRED) MAKE_CASE(ARMISD::CALL_NOLINK) MAKE_CASE(ARMISD::tSECALL) + MAKE_CASE(ARMISD::t2CALL_BTI) MAKE_CASE(ARMISD::BRCOND) MAKE_CASE(ARMISD::BR_JT) MAKE_CASE(ARMISD::BR2_JT) @@ -2321,6 +2322,12 @@ ARMTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI, bool isCmseNSCall = false; bool isSibCall = false; bool PreferIndirect = false; + bool GuardWithBTI = false; + + // Lower 'returns_twice' calls to a pseudo-instruction. + if (CLI.CB && CLI.CB->getAttributes().hasFnAttr(Attribute::ReturnsTwice) && + !Subtarget->getNoBTIAtReturnTwice()) + GuardWithBTI = AFI->branchTargetEnforcement(); // Determine whether this is a non-secure function call. if (CLI.CB && CLI.CB->getAttributes().hasFnAttr("cmse_nonsecure_call")) @@ -2726,7 +2733,9 @@ ARMTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI, // FIXME: handle tail calls differently. unsigned CallOpc; if (Subtarget->isThumb()) { - if (isCmseNSCall) + if (GuardWithBTI) + CallOpc = ARMISD::t2CALL_BTI; + else if (isCmseNSCall) CallOpc = ARMISD::tSECALL; else if ((!isDirect || isARMFunc) && !Subtarget->hasV5TOps()) CallOpc = ARMISD::CALL_NOLINK; diff --git a/llvm/lib/Target/ARM/ARMISelLowering.h b/llvm/lib/Target/ARM/ARMISelLowering.h index e3b4223..1c5f838 100644 --- a/llvm/lib/Target/ARM/ARMISelLowering.h +++ b/llvm/lib/Target/ARM/ARMISelLowering.h @@ -69,6 +69,7 @@ class VectorType; CALL_PRED, // Function call that's predicable. CALL_NOLINK, // Function call with branch not branch-and-link. tSECALL, // CMSE non-secure function call. + t2CALL_BTI, // Thumb function call followed by BTI instruction. BRCOND, // Conditional branch. BR_JT, // Jumptable branch. BR2_JT, // Jumptable branch (2 level - jumptable entry is a jump). diff --git a/llvm/lib/Target/ARM/ARMInstrThumb2.td b/llvm/lib/Target/ARM/ARMInstrThumb2.td index 4471317..6e8e61c 100644 --- a/llvm/lib/Target/ARM/ARMInstrThumb2.td +++ b/llvm/lib/Target/ARM/ARMInstrThumb2.td @@ -5736,3 +5736,10 @@ def t2BTI : PACBTIHintSpaceNoOpsInst<"bti", 0b00001111>; def t2AUT : PACBTIHintSpaceUseInst<"aut", 0b00101101> { let hasSideEffects = 1; } + +def ARMt2CallBTI : SDNode<"ARMISD::t2CALL_BTI", SDT_ARMcall, + [SDNPHasChain, SDNPOptInGlue, SDNPOutGlue, SDNPVariadic]>; + +def t2CALL_BTI : PseudoInst<(outs), (ins pred:$p, thumb_bl_target:$func), + IIC_Br, [(ARMt2CallBTI tglobaladdr:$func)]>, + Requires<[IsThumb2]>, Sched<[WriteBrL]>; diff --git a/llvm/lib/Target/ARM/ARMSubtarget.h b/llvm/lib/Target/ARM/ARMSubtarget.h index d51a888..cb46a6b7 100644 --- a/llvm/lib/Target/ARM/ARMSubtarget.h +++ b/llvm/lib/Target/ARM/ARMSubtarget.h @@ -534,6 +534,10 @@ protected: /// Selected instruction itineraries (one entry per itinerary class.) InstrItineraryData InstrItins; + /// NoBTIAtReturnTwice - Don't place a BTI instruction after + /// return-twice constructs (setjmp) + bool NoBTIAtReturnTwice = false; + /// Options passed via command line that could influence the target const TargetOptions &Options; @@ -948,6 +952,8 @@ public: bool hardenSlsRetBr() const { return HardenSlsRetBr; } bool hardenSlsBlr() const { return HardenSlsBlr; } bool hardenSlsNoComdat() const { return HardenSlsNoComdat; } + + bool getNoBTIAtReturnTwice() const { return NoBTIAtReturnTwice; } }; } // end namespace llvm diff --git a/llvm/test/CodeGen/ARM/setjmp-bti-basic.ll b/llvm/test/CodeGen/ARM/setjmp-bti-basic.ll new file mode 100644 index 0000000..e18f87d --- /dev/null +++ b/llvm/test/CodeGen/ARM/setjmp-bti-basic.ll @@ -0,0 +1,50 @@ +; RUN: llc -mtriple=thumbv8.1m.main-arm-none-eabi < %s | FileCheck %s --check-prefix=BTI +; RUN: llc -mtriple=thumbv8.1m.main-arm-none-eabi -mattr=+no-bti-at-return-twice < %s | \ +; RUN: FileCheck %s --check-prefix=NOBTI + +; C source +; -------- +; jmp_buf buf; +; +; extern void bar(int x); +; +; int foo(int x) { +; if (setjmp(buf)) +; x = 0; +; else +; bar(x); +; return x; +; } + +@buf = global [20 x i64] zeroinitializer, align 8 + +define i32 @foo(i32 %x) { +; BTI-LABEL: foo: +; BTI: bl setjmp +; BTI-NEXT: bti +; NOBTI-LABEL: foo: +; NOBTI: bl setjmp +; NOBTI-NOT: bti + +entry: + %call = call i32 @setjmp(i64* getelementptr inbounds ([20 x i64], [20 x i64]* @buf, i32 0, i32 0)) #0 + %tobool.not = icmp eq i32 %call, 0 + br i1 %tobool.not, label %if.else, label %if.end + +if.else: ; preds = %entry + call void @bar(i32 %x) + br label %if.end + +if.end: ; preds = %entry, %if.else + %x.addr.0 = phi i32 [ %x, %if.else ], [ 0, %entry ] + ret i32 %x.addr.0 +} + +declare void @bar(i32) +declare i32 @setjmp(i64*) #0 + +attributes #0 = { returns_twice } + +!llvm.module.flags = !{!0} + +!0 = !{i32 1, !"branch-target-enforcement", i32 1} diff --git a/llvm/test/CodeGen/ARM/setjmp-bti-outliner.ll b/llvm/test/CodeGen/ARM/setjmp-bti-outliner.ll new file mode 100644 index 0000000..20fd176 --- /dev/null +++ b/llvm/test/CodeGen/ARM/setjmp-bti-outliner.ll @@ -0,0 +1,92 @@ +; RUN: llc -mtriple=thumbv8.1m.main-arm-none-eabi -enable-machine-outliner < %s | \ +; RUN: FileCheck %s --check-prefix=BTI +; RUN: llc -mtriple=thumbv8.1m.main-arm-none-eabi -enable-machine-outliner -mattr=+no-bti-at-return-twice < %s | FileCheck %s --check-prefix=NOBTI + +; C source +; -------- +; jmp_buf buf; +; +; extern void h(int a, int b, int *c); +; +; int f(int a, int b, int c, int d) { +; if (setjmp(buf) != 0) +; return -1; +; h(a, b, &a); +; return 2 + a * (a + b) / (c + d); +; } +; +; int g(int a, int b, int c, int d) { +; if (setjmp(buf) != 0) +; return -1; +; h(a, b, &a); +; return 1 + a * (a + b) / (c + d); +; } + +@buf = global [20 x i64] zeroinitializer, align 8 + +define i32 @f(i32 %a, i32 %b, i32 %c, i32 %d) { +; BTI-LABEL: f: +; BTI: bl OUTLINED_FUNCTION_0 +; BTI-NEXT: bti +; NOBTI-LABEL: f: +; NOBTI: bl OUTLINED_FUNCTION_0 +; NOBTI-NEXT: cbz r0, .LBB0_2 +entry: + %a.addr = alloca i32, align 4 + store i32 %a, i32* %a.addr, align 4 + %call = call i32 @setjmp(i64* getelementptr inbounds ([20 x i64], [20 x i64]* @buf, i32 0, i32 0)) #0 + %cmp.not = icmp eq i32 %call, 0 + br i1 %cmp.not, label %if.end, label %return + +if.end: ; preds = %entry + call void @h(i32 %a, i32 %b, i32* nonnull %a.addr) + %0 = load i32, i32* %a.addr, align 4 + %add = add nsw i32 %0, %b + %mul = mul nsw i32 %add, %0 + %add1 = add nsw i32 %d, %c + %div = sdiv i32 %mul, %add1 + %add2 = add nsw i32 %div, 2 + br label %return + +return: ; preds = %entry, %if.end + %retval.0 = phi i32 [ %add2, %if.end ], [ -1, %entry ] + ret i32 %retval.0 +} + +define i32 @g(i32 %a, i32 %b, i32 %c, i32 %d) { +; BTI-LABEL: g: +; BTI: bl OUTLINED_FUNCTION_0 +; BTI-NEXT: bti +; NOBTI-LABEL: g: +; NOBTI: bl OUTLINED_FUNCTION_0 +; NOBTI-NEXT: cbz r0, .LBB1_2 +entry: + %a.addr = alloca i32, align 4 + store i32 %a, i32* %a.addr, align 4 + %call = call i32 @setjmp(i64* getelementptr inbounds ([20 x i64], [20 x i64]* @buf, i32 0, i32 0)) #0 + %cmp.not = icmp eq i32 %call, 0 + br i1 %cmp.not, label %if.end, label %return + +if.end: ; preds = %entry + call void @h(i32 %a, i32 %b, i32* nonnull %a.addr) + %0 = load i32, i32* %a.addr, align 4 + %add = add nsw i32 %0, %b + %mul = mul nsw i32 %add, %0 + %add1 = add nsw i32 %d, %c + %div = sdiv i32 %mul, %add1 + %add2 = add nsw i32 %div, 1 + br label %return + +return: ; preds = %entry, %if.end + %retval.0 = phi i32 [ %add2, %if.end ], [ -1, %entry ] + ret i32 %retval.0 +} + +declare void @h(i32, i32, i32*) +declare i32 @setjmp(i64*) #0 + +attributes #0 = { returns_twice } + +!llvm.module.flags = !{!0} + +!0 = !{i32 1, !"branch-target-enforcement", i32 1} -- 2.7.4