[libunwind] [libunwind] Add RISC-V Zicfiss shadow stack support for exception unwinding (PR #231840)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Oct 14 02:46:54 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libunwind
Author: A-Shehab
<details>
<summary>Changes</summary>
When Zicfiss shadow stack enforcement is active, exception unwinding breaks: phase 2 skips several frames without executing their returns, leaving stale entries on the shadow stack, so the next executed return faults on a return address mismatch. The resume into the landing pad must also avoid any call instruction, which would push a bogus shadow stack entry, and the return address should be verified against the shadow stack before resuming.
Extend the existing shadow stack unwinding support (x86 CET, AArch64 GCS) to RISC-V Zicfiss so unwinding pops the skipped frames' shadow stack entries, checks the return address, and resumes without a call.
---
Full diff: https://github.com/llvm/llvm-project/pull/231840.diff
5 Files Affected:
- (modified) libunwind/src/Registers.hpp (+10-1)
- (modified) libunwind/src/UnwindCursor.hpp (+6-3)
- (modified) libunwind/src/UnwindLevel1.c (+24-2)
- (modified) libunwind/src/UnwindRegistersRestore.S (+1-1)
- (modified) libunwind/src/shadow_stack_unwind.h (+17)
``````````diff
diff --git a/libunwind/src/Registers.hpp b/libunwind/src/Registers.hpp
index 4870e315962e0..3a6fc58d78063 100644
--- a/libunwind/src/Registers.hpp
+++ b/libunwind/src/Registers.hpp
@@ -4271,6 +4271,15 @@ inline const char *Registers_hexagon::getRegisterName(int regNum) {
/// Registers_riscv holds the register state of a thread in a RISC-V
/// process.
+class _LIBUNWIND_HIDDEN Registers_riscv;
+extern "C" void __libunwind_Registers_riscv_jumpto(Registers_riscv *);
+
+#if defined(_LIBUNWIND_USE_ZICFISS)
+extern "C" void *__libunwind_shstk_get_jump_target() {
+ return reinterpret_cast<void *>(&__libunwind_Registers_riscv_jumpto);
+}
+#endif
+
// This check makes it safe when LIBUNWIND_ENABLE_CROSS_UNWINDING enabled.
# ifdef __riscv
# if __riscv_xlen == 32
@@ -4321,7 +4330,7 @@ class _LIBUNWIND_HIDDEN Registers_riscv {
v128 getVectorRegister(int num) const;
void setVectorRegister(int num, v128 value);
static const char *getRegisterName(int num);
- void jumpto();
+ void jumpto() { __libunwind_Registers_riscv_jumpto(this); }
static constexpr int lastDwarfRegNum() {
return _LIBUNWIND_HIGHEST_DWARF_REGISTER_RISCV;
}
diff --git a/libunwind/src/UnwindCursor.hpp b/libunwind/src/UnwindCursor.hpp
index ad110359e26d2..5e148c5107b80 100644
--- a/libunwind/src/UnwindCursor.hpp
+++ b/libunwind/src/UnwindCursor.hpp
@@ -507,7 +507,8 @@ class _LIBUNWIND_HIDDEN AbstractUnwindCursor {
}
#endif
-#if defined(_LIBUNWIND_USE_CET) || defined(_LIBUNWIND_USE_GCS)
+#if defined(_LIBUNWIND_USE_CET) || defined(_LIBUNWIND_USE_GCS) || \
+ defined(_LIBUNWIND_USE_ZICFISS)
virtual void *get_registers() {
_LIBUNWIND_ABORT("get_registers not implemented");
}
@@ -995,7 +996,8 @@ class UnwindCursor : public AbstractUnwindCursor {
virtual uintptr_t getDataRelBase();
#endif
-#if defined(_LIBUNWIND_USE_CET) || defined(_LIBUNWIND_USE_GCS)
+#if defined(_LIBUNWIND_USE_CET) || defined(_LIBUNWIND_USE_GCS) || \
+ defined(_LIBUNWIND_USE_ZICFISS)
virtual void *get_registers() { return &_registers; }
#endif
@@ -3512,7 +3514,8 @@ bool UnwindCursor<A, R>::isReadableAddr(const pint_t addr) const {
}
#endif
-#if defined(_LIBUNWIND_USE_CET) || defined(_LIBUNWIND_USE_GCS)
+#if defined(_LIBUNWIND_USE_CET) || defined(_LIBUNWIND_USE_GCS) || \
+ defined(_LIBUNWIND_USE_ZICFISS)
extern "C" void *__libunwind_shstk_get_registers(unw_cursor_t *cursor) {
AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor;
return co->get_registers();
diff --git a/libunwind/src/UnwindLevel1.c b/libunwind/src/UnwindLevel1.c
index 834fdae6a76d3..9956b4029524c 100644
--- a/libunwind/src/UnwindLevel1.c
+++ b/libunwind/src/UnwindLevel1.c
@@ -47,7 +47,8 @@
// instructions, so for these architectures, normal function calls should be
// avoided when invoking the `jumpto()` function. To do this, we use inline
// assemblies to "goto" the `jumpto()` for these architectures.
-#if !defined(_LIBUNWIND_USE_CET) && !defined(_LIBUNWIND_USE_GCS)
+#if !defined(_LIBUNWIND_USE_CET) && !defined(_LIBUNWIND_USE_GCS) && \
+ !defined(_LIBUNWIND_USE_ZICFISS)
#define __unw_phase2_resume(cursor, payload) \
do { \
__unw_resume_with_frames_walked((cursor), (payload)); \
@@ -88,6 +89,23 @@
: "r"(shstkRegContext), "r"(shstkJumpAddress) \
: "x0", "x1"); \
} while (0)
+#elif defined(_LIBUNWIND_TARGET_RISCV) && defined(_LIBUNWIND_USE_ZICFISS)
+#if __riscv_xlen == 32
+#define __shstk_step_size (4)
+#else
+#define __shstk_step_size (8)
+#endif
+#define __unw_phase2_resume(cursor, payload) \
+ do { \
+ _LIBUNWIND_POP_SHSTK_SSP((payload)); \
+ void *shstkRegContext = __libunwind_shstk_get_registers((cursor)); \
+ void *shstkJumpAddress = __libunwind_shstk_get_jump_target(); \
+ __asm__ volatile("mv a0, %0\n\t" \
+ "jr %1\n\t" \
+ : \
+ : "r"(shstkRegContext), "r"(shstkJumpAddress) \
+ : "a0"); \
+ } while (0)
#endif
// We need this helper function as the semantics of casting between integers and
@@ -224,6 +242,9 @@ unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor,
unsigned long shadowStackTop = 0;
if (__chkfeat(_CHKFEAT_GCS))
shadowStackTop = (unsigned long)__gcspr();
+#elif defined(_LIBUNWIND_USE_ZICFISS)
+ unsigned long shadowStackTop;
+ __asm__ volatile("ssrdp %0" : "=r"(shadowStackTop));
#endif
// Walk each frame until we reach where search phase said to stop.
while (true) {
@@ -280,7 +301,8 @@ unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor,
// stack against return address stored in shadow stack, if the 2 addresses don't
// match, it means return address in normal stack has been corrupted, we return
// _URC_FATAL_PHASE2_ERROR.
-#if defined(_LIBUNWIND_USE_CET) || defined(_LIBUNWIND_USE_GCS)
+#if defined(_LIBUNWIND_USE_CET) || defined(_LIBUNWIND_USE_GCS) || \
+ defined(_LIBUNWIND_USE_ZICFISS)
if (shadowStackTop != 0) {
unw_word_t retInNormalStack;
__unw_get_reg(cursor, UNW_REG_IP, &retInNormalStack);
diff --git a/libunwind/src/UnwindRegistersRestore.S b/libunwind/src/UnwindRegistersRestore.S
index b299bcd0a7594..2c0a673531216 100644
--- a/libunwind/src/UnwindRegistersRestore.S
+++ b/libunwind/src/UnwindRegistersRestore.S
@@ -1264,7 +1264,7 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind15Registers_sparc6jumptoEv)
// thread_state pointer is in a0
//
.p2align 2
-DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind15Registers_riscv6jumptoEv)
+DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_riscv_jumpto)
# if defined(__riscv_flen)
.irp i,FROM_0_TO_31
FLOAD f\i, (RISCV_FOFFSET + RISCV_FSIZE * \i)(a0)
diff --git a/libunwind/src/shadow_stack_unwind.h b/libunwind/src/shadow_stack_unwind.h
index b00ca2c9327c7..e102f9b9293ba 100644
--- a/libunwind/src/shadow_stack_unwind.h
+++ b/libunwind/src/shadow_stack_unwind.h
@@ -35,6 +35,23 @@
} while (0)
#endif
+// On RISC-V we use _LIBUNWIND_USE_ZICFISS to indicate that Zicfiss shadow
+// stack is supported. The SSP CSR is adjusted directly to pop stale entries.
+#if defined(_LIBUNWIND_TARGET_RISCV) && defined(__riscv_shadow_stack)
+#define _LIBUNWIND_USE_ZICFISS 1
+
+#define _LIBUNWIND_POP_SHSTK_SSP(x) \
+ do { \
+ unsigned long ssp; \
+ __asm__ volatile("ssrdp %0" : "=r"(ssp)); \
+ if (ssp != 0) { \
+ unsigned long _pop_n = (x); \
+ ssp += _pop_n * (__riscv_xlen / 8); \
+ __asm__ volatile("csrw ssp, %0" :: "r"(ssp)); \
+ } \
+ } while (0)
+#endif
+
// On AArch64 we use _LIBUNWIND_USE_GCS to indicate that GCS is supported. We
// need to guard any use of GCS instructions with __chkfeat though, as GCS may
// not be enabled.
``````````
</details>
https://github.com/llvm/llvm-project/pull/231840
More information about the cfe-commits
mailing list