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 @@ -495,8 +495,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 @@ -1029,7 +1029,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 @@ -1,9 +1,23 @@ -// RUN: %clang_cc1 -triple x86_64-linux-unknown -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=UNBLACKLISTED %s +// RUN: %clang_cc1 -triple x86_64-linux-unknown -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=UNBLOCKLISTED %s -// RUN: %clang_cc1 -D ATTR -triple x86_64-linux-unknown -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=BLACKLISTED %s +// RUN: %clang_cc1 -D ATTR -triple x86_64-linux-unknown -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=BLOCKLISTED %s // 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 -fsanitize-blacklist=%t -triple x86_64-linux-unknown -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=BLOCKLISTED %s + +// RUN: %clang_cc1 -triple riscv32-linux-gnu -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=UNBLOCKLISTED %s + +// RUN: %clang_cc1 -D ATTR -triple riscv32-linux-gnu -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=BLOCKLISTED %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=BLOCKLISTED %s + +// RUN: %clang_cc1 -triple riscv64-linux-gnu -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=UNBLOCKLISTED %s + +// RUN: %clang_cc1 -D ATTR -triple riscv64-linux-gnu -emit-llvm -o - %s -fsanitize=shadow-call-stack | FileCheck -check-prefix=BLOCKLISTED %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=BLOCKLISTED %s #ifdef ATTR __attribute__((no_sanitize("shadow-call-stack"))) @@ -12,5 +26,5 @@ // CHECK: define i32 @foo(i32* %a) -// BLACKLISTED-NOT: attributes {{.*}}shadowcallstack{{.*}} -// UNBLACKLISTED: attributes {{.*}}shadowcallstack{{.*}} +// BLOCKLISTED-NOT: attributes {{.*}}shadowcallstack{{.*}} +// UNBLOCKLISTED: attributes {{.*}}shadowcallstack{{.*}} 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,104 @@ 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, + const DebugLoc &DL) { + if (!MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack)) + return; + + const auto &STI = MF.getSubtarget(); + Register RAReg = STI.getRegisterInfo()->getRARegister(); + + // Do not save RA to the SCS if it's not saved to the regular stack, + // i.e. RA is not at risk of being to overwritten. + std::vector &CSI = MF.getFrameInfo().getCalleeSavedInfo(); + if (std::none_of(CSI.begin(), CSI.end(), + [&](CalleeSavedInfo &CSR) { return CSR.getReg() == RAReg; })) + return; + + Register SCSPReg = RISCVABI::getSCSPReg(); + + auto &Ctx = MF.getFunction().getContext(); + if (!STI.isRegisterReservedByUser(SCSPReg)) { + Ctx.diagnose(DiagnosticInfoUnsupported{MF.getFunction(), + "x18 not reserved by user for Shadow Call Stack."}); + return; + } + + const auto *RVFI = MF.getInfo(); + if (RVFI->useSaveRestoreLibCalls(MF)) { + Ctx.diagnose(DiagnosticInfoUnsupported{MF.getFunction(), + "Shadow Call Stack cannot be combined with Save/Restore LibCalls."}); + return; + } + + const RISCVInstrInfo *TII = STI.getInstrInfo(); + bool IsRV64 = STI.hasFeature(RISCV::Feature64Bit); + int64_t SlotSize = STI.getXLen() / 8; + // Store return address to shadow call stack + // s[w|d] ra, 0(s2) + // addi s2, s2, [4|8] + BuildMI(MBB, MI, DL, TII->get(IsRV64 ? RISCV::SD : RISCV::SW)) + .addReg(RAReg) + .addReg(SCSPReg) + .addImm(0); + BuildMI(MBB, MI, DL, TII->get(RISCV::ADDI)) + .addReg(SCSPReg, RegState::Define) + .addReg(SCSPReg) + .addImm(SlotSize); +} + +static void emitSCSEpilogue(MachineFunction &MF, MachineBasicBlock &MBB, + MachineBasicBlock::iterator MI, + const DebugLoc &DL) { + if (!MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack)) + return; + + const auto &STI = MF.getSubtarget(); + Register RAReg = STI.getRegisterInfo()->getRARegister(); + + // Do not restore RA from the SCS if it's not saved to the regular stack, + // i.e. RA is not at risk of being to overwritten. + std::vector &CSI = MF.getFrameInfo().getCalleeSavedInfo(); + if (std::none_of(CSI.begin(), CSI.end(), + [&](CalleeSavedInfo &CSR) { return CSR.getReg() == RAReg; })) + return; + + Register SCSPReg = RISCVABI::getSCSPReg(); + + auto &Ctx = MF.getFunction().getContext(); + if (!STI.isRegisterReservedByUser(SCSPReg)) { + Ctx.diagnose(DiagnosticInfoUnsupported{MF.getFunction(), + "x18 not reserved by user for Shadow Call Stack."}); + return; + } + + const auto *RVFI = MF.getInfo(); + if (RVFI->useSaveRestoreLibCalls(MF)) { + Ctx.diagnose(DiagnosticInfoUnsupported{MF.getFunction(), + "Shadow Call Stack cannot be combined with Save/Restore LibCalls."}); + return; + } + + const RISCVInstrInfo *TII = STI.getInstrInfo(); + bool IsRV64 = STI.hasFeature(RISCV::Feature64Bit); + int64_t SlotSize = STI.getXLen() / 8; + // Load return address from shadow call stack + // l[w|d] ra, -[4|8](s2) + // addi s2, s2, -[4|8] + BuildMI(MBB, MI, DL, TII->get(IsRV64 ? RISCV::LD : RISCV::LW)) + .addReg(RAReg, RegState::Define) + .addReg(SCSPReg) + .addImm(-SlotSize); + BuildMI(MBB, MI, DL, TII->get(RISCV::ADDI)) + .addReg(SCSPReg, RegState::Define) + .addReg(SCSPReg) + .addImm(-SlotSize); +} + // 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,15 +320,18 @@ Register SPReg = getSPReg(STI); Register BPReg = RISCVABI::getBPReg(); + // Debug location must be unknown since the first debug location is used + // to determine the end of the prologue. + DebugLoc DL; + + // Emit prologue for shadow call stack. + emitSCSPrologue(MF, MBB, MBBI, DL); + // Since spillCalleeSavedRegisters may have inserted a libcall, skip past // any instructions marked as FrameSetup while (MBBI != MBB.end() && MBBI->getFlag(MachineInstr::FrameSetup)) ++MBBI; - // Debug location must be unknown since the first debug location is used - // to determine the end of the prologue. - DebugLoc DL; - // Determine the correct frame layout determineFrameLayout(MF); @@ -457,6 +558,9 @@ // Deallocate stack adjustReg(MBB, MBBI, DL, SPReg, SPReg, StackSize, MachineInstr::FrameDestroy); + + // Emit epilogue for shadow call stack. + emitSCSEpilogue(MF, MBB, MBBI, DL); } int RISCVFrameLowering::getFrameIndexReference(const MachineFunction &MF, diff --git a/llvm/lib/Target/RISCV/Utils/RISCVBaseInfo.h b/llvm/lib/Target/RISCV/Utils/RISCVBaseInfo.h --- a/llvm/lib/Target/RISCV/Utils/RISCVBaseInfo.h +++ b/llvm/lib/Target/RISCV/Utils/RISCVBaseInfo.h @@ -208,6 +208,9 @@ // Returns the register used to hold the stack pointer after realignment. Register getBPReg(); +// Returns the register holding shadow call stack pointer. +Register getSCSPReg(); + } // namespace RISCVABI namespace RISCVFeatures { diff --git a/llvm/lib/Target/RISCV/Utils/RISCVBaseInfo.cpp b/llvm/lib/Target/RISCV/Utils/RISCVBaseInfo.cpp --- a/llvm/lib/Target/RISCV/Utils/RISCVBaseInfo.cpp +++ b/llvm/lib/Target/RISCV/Utils/RISCVBaseInfo.cpp @@ -67,6 +67,9 @@ // saved registers and X8 will be used as fp. So we choose X9 as bp. Register getBPReg() { return RISCV::X9; } +// Returns the register holding shadow call stack pointer. +Register getSCSPReg() { return RISCV::X18; } + } // namespace RISCVABI namespace RISCVFeatures { 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,174 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x18 -verify-machineinstrs < %s \ +; RUN: | FileCheck %s --check-prefix=RV32 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x18 -verify-machineinstrs < %s \ +; RUN: | FileCheck %s --check-prefix=RV64 + +define void @f1() shadowcallstack { +; RV32-LABEL: f1: +; RV32: # %bb.0: +; RV32-NEXT: ret +; +; RV64-LABEL: f1: +; RV64: # %bb.0: +; RV64-NEXT: ret + ret void +} + +declare void @foo() + +define void @f2() shadowcallstack { +; RV32-LABEL: f2: +; RV32: # %bb.0: +; RV32-NEXT: tail foo +; +; RV64-LABEL: f2: +; RV64: # %bb.0: +; RV64-NEXT: tail foo + tail call void @foo() + ret void +} + +declare i32 @bar() + +define i32 @f3() shadowcallstack { +; RV32-LABEL: f3: +; RV32: # %bb.0: +; RV32-NEXT: sw ra, 0(s2) +; RV32-NEXT: addi s2, s2, 4 +; RV32-NEXT: addi sp, sp, -16 +; RV32-NEXT: .cfi_def_cfa_offset 16 +; RV32-NEXT: sw ra, 12(sp) +; RV32-NEXT: .cfi_offset ra, -4 +; RV32-NEXT: call bar +; RV32-NEXT: lw ra, 12(sp) +; RV32-NEXT: addi sp, sp, 16 +; RV32-NEXT: lw ra, -4(s2) +; RV32-NEXT: addi s2, s2, -4 +; RV32-NEXT: ret +; +; RV64-LABEL: f3: +; RV64: # %bb.0: +; RV64-NEXT: sd ra, 0(s2) +; RV64-NEXT: addi s2, s2, 8 +; RV64-NEXT: addi sp, sp, -16 +; RV64-NEXT: .cfi_def_cfa_offset 16 +; RV64-NEXT: sd ra, 8(sp) +; RV64-NEXT: .cfi_offset ra, -8 +; RV64-NEXT: call bar +; RV64-NEXT: ld ra, 8(sp) +; RV64-NEXT: addi sp, sp, 16 +; RV64-NEXT: ld ra, -8(s2) +; RV64-NEXT: addi s2, s2, -8 +; RV64-NEXT: ret + %res = call i32 @bar() + %res1 = add i32 %res, 1 + ret i32 %res +} + +define i32 @f4() shadowcallstack { +; RV32-LABEL: f4: +; RV32: # %bb.0: +; RV32-NEXT: sw ra, 0(s2) +; RV32-NEXT: addi s2, s2, 4 +; RV32-NEXT: addi sp, sp, -16 +; RV32-NEXT: .cfi_def_cfa_offset 16 +; RV32-NEXT: sw ra, 12(sp) +; RV32-NEXT: sw s0, 8(sp) +; RV32-NEXT: sw s1, 4(sp) +; RV32-NEXT: sw s3, 0(sp) +; RV32-NEXT: .cfi_offset ra, -4 +; RV32-NEXT: .cfi_offset s0, -8 +; RV32-NEXT: .cfi_offset s1, -12 +; RV32-NEXT: .cfi_offset s3, -16 +; RV32-NEXT: call bar +; RV32-NEXT: mv s3, a0 +; RV32-NEXT: call bar +; RV32-NEXT: mv s1, a0 +; RV32-NEXT: call bar +; RV32-NEXT: mv s0, a0 +; RV32-NEXT: call bar +; RV32-NEXT: add a1, s3, s1 +; RV32-NEXT: add a0, s0, a0 +; RV32-NEXT: add a0, a1, a0 +; RV32-NEXT: lw s3, 0(sp) +; RV32-NEXT: lw s1, 4(sp) +; RV32-NEXT: lw s0, 8(sp) +; RV32-NEXT: lw ra, 12(sp) +; RV32-NEXT: addi sp, sp, 16 +; RV32-NEXT: lw ra, -4(s2) +; RV32-NEXT: addi s2, s2, -4 +; RV32-NEXT: ret +; +; RV64-LABEL: f4: +; RV64: # %bb.0: +; RV64-NEXT: sd ra, 0(s2) +; RV64-NEXT: addi s2, s2, 8 +; RV64-NEXT: addi sp, sp, -32 +; RV64-NEXT: .cfi_def_cfa_offset 32 +; RV64-NEXT: sd ra, 24(sp) +; RV64-NEXT: sd s0, 16(sp) +; RV64-NEXT: sd s1, 8(sp) +; RV64-NEXT: sd s3, 0(sp) +; RV64-NEXT: .cfi_offset ra, -8 +; RV64-NEXT: .cfi_offset s0, -16 +; RV64-NEXT: .cfi_offset s1, -24 +; RV64-NEXT: .cfi_offset s3, -32 +; RV64-NEXT: call bar +; RV64-NEXT: mv s3, a0 +; RV64-NEXT: call bar +; RV64-NEXT: mv s1, a0 +; RV64-NEXT: call bar +; RV64-NEXT: mv s0, a0 +; RV64-NEXT: call bar +; RV64-NEXT: add a1, s3, s1 +; RV64-NEXT: add a0, s0, a0 +; RV64-NEXT: addw a0, a1, a0 +; RV64-NEXT: ld s3, 0(sp) +; RV64-NEXT: ld s1, 8(sp) +; RV64-NEXT: ld s0, 16(sp) +; RV64-NEXT: ld ra, 24(sp) +; RV64-NEXT: addi sp, sp, 32 +; RV64-NEXT: ld ra, -8(s2) +; RV64-NEXT: addi s2, s2, -8 +; RV64-NEXT: ret + %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 + ret i32 %res1234 +} + +define i32 @f5() shadowcallstack nounwind { +; RV32-LABEL: f5: +; RV32: # %bb.0: +; RV32-NEXT: sw ra, 0(s2) +; RV32-NEXT: addi s2, s2, 4 +; RV32-NEXT: addi sp, sp, -16 +; RV32-NEXT: sw ra, 12(sp) +; RV32-NEXT: call bar +; RV32-NEXT: lw ra, 12(sp) +; RV32-NEXT: addi sp, sp, 16 +; RV32-NEXT: lw ra, -4(s2) +; RV32-NEXT: addi s2, s2, -4 +; RV32-NEXT: ret +; +; RV64-LABEL: f5: +; RV64: # %bb.0: +; RV64-NEXT: sd ra, 0(s2) +; RV64-NEXT: addi s2, s2, 8 +; RV64-NEXT: addi sp, sp, -16 +; RV64-NEXT: sd ra, 8(sp) +; RV64-NEXT: call bar +; RV64-NEXT: ld ra, 8(sp) +; RV64-NEXT: addi sp, sp, 16 +; RV64-NEXT: ld ra, -8(s2) +; RV64-NEXT: addi s2, s2, -8 +; RV64-NEXT: ret + %res = call i32 @bar() + %res1 = add i32 %res, 1 + ret i32 %res +}