diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -491,8 +491,10 @@ << lastArgumentForMask(D, Args, Kinds & NeedsLTO) << "-flto"; } - if ((Kinds & SanitizerKind::ShadowCallStack) && TC.getTriple().isAArch64() && - !llvm::AArch64::isX18ReservedByDefault(TC.getTriple()) && + if ((Kinds & SanitizerKind::ShadowCallStack) && + ((TC.getTriple().isAArch64() && + !llvm::AArch64::isX18ReservedByDefault(TC.getTriple())) || + TC.getTriple().isRISCV()) && !Args.hasArg(options::OPT_ffixed_x18)) { D.Diag(diag::err_drv_argument_only_allowed_with) << lastArgumentForMask(D, Args, Kinds & SanitizerKind::ShadowCallStack) diff --git a/clang/lib/Driver/ToolChain.cpp b/clang/lib/Driver/ToolChain.cpp --- a/clang/lib/Driver/ToolChain.cpp +++ b/clang/lib/Driver/ToolChain.cpp @@ -1024,7 +1024,8 @@ getTriple().getArch() == llvm::Triple::arm || getTriple().isWasm() || getTriple().isAArch64()) Res |= SanitizerKind::CFIICall; - if (getTriple().getArch() == llvm::Triple::x86_64 || getTriple().isAArch64()) + if (getTriple().getArch() == llvm::Triple::x86_64 || + getTriple().isAArch64() || getTriple().isRISCV()) Res |= SanitizerKind::ShadowCallStack; if (getTriple().isAArch64()) Res |= SanitizerKind::MemTag; diff --git a/clang/test/CodeGen/shadowcallstack-attr.c b/clang/test/CodeGen/shadowcallstack-attr.c --- a/clang/test/CodeGen/shadowcallstack-attr.c +++ b/clang/test/CodeGen/shadowcallstack-attr.c @@ -5,6 +5,20 @@ // RUN: echo -e "[shadow-call-stack]\nfun:foo" > %t // RUN: %clang_cc1 -fsanitize-blacklist=%t -triple x86_64-linux-unknown -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=BLACKLISTED %s +// RUN: %clang_cc1 -triple riscv32-linux-gnu -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=UNBLACKLISTED %s + +// RUN: %clang_cc1 -D ATTR -triple riscv32-linux-gnu -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=BLACKLISTED %s + +// RUN: echo -e "[shadow-call-stack]\nfun:foo" > %t +// RUN: %clang_cc1 -fsanitize-blacklist=%t -triple riscv32-linux-gnu -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=BLACKLISTED %s + +// RUN: %clang_cc1 -triple riscv64-linux-gnu -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=UNBLACKLISTED %s + +// RUN: %clang_cc1 -D ATTR -triple riscv64-linux-gnu -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=BLACKLISTED %s + +// RUN: echo -e "[shadow-call-stack]\nfun:foo" > %t +// RUN: %clang_cc1 -fsanitize-blacklist=%t -triple riscv64-linux-gnu -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=BLACKLISTED %s + #ifdef ATTR __attribute__((no_sanitize("shadow-call-stack"))) #endif diff --git a/clang/test/Driver/sanitizer-ld.c b/clang/test/Driver/sanitizer-ld.c --- a/clang/test/Driver/sanitizer-ld.c +++ b/clang/test/Driver/sanitizer-ld.c @@ -615,6 +615,16 @@ // CHECK-SHADOWCALLSTACK-LINUX-AARCH64: '-fsanitize=shadow-call-stack' only allowed with '-ffixed-x18' // RUN: %clang -fsanitize=shadow-call-stack %s -### -o %t.o 2>&1 \ +// RUN: -target riscv32-unknown-elf -fuse-ld=ld \ +// RUN: | FileCheck --check-prefix=CHECK-SHADOWCALLSTACK-ELF-RISCV32 %s +// CHECK-SHADOWCALLSTACK-ELF-RISCV32: '-fsanitize=shadow-call-stack' only allowed with '-ffixed-x18' + +// RUN: %clang -fsanitize=shadow-call-stack %s -### -o %t.o 2>&1 \ +// RUN: -target riscv64-unknown-linux -fuse-ld=ld \ +// RUN: | FileCheck --check-prefix=CHECK-SHADOWCALLSTACK-LINUX-RISCV64 %s +// CHECK-SHADOWCALLSTACK-LINUX-RISCV64: '-fsanitize=shadow-call-stack' only allowed with '-ffixed-x18' + +// RUN: %clang -fsanitize=shadow-call-stack %s -### -o %t.o 2>&1 \ // RUN: -target aarch64-unknown-linux -fuse-ld=ld -ffixed-x18 \ // RUN: | FileCheck --check-prefix=CHECK-SHADOWCALLSTACK-LINUX-AARCH64-X18 %s // RUN: %clang -fsanitize=shadow-call-stack %s -### -o %t.o 2>&1 \ diff --git a/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp b/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp --- a/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp +++ b/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp @@ -23,6 +23,78 @@ using namespace llvm; +// For now we use x18, a.k.a s2, as pointer to shadow call stack. +// User should explicitly set -ffixed-x18 and not use x18 in their asm. +static void emitSCSPrologue(MachineFunction &MF, MachineBasicBlock &MBB, + MachineBasicBlock::iterator MI) { + if (!MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack)) + return; + + std::vector &CSI = MF.getFrameInfo().getCalleeSavedInfo(); + if (std::none_of(CSI.begin(), CSI.end(), + [] (CalleeSavedInfo &CSR) { return CSR.getReg() == RISCV::X1; })) + return; + + const auto &STI = MF.getSubtarget(); + // Emit an error message and bail out. + if (!STI.isRegisterReservedByUser(RISCV::X18)) { + MF.getFunction().getContext().diagnose(DiagnosticInfoUnsupported{ + MF.getFunction(), "x18 not reserved by user for Shadow Call Stack."}); + return; + } + + DebugLoc DL = MI != MBB.end() ? MI->getDebugLoc() : DebugLoc(); + + const RISCVInstrInfo *TII = STI.getInstrInfo(); + int64_t SlotSize = STI.getXLen() / 8; + // Store return address to shadow call stack + // sw ra, 0(s2) + // addi s2, s2, 4 + BuildMI(MBB, MI, DL, TII->get(RISCV::SW)) + .addReg(RISCV::X1) + .addReg(RISCV::X18) + .addImm(0); + BuildMI(MBB, MI, DL, TII->get(RISCV::ADDI)) + .addReg(RISCV::X18, RegState::Define) + .addReg(RISCV::X18) + .addImm(SlotSize); +} + +static void emitSCSEpilogue(MachineFunction &MF, MachineBasicBlock &MBB, + MachineBasicBlock::iterator MI) { + if (!MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack)) + return; + + std::vector &CSI = MF.getFrameInfo().getCalleeSavedInfo(); + if (std::none_of(CSI.begin(), CSI.end(), + [] (CalleeSavedInfo &CSR) { return CSR.getReg() == RISCV::X1; })) + return; + + const auto &STI = MF.getSubtarget(); + // Emit an error message and bail out. + if (!STI.isRegisterReservedByUser(RISCV::X18)) { + MF.getFunction().getContext().diagnose(DiagnosticInfoUnsupported{ + MF.getFunction(), "x18 not reserved by user for Shadow Call Stack."}); + return; + } + + DebugLoc DL = MI != MBB.end() ? MI->getDebugLoc() : DebugLoc(); + + const RISCVInstrInfo *TII = STI.getInstrInfo(); + int64_t SlotSize = STI.getXLen() / 8; + // Load return address from shadow call stack + // addi s2, s2, -4 + // lw ra, 0(s2) + BuildMI(MBB, MI, DL, TII->get(RISCV::ADDI)) + .addReg(RISCV::X18, RegState::Define) + .addReg(RISCV::X18) + .addImm(-SlotSize); + BuildMI(MBB, MI, DL, TII->get(RISCV::LW)) + .addReg(RISCV::X1, RegState::Define) + .addReg(RISCV::X18) + .addImm(0); +} + // Get the ID of the libcall used for spilling and restoring callee saved // registers. The ID is representative of the number of registers saved or // restored by the libcall, except it is zero-indexed - ID 0 corresponds to a @@ -222,6 +294,9 @@ Register SPReg = getSPReg(STI); Register BPReg = RISCVABI::getBPReg(); + // Emit prologue for shadow call stack. + emitSCSPrologue(MF, MBB, MBBI); + // Since spillCalleeSavedRegisters may have inserted a libcall, skip past // any instructions marked as FrameSetup while (MBBI != MBB.end() && MBBI->getFlag(MachineInstr::FrameSetup)) @@ -457,6 +532,9 @@ // Deallocate stack adjustReg(MBB, MBBI, DL, SPReg, SPReg, StackSize, MachineInstr::FrameDestroy); + + // Emit epilogue for shadow call stack. + emitSCSEpilogue(MF, MBB, MBBI); } int RISCVFrameLowering::getFrameIndexReference(const MachineFunction &MF, diff --git a/llvm/test/CodeGen/RISCV/shadowcallstack.ll b/llvm/test/CodeGen/RISCV/shadowcallstack.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/RISCV/shadowcallstack.ll @@ -0,0 +1,88 @@ +; RUN: llc -verify-machineinstrs -o - %s -mtriple=riscv32-linux-gnu -mattr=+reserve-x18 | FileCheck --check-prefix=RISCV32 %s + +; RUN: llc -verify-machineinstrs -o - %s -mtriple=riscv64-linux-gnu -mattr=+reserve-x18 | FileCheck --check-prefix=RISCV64 %s + +define void @f1() shadowcallstack { + ; CHECK: f1: + ; CHECK-NOT: x18 + ; CHECK: ret + ret void +} + +declare void @foo() + +define void @f2() shadowcallstack { + ; CHECK: f2: + ; CHECK-NOT: x18 + ; CHECK: tail foo + tail call void @foo() + ret void +} + +declare i32 @bar() + +define i32 @f3() shadowcallstack { + ; CHECK: f3: + ; RISCV32: sw ra, 0(s2) + ; RISCV32-NEXT: addi s2, s2, 4 + ; RISCV64: sw ra, 0(s2) + ; RISCV64-NEXT: addi s2, s2, 8 + ; CHECK: addi sp, sp, -16 + ; CHECK: sw ra, 12(sp) + %res = call i32 @bar() + %res1 = add i32 %res, 1 + ; CHECK: lw ra, 12(sp) + ; CHECK: addi sp, sp, 16 + ; RISCV32: addi s2, s2, -4 + ; RISCV32-NEXT: lw ra, 0(s2) + ; RISCV64: addi s2, s2, -8 + ; RISCV64-NEXT: lw ra, 0(s2) + ; CHECK: ret + ret i32 %res +} + +define i32 @f4() shadowcallstack { + ; CHECK: f4: + ; RISCV32: sw ra, 0(s2) + ; RISCV32-NEXT: addi s2, s2, 4 + ; RISCV64: sw ra, 0(s2) + ; RISCV64-NEXT: addi s2, s2, 8 + ; CHECK: addi sp, sp, -16 + ; CHECK: sw ra, 12(sp) + %res1 = call i32 @bar() + %res2 = call i32 @bar() + %res3 = call i32 @bar() + %res4 = call i32 @bar() + %res12 = add i32 %res1, %res2 + %res34 = add i32 %res3, %res4 + %res1234 = add i32 %res12, %res34 + ; CHECK: lw ra, 12(sp) + ; CHECK: addi sp, sp, 16 + ; RISCV32: addi s2, s2, -4 + ; RISCV32-NEXT: lw ra, 0(s2) + ; RISCV64: addi s2, s2, -8 + ; RISCV64-NEXT: lw ra, 0(s2) + ; CHECK: ret + ret i32 %res1234 +} + +define i32 @f5() shadowcallstack nounwind { + ; CHECK: f5: + ; CHECK-NOT: .cfi_def_cfa_offset + ; RISCV32: sw ra, 0(s2) + ; RISCV32-NEXT: addi s2, s2, 4 + ; RISCV64: sw ra, 0(s2) + ; RISCV64-NEXT: addi s2, s2, 8 + ; CHECK: addi sp, sp, -16 + ; CHECK: sw ra, 12(sp) + %res = call i32 @bar() + %res1 = add i32 %res, 1 + ; CHECK: lw ra, 12(sp) + ; CHECK: addi sp, sp, 16 + ; RISCV32: addi s2, s2, -4 + ; RISCV32-NEXT: lw ra, 0(s2) + ; RISCV64: addi s2, s2, -8 + ; RISCV64-NEXT: lw ra, 0(s2) + ; CHECK: ret + ret i32 %res +}