[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