Sorry for the slow review.
Matthieu Longo <matthieu.lo...@arm.com> writes:
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).
The abstraction generally looks good. My main comment is that,
if we are putting the new behaviour behind target macros (which I
agree is a good idea), could we also have a target macro to abstract:
+#if defined(__aarch64__) && !defined (__ILP32__)
+ unsigned char signing_key;
+#endif
? E.g. something like:
#ifdef MD_CFI_STATE
MD_CFI_STATE md;
#endif
with aarch64 defining:
#define MD_CFI_STATE struct { unsigned char signing_key; }
(Names and organisation are just suggestions.)
It might be good to try running the patch through
contrig/check_GNU_style.py
since the GCC coding standards are quite picky about formatting and
stylistic issues. The main ones I spotted were:
- In files that already use block comments, all comments should be
block comments rather than // comments. The comments should end
with ". " (full stop and two spaces).
- Block comments are formatted as:
/* Line 1
Line 2. */
rather than as:
/* Line 1
* Line 2. */
- Function names are generally all lowrecase (e.g. "ra" rather than "RA").
- In function calls, there should be a space between the function and
the opening "("
- For pointer types, there should be a space before a "*" (or string of
"*"s), but no space afterwards.
- "const" qualifiers generally go before the type that they qualify,
rather than afterwards.
- The line width should be 80 characters or fewer. (The patch was pretty
good about this, but there were a couple of long lines).
- In multi-line conditions, the ||s and &&s go at the beginning of lines,
rather than at the end. (Same for infix operators in general.)
More detailed comments below.
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;
How about making the comment:
/* 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. */
(if you agree that's a reasonable summary).
+
+ // 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.
How about being a bit more specific about the intent:
/* 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. */
+ const int reg = AARCH64_DWARF_REGNUM_RA_STATE;
+ fs->regs.how[reg] = REG_ARCHEXT;
Very minor, but I think this would be easier to read without the temporary.
+ 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
expressions
+ 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.
It's also used to describe the initial state.
+ // 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.
nit: drop the trailing full stop, since the following line continues
the sentence.
+ // (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;
Is this change to the context necessary for the refactor, or is it
instead needed for the follow-on patches? If it's part of the refactor,
could you explain why it's needed now but wasn't before?
This is the only part that didn't look like a no-op change.
+ }
- /* 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.
IMO it'd be clearer without this line. Alternatively, we could make it:
else if (signing_method == AARCH64_RA_no_signing)
return addr;
gcc_unreachable ();
}
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);
Could we move the assert into aarch64_fs_RA_state_toggle, or do later
patches not want that?
+
+ // 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.
This would more usually be written as a comment above the case, rather than
to the right.
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
this information (singular)
Thanks for doing this. It's definitely a cleaner structure than what
we had before.
Richard
+ * 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. */