diff --git a/libgcc/config/aarch64/aarch64-unwind.h b/libgcc/config/aarch64/aarch64-unwind.h
index daf96624b5e..cc225a7e207 100644
--- a/libgcc/config/aarch64/aarch64-unwind.h
+++ b/libgcc/config/aarch64/aarch64-unwind.h
@@ -25,55 +25,155 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 #if !defined (AARCH64_UNWIND_H) && !defined (__ILP32__)
 #define AARCH64_UNWIND_H
 
-#define DWARF_REGNUM_AARCH64_RA_STATE 34
+#include "ansidecl.h"
+#include <stdbool.h>
+
+#define AARCH64_DWARF_REGNUM_RA_STATE 34
+#define AARCH64_DWARF_RA_STATE_MASK   0x1
+
+/* The diversifiers used to sign a function's return address. */
+typedef enum
+{
+  AARCH64_RA_no_signing = 0x0,
+  AARCH64_RA_signing_SP = 0x1,
+} __attribute__((packed)) aarch64_RA_signing_method_t;
+
+/* The key used to sign a function's return address. */
+typedef enum {
+  AARCH64_PAUTH_KEY_A,
+  AARCH64_PAUTH_KEY_B,
+} __attribute__((packed)) aarch64_pointer_auth_key;
+
+#define MD_ARCH_EXTENSION_CIE_AUG_HANDLER(fs, aug) \
+  aarch64_cie_aug_handler(fs, aug)
+
+#define MD_ARCH_EXTENSION_FRAME_INIT(context, fs) \
+  aarch64_arch_extension_frame_init(context, fs)
 
 #define MD_DEMANGLE_RETURN_ADDR(context, fs, addr) \
   aarch64_demangle_return_addr (context, fs, addr)
 
-static inline int
-aarch64_cie_signed_with_b_key (struct _Unwind_Context *context)
+static inline aarch64_RA_signing_method_t
+aarch64_context_RA_state_get(struct _Unwind_Context *context)
+{
+  const int index = AARCH64_DWARF_REGNUM_RA_STATE;
+  return _Unwind_GetGR (context, index) & AARCH64_DWARF_RA_STATE_MASK;
+}
+
+static inline aarch64_RA_signing_method_t
+aarch64_fs_RA_state_get(_Unwind_FrameState const* fs)
 {
-  const struct dwarf_fde *fde = _Unwind_Find_FDE (context->bases.func,
-						  &context->bases);
-  if (fde != NULL)
+  const int index = AARCH64_DWARF_REGNUM_RA_STATE;
+  return fs->regs.reg[index].loc.offset & AARCH64_DWARF_RA_STATE_MASK;
+}
+
+static inline void
+aarch64_fs_RA_state_set(_Unwind_FrameState *fs,
+			aarch64_RA_signing_method_t signing_method)
+{
+  fs->regs.reg[AARCH64_DWARF_REGNUM_RA_STATE].loc.offset = signing_method;
+}
+
+static inline void
+aarch64_fs_RA_state_toggle(_Unwind_FrameState *fs)
+{
+  aarch64_RA_signing_method_t signing_method = aarch64_fs_RA_state_get(fs);
+  gcc_assert (signing_method == AARCH64_RA_no_signing ||
+	      signing_method == AARCH64_RA_signing_SP);
+  aarch64_fs_RA_state_set(fs, (signing_method == AARCH64_RA_no_signing)
+			  ? AARCH64_RA_signing_SP
+			  : AARCH64_RA_no_signing);
+}
+
+/* CIE handler for custom augmentation string.  */
+static inline bool
+aarch64_cie_aug_handler(_Unwind_FrameState *fs, unsigned char aug)
+{
+  // AArch64 B-key pointer authentication.
+  if (aug == 'B')
     {
-      const struct dwarf_cie *cie = get_cie (fde);
-      if (cie != NULL)
-	{
-	  const unsigned char *aug_str = cie->augmentation;
-	  return __builtin_strchr ((const char *) aug_str,
-				   'B') == NULL ? 0 : 1;
-	}
+      fs->regs.signing_key = AARCH64_PAUTH_KEY_B;
+      return true;
     }
-  return 0;
+  return false;
 }
 
-/* Do AArch64 private extraction on ADDR_WORD based on context info CONTEXT and
-   unwind frame info FS.  If ADDR_WORD is signed, we do address authentication
-   on it using CFA of current frame.  */
+/* At the entrance of a new frame, some cached information from the CIE/FDE,
+ * and registers values related to architectural extensions require a default
+ * initialization.
+ * If any of those values related to architecture extensions had to be saved
+ * for the next frame, it should be done via the architecture extensions handler
+ * MD_FROB_UPDATE_CONTEXT in uw_update_context_1 (libgcc/unwind-dw2.c).  */
+static inline void
+aarch64_arch_extension_frame_init (struct _Unwind_Context *context ATTRIBUTE_UNUSED,
+				   _Unwind_FrameState *fs)
+{
+  // Reset previously cached CIE/FDE information.
+  fs->regs.signing_key = AARCH64_PAUTH_KEY_A;
+
+  // Reset some registers.
+  // Note: the associated fs->how table is automatically reset to REG_UNSAVED in
+  // uw_frame_state_for (libgcc/unwind-dw2.c). REG_UNSAVED means that whatever
+  // was in the previous context is the current register value. In other words,
+  // the next context inherits by default the previous value for a register.
+  // To keep this logic coherent with some architecture extensions, we need to
+  // reinitialize fs->how for some registers to REG_ARCHEXT.
+  const int reg = AARCH64_DWARF_REGNUM_RA_STATE;
+  fs->regs.how[reg] = REG_ARCHEXT;
+  aarch64_fs_RA_state_set(fs, AARCH64_RA_no_signing);
+}
 
+/* Do AArch64 private extraction on ADDR_WORD based on context info CONTEXT and
+   unwind frame info FS. If ADDR_WORD is signed, we do address authentication
+   on it using CFA of current frame.
+   Note: this function is called after uw_update_context_1 in uw_update_context.
+   uw_update_context_1 is the function in which val expression are computed. Thus
+   if DW_CFA_val_expression is used, the value of the RA state register is stored
+   in CONTEXT, not FS. (more details about when DW_CFA_val_expression is used in
+   the next comment below)  */
 static inline void *
 aarch64_demangle_return_addr (struct _Unwind_Context *context,
 			      _Unwind_FrameState *fs,
 			      _Unwind_Word addr_word)
 {
   void *addr = (void *)addr_word;
-  const int reg = DWARF_REGNUM_AARCH64_RA_STATE;
-
-  if (fs->regs.how[reg] == REG_UNSAVED)
-    return addr;
+  const int reg = AARCH64_DWARF_REGNUM_RA_STATE;
+
+  // In libgcc, REG_ARCHEXT means that the RA state register was set by an AArch64
+  // DWARF instruction and contains a valid value.
+  // Return-address signing state is normally toggled by DW_CFA_AARCH64_negate_ra_state
+  // (also knwon by its alias as DW_CFA_GNU_window_save).
+  // However, RA state register can be set directly via DW_CFA_val_expression
+  // too. GCC does not generate such CFI but some other compilers reportedly do.
+  // (see PR104689 for more details).
+  // Any other value than REG_ARCHEXT should be interpreted as if the RA state register
+  // is set by another DWARF instruction, and the value is fetchable via _Unwind_GetGR.
+  aarch64_RA_signing_method_t signing_method = AARCH64_RA_no_signing;
+  if (fs->regs.how[reg] == REG_ARCHEXT)
+    {
+      signing_method = aarch64_fs_RA_state_get(fs);
+    }
+  else if (fs->regs.how[reg] != REG_UNSAVED)
+    {
+      signing_method = aarch64_context_RA_state_get(context);
+
+      // If the value was set in context, CONTEXT->by_value was set to 1.
+      // uw_install_context_1 contains an assert on by_value, to check that all
+      // registers values have been resolved before installing the context.
+      // This will make the unwinding crash if we don't reset CONTEXT->by_value,
+      // and CONTEXT->reg[reg].
+      context->reg[reg] = _Unwind_Get_Unwind_Context_Reg_Val(0x0);
+      context->by_value[reg] = 0;
+    }
 
-  /* Return-address signing state is toggled by DW_CFA_GNU_window_save (where
-     REG_UNSAVED/REG_UNSAVED_ARCHEXT means RA signing is disabled/enabled),
-     or set by a DW_CFA_expression.  */
-  if (fs->regs.how[reg] == REG_UNSAVED_ARCHEXT
-      || (_Unwind_GetGR (context, reg) & 0x1) != 0)
+  if (signing_method == AARCH64_RA_signing_SP)
     {
       _Unwind_Word salt = (_Unwind_Word) context->cfa;
-      if (aarch64_cie_signed_with_b_key (context) != 0)
+      if (fs->regs.signing_key == AARCH64_PAUTH_KEY_B)
 	return __builtin_aarch64_autib1716 (addr, salt);
       return __builtin_aarch64_autia1716 (addr, salt);
     }
+  // else {} // signing_method == AARCH64_RA_no_signing, nothing to do.
 
   return addr;
 }
diff --git a/libgcc/unwind-dw2-execute_cfa.h b/libgcc/unwind-dw2-execute_cfa.h
index a6b249f9ad6..2ea78c4ef8d 100644
--- a/libgcc/unwind-dw2-execute_cfa.h
+++ b/libgcc/unwind-dw2-execute_cfa.h
@@ -271,23 +271,33 @@
 	      fs->regs.how[reg] = REG_SAVED_VAL_EXP;
 	      fs->regs.reg[reg].loc.exp = insn_ptr;
 	    }
+	  // Don't execute the expression, but jump over it by adding
+	  // DW_FORM_block's size to insn_ptr.
 	  insn_ptr = read_uleb128 (insn_ptr, &utmp);
 	  insn_ptr += utmp;
 	  break;
 
-	case DW_CFA_GNU_window_save:
 #if defined (__aarch64__) && !defined (__ILP32__)
-	  /* This CFA is multiplexed with Sparc.  On AArch64 it's used to toggle
-	     return address signing status.  REG_UNSAVED/REG_UNSAVED_ARCHEXT
-	     mean RA signing is disabled/enabled.  */
-	  reg = DWARF_REGNUM_AARCH64_RA_STATE;
-	  gcc_assert (fs->regs.how[reg] == REG_UNSAVED
-		      || fs->regs.how[reg] == REG_UNSAVED_ARCHEXT);
-	  if (fs->regs.how[reg] == REG_UNSAVED)
-	    fs->regs.how[reg] = REG_UNSAVED_ARCHEXT;
-	  else
-	    fs->regs.how[reg] = REG_UNSAVED;
+	case DW_CFA_AARCH64_negate_ra_state:
+	  // This CFA is multiplexed with Sparc.
+	  // On AArch64 it's used to toggle the status of return address signing
+	  // with SP as a diversifier.
+	  // - REG_ARCHEXT means that the RA state register in FS contains a
+	  // valid value, and that no other DWARF directive has changed the value
+	  // of this register.
+	  // - any other value is not compatible with negating the RA state.
+	  reg = AARCH64_DWARF_REGNUM_RA_STATE;
+
+	  // /!\ Mixing DW_CFA_val_expression with DW_CFA_AARCH64_negate_ra_state
+	  // will result in an undefined behavior (likely an unwinding failure),
+	  // as the chronology of the DWARF directives will be broken.
+	  gcc_assert (fs->regs.how[reg] == REG_ARCHEXT);
+
+	  // Toggle current state
+	  aarch64_fs_RA_state_toggle(fs);
+	  break;
 #else
+	case DW_CFA_GNU_window_save:
 	  /* ??? Hardcoded for SPARC register window configuration.  */
 	  if (__LIBGCC_DWARF_FRAME_REGISTERS__ >= 32)
 	    for (reg = 16; reg < 32; ++reg)
@@ -295,8 +305,8 @@
 		fs->regs.how[reg] = REG_SAVED_OFFSET;
 		fs->regs.reg[reg].loc.offset = (reg - 16) * sizeof (void *);
 	      }
-#endif
 	  break;
+#endif
 
 	case DW_CFA_GNU_args_size:
 	  insn_ptr = read_uleb128 (insn_ptr, &utmp);
diff --git a/libgcc/unwind-dw2.c b/libgcc/unwind-dw2.c
index 0849e89cd34..fb60853825d 100644
--- a/libgcc/unwind-dw2.c
+++ b/libgcc/unwind-dw2.c
@@ -501,11 +501,11 @@ extract_cie_info (const struct dwarf_cie *cie, struct _Unwind_Context *context,
 	  fs->signal_frame = 1;
 	  aug += 1;
 	}
-      /* aarch64 B-key pointer authentication.  */
-      else if (aug[0] == 'B')
-	{
-	  aug += 1;
-      }
+
+#if defined(MD_ARCH_EXTENSION_CIE_AUG_HANDLER)
+      else if (MD_ARCH_EXTENSION_CIE_AUG_HANDLER(fs, aug[0]))
+	aug += 1;
+#endif
 
       /* Otherwise we have an unknown augmentation string.
 	 Bail unless we saw a 'z' prefix.  */
@@ -996,6 +996,9 @@ uw_frame_state_for (struct _Unwind_Context *context, _Unwind_FrameState *fs)
 
   memset (&fs->regs.how[0], 0,
 	  sizeof (*fs) - offsetof (_Unwind_FrameState, regs.how[0]));
+#if defined(MD_ARCH_EXTENSION_FRAME_INIT)
+  MD_ARCH_EXTENSION_FRAME_INIT(context, fs);
+#endif
   context->args_size = 0;
   context->lsda = 0;
 
@@ -1197,7 +1200,11 @@ uw_update_context_1 (struct _Unwind_Context *context, _Unwind_FrameState *fs)
       {
       case REG_UNSAVED:
       case REG_UNDEFINED:
-      case REG_UNSAVED_ARCHEXT:
+      case REG_ARCHEXT: // If the value depends on an augmenter, then there is
+			// no processing to do here, and the value computation
+			// should be delayed until the architecture handler
+			// computes the value correctly based on the augmenter
+			// information.
 	break;
 
       case REG_SAVED_OFFSET:
diff --git a/libgcc/unwind-dw2.h b/libgcc/unwind-dw2.h
index 0dd8611f697..b75f4c65f98 100644
--- a/libgcc/unwind-dw2.h
+++ b/libgcc/unwind-dw2.h
@@ -22,16 +22,17 @@
    see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
    <http://www.gnu.org/licenses/>.  */
 
-enum {
+enum register_rule
+{
   REG_UNSAVED,
   REG_SAVED_OFFSET,
   REG_SAVED_REG,
   REG_SAVED_EXP,
   REG_SAVED_VAL_OFFSET,
   REG_SAVED_VAL_EXP,
-  REG_UNSAVED_ARCHEXT,		/* Target specific extension.  */
+  REG_ARCHEXT,		/* Target specific extension.  */
   REG_UNDEFINED
-};
+} __attribute__((packed));
 
 /* The result of interpreting the frame unwind info for a frame.
    This is all symbolic at this point, as none of the values can
@@ -49,7 +50,7 @@ typedef struct
 	const unsigned char *exp;
       } loc;
     } reg[__LIBGCC_DWARF_FRAME_REGISTERS__+1];
-    unsigned char how[__LIBGCC_DWARF_FRAME_REGISTERS__+1];
+    enum register_rule how[__LIBGCC_DWARF_FRAME_REGISTERS__+1];
 
     enum {
       CFA_UNSET,
@@ -65,6 +66,14 @@ typedef struct
     _Unwind_Sword cfa_offset;
     _Unwind_Word cfa_reg;
     const unsigned char *cfa_exp;
+
+    /* Architecture extensions information from CIE/FDE.
+     * Note: those information have to be saved in struct frame_state_reg_info
+     * instead of _Unwind_FrameState as DW_CFA_restore_state has to be able to
+     * restore them.  */
+#if defined(__aarch64__) && !defined (__ILP32__)
+    unsigned char signing_key;
+#endif
   } regs;
 
   /* The PC described by the current frame state.  */
