[Hexagon] Add ShadowCallStack support#200508
Conversation
Implement the software ShadowCallStack for Hexagon. On Hexagon, r19 is used as the shadow stack pointer (reserved via -ffixed-r19). On function entry the LR (r31) is saved to the shadow stack and the pointer is advanced; on exit the LR is restored from the shadow stack before returning. Prologue sequence: r19 = add(r19, llvm#4) memw(r19+#-4) = r31 Epilogue sequence (between deallocframe/jumpr r31): r31 = memw(r19+#-4) r19 = add(r19, #-4) SCS is only emitted when hasFP() is true: the function uses allocframe. Leaf functions that never touch the stack do not need protection. When SCS is active the combined L4_return (deallocframe+jump) is not used; instead L2_deallocframe and PS_jmpret are kept separate so the shadow-stack restore can be inserted between them.
|
@llvm/pr-subscribers-backend-hexagon Author: Brian Cain (androm3da) ChangesImplement the software ShadowCallStack for Hexagon. On Hexagon, r19 is used as the shadow stack pointer (reserved via -ffixed-r19). On function entry the LR (r31) is saved to the shadow stack and the pointer is advanced; on exit the LR is restored from the shadow stack before returning. Prologue sequence: Epilogue sequence (between deallocframe/jumpr r31): SCS is only emitted when hasFP() is true: the function uses allocframe. Leaf functions that never touch the stack do not need protection. When SCS is active the combined L4_return (deallocframe+jump) is not used; instead L2_deallocframe and PS_jmpret are kept separate so the shadow-stack restore can be inserted between them. Patch is 23.88 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/200508.diff 9 Files Affected:
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index 31660dd29407c..c5c5b305eafef 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -784,6 +784,14 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
<< "-ffixed-x18";
}
+ if ((Kinds & SanitizerKind::ShadowCallStack) &&
+ TC.getTriple().getArch() == llvm::Triple::hexagon &&
+ !Args.hasArg(options::OPT_ffixed_r19) && DiagnoseErrors) {
+ D.Diag(diag::err_drv_argument_only_allowed_with)
+ << lastArgumentForMask(D, Args, Kinds & SanitizerKind::ShadowCallStack)
+ << "-ffixed-r19";
+ }
+
// Report error if there are non-trapping sanitizers that require
// c++abi-specific parts of UBSan runtime, and they are not provided by the
// toolchain. We don't have a good way to check the latter, so we just
diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index c4296c25bb9de..53a5bad03a151 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -991,6 +991,8 @@ Linux::getSupportedSanitizers(StringRef BoundArch,
if (IsX86_64 || IsAArch64 || IsRISCV64) {
Res |= SanitizerKind::HWAddress;
}
+ if (IsHexagon)
+ Res |= SanitizerKind::ShadowCallStack;
if (IsX86_64 || IsAArch64) {
Res |= SanitizerKind::KernelHWAddress;
}
diff --git a/clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c b/clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c
new file mode 100644
index 0000000000000..dcb320f041168
--- /dev/null
+++ b/clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c
@@ -0,0 +1,23 @@
+// Test that -fsanitize=shadow-call-stack on Hexagon requires -ffixed-r19.
+
+// RUN: not %clang --target=hexagon-unknown-linux-musl \
+// RUN: -fsanitize=shadow-call-stack %s -### 2>&1 \
+// RUN: | FileCheck %s --check-prefix=HEXAGON-SCS-NO-R19
+
+// RUN: %clang --target=hexagon-unknown-linux-musl \
+// RUN: -fsanitize=shadow-call-stack -ffixed-r19 %s -### 2>&1 \
+// RUN: | FileCheck %s --check-prefix=HEXAGON-SCS-WITH-R19
+
+// HEXAGON-SCS-NO-R19: error: invalid argument '-fsanitize=shadow-call-stack' only allowed with '-ffixed-r19'
+// HEXAGON-SCS-WITH-R19: "-fsanitize=shadow-call-stack"
+
+// RUN: %clang --target=hexagon-unknown-linux-musl \
+// RUN: -fsanitize=shadow-call-stack -fno-sanitize=shadow-call-stack %s -### 2>&1 \
+// RUN: | FileCheck %s --check-prefix=HEXAGON-SCS-DISABLED
+// HEXAGON-SCS-DISABLED-NOT: error:
+// HEXAGON-SCS-DISABLED-NOT: "-fsanitize=shadow-call-stack"
+
+// RUN: not %clang --target=hexagon-unknown-elf \
+// RUN: -fsanitize=shadow-call-stack %s -### 2>&1 \
+// RUN: | FileCheck %s --check-prefix=HEXAGON-SCS-ELF-NO-R19
+// HEXAGON-SCS-ELF-NO-R19: error: invalid argument '-fsanitize=shadow-call-stack' only allowed with '-ffixed-r19'
diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
index 0cc4165c8dbd5..2f2173012564d 100644
--- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
+++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
@@ -124,7 +124,7 @@ set(ALL_XRAY_SUPPORTED_ARCH ${X86_64} ${ARM32} ${ARM64} ${MIPS32} ${MIPS64}
powerpc64le ${HEXAGON} ${LOONGARCH64} ${RISCV32} ${RISCV64} ${S390X})
endif()
set(ALL_XRAY_DSO_SUPPORTED_ARCH ${X86_64} ${ARM64})
-set(ALL_SHADOWCALLSTACK_SUPPORTED_ARCH ${ARM64})
+set(ALL_SHADOWCALLSTACK_SUPPORTED_ARCH ${ARM64} ${HEXAGON})
if (UNIX)
if (OS_NAME MATCHES "Linux")
diff --git a/compiler-rt/test/shadowcallstack/libc_support.h b/compiler-rt/test/shadowcallstack/libc_support.h
index 9c8e0566380bc..00a3a38a8780b 100644
--- a/compiler-rt/test/shadowcallstack/libc_support.h
+++ b/compiler-rt/test/shadowcallstack/libc_support.h
@@ -32,6 +32,32 @@ __attribute__((noinline)) void scs_fputs_stdout(const char *p) {
: "x0", "x1", "x2", "x8");
}
+#elif defined(__hexagon__)
+# ifndef __linux__
+# error Unsupported platform
+# endif
+
+size_t scs_strlen(const char *p) {
+ size_t retval = 0;
+ while (*p++)
+ retval++;
+ return retval;
+}
+
+__attribute__((noinline)) void scs_fputs_stdout(const char *p) {
+ size_t len = scs_strlen(p);
+ __asm__ __volatile__("{\n\t"
+ " r0 = #1\n\t" // fd = stdout
+ " r1 = %[buf]\n\t"
+ " r2 = %[count]\n\t"
+ " r6 = #64\n\t" // __NR_write
+ "}\n\t"
+ "trap0(#1)\n\t"
+ :
+ : [buf] "r"(p), [count] "r"(len)
+ : "r0", "r1", "r2", "r6", "memory");
+}
+
#else
#error Unsupported platform
#endif
diff --git a/compiler-rt/test/shadowcallstack/lit.cfg.py b/compiler-rt/test/shadowcallstack/lit.cfg.py
index 5b95deb1b0986..81151b0b7cbbc 100644
--- a/compiler-rt/test/shadowcallstack/lit.cfg.py
+++ b/compiler-rt/test/shadowcallstack/lit.cfg.py
@@ -25,6 +25,8 @@
scs_arch_cflags = config.target_cflags
if config.target_arch == "aarch64":
scs_arch_cflags += " -ffixed-x18 "
+elif config.target_arch == "hexagon":
+ scs_arch_cflags += " -ffixed-r19 "
config.substitutions.append(
(
"%clang_scs ",
@@ -32,5 +34,9 @@
)
)
-if config.target_os not in ["Linux"] or config.target_arch not in ["aarch64", "riscv64"]:
+if config.target_os not in ["Linux"] or config.target_arch not in [
+ "aarch64",
+ "hexagon",
+ "riscv64",
+]:
config.unsupported = True
diff --git a/compiler-rt/test/shadowcallstack/minimal_runtime.h b/compiler-rt/test/shadowcallstack/minimal_runtime.h
index fab4fdf8006f7..09a6bcf1efa57 100644
--- a/compiler-rt/test/shadowcallstack/minimal_runtime.h
+++ b/compiler-rt/test/shadowcallstack/minimal_runtime.h
@@ -19,6 +19,8 @@ static void __shadowcallstack_init() {
#if defined(__aarch64__)
__asm__ __volatile__("mov x18, %0" ::"r"(stack));
+#elif defined(__hexagon__)
+ __asm__ __volatile__("r19 = %0" ::"r"(stack));
#else
#error Unsupported platform
#endif
diff --git a/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp b/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp
index 0f73efc243986..e60f911f2a3c6 100644
--- a/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp
@@ -21,6 +21,8 @@
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
+#include "llvm/BinaryFormat/Dwarf.h"
+#include "llvm/CodeGen/CFIInstBuilder.h"
#include "llvm/CodeGen/LivePhysRegs.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineDominators.h"
@@ -148,6 +150,94 @@
using namespace llvm;
+static void emitSCSPrologue(MachineFunction &MF, MachineBasicBlock &MBB,
+ MachineBasicBlock::iterator MI,
+ const DebugLoc &DL) {
+ if (!MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack))
+ return;
+
+ const auto &HST = MF.getSubtarget<HexagonSubtarget>();
+ // Hexagon saves LR (R31) via allocframe. If there is no frame, LR is
+ // not on the regular stack and does not need shadow-stack protection.
+ if (!HST.getFrameLowering()->hasFP(MF))
+ return;
+
+ Register SCSPReg = Hexagon::R19;
+ if (!MF.getSubtarget().isRegisterReservedByUser(SCSPReg))
+ report_fatal_error("Must reserve r19 to use shadow call stack on Hexagon");
+
+ const auto &HII = *HST.getInstrInfo();
+
+ // The prologue is emitted once but the epilogue runs in each return block.
+ // Paths that exit via a noreturn call (PS_call_nr) or llvm.eh.return have no
+ // matching epilogue and leak one shadow-stack slot per such exit
+ // For EH return the unwinder restores r19 from the CFI val_expression below.
+
+ // r19 = add(r19, #4)
+ BuildMI(MBB, MI, DL, HII.get(Hexagon::A2_addi), SCSPReg)
+ .addReg(SCSPReg)
+ .addImm(4)
+ .setMIFlag(MachineInstr::FrameSetup);
+ // memw(r19 + #-4) = r31
+ BuildMI(MBB, MI, DL, HII.get(Hexagon::S2_storeri_io))
+ .addReg(SCSPReg)
+ .addImm(-4)
+ .addReg(Hexagon::R31)
+ .setMIFlag(MachineInstr::FrameSetup);
+
+ MBB.addLiveIn(SCSPReg);
+
+ if (!MF.needsFrameMoves())
+ return;
+
+ // CFI: DW_CFA_val_expression for the SCS register, DW_OP_bregN -4
+ // Tells the unwinder that the SCS register at entry = current value - 4.
+ const auto &TRI = *MF.getSubtarget().getRegisterInfo();
+ unsigned DwarfSCSReg = TRI.getDwarfRegNum(SCSPReg, /*IsEH=*/true);
+ // DW_OP_breg0..DW_OP_breg31 (0x70..0x8f) are 32 opcodes indexed by
+ // register number, so the register number must fit in [0, 31].
+ assert(DwarfSCSReg < 32 && "SCS register should be < 32");
+ const char CFIInst[] = {
+ (char)dwarf::DW_CFA_val_expression,
+ (char)DwarfSCSReg,
+ 2, // expression length
+ (char)(unsigned)(dwarf::DW_OP_breg0 + DwarfSCSReg),
+ (char)(-4 & 0x7f), // SLEB128 -4
+ };
+ CFIInstBuilder(MBB, MI, MachineInstr::FrameSetup)
+ .buildEscape(StringRef(CFIInst, sizeof(CFIInst)));
+}
+
+static void emitSCSEpilogue(MachineFunction &MF, MachineBasicBlock &MBB,
+ MachineBasicBlock::iterator MI,
+ const DebugLoc &DL) {
+ if (!MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack))
+ return;
+
+ // hasFP() is true at both call sites: the non-vararg path in
+ // insertEpilogueInBlock returns early when !hasFP(), and the vararg+musl
+ // path is inside the hasFP() branch.
+ assert(MF.getSubtarget<HexagonSubtarget>().getFrameLowering()->hasFP(MF) &&
+ "SCS epilogue requires a frame");
+
+ Register SCSPReg = Hexagon::R19;
+ const auto &HII = *MF.getSubtarget<HexagonSubtarget>().getInstrInfo();
+
+ // r31 = memw(r19 + #-4)
+ BuildMI(MBB, MI, DL, HII.get(Hexagon::L2_loadri_io), Hexagon::R31)
+ .addReg(SCSPReg)
+ .addImm(-4)
+ .setMIFlag(MachineInstr::FrameDestroy);
+ // r19 = add(r19, #-4)
+ BuildMI(MBB, MI, DL, HII.get(Hexagon::A2_addi), SCSPReg)
+ .addReg(SCSPReg)
+ .addImm(-4)
+ .setMIFlag(MachineInstr::FrameDestroy);
+
+ if (MF.needsFrameMoves())
+ CFIInstBuilder(MBB, MI, MachineInstr::FrameDestroy).buildRestore(SCSPReg);
+}
+
static cl::opt<bool> DisableDeallocRet("disable-hexagon-dealloc-ret",
cl::Hidden, cl::desc("Disable Dealloc Return for Hexagon target"));
@@ -511,6 +601,19 @@ void HexagonFrameLowering::emitPrologue(MachineFunction &MF,
bool PrologueStubs = false;
insertCSRSpillsInBlock(*PrologB, CSI, HRI, PrologueStubs);
insertPrologueInBlock(*PrologB, PrologueStubs);
+ // Insert the SCS prologue after all FrameSetup instructions so that it
+ // follows allocframe and any CSR spills in the instruction stream. The
+ // packetizer may still fuse the SCS store with the first call in the
+ // function, but because Hexagon packets use old-value reads the original
+ // R31 is always what is stored.
+ {
+ MachineBasicBlock::iterator AfterProlog = PrologB->begin();
+ while (AfterProlog != PrologB->end() &&
+ AfterProlog->getFlag(MachineInstr::FrameSetup))
+ ++AfterProlog;
+ DebugLoc PrologDL = PrologB->findDebugLoc(AfterProlog);
+ emitSCSPrologue(MF, *PrologB, AfterProlog, PrologDL);
+ }
updateEntryPaths(MF, *PrologB);
if (EpilogB) {
@@ -786,6 +889,8 @@ void HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const {
// Handle EH_RETURN.
if (RetOpc == Hexagon::EH_RETURN_JMPR) {
+ // EH paths overwrite R31 with a handler address; the shadow stack is
+ // not read on this path, so no SCS epilogue is needed.
BuildMI(MBB, InsertPt, dl, HII.get(Hexagon::L2_deallocframe))
.addDef(Hexagon::D15)
.addReg(Hexagon::R30);
@@ -797,6 +902,10 @@ void HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const {
// Check for RESTORE_DEALLOC_RET* tail call. Don't emit an extra dealloc-
// frame instruction if we encounter it.
+ // These spill-stub tail calls include r19 in their save range, but SCS
+ // requires -ffixed-r19, which prevents the allocator from selecting stubs
+ // that cover r19. The two features are therefore mutually exclusive and no
+ // SCS epilogue is needed here.
if (RetOpc == Hexagon::RESTORE_DEALLOC_RET_JMP_V4 ||
RetOpc == Hexagon::RESTORE_DEALLOC_RET_JMP_V4_PIC ||
RetOpc == Hexagon::RESTORE_DEALLOC_RET_JMP_V4_EXT ||
@@ -816,29 +925,45 @@ void HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const {
// It is possible that the restoring code is a call to a library function.
// All of the restore* functions include "deallocframe", so we need to make
// sure that we don't add an extra one.
+ bool NeedsSCS = MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack);
bool NeedsDeallocframe = true;
+ unsigned PrevOpc = 0;
if (!MBB.empty() && InsertPt != MBB.begin()) {
MachineBasicBlock::iterator PrevIt = std::prev(InsertPt);
- unsigned COpc = PrevIt->getOpcode();
- if (COpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4 ||
- COpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_PIC ||
- COpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT ||
- COpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT_PIC ||
- COpc == Hexagon::PS_call_nr || COpc == Hexagon::PS_callr_nr)
+ PrevOpc = PrevIt->getOpcode();
+ if (PrevOpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4 ||
+ PrevOpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_PIC ||
+ PrevOpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT ||
+ PrevOpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT_PIC ||
+ PrevOpc == Hexagon::PS_call_nr || PrevOpc == Hexagon::PS_callr_nr)
NeedsDeallocframe = false;
}
if (!MF.getSubtarget<HexagonSubtarget>().isEnvironmentMusl() ||
!MF.getFunction().isVarArg()) {
- if (!NeedsDeallocframe)
+ if (!NeedsDeallocframe) {
+ // RESTORE_DEALLOC_BEFORE_TAILCALL stubs include r19 in their save range,
+ // but SCS requires -ffixed-r19 which prevents the allocator from
+ // selecting stubs that cover r19, so SCS and stubs are mutually
+ // exclusive. PS_call_nr/PS_callr_nr are noreturn calls so the shadow
+ // stack entry is never read - no SCS epilogue is needed on either path.
+ if (NeedsSCS && PrevOpc != Hexagon::PS_call_nr &&
+ PrevOpc != Hexagon::PS_callr_nr)
+ report_fatal_error("SCS with RESTORE_DEALLOC stub: "
+ "-ffixed-r19 should have prevented this");
return;
+ }
// If the returning instruction is PS_jmpret, replace it with
// dealloc_return, otherwise just add deallocframe. The function
// could be returning via a tail call.
- if (RetOpc != Hexagon::PS_jmpret || DisableDeallocRet) {
+ if (RetOpc != Hexagon::PS_jmpret || DisableDeallocRet || NeedsSCS) {
BuildMI(MBB, InsertPt, dl, HII.get(Hexagon::L2_deallocframe))
.addDef(Hexagon::D15)
.addReg(Hexagon::R30);
+ // When shadow call stack is active, overwrite R31 restored by
+ // deallocframe with the shadow-stack copy, then retract the pointer.
+ if (NeedsSCS)
+ emitSCSEpilogue(MF, MBB, InsertPt, dl);
return;
}
unsigned NewOpc = Hexagon::L4_return;
@@ -858,11 +983,14 @@ void HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const {
MachineBasicBlock::iterator Term = MBB.getFirstTerminator();
MachineBasicBlock::iterator I = (Term == MBB.begin()) ? MBB.end()
: std::prev(Term);
- if (I == MBB.end() ||
- (I->getOpcode() != Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT &&
- I->getOpcode() != Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT_PIC &&
- I->getOpcode() != Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4 &&
- I->getOpcode() != Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_PIC))
+ bool HasRestoreStub =
+ I != MBB.end() &&
+ (I->getOpcode() == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT ||
+ I->getOpcode() ==
+ Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT_PIC ||
+ I->getOpcode() == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4 ||
+ I->getOpcode() == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_PIC);
+ if (!HasRestoreStub)
BuildMI(MBB, InsertPt, dl, HII.get(Hexagon::L2_deallocframe))
.addDef(Hexagon::D15)
.addReg(Hexagon::R30);
@@ -870,6 +998,11 @@ void HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const {
BuildMI(MBB, InsertPt, dl, HII.get(Hexagon::A2_addi), SP)
.addReg(SP)
.addImm(RegisterSavedAreaSizePlusPadding);
+ // RESTORE_DEALLOC stubs are mutually exclusive with SCS (-ffixed-r19
+ // prevents stubs that cover r19), so only emit SCS epilogue when we
+ // emitted our own deallocframe above.
+ if (NeedsSCS && !HasRestoreStub)
+ emitSCSEpilogue(MF, MBB, InsertPt, dl);
}
}
diff --git a/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll b/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
new file mode 100644
index 0000000000000..5768535959fc9
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
@@ -0,0 +1,224 @@
+; RUN: llc -mtriple=hexagon -mattr=+reserved-r19 < %s | FileCheck %s
+;; Test that the backend fatally errors without reserved-r19
+; RUN: not --crash llc -mtriple=hexagon < %s 2>&1 | FileCheck %s --check-prefix=ERR
+; RUN: llc -mtriple=hexagon -mattr=+reserved-r19 < %s | FileCheck %s --check-prefix=CFI
+; RUN: llc -mtriple=hexagon-unknown-linux-musl -mattr=+reserved-r19 < %s | FileCheck %s --check-prefix=MUSL
+
+;; Leaf function - no LR spill, SCS should not emit any r19 instructions.
+; CHECK-LABEL: leaf:
+; CHECK-NOT: r19
+; CHECK: jumpr r31
+
+;; Non-leaf function - SCS emits prologue (addi + store) and epilogue (load + addi).
+;; The SCS store is fused into the same packet as the first call; because
+;; Hexagon packets use old-value reads the original R31 is saved regardless.
+;; The epilogue load and addi are also in the same packet; the load uses the
+;; old (pre-decrement) r19 value per Hexagon packet semantics, and the -4
+;; offset correctly addresses the saved slot.
+; CHECK-LABEL: nonleaf:
+; CHECK: r19 = add(r19,#4)
+; CHECK: call bar
+; CHECK: memw(r19+#-4) = r31
+; CHECK-DAG: r19 = add(r19,#-4)
+; CHECK-DAG: r31 = memw(r19+#-4)
+; CHECK: jumpr r31
+
+;; Multi-call function - only one SCS prologue/epilogue pair, not one per call.
+; CHECK-LABEL: twocalls:
+; CHECK: r19 = add(r19,#4)
+; CHECK: call bar
+; CHECK: memw(r19+#-4) = r31
+; CHECK: call bar
+; CHECK-DAG: r19 = add(r19,#-4)
+; CHECK-DAG: r31 = memw(r19+#-4)
+; CHECK: jumpr r31
+
+;; Conditional call (shrink-wrapping): the early-return path is a leaf and
+;; has no SCS prologue/epilogue. The call path gets the SCS pair.
+; CHECK-LABEL: condcall:
+; CHECK: if (!p0.new) jumpr:nt r31
+; CHECK: r19 = add(r19,#4)
+; CHECK: call bar
+; CHECK: memw(r19+#-4) = r31
+; CHECK-DAG: r19 = add(r19,#-4)
+; CHECK-DAG: r31 = memw(r19+#-4)
+; CHECK: jumpr r31
+
+;; Tail call - SCS prologue and epilogue are both emitted; the epilogue
+;; instructions and the tail jump are fused into the same packet.
+; CHECK-LABEL: tailcall:
+; CHECK: r19 = add(r19,#4)
+; CHECK: memw(r19+#-4) = r31
+; CHECK-DAG: r19 = add(r19,#-4)
+; CHECK-DAG: r31 = memw(r19+#-4)
+; CHECK-DAG: jump bar
+
+;; Noreturn call - SCS prologue is emitted but no SCS epilogue since the
+;; function never returns.
+; CHECK-LABEL: noret:
+; CHECK: r19 = add(r19,#4)
+; CHECK: memw(r19+#-4) = r31
+; CHECK-NOT: r31 = memw
+; CHECK-NOT: r19 = add(r19,#-4)
+; CHECK-LABEL: nonleaf_cfi:
+
+;; Without r19 reserved, SCS should report an error.
+; ERR: Must reserve r19 to use shadow call stack on Hexagon
+
+;; Non-leaf with uwtable - exercises CFI escape (DW_CFA_val_expression for r19)
+;; and cfi_restore on epilogue.
+; CFI-LABEL: nonleaf_cfi:
+; CFI: r19 = add(r19,#4)
+; CFI: memw(r19+#-4) = r31
+; CFI: .cfi_escape 0x16, 0x13, 0x02, 0x83, 0x7c
+; CFI-DAG: r31 = memw(r19+#-4)
+; CFI-DAG: r19 = add(r19,#-4)
+; CFI: .cfi_restore r19
+; CFI: jumpr r31
+
+;; Musl vararg - exercises the vararg epilogue path with SCS.
+; MUSL-LABEL: vararg_musl:
+; MUSL: r19 = add(r19,#4)
+; MUSL: ...
[truncated]
|
|
@llvm/pr-subscribers-clang-driver Author: Brian Cain (androm3da) ChangesImplement the software ShadowCallStack for Hexagon. On Hexagon, r19 is used as the shadow stack pointer (reserved via -ffixed-r19). On function entry the LR (r31) is saved to the shadow stack and the pointer is advanced; on exit the LR is restored from the shadow stack before returning. Prologue sequence: Epilogue sequence (between deallocframe/jumpr r31): SCS is only emitted when hasFP() is true: the function uses allocframe. Leaf functions that never touch the stack do not need protection. When SCS is active the combined L4_return (deallocframe+jump) is not used; instead L2_deallocframe and PS_jmpret are kept separate so the shadow-stack restore can be inserted between them. Patch is 23.88 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/200508.diff 9 Files Affected:
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index 31660dd29407c..c5c5b305eafef 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -784,6 +784,14 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
<< "-ffixed-x18";
}
+ if ((Kinds & SanitizerKind::ShadowCallStack) &&
+ TC.getTriple().getArch() == llvm::Triple::hexagon &&
+ !Args.hasArg(options::OPT_ffixed_r19) && DiagnoseErrors) {
+ D.Diag(diag::err_drv_argument_only_allowed_with)
+ << lastArgumentForMask(D, Args, Kinds & SanitizerKind::ShadowCallStack)
+ << "-ffixed-r19";
+ }
+
// Report error if there are non-trapping sanitizers that require
// c++abi-specific parts of UBSan runtime, and they are not provided by the
// toolchain. We don't have a good way to check the latter, so we just
diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index c4296c25bb9de..53a5bad03a151 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -991,6 +991,8 @@ Linux::getSupportedSanitizers(StringRef BoundArch,
if (IsX86_64 || IsAArch64 || IsRISCV64) {
Res |= SanitizerKind::HWAddress;
}
+ if (IsHexagon)
+ Res |= SanitizerKind::ShadowCallStack;
if (IsX86_64 || IsAArch64) {
Res |= SanitizerKind::KernelHWAddress;
}
diff --git a/clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c b/clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c
new file mode 100644
index 0000000000000..dcb320f041168
--- /dev/null
+++ b/clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c
@@ -0,0 +1,23 @@
+// Test that -fsanitize=shadow-call-stack on Hexagon requires -ffixed-r19.
+
+// RUN: not %clang --target=hexagon-unknown-linux-musl \
+// RUN: -fsanitize=shadow-call-stack %s -### 2>&1 \
+// RUN: | FileCheck %s --check-prefix=HEXAGON-SCS-NO-R19
+
+// RUN: %clang --target=hexagon-unknown-linux-musl \
+// RUN: -fsanitize=shadow-call-stack -ffixed-r19 %s -### 2>&1 \
+// RUN: | FileCheck %s --check-prefix=HEXAGON-SCS-WITH-R19
+
+// HEXAGON-SCS-NO-R19: error: invalid argument '-fsanitize=shadow-call-stack' only allowed with '-ffixed-r19'
+// HEXAGON-SCS-WITH-R19: "-fsanitize=shadow-call-stack"
+
+// RUN: %clang --target=hexagon-unknown-linux-musl \
+// RUN: -fsanitize=shadow-call-stack -fno-sanitize=shadow-call-stack %s -### 2>&1 \
+// RUN: | FileCheck %s --check-prefix=HEXAGON-SCS-DISABLED
+// HEXAGON-SCS-DISABLED-NOT: error:
+// HEXAGON-SCS-DISABLED-NOT: "-fsanitize=shadow-call-stack"
+
+// RUN: not %clang --target=hexagon-unknown-elf \
+// RUN: -fsanitize=shadow-call-stack %s -### 2>&1 \
+// RUN: | FileCheck %s --check-prefix=HEXAGON-SCS-ELF-NO-R19
+// HEXAGON-SCS-ELF-NO-R19: error: invalid argument '-fsanitize=shadow-call-stack' only allowed with '-ffixed-r19'
diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
index 0cc4165c8dbd5..2f2173012564d 100644
--- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
+++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
@@ -124,7 +124,7 @@ set(ALL_XRAY_SUPPORTED_ARCH ${X86_64} ${ARM32} ${ARM64} ${MIPS32} ${MIPS64}
powerpc64le ${HEXAGON} ${LOONGARCH64} ${RISCV32} ${RISCV64} ${S390X})
endif()
set(ALL_XRAY_DSO_SUPPORTED_ARCH ${X86_64} ${ARM64})
-set(ALL_SHADOWCALLSTACK_SUPPORTED_ARCH ${ARM64})
+set(ALL_SHADOWCALLSTACK_SUPPORTED_ARCH ${ARM64} ${HEXAGON})
if (UNIX)
if (OS_NAME MATCHES "Linux")
diff --git a/compiler-rt/test/shadowcallstack/libc_support.h b/compiler-rt/test/shadowcallstack/libc_support.h
index 9c8e0566380bc..00a3a38a8780b 100644
--- a/compiler-rt/test/shadowcallstack/libc_support.h
+++ b/compiler-rt/test/shadowcallstack/libc_support.h
@@ -32,6 +32,32 @@ __attribute__((noinline)) void scs_fputs_stdout(const char *p) {
: "x0", "x1", "x2", "x8");
}
+#elif defined(__hexagon__)
+# ifndef __linux__
+# error Unsupported platform
+# endif
+
+size_t scs_strlen(const char *p) {
+ size_t retval = 0;
+ while (*p++)
+ retval++;
+ return retval;
+}
+
+__attribute__((noinline)) void scs_fputs_stdout(const char *p) {
+ size_t len = scs_strlen(p);
+ __asm__ __volatile__("{\n\t"
+ " r0 = #1\n\t" // fd = stdout
+ " r1 = %[buf]\n\t"
+ " r2 = %[count]\n\t"
+ " r6 = #64\n\t" // __NR_write
+ "}\n\t"
+ "trap0(#1)\n\t"
+ :
+ : [buf] "r"(p), [count] "r"(len)
+ : "r0", "r1", "r2", "r6", "memory");
+}
+
#else
#error Unsupported platform
#endif
diff --git a/compiler-rt/test/shadowcallstack/lit.cfg.py b/compiler-rt/test/shadowcallstack/lit.cfg.py
index 5b95deb1b0986..81151b0b7cbbc 100644
--- a/compiler-rt/test/shadowcallstack/lit.cfg.py
+++ b/compiler-rt/test/shadowcallstack/lit.cfg.py
@@ -25,6 +25,8 @@
scs_arch_cflags = config.target_cflags
if config.target_arch == "aarch64":
scs_arch_cflags += " -ffixed-x18 "
+elif config.target_arch == "hexagon":
+ scs_arch_cflags += " -ffixed-r19 "
config.substitutions.append(
(
"%clang_scs ",
@@ -32,5 +34,9 @@
)
)
-if config.target_os not in ["Linux"] or config.target_arch not in ["aarch64", "riscv64"]:
+if config.target_os not in ["Linux"] or config.target_arch not in [
+ "aarch64",
+ "hexagon",
+ "riscv64",
+]:
config.unsupported = True
diff --git a/compiler-rt/test/shadowcallstack/minimal_runtime.h b/compiler-rt/test/shadowcallstack/minimal_runtime.h
index fab4fdf8006f7..09a6bcf1efa57 100644
--- a/compiler-rt/test/shadowcallstack/minimal_runtime.h
+++ b/compiler-rt/test/shadowcallstack/minimal_runtime.h
@@ -19,6 +19,8 @@ static void __shadowcallstack_init() {
#if defined(__aarch64__)
__asm__ __volatile__("mov x18, %0" ::"r"(stack));
+#elif defined(__hexagon__)
+ __asm__ __volatile__("r19 = %0" ::"r"(stack));
#else
#error Unsupported platform
#endif
diff --git a/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp b/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp
index 0f73efc243986..e60f911f2a3c6 100644
--- a/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp
@@ -21,6 +21,8 @@
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
+#include "llvm/BinaryFormat/Dwarf.h"
+#include "llvm/CodeGen/CFIInstBuilder.h"
#include "llvm/CodeGen/LivePhysRegs.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineDominators.h"
@@ -148,6 +150,94 @@
using namespace llvm;
+static void emitSCSPrologue(MachineFunction &MF, MachineBasicBlock &MBB,
+ MachineBasicBlock::iterator MI,
+ const DebugLoc &DL) {
+ if (!MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack))
+ return;
+
+ const auto &HST = MF.getSubtarget<HexagonSubtarget>();
+ // Hexagon saves LR (R31) via allocframe. If there is no frame, LR is
+ // not on the regular stack and does not need shadow-stack protection.
+ if (!HST.getFrameLowering()->hasFP(MF))
+ return;
+
+ Register SCSPReg = Hexagon::R19;
+ if (!MF.getSubtarget().isRegisterReservedByUser(SCSPReg))
+ report_fatal_error("Must reserve r19 to use shadow call stack on Hexagon");
+
+ const auto &HII = *HST.getInstrInfo();
+
+ // The prologue is emitted once but the epilogue runs in each return block.
+ // Paths that exit via a noreturn call (PS_call_nr) or llvm.eh.return have no
+ // matching epilogue and leak one shadow-stack slot per such exit
+ // For EH return the unwinder restores r19 from the CFI val_expression below.
+
+ // r19 = add(r19, #4)
+ BuildMI(MBB, MI, DL, HII.get(Hexagon::A2_addi), SCSPReg)
+ .addReg(SCSPReg)
+ .addImm(4)
+ .setMIFlag(MachineInstr::FrameSetup);
+ // memw(r19 + #-4) = r31
+ BuildMI(MBB, MI, DL, HII.get(Hexagon::S2_storeri_io))
+ .addReg(SCSPReg)
+ .addImm(-4)
+ .addReg(Hexagon::R31)
+ .setMIFlag(MachineInstr::FrameSetup);
+
+ MBB.addLiveIn(SCSPReg);
+
+ if (!MF.needsFrameMoves())
+ return;
+
+ // CFI: DW_CFA_val_expression for the SCS register, DW_OP_bregN -4
+ // Tells the unwinder that the SCS register at entry = current value - 4.
+ const auto &TRI = *MF.getSubtarget().getRegisterInfo();
+ unsigned DwarfSCSReg = TRI.getDwarfRegNum(SCSPReg, /*IsEH=*/true);
+ // DW_OP_breg0..DW_OP_breg31 (0x70..0x8f) are 32 opcodes indexed by
+ // register number, so the register number must fit in [0, 31].
+ assert(DwarfSCSReg < 32 && "SCS register should be < 32");
+ const char CFIInst[] = {
+ (char)dwarf::DW_CFA_val_expression,
+ (char)DwarfSCSReg,
+ 2, // expression length
+ (char)(unsigned)(dwarf::DW_OP_breg0 + DwarfSCSReg),
+ (char)(-4 & 0x7f), // SLEB128 -4
+ };
+ CFIInstBuilder(MBB, MI, MachineInstr::FrameSetup)
+ .buildEscape(StringRef(CFIInst, sizeof(CFIInst)));
+}
+
+static void emitSCSEpilogue(MachineFunction &MF, MachineBasicBlock &MBB,
+ MachineBasicBlock::iterator MI,
+ const DebugLoc &DL) {
+ if (!MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack))
+ return;
+
+ // hasFP() is true at both call sites: the non-vararg path in
+ // insertEpilogueInBlock returns early when !hasFP(), and the vararg+musl
+ // path is inside the hasFP() branch.
+ assert(MF.getSubtarget<HexagonSubtarget>().getFrameLowering()->hasFP(MF) &&
+ "SCS epilogue requires a frame");
+
+ Register SCSPReg = Hexagon::R19;
+ const auto &HII = *MF.getSubtarget<HexagonSubtarget>().getInstrInfo();
+
+ // r31 = memw(r19 + #-4)
+ BuildMI(MBB, MI, DL, HII.get(Hexagon::L2_loadri_io), Hexagon::R31)
+ .addReg(SCSPReg)
+ .addImm(-4)
+ .setMIFlag(MachineInstr::FrameDestroy);
+ // r19 = add(r19, #-4)
+ BuildMI(MBB, MI, DL, HII.get(Hexagon::A2_addi), SCSPReg)
+ .addReg(SCSPReg)
+ .addImm(-4)
+ .setMIFlag(MachineInstr::FrameDestroy);
+
+ if (MF.needsFrameMoves())
+ CFIInstBuilder(MBB, MI, MachineInstr::FrameDestroy).buildRestore(SCSPReg);
+}
+
static cl::opt<bool> DisableDeallocRet("disable-hexagon-dealloc-ret",
cl::Hidden, cl::desc("Disable Dealloc Return for Hexagon target"));
@@ -511,6 +601,19 @@ void HexagonFrameLowering::emitPrologue(MachineFunction &MF,
bool PrologueStubs = false;
insertCSRSpillsInBlock(*PrologB, CSI, HRI, PrologueStubs);
insertPrologueInBlock(*PrologB, PrologueStubs);
+ // Insert the SCS prologue after all FrameSetup instructions so that it
+ // follows allocframe and any CSR spills in the instruction stream. The
+ // packetizer may still fuse the SCS store with the first call in the
+ // function, but because Hexagon packets use old-value reads the original
+ // R31 is always what is stored.
+ {
+ MachineBasicBlock::iterator AfterProlog = PrologB->begin();
+ while (AfterProlog != PrologB->end() &&
+ AfterProlog->getFlag(MachineInstr::FrameSetup))
+ ++AfterProlog;
+ DebugLoc PrologDL = PrologB->findDebugLoc(AfterProlog);
+ emitSCSPrologue(MF, *PrologB, AfterProlog, PrologDL);
+ }
updateEntryPaths(MF, *PrologB);
if (EpilogB) {
@@ -786,6 +889,8 @@ void HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const {
// Handle EH_RETURN.
if (RetOpc == Hexagon::EH_RETURN_JMPR) {
+ // EH paths overwrite R31 with a handler address; the shadow stack is
+ // not read on this path, so no SCS epilogue is needed.
BuildMI(MBB, InsertPt, dl, HII.get(Hexagon::L2_deallocframe))
.addDef(Hexagon::D15)
.addReg(Hexagon::R30);
@@ -797,6 +902,10 @@ void HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const {
// Check for RESTORE_DEALLOC_RET* tail call. Don't emit an extra dealloc-
// frame instruction if we encounter it.
+ // These spill-stub tail calls include r19 in their save range, but SCS
+ // requires -ffixed-r19, which prevents the allocator from selecting stubs
+ // that cover r19. The two features are therefore mutually exclusive and no
+ // SCS epilogue is needed here.
if (RetOpc == Hexagon::RESTORE_DEALLOC_RET_JMP_V4 ||
RetOpc == Hexagon::RESTORE_DEALLOC_RET_JMP_V4_PIC ||
RetOpc == Hexagon::RESTORE_DEALLOC_RET_JMP_V4_EXT ||
@@ -816,29 +925,45 @@ void HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const {
// It is possible that the restoring code is a call to a library function.
// All of the restore* functions include "deallocframe", so we need to make
// sure that we don't add an extra one.
+ bool NeedsSCS = MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack);
bool NeedsDeallocframe = true;
+ unsigned PrevOpc = 0;
if (!MBB.empty() && InsertPt != MBB.begin()) {
MachineBasicBlock::iterator PrevIt = std::prev(InsertPt);
- unsigned COpc = PrevIt->getOpcode();
- if (COpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4 ||
- COpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_PIC ||
- COpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT ||
- COpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT_PIC ||
- COpc == Hexagon::PS_call_nr || COpc == Hexagon::PS_callr_nr)
+ PrevOpc = PrevIt->getOpcode();
+ if (PrevOpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4 ||
+ PrevOpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_PIC ||
+ PrevOpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT ||
+ PrevOpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT_PIC ||
+ PrevOpc == Hexagon::PS_call_nr || PrevOpc == Hexagon::PS_callr_nr)
NeedsDeallocframe = false;
}
if (!MF.getSubtarget<HexagonSubtarget>().isEnvironmentMusl() ||
!MF.getFunction().isVarArg()) {
- if (!NeedsDeallocframe)
+ if (!NeedsDeallocframe) {
+ // RESTORE_DEALLOC_BEFORE_TAILCALL stubs include r19 in their save range,
+ // but SCS requires -ffixed-r19 which prevents the allocator from
+ // selecting stubs that cover r19, so SCS and stubs are mutually
+ // exclusive. PS_call_nr/PS_callr_nr are noreturn calls so the shadow
+ // stack entry is never read - no SCS epilogue is needed on either path.
+ if (NeedsSCS && PrevOpc != Hexagon::PS_call_nr &&
+ PrevOpc != Hexagon::PS_callr_nr)
+ report_fatal_error("SCS with RESTORE_DEALLOC stub: "
+ "-ffixed-r19 should have prevented this");
return;
+ }
// If the returning instruction is PS_jmpret, replace it with
// dealloc_return, otherwise just add deallocframe. The function
// could be returning via a tail call.
- if (RetOpc != Hexagon::PS_jmpret || DisableDeallocRet) {
+ if (RetOpc != Hexagon::PS_jmpret || DisableDeallocRet || NeedsSCS) {
BuildMI(MBB, InsertPt, dl, HII.get(Hexagon::L2_deallocframe))
.addDef(Hexagon::D15)
.addReg(Hexagon::R30);
+ // When shadow call stack is active, overwrite R31 restored by
+ // deallocframe with the shadow-stack copy, then retract the pointer.
+ if (NeedsSCS)
+ emitSCSEpilogue(MF, MBB, InsertPt, dl);
return;
}
unsigned NewOpc = Hexagon::L4_return;
@@ -858,11 +983,14 @@ void HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const {
MachineBasicBlock::iterator Term = MBB.getFirstTerminator();
MachineBasicBlock::iterator I = (Term == MBB.begin()) ? MBB.end()
: std::prev(Term);
- if (I == MBB.end() ||
- (I->getOpcode() != Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT &&
- I->getOpcode() != Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT_PIC &&
- I->getOpcode() != Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4 &&
- I->getOpcode() != Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_PIC))
+ bool HasRestoreStub =
+ I != MBB.end() &&
+ (I->getOpcode() == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT ||
+ I->getOpcode() ==
+ Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT_PIC ||
+ I->getOpcode() == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4 ||
+ I->getOpcode() == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_PIC);
+ if (!HasRestoreStub)
BuildMI(MBB, InsertPt, dl, HII.get(Hexagon::L2_deallocframe))
.addDef(Hexagon::D15)
.addReg(Hexagon::R30);
@@ -870,6 +998,11 @@ void HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const {
BuildMI(MBB, InsertPt, dl, HII.get(Hexagon::A2_addi), SP)
.addReg(SP)
.addImm(RegisterSavedAreaSizePlusPadding);
+ // RESTORE_DEALLOC stubs are mutually exclusive with SCS (-ffixed-r19
+ // prevents stubs that cover r19), so only emit SCS epilogue when we
+ // emitted our own deallocframe above.
+ if (NeedsSCS && !HasRestoreStub)
+ emitSCSEpilogue(MF, MBB, InsertPt, dl);
}
}
diff --git a/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll b/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
new file mode 100644
index 0000000000000..5768535959fc9
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
@@ -0,0 +1,224 @@
+; RUN: llc -mtriple=hexagon -mattr=+reserved-r19 < %s | FileCheck %s
+;; Test that the backend fatally errors without reserved-r19
+; RUN: not --crash llc -mtriple=hexagon < %s 2>&1 | FileCheck %s --check-prefix=ERR
+; RUN: llc -mtriple=hexagon -mattr=+reserved-r19 < %s | FileCheck %s --check-prefix=CFI
+; RUN: llc -mtriple=hexagon-unknown-linux-musl -mattr=+reserved-r19 < %s | FileCheck %s --check-prefix=MUSL
+
+;; Leaf function - no LR spill, SCS should not emit any r19 instructions.
+; CHECK-LABEL: leaf:
+; CHECK-NOT: r19
+; CHECK: jumpr r31
+
+;; Non-leaf function - SCS emits prologue (addi + store) and epilogue (load + addi).
+;; The SCS store is fused into the same packet as the first call; because
+;; Hexagon packets use old-value reads the original R31 is saved regardless.
+;; The epilogue load and addi are also in the same packet; the load uses the
+;; old (pre-decrement) r19 value per Hexagon packet semantics, and the -4
+;; offset correctly addresses the saved slot.
+; CHECK-LABEL: nonleaf:
+; CHECK: r19 = add(r19,#4)
+; CHECK: call bar
+; CHECK: memw(r19+#-4) = r31
+; CHECK-DAG: r19 = add(r19,#-4)
+; CHECK-DAG: r31 = memw(r19+#-4)
+; CHECK: jumpr r31
+
+;; Multi-call function - only one SCS prologue/epilogue pair, not one per call.
+; CHECK-LABEL: twocalls:
+; CHECK: r19 = add(r19,#4)
+; CHECK: call bar
+; CHECK: memw(r19+#-4) = r31
+; CHECK: call bar
+; CHECK-DAG: r19 = add(r19,#-4)
+; CHECK-DAG: r31 = memw(r19+#-4)
+; CHECK: jumpr r31
+
+;; Conditional call (shrink-wrapping): the early-return path is a leaf and
+;; has no SCS prologue/epilogue. The call path gets the SCS pair.
+; CHECK-LABEL: condcall:
+; CHECK: if (!p0.new) jumpr:nt r31
+; CHECK: r19 = add(r19,#4)
+; CHECK: call bar
+; CHECK: memw(r19+#-4) = r31
+; CHECK-DAG: r19 = add(r19,#-4)
+; CHECK-DAG: r31 = memw(r19+#-4)
+; CHECK: jumpr r31
+
+;; Tail call - SCS prologue and epilogue are both emitted; the epilogue
+;; instructions and the tail jump are fused into the same packet.
+; CHECK-LABEL: tailcall:
+; CHECK: r19 = add(r19,#4)
+; CHECK: memw(r19+#-4) = r31
+; CHECK-DAG: r19 = add(r19,#-4)
+; CHECK-DAG: r31 = memw(r19+#-4)
+; CHECK-DAG: jump bar
+
+;; Noreturn call - SCS prologue is emitted but no SCS epilogue since the
+;; function never returns.
+; CHECK-LABEL: noret:
+; CHECK: r19 = add(r19,#4)
+; CHECK: memw(r19+#-4) = r31
+; CHECK-NOT: r31 = memw
+; CHECK-NOT: r19 = add(r19,#-4)
+; CHECK-LABEL: nonleaf_cfi:
+
+;; Without r19 reserved, SCS should report an error.
+; ERR: Must reserve r19 to use shadow call stack on Hexagon
+
+;; Non-leaf with uwtable - exercises CFI escape (DW_CFA_val_expression for r19)
+;; and cfi_restore on epilogue.
+; CFI-LABEL: nonleaf_cfi:
+; CFI: r19 = add(r19,#4)
+; CFI: memw(r19+#-4) = r31
+; CFI: .cfi_escape 0x16, 0x13, 0x02, 0x83, 0x7c
+; CFI-DAG: r31 = memw(r19+#-4)
+; CFI-DAG: r19 = add(r19,#-4)
+; CFI: .cfi_restore r19
+; CFI: jumpr r31
+
+;; Musl vararg - exercises the vararg epilogue path with SCS.
+; MUSL-LABEL: vararg_musl:
+; MUSL: r19 = add(r19,#4)
+; MUSL: ...
[truncated]
|
|
If desired I can decompose this into independent clang, llvm, compiler-rt PRs. |
How to use the Graphite Merge QueueAdd the label FP Bundles to this PR to add it to the merge queue. You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. |
Implement the software ShadowCallStack for Hexagon.
On Hexagon, r19 is used as the shadow stack pointer (reserved via -ffixed-r19). On function entry the LR (r31) is saved to the shadow stack and the pointer is advanced; on exit the LR is restored from the shadow stack before returning.
Prologue sequence:
Epilogue sequence (between deallocframe/jumpr r31):
SCS is only emitted when hasFP() is true: the function uses allocframe. Leaf functions that never touch the stack do not need protection.
When SCS is active the combined L4_return (deallocframe+jump) is not used; instead L2_deallocframe and PS_jmpret are kept separate so the shadow-stack restore can be inserted between them.