#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);
}
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)
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);