https://gcc.gnu.org/g:f531673917e4f80ad51eda0d806f0479c501a907

commit r15-3800-gf531673917e4f80ad51eda0d806f0479c501a907
Author: Matthieu Longo <matthieu.lo...@arm.com>
Date:   Mon Sep 23 15:03:30 2024 +0100

    aarch64: store signing key and signing method in DWARF _Unwind_FrameState
    
    This patch is only a refactoring of the existing implementation
    of PAuth and returned-address signing. The existing behavior is
    preserved.
    
    _Unwind_FrameState already contains several CIE and FDE information
    (see the attributes below the comment "The information we care
    about from the CIE/FDE" in libgcc/unwind-dw2.h).
    The patch aims at moving the information from DWARF CIE (signing
    key stored in the augmentation string) and FDE (the used signing
    method) into _Unwind_FrameState along the already-stored CIE and
    FDE information.
    Note: those information have to be saved in frame_state_reg_info
    instead of _Unwind_FrameState as they need to be savable by
    DW_CFA_remember_state and restorable by DW_CFA_restore_state, that
    both rely on the attribute "prev".
    
    Those new information in _Unwind_FrameState simplifies the look-up
    of the signing key when the return address is demangled. It also
    allows future signing methods to be easily added.
    
    _Unwind_FrameState is not a part of the public API of libunwind,
    so the change is backward compatible.
    
    A new architecture-specific handler MD_ARCH_EXTENSION_FRAME_INIT
    allows to reset values (if needed) in the frame state and unwind
    context before changing the frame state to the caller context.
    
    A new architecture-specific handler MD_ARCH_EXTENSION_CIE_AUG_HANDLER
    isolates the architecture-specific augmentation strings in AArch64
    backend, and allows others architectures to reuse augmentation
    strings that would have clashed with AArch64 DWARF extensions.
    
    aarch64_demangle_return_addr, DW_CFA_AARCH64_negate_ra_state and
    DW_CFA_val_expression cases in libgcc/unwind-dw2-execute_cfa.h
    were documented to clarify where the value of the RA state register
    is stored (FS and CONTEXT respectively).
    
    libgcc/ChangeLog:
    
            * config/aarch64/aarch64-unwind.h
            (AARCH64_DWARF_RA_STATE_MASK): The mask for RA state register.
            (aarch64_ra_signing_method_t): The diversifiers used to sign a
            function's return address.
            (aarch64_pointer_auth_key): The key used to sign a function's
            return address.
            (aarch64_cie_signed_with_b_key): Deleted as the signing key is
            available now in _Unwind_FrameState.
            (MD_ARCH_EXTENSION_CIE_AUG_HANDLER): New CIE augmentation string
            handler for architecture extensions.
            (MD_ARCH_EXTENSION_FRAME_INIT): New architecture-extension
            initialization routine for DWARF frame state and context before
            execution of DWARF instructions.
            (aarch64_context_ra_state_get): Read RA state register from CONTEXT.
            (aarch64_ra_state_get): Read RA state register from FS.
            (aarch64_ra_state_set): Write RA state register into FS.
            (aarch64_ra_state_toggle): Toggle RA state register in FS.
            (aarch64_cie_aug_handler): Handler AArch64 augmentation strings.
            (aarch64_arch_extension_frame_init): Initialize defaults for the
            signing key (PAUTH_KEY_A), and RA state register (RA_no_signing).
            (aarch64_demangle_return_addr): Rely on the frame registers and
            the signing_key attribute in _Unwind_FrameState.
            * unwind-dw2-execute_cfa.h:
            Use the right alias DW_CFA_AARCH64_negate_ra_state for __aarch64__
            instead of DW_CFA_GNU_window_save.
            (DW_CFA_AARCH64_negate_ra_state): Save the signing method in RA
            state register. Toggle RA state register without resetting 'how'
            to REG_UNSAVED.
            * unwind-dw2.c:
            (extract_cie_info): Save the signing key in the current
            _Unwind_FrameState while parsing the augmentation data.
            (uw_frame_state_for): Reset some attributes related to architecture
            extensions in _Unwind_FrameState.
            (uw_update_context): Move authentication code to AArch64 unwinding.
            * unwind-dw2.h (enum register_rule): Give a name to the existing
            enum for the register rules, and replace 'unsigned char' by 'enum
            register_rule' to facilitate debugging in GDB.
            (_Unwind_FrameState): Add a new architecture-extension attribute
            to store the signing key.

Diff:
---
 libgcc/config/aarch64/aarch64-unwind.h | 145 +++++++++++++++++++++++++++------
 libgcc/unwind-dw2-execute_cfa.h        |  26 +++---
 libgcc/unwind-dw2.c                    |  19 +++--
 libgcc/unwind-dw2.h                    |  17 +++-
 4 files changed, 159 insertions(+), 48 deletions(-)

diff --git a/libgcc/config/aarch64/aarch64-unwind.h 
b/libgcc/config/aarch64/aarch64-unwind.h
index daf96624b5e1..94ea5891b4eb 100644
--- a/libgcc/config/aarch64/aarch64-unwind.h
+++ b/libgcc/config/aarch64/aarch64-unwind.h
@@ -25,52 +25,145 @@ 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 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)
 {
-  const struct dwarf_fde *fde = _Unwind_Find_FDE (context->bases.func,
-                                                 &context->bases);
-  if (fde != NULL)
+  fs->regs.reg[AARCH64_DWARF_REGNUM_RA_STATE].loc.offset = signing_method;
+}
+
+static inline void
+aarch64_fs_ra_state_toggle (_Unwind_FrameState *fs)
+{
+  /* /!\ Mixing DW_CFA_val_expression with DW_CFA_AARCH64_negate_ra_state will
+     result in undefined behavior (likely an unwinding failure), as the
+     chronology of the DWARF directives will be broken.  */
+  gcc_assert (fs->regs.how[AARCH64_DWARF_REGNUM_RA_STATE] == REG_ARCHEXT);
+
+  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;
+}
+
+/* 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)
+{
+  /* By default, DW_CFA_AARCH64_negate_ra_state assumes key A is being used
+     for signing.  This can be overridden by adding 'B' to the augmentation
+     string.  */
+  fs->regs.signing_key = AARCH64_PAUTH_KEY_A;
+
+  /* All registers are initially in state REG_UNSAVED, which indicates that
+     they inherit register values from the previous frame.  However, the
+     return address starts every frame in the "unsigned" state.  It also
+     starts every frame in a state that supports the original toggle-based
+     DW_CFA_AARCH64_negate_ra_state method of controlling RA signing.  */
+  fs->regs.how[AARCH64_DWARF_REGNUM_RA_STATE] = 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.  */
+   on it using CFA of current frame.
 
+   Note: when DW_CFA_val_expression is used, FS only records the location of 
the
+   associated CFI program, rather than the value of the expression itself.
+   The CFI program is executed by uw_update_context when updating the context,
+   so the value of the expression must be taken from CONTEXT rather than FS.  
*/
 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;
-
-  /* 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)
+  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, or is used to
+     describe the initial state set in aarch64_arch_extension_frame_init.
+     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 (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);
     }
diff --git a/libgcc/unwind-dw2-execute_cfa.h b/libgcc/unwind-dw2-execute_cfa.h
index a6b249f9ad6a..5d73e0420b1f 100644
--- a/libgcc/unwind-dw2-execute_cfa.h
+++ b/libgcc/unwind-dw2-execute_cfa.h
@@ -271,23 +271,25 @@
              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.  */
+         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 +297,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 0849e89cd343..40d64c0c0a39 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:
+      /* 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.  */
+      case REG_ARCHEXT:
        break;
 
       case REG_SAVED_OFFSET:
diff --git a/libgcc/unwind-dw2.h b/libgcc/unwind-dw2.h
index 0dd8611f6970..e6b30b7188e7 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: this information has 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.  */

Reply via email to