Enable ARMv8.1-m PACBTI support
authorLuis Machado <luis.machado@linaro.org>
Mon, 1 Nov 2021 20:14:26 +0000 (17:14 -0300)
committerLuis Machado <luis.machado@arm.com>
Wed, 6 Apr 2022 12:43:46 +0000 (13:43 +0100)
This set of changes enable support for the ARMv8.1-m PACBTI extensions [1].

The goal of the PACBTI extensions is similar in scope to that of a-profile
PAC/BTI (aarch64 only), but the underlying implementation is different.

One important difference is that the pointer authentication code is stored
in a separate register, thus we don't need to mask/unmask the return address
from a function in order to produce a correct backtrace.

The patch introduces the following modifications:

- Extend the prologue analyser for 32-bit ARM to handle some instructions
from ARMv8.1-m PACBTI: pac, aut, pacg, autg and bti. Also keep track of
return address signing/authentication instructions.

- Adds code to identify object file attributes that indicate the presence of
ARMv8.1-m PACBTI (Tag_PAC_extension, Tag_BTI_extension, Tag_PACRET_use and
Tag_BTI_use).

- Adds support for DWARF pseudo-register RA_AUTH_CODE, as described in the
aadwarf32 [2].

- Extends the dwarf unwinder to track the value of RA_AUTH_CODE.

- Decorates backtraces with the "[PAC]" identifier when a frame has signed
the return address.

- Makes GDB aware of a new XML feature "org.gnu.gdb.arm.m-profile-pacbti". This
feature is not included as an XML file on GDB's side because it is only
supported for bare metal targets.

- Additional documentation.

[1] https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/armv8-1-m-pointer-authentication-and-branch-target-identification-extension
[2] https://github.com/ARM-software/abi-aa/blob/main/aadwarf32/aadwarf32.rst

gdb/arch/arm.h
gdb/arm-tdep.c
gdb/arm-tdep.h
gdb/doc/gdb.texinfo

index f75470e7572724ab311fe859eec0689d10b9a7a7..755391fe6fe4d362d3d581e0ac39ede57518ae27 100644 (file)
 
 #include "gdbsupport/tdesc.h"
 
+/* Prologue helper macros for ARMv8.1-m PACBTI.  */
+#define IS_PAC(instruction)    (instruction == 0xf3af801d)
+#define IS_PACBTI(instruction) (instruction == 0xf3af800d)
+#define IS_BTI(instruction)    (instruction == 0xf3af800f)
+#define IS_PACG(instruction)   ((instruction & 0xfff0f0f0) == 0xfb60f000)
+#define IS_AUT(instruction)    (instruction == 0xf3af802d)
+#define IS_AUTG(instruction)   ((instruction & 0xfff00ff0) == 0xfb500f00)
+
+/* DWARF register numbers according to the AADWARF32 document.  */
+enum arm_dwarf_regnum {
+  ARM_DWARF_RA_AUTH_CODE = 143
+};
+
 /* Register numbers of various important registers.  */
 
 enum gdb_regnum {
index 4d0f3492410380b7a98d40939357f8b8ba72d469..3e91dd707b029c8e2d6abcf063a97c31f7078ee8 100644 (file)
@@ -38,6 +38,7 @@
 #include "frame-base.h"
 #include "trad-frame.h"
 #include "objfiles.h"
+#include "dwarf2.h"
 #include "dwarf2/frame.h"
 #include "gdbtypes.h"
 #include "prologue-value.h"
@@ -286,6 +287,9 @@ struct arm_prologue_cache
   /* The register used to hold the frame pointer for this frame.  */
   int framereg;
 
+  /* True if the return address is signed, false otherwise.  */
+  gdb::optional<bool> ra_signed_state;
+
   /* Saved register offsets.  */
   trad_frame_saved_reg *saved_regs;
 };
@@ -713,6 +717,7 @@ thumb_analyze_prologue (struct gdbarch *gdbarch,
   while (start < limit)
     {
       unsigned short insn;
+      gdb::optional<bool> ra_signed_state;
 
       insn = read_code_unsigned_integer (start, 2, byte_order_for_code);
 
@@ -847,6 +852,7 @@ thumb_analyze_prologue (struct gdbarch *gdbarch,
 
          inst2 = read_code_unsigned_integer (start + 2, 2,
                                              byte_order_for_code);
+         uint32_t whole_insn = (insn << 16) | inst2;
 
          if ((insn & 0xf800) == 0xf000 && (inst2 & 0xe800) == 0xe800)
            {
@@ -1100,7 +1106,37 @@ thumb_analyze_prologue (struct gdbarch *gdbarch,
              constant = read_memory_unsigned_integer (loc + 4, 4, byte_order);
              regs[bits (inst2, 8, 11)] = pv_constant (constant);
            }
-
+         /* Start of ARMv8.1-m PACBTI extension instructions.  */
+         else if (IS_PAC (whole_insn))
+           {
+             /* LR and SP are input registers.  PAC is in R12.  LR is
+                signed from this point onwards.  NOP space.  */
+             ra_signed_state = true;
+           }
+         else if (IS_PACBTI (whole_insn))
+           {
+             /* LR and SP are input registers.  PAC is in R12 and PC is a
+                valid BTI landing pad.  LR is signed from this point onwards.
+                NOP space.  */
+             ra_signed_state = true;
+           }
+         else if (IS_BTI (whole_insn))
+           {
+             /* Valid BTI landing pad.  NOP space.  */
+           }
+         else if (IS_PACG (whole_insn))
+           {
+             /* Sign Rn using Rm and store the PAC in Rd.  Rd is signed from
+                this point onwards.  */
+             ra_signed_state = true;
+           }
+         else if (IS_AUT (whole_insn) || IS_AUTG (whole_insn))
+           {
+             /* These instructions appear close to the epilogue, when signed
+                pointers are getting authenticated.  */
+             ra_signed_state = false;
+           }
+         /* End of ARMv8.1-m PACBTI extension instructions */
          else if (thumb2_instruction_changes_pc (insn, inst2))
            {
              /* Don't scan past anything that might change control flow.  */
@@ -1113,6 +1149,21 @@ thumb_analyze_prologue (struct gdbarch *gdbarch,
              unrecognized_pc = start;
            }
 
+         arm_gdbarch_tdep *tdep
+           = (arm_gdbarch_tdep *) gdbarch_tdep (gdbarch);
+
+         /* Make sure we are dealing with a target that supports ARMv8.1-m
+            PACBTI.  */
+         if (cache != nullptr && tdep->have_pacbti
+             && ra_signed_state.has_value ())
+           {
+             arm_debug_printf ("Found pacbti instruction at %s",
+                               paddress (gdbarch, start));
+             arm_debug_printf ("RA is %s",
+                               *ra_signed_state? "signed" : "not signed");
+             cache->ra_signed_state = ra_signed_state;
+           }
+
          start += 2;
        }
       else if (thumb_instruction_changes_pc (insn))
@@ -1988,6 +2039,13 @@ arm_prologue_prev_register (struct frame_info *this_frame,
     *this_cache = arm_make_prologue_cache (this_frame);
   cache = (struct arm_prologue_cache *) *this_cache;
 
+  arm_gdbarch_tdep *tdep = (arm_gdbarch_tdep *) gdbarch_tdep (gdbarch);
+
+  /* If this frame has signed the return address, mark it as so.  */
+  if (tdep->have_pacbti && cache->ra_signed_state.has_value ()
+      && *cache->ra_signed_state)
+    set_frame_previous_pc_masked (this_frame);
+
   /* If we are asked to unwind the PC, then we need to return the LR
      instead.  The prologue may save PC, but it will point into this
      frame's prologue, not the next frame's resume location.  Also
@@ -3216,6 +3274,7 @@ arm_dwarf2_prev_register (struct frame_info *this_frame, void **this_cache,
                          int regnum)
 {
   struct gdbarch * gdbarch = get_frame_arch (this_frame);
+  arm_gdbarch_tdep *tdep = (arm_gdbarch_tdep *) gdbarch_tdep (gdbarch);
   CORE_ADDR lr, cpsr;
   ULONGEST t_bit = arm_psr_thumb_bit (gdbarch);
 
@@ -3226,6 +3285,18 @@ arm_dwarf2_prev_register (struct frame_info *this_frame, void **this_cache,
         describes saves of LR.  However, that version may have an
         extra bit set to indicate Thumb state.  The bit is not
         part of the PC.  */
+
+      /* Record in the frame whether the return address was signed.  */
+      if (tdep->have_pacbti)
+       {
+         CORE_ADDR ra_auth_code
+           = frame_unwind_register_unsigned (this_frame,
+                                             tdep->pacbti_pseudo_base);
+
+         if (ra_auth_code != 0)
+           set_frame_previous_pc_masked (this_frame);
+       }
+
       lr = frame_unwind_register_unsigned (this_frame, ARM_LR_REGNUM);
       return frame_unwind_got_constant (this_frame, regnum,
                                        arm_addr_bits_remove (gdbarch, lr));
@@ -3246,24 +3317,6 @@ arm_dwarf2_prev_register (struct frame_info *this_frame, void **this_cache,
     }
 }
 
-static void
-arm_dwarf2_frame_init_reg (struct gdbarch *gdbarch, int regnum,
-                          struct dwarf2_frame_state_reg *reg,
-                          struct frame_info *this_frame)
-{
-  switch (regnum)
-    {
-    case ARM_PC_REGNUM:
-    case ARM_PS_REGNUM:
-      reg->how = DWARF2_FRAME_REG_FN;
-      reg->loc.fn = arm_dwarf2_prev_register;
-      break;
-    case ARM_SP_REGNUM:
-      reg->how = DWARF2_FRAME_REG_CFA;
-      break;
-    }
-}
-
 /* Implement the stack_frame_destroyed_p gdbarch method.  */
 
 static int
@@ -4193,6 +4246,25 @@ is_mve_pseudo (struct gdbarch *gdbarch, int regnum)
   return false;
 }
 
+/* Return true if REGNUM is a PACBTI pseudo register (ra_auth_code).  Return
+   false otherwise.
+
+   REGNUM is the raw register number and not a pseudo-relative register
+   number.  */
+
+static bool
+is_pacbti_pseudo (struct gdbarch *gdbarch, int regnum)
+{
+  arm_gdbarch_tdep *tdep = (arm_gdbarch_tdep *) gdbarch_tdep (gdbarch);
+
+  if (tdep->have_pacbti
+      && regnum >= tdep->pacbti_pseudo_base
+      && regnum < tdep->pacbti_pseudo_base + tdep->pacbti_pseudo_count)
+    return true;
+
+  return false;
+}
+
 /* Return the GDB type object for the "standard" data type of data in
    register N.  */
 
@@ -4210,6 +4282,9 @@ arm_register_type (struct gdbarch *gdbarch, int regnum)
   if (is_mve_pseudo (gdbarch, regnum))
     return builtin_type (gdbarch)->builtin_int16;
 
+  if (is_pacbti_pseudo (gdbarch, regnum))
+    return builtin_type (gdbarch)->builtin_uint32;
+
   /* If the target description has register information, we are only
      in this function so that we can override the types of
      double-precision registers for NEON.  */
@@ -4272,6 +4347,17 @@ arm_dwarf_reg_to_regnum (struct gdbarch *gdbarch, int reg)
   if (reg >= 112 && reg <= 127)
     return ARM_WR0_REGNUM + reg - 112;
 
+  /* PACBTI register containing the Pointer Authentication Code.  */
+  if (reg == ARM_DWARF_RA_AUTH_CODE)
+    {
+      arm_gdbarch_tdep *tdep = (arm_gdbarch_tdep *) gdbarch_tdep (gdbarch);
+
+      if (tdep->have_pacbti)
+       return tdep->pacbti_pseudo_base;
+
+      return -1;
+    }
+
   if (reg >= 192 && reg <= 199)
     return ARM_WC0_REGNUM + reg - 192;
 
@@ -4337,6 +4423,35 @@ arm_register_sim_regno (struct gdbarch *gdbarch, int regnum)
   internal_error (__FILE__, __LINE__, _("Bad REGNUM %d"), regnum);
 }
 
+static const unsigned char op_lit0 = DW_OP_lit0;
+
+static void
+arm_dwarf2_frame_init_reg (struct gdbarch *gdbarch, int regnum,
+                          struct dwarf2_frame_state_reg *reg,
+                          struct frame_info *this_frame)
+{
+  if (is_pacbti_pseudo (gdbarch, regnum))
+    {
+      /* Initialize RA_AUTH_CODE to zero.  */
+      reg->how = DWARF2_FRAME_REG_SAVED_VAL_EXP;
+      reg->loc.exp.start = &op_lit0;
+      reg->loc.exp.len = 1;
+      return;
+    }
+
+  switch (regnum)
+    {
+    case ARM_PC_REGNUM:
+    case ARM_PS_REGNUM:
+      reg->how = DWARF2_FRAME_REG_FN;
+      reg->loc.fn = arm_dwarf2_prev_register;
+      break;
+    case ARM_SP_REGNUM:
+      reg->how = DWARF2_FRAME_REG_CFA;
+      break;
+    }
+}
+
 /* Given BUF, which is OLD_LEN bytes ending at ENDADDR, expand
    the buffer to be NEW_LEN bytes ending at ENDADDR.  Return
    NULL if an error occurs.  BUF is freed.  */
@@ -8683,6 +8798,10 @@ arm_register_name (struct gdbarch *gdbarch, int i)
   if (is_mve_pseudo (gdbarch, i))
     return "p0";
 
+  /* RA_AUTH_CODE is used for unwinding only.  Do not assign it a name.  */
+  if (is_pacbti_pseudo (gdbarch, i))
+    return "";
+
   if (i >= ARRAY_SIZE (arm_register_names))
     /* These registers are only supported on targets which supply
        an XML description.  */
@@ -9073,6 +9192,17 @@ arm_gnu_triplet_regexp (struct gdbarch *gdbarch)
   return gdbarch_bfd_arch_info (gdbarch)->arch_name;
 }
 
+/* Implement the "get_pc_address_flags" gdbarch method.  */
+
+static std::string
+arm_get_pc_address_flags (frame_info *frame, CORE_ADDR pc)
+{
+  if (get_frame_pc_masked (frame))
+    return "PAC";
+
+  return "";
+}
+
 /* Initialize the current architecture based on INFO.  If possible,
    re-use an architecture from ARCHES, which is a list of
    architectures already created during this debugging session.
@@ -9098,6 +9228,7 @@ arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
   const struct target_desc *tdesc = info.target_desc;
   bool have_vfp = false;
   bool have_mve = false;
+  bool have_pacbti = false;
   int mve_vpr_regnum = -1;
   int register_count = ARM_NUM_REGS;
 
@@ -9220,6 +9351,31 @@ arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
                      || attr_arch == TAG_CPU_ARCH_V8_1M_MAIN
                      || attr_profile == 'M'))
                is_m = true;
+
+             /* Look for attributes that indicate support for ARMv8.1-m
+                PACBTI.  */
+             if (!tdesc_has_registers (tdesc) && is_m)
+               {
+                 int attr_pac_extension
+                   = bfd_elf_get_obj_attr_int (info.abfd, OBJ_ATTR_PROC,
+                                               Tag_PAC_extension);
+
+                 int attr_bti_extension
+                   = bfd_elf_get_obj_attr_int (info.abfd, OBJ_ATTR_PROC,
+                                               Tag_BTI_extension);
+
+                 int attr_pacret_use
+                   = bfd_elf_get_obj_attr_int (info.abfd, OBJ_ATTR_PROC,
+                                               Tag_PACRET_use);
+
+                 int attr_bti_use
+                   = bfd_elf_get_obj_attr_int (info.abfd, OBJ_ATTR_PROC,
+                                               Tag_BTI_use);
+
+                 if (attr_pac_extension != 0 || attr_bti_extension != 0
+                     || attr_pacret_use != 0 || attr_bti_use != 0)
+                   have_pacbti = true;
+               }
 #endif
            }
 
@@ -9446,6 +9602,22 @@ arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
              if (have_vfp)
                have_q_pseudos = true;
            }
+
+         /* Do we have the ARMv8.1-m PACBTI feature?  */
+         feature = tdesc_find_feature (tdesc,
+                                       "org.gnu.gdb.arm.m-profile-pacbti");
+         if (feature != nullptr)
+           {
+             /* By advertising this feature, the target acknowledges the
+                presence of the ARMv8.1-m PACBTI extensions.
+
+                We don't care for any particular registers in this group, so
+                the target is free to include whatever it deems appropriate.
+
+                The expectation is for this feature to include the PAC
+                keys.  */
+             have_pacbti = true;
+           }
        }
     }
 
@@ -9472,6 +9644,11 @@ arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
       if (is_m != tdep->is_m)
        continue;
 
+      /* Also check for ARMv8.1-m PACBTI support, since it might come from
+        the binary.  */
+      if (have_pacbti != tdep->have_pacbti)
+       continue;
+
       /* Found a match.  */
       break;
     }
@@ -9504,6 +9681,9 @@ arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
       tdep->mve_vpr_regnum = mve_vpr_regnum;
     }
 
+  /* Adjust the PACBTI feature settings.  */
+  tdep->have_pacbti = have_pacbti;
+
   arm_register_g_packet_guesses (gdbarch);
 
   /* Breakpoints.  */
@@ -9672,6 +9852,11 @@ arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
       set_gdbarch_long_double_format (gdbarch, floatformats_ieee_double);
     }
 
+  /* Hook used to decorate frames with signed return addresses, only available
+     for ARMv8.1-m PACBTI.  */
+  if (is_m && have_pacbti)
+    set_gdbarch_get_pc_address_flags (gdbarch, arm_get_pc_address_flags);
+
   if (tdesc_data != nullptr)
     {
       set_tdesc_pseudo_register_name (gdbarch, arm_register_name);
@@ -9715,8 +9900,16 @@ arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
       num_pseudos += tdep->mve_pseudo_count;
     }
 
+  /* Do we have any ARMv8.1-m PACBTI pseudo registers.  */
+  if (have_pacbti)
+    {
+      tdep->pacbti_pseudo_base = register_count + num_pseudos;
+      tdep->pacbti_pseudo_count = 1;
+      num_pseudos += tdep->pacbti_pseudo_count;
+    }
+
   /* Set some pseudo register hooks, if we have pseudo registers.  */
-  if (tdep->have_s_pseudos || have_mve)
+  if (tdep->have_s_pseudos || have_mve || have_pacbti)
     {
       set_gdbarch_num_pseudo_regs (gdbarch, num_pseudos);
       set_gdbarch_pseudo_register_read (gdbarch, arm_pseudo_read);
@@ -9778,6 +9971,14 @@ arm_dump_tdep (struct gdbarch *gdbarch, struct ui_file *file)
              tdep->mve_pseudo_count);
   gdb_printf (file, _("arm_dump_tdep: Lowest pc = 0x%lx\n"),
              (unsigned long) tdep->lowest_pc);
+  gdb_printf (file, _("arm_dump_tdep: have_pacbti = %s\n"),
+             tdep->have_pacbti? "yes" : "no");
+  gdb_printf (file, _("arm_dump_tdep: pacbti_pseudo_base = %i\n"),
+             tdep->pacbti_pseudo_base);
+  gdb_printf (file, _("arm_dump_tdep: pacbti_pseudo_count = %i\n"),
+             tdep->pacbti_pseudo_count);
+  gdb_printf (file, _("arm_dump_tdep: is_m = %s\n"),
+             tdep->is_m? "yes" : "no");
 }
 
 #if GDB_SELF_TEST
index 8a9f618539ff101b506749b7bad1afb6c3ffc8c8..ae32fffcabf093d0cb3d79fe6ab0599773b28a45 100644 (file)
@@ -119,6 +119,12 @@ struct arm_gdbarch_tdep : gdbarch_tdep
   int mve_pseudo_base = 0;     /* Number of the first MVE pseudo register.  */
   int mve_pseudo_count = 0;    /* Total number of MVE pseudo registers.  */
 
+  bool have_pacbti = false;    /* True if we have the ARMv8.1-m PACBTI
+                                  extensions.  */
+  int pacbti_pseudo_base = 0;  /* Number of the first PACBTI pseudo
+                                  register.  */
+  int pacbti_pseudo_count = 0; /* Total number of PACBTI pseudo registers.  */
+
   bool is_m = false;           /* Does the target follow the "M" profile.  */
   CORE_ADDR lowest_pc = 0;     /* Lowest address at which instructions
                                   will appear.  */
index e50618fe9ab0d43d838184f8d5980652c3d807df..e4685cd995b88a93ba29200097c2e4b4225b61ef 100644 (file)
@@ -25409,6 +25409,7 @@ target process.
 
 @subsubsection AArch64 Pointer Authentication.
 @cindex AArch64 Pointer Authentication.
+@anchor{AArch64 PAC}
 
 When @value{GDBN} is debugging the AArch64 architecture, and the program is
 using the v8.3-A feature Pointer Authentication (PAC), then whenever the link
@@ -46604,6 +46605,12 @@ quad-precision registers from pairs of double-precision registers.
 If this feature is present, @samp{org.gnu.gdb.arm.vfp} must also
 be present and include 32 double-precision registers.
 
+The @samp{org.gnu.gdb.arm.m-profile-pacbti} feature is optional, and
+acknowledges support for the ARMv8.1-m PACBTI extensions.  @value{GDBN}
+will track return address signing states and will decorate backtraces using
+the [PAC] marker, similar to AArch64's PAC extension.
+@xref{AArch64 PAC}.
+
 @node i386 Features
 @subsection i386 Features
 @cindex target descriptions, i386 features