summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndrew Kilroy <andrew.kilroy@arm.com>2022-02-17 12:19:23 +0000
committerDave Watson <dade.watson@gmail.com>2022-05-22 10:55:03 -0700
commitf67ef2867bc5e74cdadb3acb790c3fc6161b22e9 (patch)
treec63d5700f3d7c128a7a87354ff7a51e74be5b494 /src
parente07b43c02d5cf1ea060c018fdf2e2ad34b7c7d80 (diff)
downloadlibunwind-f67ef2867bc5e74cdadb3acb790c3fc6161b22e9.tar.gz
Unwind with pointer authentication on arm64
This patch adds the ability for libunwind to unwind a stack where the return address obtained from the arm64 link register (x30) has a pointer authentication code (PAC). Without this patch, the unw_step function terminates early, leaving the user the impression there is only a leaf frame when there are more. The reason for the premature termination is that an aarch64 specific CFI opcode 'DW_CFA_AARCH64_negate_ra_state' is not recognised, and treated as unexpected. Upon the next iteration's call of unw_step, it reports the end of the stack. For an expected callstack of loop // most recent call call3 call2 call1 main // oldest call The caller of libunwind observes: unw_step() = 1 unw_get_reg(UNW_REG_IP) = 0, pc = 0xaaaae4720798 unw_step() = 1 unw_get_reg(UNW_REG_IP) = 0, pc = 0x5faaaae47207cc // ip with PAC unw_step() = 0 // premature end For remote unwinding, this patch adds an optional callback function 'ptrauth_insn_mask' on unw_accessors_t so that unw_step can correctly strip off the PAC to leave the correct return address. This also has the benefit that an attempt to get the name of the function with unw_get_proc_name can succeed. The callback is given the original unw_addr_space_t and as_arg as given to unw_init_remote. The application needs to ensure the mask to be returned is somehow available on the as_arg and callback should return it. The callback is only needed for remote unwinding. In local unwinding, an instruction is used to strip the PAC. A description of pointer authentication is in the armv8 reference manual available here: https://developer.arm.com/documentation/ddi0487/ha/?lang=en (Version H.a, section D5.1.5 Pointer authentication in AArch64 state, page D5-4772) Also documentation for the use of DWARF with pointer authentication, in particular the RA_SIGN_STATE: https://github.com/ARM-software/abi-aa/blob/main/aadwarf64/aadwarf64.rst
Diffstat (limited to 'src')
-rw-r--r--src/aarch64/Ginit.c6
-rw-r--r--src/aarch64/Gregs.c3
-rw-r--r--src/dwarf/Gparser.c80
3 files changed, 89 insertions, 0 deletions
diff --git a/src/aarch64/Ginit.c b/src/aarch64/Ginit.c
index fe6e511d..17b8fcbc 100644
--- a/src/aarch64/Ginit.c
+++ b/src/aarch64/Ginit.c
@@ -408,6 +408,11 @@ get_static_proc_name (unw_addr_space_t as, unw_word_t ip,
return _Uelf64_get_proc_name (as, getpid (), ip, buf, buf_len, offp);
}
+static unw_word_t empty_ptrauth_mask(unw_addr_space_t addr_space_unused, void *as_arg_unused)
+{
+ return 0;
+}
+
HIDDEN void
aarch64_local_addr_space_init (void)
{
@@ -421,6 +426,7 @@ aarch64_local_addr_space_init (void)
local_addr_space.acc.access_fpreg = access_fpreg;
local_addr_space.acc.resume = aarch64_local_resume;
local_addr_space.acc.get_proc_name = get_static_proc_name;
+ local_addr_space.acc.ptrauth_insn_mask = empty_ptrauth_mask;
local_addr_space.big_endian = target_is_big_endian();
unw_flush_cache (&local_addr_space, 0, 0);
}
diff --git a/src/aarch64/Gregs.c b/src/aarch64/Gregs.c
index a8843734..5ae47254 100644
--- a/src/aarch64/Gregs.c
+++ b/src/aarch64/Gregs.c
@@ -88,6 +88,9 @@ tdep_access_reg (struct cursor *c, unw_regnum_t reg, unw_word_t *valp,
case UNW_AARCH64_PSTATE:
loc = c->dwarf.loc[reg];
break;
+ case UNW_AARCH64_RA_SIGN_STATE:
+ Debug (1, "Reading from ra sign state not supported: %u\n", reg);
+ return -UNW_EBADREG;
case UNW_AARCH64_SP:
if (write)
diff --git a/src/dwarf/Gparser.c b/src/dwarf/Gparser.c
index 7cfc4e50..edd34526 100644
--- a/src/dwarf/Gparser.c
+++ b/src/dwarf/Gparser.c
@@ -87,6 +87,13 @@ empty_rstate_stack(dwarf_stackable_reg_state_t **rs_stack)
pop_rstate_stack(rs_stack);
}
+#ifdef UNW_TARGET_AARCH64
+
+static void
+aarch64_negate_ra_sign_state(dwarf_state_record_t *sr);
+
+#endif
+
/* Run a CFI program to update the register state. */
static int
run_cfi_program (struct dwarf_cursor *c, dwarf_state_record_t *sr,
@@ -404,6 +411,11 @@ run_cfi_program (struct dwarf_cursor *c, dwarf_state_record_t *sr,
(regnum - 16) * sizeof (unw_word_t));
Debug (15, "CFA_GNU_window_save\n");
break;
+#elif UNW_TARGET_AARCH64
+ /* This is a specific opcode on aarch64, DW_CFA_AARCH64_negate_ra_state */
+ Debug (15, "DW_CFA_AARCH64_negate_ra_state\n");
+ aarch64_negate_ra_sign_state(sr);
+ break;
#else
/* FALL THROUGH */
#endif
@@ -767,6 +779,61 @@ eval_location_expr (struct dwarf_cursor *c, unw_word_t stack_val, unw_addr_space
return 0;
}
+
+#ifdef UNW_TARGET_AARCH64
+#include "libunwind-aarch64.h"
+
+static void
+aarch64_negate_ra_sign_state(dwarf_state_record_t *sr)
+{
+ unw_word_t ra_sign_state = sr->rs_current.reg.val[UNW_AARCH64_RA_SIGN_STATE];
+ ra_sign_state ^= 0x1;
+ set_reg(sr, UNW_AARCH64_RA_SIGN_STATE, DWARF_WHERE_SAME, ra_sign_state);
+}
+
+static unw_word_t
+aarch64_strip_pac_remote(unw_accessors_t *a, unw_addr_space_t as, void *arg, unw_word_t old_ip)
+{
+ if (a->ptrauth_insn_mask)
+ {
+ unw_word_t ip, insn_mask;
+
+ insn_mask = a->ptrauth_insn_mask(as, arg);
+ ip = old_ip & (~insn_mask);
+
+ Debug(15, "stripping pac from address, before: %lx, after: %lx\n", old_ip, ip);
+ return ip;
+ }
+ else
+ {
+ Debug(15, "return address %lx might be signed, but no means to obtain mask\n", old_ip);
+ return old_ip;
+ }
+}
+
+static unw_word_t
+aarch64_strip_pac_local(unw_word_t in_addr)
+{
+ unw_word_t out_addr = in_addr;
+
+#ifdef __aarch64__
+ // Strip the PAC with XPACLRI instruction
+ register unsigned long long x30 __asm__("x30") = in_addr;
+ __asm__("hint 0x7" : "+r" (x30));
+ out_addr = x30;
+#endif
+
+ return out_addr;
+}
+
+static unw_word_t
+aarch64_get_ra_sign_state(struct dwarf_reg_state *rs)
+{
+ return rs->reg.val[UNW_AARCH64_RA_SIGN_STATE];
+}
+
+#endif
+
static int
apply_reg_state (struct dwarf_cursor *c, struct dwarf_reg_state *rs)
{
@@ -892,6 +959,19 @@ apply_reg_state (struct dwarf_cursor *c, struct dwarf_reg_state *rs)
ret = dwarf_get (c, c->loc[rs->ret_addr_column], &ip);
if (ret < 0)
return ret;
+#ifdef UNW_TARGET_AARCH64
+ if (aarch64_get_ra_sign_state(rs))
+ {
+ if (c->as != unw_local_addr_space)
+ {
+ ip = aarch64_strip_pac_remote(a, as, arg, ip);
+ }
+ else
+ {
+ ip = aarch64_strip_pac_local(ip);
+ }
+ }
+#endif
c->ip = ip;
ret = 1;
}