aarch64: Fix pac-ret with unusual dwarf in libgcc unwinder [PR104689]

Message ID 20220510084511.3930244-1-szabolcs.nagy@arm.com
State Committed
Commit 0d344b557604e966dc7f91739881f03e1f221efd
Headers
Series aarch64: Fix pac-ret with unusual dwarf in libgcc unwinder [PR104689] |

Commit Message

Szabolcs Nagy May 10, 2022, 8:45 a.m. UTC
  The RA_SIGN_STATE dwarf pseudo-register is normally only set using the
DW_CFA_AARCH64_negate_ra_state (== DW_CFA_window_save) operation which
toggles the return address signedness state (the default state is 0).
(It may be set by remember/restore_state CFI too, those save/restore
the state of all registers.)

However RA_SIGN_STATE can be set directly via DW_CFA_val_expression too.
GCC does not generate such CFI but some other compilers reportedly do.

Note: the toggle operation must not be mixed with other dwarf register
rule CFI within the same CIE and FDE.

In libgcc we assume REG_UNSAVED means the RA_STATE is set using toggle
operations, otherwise we assume its value is set by other CFI.

libgcc/ChangeLog:

	PR target/104689
	* config/aarch64/aarch64-unwind.h (aarch64_frob_update_context):
	Handle the !REG_UNSAVED case.
	* unwind-dw2.c (execute_cfa_program): Fail toggle if !REG_UNSAVED.

gcc/testsuite/ChangeLog:

	PR target/104689
	* gcc.target/aarch64/pr104689.c: New test.
---
 gcc/testsuite/gcc.target/aarch64/pr104689.c | 149 ++++++++++++++++++++
 libgcc/config/aarch64/aarch64-unwind.h      |   8 +-
 libgcc/unwind-dw2.c                         |   4 +-
 3 files changed, 159 insertions(+), 2 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/aarch64/pr104689.c
  

Comments

Richard Sandiford May 13, 2022, 3:35 p.m. UTC | #1
Szabolcs Nagy via Gcc-patches <gcc-patches@gcc.gnu.org> writes:
> The RA_SIGN_STATE dwarf pseudo-register is normally only set using the
> DW_CFA_AARCH64_negate_ra_state (== DW_CFA_window_save) operation which
> toggles the return address signedness state (the default state is 0).
> (It may be set by remember/restore_state CFI too, those save/restore
> the state of all registers.)
>
> However RA_SIGN_STATE can be set directly via DW_CFA_val_expression too.
> GCC does not generate such CFI but some other compilers reportedly do.
>
> Note: the toggle operation must not be mixed with other dwarf register
> rule CFI within the same CIE and FDE.
>
> In libgcc we assume REG_UNSAVED means the RA_STATE is set using toggle
> operations, otherwise we assume its value is set by other CFI.

AFAIK, this is the first time I've looked at the RA_SIGN_STATE code,
so this is probably a naive point/question, but: it seems a bit
underhand for the existing code to be using REG_UNSAVED and
loc.offset to hold the toggle state.  Would it make sense to add
a new enum value for known, pre-evaluated constants?  _Unwind_GetGR
would then DTRT for both cases.

That's a comment about the pre-existing code though.  I agree this
patch looks like the right fix if we keep to the current approach.

Thanks,
Richard

>
> libgcc/ChangeLog:
>
> 	PR target/104689
> 	* config/aarch64/aarch64-unwind.h (aarch64_frob_update_context):
> 	Handle the !REG_UNSAVED case.
> 	* unwind-dw2.c (execute_cfa_program): Fail toggle if !REG_UNSAVED.
>
> gcc/testsuite/ChangeLog:
>
> 	PR target/104689
> 	* gcc.target/aarch64/pr104689.c: New test.
> ---
>  gcc/testsuite/gcc.target/aarch64/pr104689.c | 149 ++++++++++++++++++++
>  libgcc/config/aarch64/aarch64-unwind.h      |   8 +-
>  libgcc/unwind-dw2.c                         |   4 +-
>  3 files changed, 159 insertions(+), 2 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.target/aarch64/pr104689.c
>
> diff --git a/gcc/testsuite/gcc.target/aarch64/pr104689.c b/gcc/testsuite/gcc.target/aarch64/pr104689.c
> new file mode 100644
> index 00000000000..3b7adbdfe7d
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/pr104689.c
> @@ -0,0 +1,149 @@
> +/* PR target/104689. Unwind across pac-ret frames with unusual dwarf.  */
> +/* { dg-do run } */
> +/* { dg-require-effective-target lp64 } */
> +/* { dg-options "-fexceptions -O2" } */
> +
> +#include <unwind.h>
> +#include <stdlib.h>
> +#include <stdio.h>
> +
> +#define die() \
> +  do { \
> +    printf ("%s:%d: reached unexpectedly.\n", __FILE__, __LINE__); \
> +    fflush (stdout); \
> +    abort (); \
> +  } while (0)
> +
> +
> +/* Code to invoke unwinding with a logging callback.  */
> +
> +static struct _Unwind_Exception exc;
> +
> +static _Unwind_Reason_Code
> +force_unwind_stop (int version, _Unwind_Action actions,
> +                   _Unwind_Exception_Class exc_class,
> +                   struct _Unwind_Exception *exc_obj,
> +                   struct _Unwind_Context *context,
> +                   void *stop_parameter)
> +{
> +  printf ("%s: CFA: %p PC: %p actions: %d\n",
> +	  __func__,
> +	  (void *)_Unwind_GetCFA (context),
> +	  (void *)_Unwind_GetIP (context),
> +	  (int)actions);
> +  if (actions & _UA_END_OF_STACK)
> +    die ();
> +  return _URC_NO_REASON;
> +}
> +
> +static void force_unwind (void)
> +{
> +#ifndef __USING_SJLJ_EXCEPTIONS__
> +  _Unwind_ForcedUnwind (&exc, force_unwind_stop, 0);
> +#else
> +  _Unwind_SjLj_ForcedUnwind (&exc, force_unwind_stop, 0);
> +#endif
> +}
> +
> +
> +/* Define functions with unusual pac-ret dwarf via top level asm.  */
> +
> +#define STR(x) #x
> +#define DW_CFA_val_expression 0x16
> +#define RA_SIGN_STATE 34
> +#define DW_OP_lit0 0x30
> +#define DW_OP_lit1 0x31
> +
> +#define cfi_escape(a1, a2, a3, a4) \
> +  ".cfi_escape " STR(a1) ", " STR(a2) ", " STR(a3) ", " STR(a4)
> +
> +/* Bytes: 0x16 0x22 0x01 0x30  */
> +#define SET_RA_STATE_0 \
> +  cfi_escape (DW_CFA_val_expression, RA_SIGN_STATE, 1, DW_OP_lit0)
> +
> +/* Bytes: 0x16 0x22 0x01 0x31  */
> +#define SET_RA_STATE_1 \
> +  cfi_escape (DW_CFA_val_expression, RA_SIGN_STATE, 1, DW_OP_lit1)
> +
> +/* These function call their argument.  */
> +void unusual_pac_ret (void *);
> +void unusual_no_pac_ret (void *);
> +
> +asm(""
> +".global unusual_pac_ret\n"
> +".type unusual_pac_ret, %function\n"
> +"unusual_pac_ret:\n"
> +"	.cfi_startproc\n"
> +"	" SET_RA_STATE_0 "\n"
> +"	hint	25 // paciasp\n"
> +"	" SET_RA_STATE_1 "\n"
> +"	stp	x29, x30, [sp, -16]!\n"
> +"	.cfi_def_cfa_offset 16\n"
> +"	.cfi_offset 29, -16\n"
> +"	.cfi_offset 30, -8\n"
> +"	mov	x29, sp\n"
> +"	blr	x0\n"
> +"	ldp	x29, x30, [sp], 16\n"
> +"	.cfi_restore 30\n"
> +"	.cfi_restore 29\n"
> +"	.cfi_def_cfa_offset 0\n"
> +"	hint	29 // autiasp\n"
> +"	" SET_RA_STATE_0 "\n"
> +"	ret\n"
> +"	.cfi_endproc\n");
> +
> +asm(""
> +".global unusual_no_pac_ret\n"
> +".type unusual_no_pac_ret, %function\n"
> +"unusual_no_pac_ret:\n"
> +"	.cfi_startproc\n"
> +"	" SET_RA_STATE_0 "\n"
> +"	stp	x29, x30, [sp, -16]!\n"
> +"	.cfi_def_cfa_offset 16\n"
> +"	.cfi_offset 29, -16\n"
> +"	.cfi_offset 30, -8\n"
> +"	mov	x29, sp\n"
> +"	blr	x0\n"
> +"	ldp	x29, x30, [sp], 16\n"
> +"	.cfi_restore 30\n"
> +"	.cfi_restore 29\n"
> +"	.cfi_def_cfa_offset 0\n"
> +"	ret\n"
> +"	.cfi_endproc\n");
> +
> +
> +/* Functions to create a call chain with mixed pac-ret dwarf.  */
> +
> +__attribute__((target("branch-protection=pac-ret")))
> +static void f2_pac_ret (void)
> +{
> +  force_unwind ();
> +  die ();
> +}
> +
> +__attribute__((target("branch-protection=none")))
> +static void f1_no_pac_ret (void)
> +{
> +  unusual_pac_ret (f2_pac_ret);
> +  die ();
> +}
> +
> +__attribute__((noinline, target("branch-protection=pac-ret")))
> +static void f0_pac_ret (void)
> +{
> +  unusual_no_pac_ret (f1_no_pac_ret);
> +  die ();
> +}
> +
> +static void cleanup_handler (void *p)
> +{
> +  printf ("%s: Success.\n", __func__);
> +  exit (0);
> +}
> +
> +int main ()
> +{
> +  char dummy __attribute__((cleanup (cleanup_handler)));
> +  f0_pac_ret ();
> +  die ();
> +}
> diff --git a/libgcc/config/aarch64/aarch64-unwind.h b/libgcc/config/aarch64/aarch64-unwind.h
> index 40b22d3c288..e082e957821 100644
> --- a/libgcc/config/aarch64/aarch64-unwind.h
> +++ b/libgcc/config/aarch64/aarch64-unwind.h
> @@ -78,7 +78,13 @@ static inline void
>  aarch64_frob_update_context (struct _Unwind_Context *context,
>  			     _Unwind_FrameState *fs)
>  {
> -  if (fs->regs.reg[DWARF_REGNUM_AARCH64_RA_STATE].loc.offset & 0x1)
> +  const int reg = DWARF_REGNUM_AARCH64_RA_STATE;
> +  int ra_signed;
> +  if (fs->regs.reg[reg].how == REG_UNSAVED)
> +    ra_signed = fs->regs.reg[reg].loc.offset & 0x1;
> +  else
> +    ra_signed = _Unwind_GetGR (context, reg) & 0x1;
> +  if (ra_signed)
>      /* The flag is used for re-authenticating EH handler's address.  */
>      context->flags |= RA_SIGNED_BIT;
>    else
> diff --git a/libgcc/unwind-dw2.c b/libgcc/unwind-dw2.c
> index 6ccd8853076..a2eb66dc0de 100644
> --- a/libgcc/unwind-dw2.c
> +++ b/libgcc/unwind-dw2.c
> @@ -1204,7 +1204,9 @@ execute_cfa_program (const unsigned char *insn_ptr,
>  #if defined (__aarch64__) && !defined (__ILP32__)
>  	  /* This CFA is multiplexed with Sparc.  On AArch64 it's used to toggle
>  	     return address signing status.  */
> -	  fs->regs.reg[DWARF_REGNUM_AARCH64_RA_STATE].loc.offset ^= 1;
> +	  reg = DWARF_REGNUM_AARCH64_RA_STATE;
> +	  gcc_assert (fs->regs.reg[reg].how == REG_UNSAVED);
> +	  fs->regs.reg[reg].loc.offset ^= 1;
>  #else
>  	  /* ??? Hardcoded for SPARC register window configuration.  */
>  	  if (__LIBGCC_DWARF_FRAME_REGISTERS__ >= 32)
  
Szabolcs Nagy May 24, 2022, 4:43 p.m. UTC | #2
The 05/13/2022 16:35, Richard Sandiford wrote:
> Szabolcs Nagy via Gcc-patches <gcc-patches@gcc.gnu.org> writes:
> > The RA_SIGN_STATE dwarf pseudo-register is normally only set using the
> > DW_CFA_AARCH64_negate_ra_state (== DW_CFA_window_save) operation which
> > toggles the return address signedness state (the default state is 0).
> > (It may be set by remember/restore_state CFI too, those save/restore
> > the state of all registers.)
> >
> > However RA_SIGN_STATE can be set directly via DW_CFA_val_expression too.
> > GCC does not generate such CFI but some other compilers reportedly do.
> >
> > Note: the toggle operation must not be mixed with other dwarf register
> > rule CFI within the same CIE and FDE.
> >
> > In libgcc we assume REG_UNSAVED means the RA_STATE is set using toggle
> > operations, otherwise we assume its value is set by other CFI.
> 
> AFAIK, this is the first time I've looked at the RA_SIGN_STATE code,
> so this is probably a naive point/question, but: it seems a bit
> underhand for the existing code to be using REG_UNSAVED and
> loc.offset to hold the toggle state.  Would it make sense to add
> a new enum value for known, pre-evaluated constants?  _Unwind_GetGR
> would then DTRT for both cases.
> 
> That's a comment about the pre-existing code though.  I agree this
> patch looks like the right fix if we keep to the current approach.

yes, this is a hack. i looked at introducing a generic REG_*
enum to deal with RA_SIGN_STATE now, but it's a bit awkward:

normally frame state for a reg starts out REG_UNSAVED, which
should mean 0 value for the RA_SIGN_STATE pseudo register.

when moving up frames the uw context gets copied and updated
according to the frame state (where REG_UNSAVED normally means
unmodified copy), this is not right for RA_SIGN_STATE which
should be reset in the absence of related dwarf ops. we can
fix this up in target hooks for update context, but we still
have to special case REG_UNSAVED.

i think introducing a new REG_CONST does not simplify the
aarch64 target code (we might need further changes to get
a clean solution).
  
Richard Sandiford May 25, 2022, 7:40 a.m. UTC | #3
Szabolcs Nagy <szabolcs.nagy@arm.com> writes:
> The 05/13/2022 16:35, Richard Sandiford wrote:
>> Szabolcs Nagy via Gcc-patches <gcc-patches@gcc.gnu.org> writes:
>> > The RA_SIGN_STATE dwarf pseudo-register is normally only set using the
>> > DW_CFA_AARCH64_negate_ra_state (== DW_CFA_window_save) operation which
>> > toggles the return address signedness state (the default state is 0).
>> > (It may be set by remember/restore_state CFI too, those save/restore
>> > the state of all registers.)
>> >
>> > However RA_SIGN_STATE can be set directly via DW_CFA_val_expression too.
>> > GCC does not generate such CFI but some other compilers reportedly do.
>> >
>> > Note: the toggle operation must not be mixed with other dwarf register
>> > rule CFI within the same CIE and FDE.
>> >
>> > In libgcc we assume REG_UNSAVED means the RA_STATE is set using toggle
>> > operations, otherwise we assume its value is set by other CFI.
>> 
>> AFAIK, this is the first time I've looked at the RA_SIGN_STATE code,
>> so this is probably a naive point/question, but: it seems a bit
>> underhand for the existing code to be using REG_UNSAVED and
>> loc.offset to hold the toggle state.  Would it make sense to add
>> a new enum value for known, pre-evaluated constants?  _Unwind_GetGR
>> would then DTRT for both cases.
>> 
>> That's a comment about the pre-existing code though.  I agree this
>> patch looks like the right fix if we keep to the current approach.
>
> yes, this is a hack. i looked at introducing a generic REG_*
> enum to deal with RA_SIGN_STATE now, but it's a bit awkward:
>
> normally frame state for a reg starts out REG_UNSAVED, which
> should mean 0 value for the RA_SIGN_STATE pseudo register.
>
> when moving up frames the uw context gets copied and updated
> according to the frame state (where REG_UNSAVED normally means
> unmodified copy), this is not right for RA_SIGN_STATE which
> should be reset in the absence of related dwarf ops. we can
> fix this up in target hooks for update context, but we still
> have to special case REG_UNSAVED.
>
> i think introducing a new REG_CONST does not simplify the
> aarch64 target code (we might need further changes to get
> a clean solution).

Ah, yeah, the zero reset would still need to be shoe-horned
in somehow.

OK for the original patch in that case.

Thanks,
Richard
  

Patch

diff --git a/gcc/testsuite/gcc.target/aarch64/pr104689.c b/gcc/testsuite/gcc.target/aarch64/pr104689.c
new file mode 100644
index 00000000000..3b7adbdfe7d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/pr104689.c
@@ -0,0 +1,149 @@ 
+/* PR target/104689. Unwind across pac-ret frames with unusual dwarf.  */
+/* { dg-do run } */
+/* { dg-require-effective-target lp64 } */
+/* { dg-options "-fexceptions -O2" } */
+
+#include <unwind.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#define die() \
+  do { \
+    printf ("%s:%d: reached unexpectedly.\n", __FILE__, __LINE__); \
+    fflush (stdout); \
+    abort (); \
+  } while (0)
+
+
+/* Code to invoke unwinding with a logging callback.  */
+
+static struct _Unwind_Exception exc;
+
+static _Unwind_Reason_Code
+force_unwind_stop (int version, _Unwind_Action actions,
+                   _Unwind_Exception_Class exc_class,
+                   struct _Unwind_Exception *exc_obj,
+                   struct _Unwind_Context *context,
+                   void *stop_parameter)
+{
+  printf ("%s: CFA: %p PC: %p actions: %d\n",
+	  __func__,
+	  (void *)_Unwind_GetCFA (context),
+	  (void *)_Unwind_GetIP (context),
+	  (int)actions);
+  if (actions & _UA_END_OF_STACK)
+    die ();
+  return _URC_NO_REASON;
+}
+
+static void force_unwind (void)
+{
+#ifndef __USING_SJLJ_EXCEPTIONS__
+  _Unwind_ForcedUnwind (&exc, force_unwind_stop, 0);
+#else
+  _Unwind_SjLj_ForcedUnwind (&exc, force_unwind_stop, 0);
+#endif
+}
+
+
+/* Define functions with unusual pac-ret dwarf via top level asm.  */
+
+#define STR(x) #x
+#define DW_CFA_val_expression 0x16
+#define RA_SIGN_STATE 34
+#define DW_OP_lit0 0x30
+#define DW_OP_lit1 0x31
+
+#define cfi_escape(a1, a2, a3, a4) \
+  ".cfi_escape " STR(a1) ", " STR(a2) ", " STR(a3) ", " STR(a4)
+
+/* Bytes: 0x16 0x22 0x01 0x30  */
+#define SET_RA_STATE_0 \
+  cfi_escape (DW_CFA_val_expression, RA_SIGN_STATE, 1, DW_OP_lit0)
+
+/* Bytes: 0x16 0x22 0x01 0x31  */
+#define SET_RA_STATE_1 \
+  cfi_escape (DW_CFA_val_expression, RA_SIGN_STATE, 1, DW_OP_lit1)
+
+/* These function call their argument.  */
+void unusual_pac_ret (void *);
+void unusual_no_pac_ret (void *);
+
+asm(""
+".global unusual_pac_ret\n"
+".type unusual_pac_ret, %function\n"
+"unusual_pac_ret:\n"
+"	.cfi_startproc\n"
+"	" SET_RA_STATE_0 "\n"
+"	hint	25 // paciasp\n"
+"	" SET_RA_STATE_1 "\n"
+"	stp	x29, x30, [sp, -16]!\n"
+"	.cfi_def_cfa_offset 16\n"
+"	.cfi_offset 29, -16\n"
+"	.cfi_offset 30, -8\n"
+"	mov	x29, sp\n"
+"	blr	x0\n"
+"	ldp	x29, x30, [sp], 16\n"
+"	.cfi_restore 30\n"
+"	.cfi_restore 29\n"
+"	.cfi_def_cfa_offset 0\n"
+"	hint	29 // autiasp\n"
+"	" SET_RA_STATE_0 "\n"
+"	ret\n"
+"	.cfi_endproc\n");
+
+asm(""
+".global unusual_no_pac_ret\n"
+".type unusual_no_pac_ret, %function\n"
+"unusual_no_pac_ret:\n"
+"	.cfi_startproc\n"
+"	" SET_RA_STATE_0 "\n"
+"	stp	x29, x30, [sp, -16]!\n"
+"	.cfi_def_cfa_offset 16\n"
+"	.cfi_offset 29, -16\n"
+"	.cfi_offset 30, -8\n"
+"	mov	x29, sp\n"
+"	blr	x0\n"
+"	ldp	x29, x30, [sp], 16\n"
+"	.cfi_restore 30\n"
+"	.cfi_restore 29\n"
+"	.cfi_def_cfa_offset 0\n"
+"	ret\n"
+"	.cfi_endproc\n");
+
+
+/* Functions to create a call chain with mixed pac-ret dwarf.  */
+
+__attribute__((target("branch-protection=pac-ret")))
+static void f2_pac_ret (void)
+{
+  force_unwind ();
+  die ();
+}
+
+__attribute__((target("branch-protection=none")))
+static void f1_no_pac_ret (void)
+{
+  unusual_pac_ret (f2_pac_ret);
+  die ();
+}
+
+__attribute__((noinline, target("branch-protection=pac-ret")))
+static void f0_pac_ret (void)
+{
+  unusual_no_pac_ret (f1_no_pac_ret);
+  die ();
+}
+
+static void cleanup_handler (void *p)
+{
+  printf ("%s: Success.\n", __func__);
+  exit (0);
+}
+
+int main ()
+{
+  char dummy __attribute__((cleanup (cleanup_handler)));
+  f0_pac_ret ();
+  die ();
+}
diff --git a/libgcc/config/aarch64/aarch64-unwind.h b/libgcc/config/aarch64/aarch64-unwind.h
index 40b22d3c288..e082e957821 100644
--- a/libgcc/config/aarch64/aarch64-unwind.h
+++ b/libgcc/config/aarch64/aarch64-unwind.h
@@ -78,7 +78,13 @@  static inline void
 aarch64_frob_update_context (struct _Unwind_Context *context,
 			     _Unwind_FrameState *fs)
 {
-  if (fs->regs.reg[DWARF_REGNUM_AARCH64_RA_STATE].loc.offset & 0x1)
+  const int reg = DWARF_REGNUM_AARCH64_RA_STATE;
+  int ra_signed;
+  if (fs->regs.reg[reg].how == REG_UNSAVED)
+    ra_signed = fs->regs.reg[reg].loc.offset & 0x1;
+  else
+    ra_signed = _Unwind_GetGR (context, reg) & 0x1;
+  if (ra_signed)
     /* The flag is used for re-authenticating EH handler's address.  */
     context->flags |= RA_SIGNED_BIT;
   else
diff --git a/libgcc/unwind-dw2.c b/libgcc/unwind-dw2.c
index 6ccd8853076..a2eb66dc0de 100644
--- a/libgcc/unwind-dw2.c
+++ b/libgcc/unwind-dw2.c
@@ -1204,7 +1204,9 @@  execute_cfa_program (const unsigned char *insn_ptr,
 #if defined (__aarch64__) && !defined (__ILP32__)
 	  /* This CFA is multiplexed with Sparc.  On AArch64 it's used to toggle
 	     return address signing status.  */
-	  fs->regs.reg[DWARF_REGNUM_AARCH64_RA_STATE].loc.offset ^= 1;
+	  reg = DWARF_REGNUM_AARCH64_RA_STATE;
+	  gcc_assert (fs->regs.reg[reg].how == REG_UNSAVED);
+	  fs->regs.reg[reg].loc.offset ^= 1;
 #else
 	  /* ??? Hardcoded for SPARC register window configuration.  */
 	  if (__LIBGCC_DWARF_FRAME_REGISTERS__ >= 32)