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.
---
libgcc/config/aarch64/aarch64-unwind.h | 154 ++++++++++++++++++++-----
libgcc/unwind-dw2-execute_cfa.h | 34 ++++--
libgcc/unwind-dw2.c | 19 ++-
libgcc/unwind-dw2.h | 17 ++-
4 files changed, 175 insertions(+), 49 deletions(-)
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. */