Skip to content
Merged
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
8 changes: 8 additions & 0 deletions clang/lib/Driver/SanitizerArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,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
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Driver/ToolChains/Linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,8 @@ Linux::getSupportedSanitizers(BoundArch BA,
if (IsX86_64 || IsAArch64 || IsRISCV64) {
Res |= SanitizerKind::HWAddress;
}
if (IsHexagon)
Res |= SanitizerKind::ShadowCallStack;
if (IsX86_64 || IsAArch64) {
Res |= SanitizerKind::KernelHWAddress;
}
Expand Down
12 changes: 12 additions & 0 deletions clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// 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"
2 changes: 1 addition & 1 deletion compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,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")
Expand Down
26 changes: 26 additions & 0 deletions compiler-rt/test/shadowcallstack/libc_support.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 7 additions & 1 deletion compiler-rt/test/shadowcallstack/lit.cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,18 @@
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 ",
config.clang + " -O0 -fsanitize=shadow-call-stack " + scs_arch_cflags + " ",
)
)

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
2 changes: 2 additions & 0 deletions compiler-rt/test/shadowcallstack/minimal_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
154 changes: 141 additions & 13 deletions llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -148,6 +150,89 @@

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();

// 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. Check defensively.
if (!MF.getSubtarget<HexagonSubtarget>().getFrameLowering()->hasFP(MF))
report_fatal_error("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"));

Expand Down Expand Up @@ -511,6 +596,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) {
Expand Down Expand Up @@ -786,6 +884,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);
Expand All @@ -797,6 +897,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 ||
Expand All @@ -816,29 +920,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;
Expand All @@ -858,18 +978,26 @@ 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);
if (RegisterSavedAreaSizePlusPadding != 0)
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);
}
}

Expand Down
Loading
Loading