]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
Add FreeBSD support for CHERI-RISC-V.
authorJohn Baldwin <jhb@FreeBSD.org>
Wed, 12 Oct 2022 00:15:37 +0000 (17:15 -0700)
committerJohn Baldwin <jhb@FreeBSD.org>
Thu, 13 Oct 2022 18:25:32 +0000 (11:25 -0700)
- Register maps and sets for the capability register set and support
  for them in core dumps.

- A CheriABI signal frame unwinder.

- Support CheriABI when fetching the address of TLS variables.

- Extend fbsd_report_signal_info for CHERI exceptions to give the
  name of the relevant capability register.

gdb/riscv-fbsd-tdep.c
gdb/riscv-fbsd-tdep.h

index 7efd833a94b213ab1de57e3f4694c3844c969243..dda24d22c7238051b3361e8f418d9dc4f13628f3 100644 (file)
@@ -53,6 +53,25 @@ static const struct regcache_map_entry riscv_fbsd_fpregmap[] =
     { 0 }
   };
 
+static const struct regcache_map_entry riscv_fbsd_capregmap[] =
+  {
+    { 1, RISCV_CRA_REGNUM, 0 },
+    { 1, RISCV_CSP_REGNUM, 0 },
+    { 1, RISCV_CGP_REGNUM, 0 },
+    { 1, RISCV_CTP_REGNUM, 0 },
+    { 3, RISCV_CNULL_REGNUM + 5, 0 },  /* ct0 - t2 */
+    { 4, RISCV_CNULL_REGNUM + 28, 0 }, /* ct3 - t6 */
+    { 2, RISCV_CFP_REGNUM, 0 },                /* cs0 - s1 */
+    { 10, RISCV_CNULL_REGNUM + 18, 0 },        /* cs2 - s11 */
+    { 8, RISCV_CA0_REGNUM, 0 },                /* ca0 - a7 */
+    { 1, RISCV_PCC_REGNUM, 0 },
+    { 1, RISCV_DDC_REGNUM, 0 },
+    { 2, REGCACHE_MAP_SKIP, 8 },       /* cap_valid and pad */
+    { 0 }
+  };
+
+#define        CAP_VALID_OFFSET                (16 * 33)
+
 /* Register set definitions.  */
 
 const struct regset riscv_fbsd_gregset =
@@ -65,6 +84,126 @@ const struct regset riscv_fbsd_fpregset =
     riscv_fbsd_fpregmap, riscv_supply_regset, regcache_collect_regset
   };
 
+static int
+cap_valid_regno(int idx)
+{
+  switch (idx)
+    {
+    case 0:
+      return RISCV_CRA_REGNUM;
+    case 1:
+      return RISCV_CSP_REGNUM;
+    case 2:
+      return RISCV_CGP_REGNUM;
+    case 3:
+      return RISCV_CTP_REGNUM;
+    case 4:
+    case 5:
+    case 6:
+      return RISCV_CNULL_REGNUM + 5 + (idx - 4);
+    case 7:
+    case 8:
+    case 9:
+    case 10:
+      return RISCV_CNULL_REGNUM + 28 + (idx - 7);
+    case 11:
+    case 12:
+      return RISCV_CFP_REGNUM + (idx - 11);
+    case 13:
+    case 14:
+    case 15:
+    case 16:
+    case 17:
+    case 18:
+    case 19:
+    case 20:
+    case 21:
+    case 22:
+      return RISCV_CNULL_REGNUM + 18 + (idx - 13);
+    case 23:
+    case 24:
+    case 25:
+    case 26:
+    case 27:
+    case 28:
+    case 29:
+    case 30:
+      return RISCV_CA0_REGNUM + (idx - 23);
+    case 31:
+      return RISCV_PCC_REGNUM;
+    case 32:
+      return RISCV_DDC_REGNUM;
+    default:
+      return -1;
+    }
+}
+
+static void
+riscv_fbsd_supply_capregset (const struct regset *regset,
+                            struct regcache *regcache,
+                            int regnum, const void *buf, size_t size)
+{
+  struct gdbarch *gdbarch = regcache->arch ();
+
+  regcache->supply_regset (regset, regnum, buf, size);
+  if (regnum == -1 || regnum == RISCV_CNULL_REGNUM)
+    regcache->raw_supply_zeroed (RISCV_CNULL_REGNUM);
+
+  uint64_t cap_valid = extract_unsigned_integer ((const gdb_byte *)buf
+                                                + CAP_VALID_OFFSET, 8,
+                                                gdbarch_byte_order (gdbarch));
+  for (unsigned i = 0; i < 33; i++)
+    {
+      int regno;
+
+      regno = cap_valid_regno(i);
+      if (regno == -1)
+       continue;
+      if (regnum == -1 || regno == regnum)
+       regcache->raw_supply_tag (regno, cap_valid & 1);
+      cap_valid >>= 1;
+    }
+}
+
+static void
+riscv_fbsd_collect_capregset (const struct regset *regset,
+                               const struct regcache *regcache,
+                               int regnum, void *buf, size_t size)
+{
+  struct gdbarch *gdbarch = regcache->arch ();
+
+  regcache->collect_regset (regset, regnum, buf, size);
+
+  uint64_t cap_valid = extract_unsigned_integer ((const gdb_byte *)buf
+                                                + CAP_VALID_OFFSET, 8,
+                                                gdbarch_byte_order (gdbarch));
+  for (unsigned i = 0; i < 33; i++)
+    {
+      uint64_t mask;
+      int regno;
+
+      regno = cap_valid_regno(i);
+      if (regno == -1)
+       continue;
+      if (regnum == -1 || regno == regnum)
+       {
+         mask = (uint64_t)1 << i;
+         if (regcache->raw_collect_tag (regno))
+           cap_valid |= mask;
+         else
+           cap_valid &= ~mask;
+       }
+    }
+  store_unsigned_integer ((gdb_byte *)buf + CAP_VALID_OFFSET, 8,
+                         gdbarch_byte_order (gdbarch), cap_valid);
+}
+
+const struct regset riscv_fbsd_capregset =
+  {
+    riscv_fbsd_capregmap,
+    riscv_fbsd_supply_capregset, riscv_fbsd_collect_capregset
+  };
+
 /* Implement the "iterate_over_regset_sections" gdbarch method.  */
 
 static void
@@ -78,6 +217,10 @@ riscv_fbsd_iterate_over_regset_sections (struct gdbarch *gdbarch,
       &riscv_fbsd_gregset, NULL, cb_data);
   cb (".reg2", RISCV_FBSD_SIZEOF_FPREGSET, RISCV_FBSD_SIZEOF_FPREGSET,
       &riscv_fbsd_fpregset, NULL, cb_data);
+  if (riscv_isa_clen (gdbarch) != 0)
+    cb (".reg-cap", RISCV_FBSD_NUM_CAPREGS * riscv_isa_clen (gdbarch),
+       RISCV_FBSD_NUM_CAPREGS * riscv_isa_clen (gdbarch),
+       &riscv_fbsd_capregset, NULL, cb_data);
 }
 
 /* In a signal frame, sp points to a 'struct sigframe' which is
@@ -160,6 +303,82 @@ static const struct tramp_frame riscv_fbsd_sigframe =
   riscv_fbsd_sigframe_init
 };
 
+/* The CheriABI sigframe replaces struct gpregs at offset 0 of
+   mcontext_t with a struct capregs.  This holds capability-sized
+   registers for all GPRs.  */
+
+#define RISCVC_SIGFRAME_UCONTEXT_OFFSET                112
+
+/* Implement the "init" method of struct tramp_frame.  */
+
+static void
+riscv_fbsd_cheriabi_sigframe_init (const struct tramp_frame *self,
+                                  struct frame_info *this_frame,
+                                  struct trad_frame_cache *this_cache,
+                                  CORE_ADDR func)
+{
+  struct gdbarch *gdbarch = get_frame_arch (this_frame);
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  CORE_ADDR sp = get_frame_sp (this_frame);
+  CORE_ADDR mcontext_addr
+    = (sp
+       + RISCVC_SIGFRAME_UCONTEXT_OFFSET
+       + RISCV_UCONTEXT_MCONTEXT_OFFSET);
+  gdb_byte buf[4];
+
+  trad_frame_set_reg_regmap (this_cache, riscv_fbsd_capregmap, mcontext_addr,
+                            RISCV_FBSD_NUM_CAPREGS * riscv_isa_clen (gdbarch));
+
+  CORE_ADDR fpregs_addr
+    = mcontext_addr + RISCV_FBSD_NUM_CAPREGS * riscv_isa_clen (gdbarch);
+  CORE_ADDR fp_flags_addr
+    = fpregs_addr + RISCV_FBSD_SIZEOF_FPREGSET;
+  if (target_read_memory (fp_flags_addr, buf, 4) == 0
+      && (extract_unsigned_integer (buf, 4, byte_order)
+         & RISCV_MCONTEXT_FLAG_FP_VALID))
+    trad_frame_set_reg_regmap (this_cache, riscv_fbsd_fpregmap, fpregs_addr,
+                              RISCV_FBSD_SIZEOF_FPREGSET);
+
+  trad_frame_set_id (this_cache, frame_id_build (sp, func));
+}
+
+static const struct tramp_frame riscv_fbsd_cheriabi_sigframe =
+{
+  SIGTRAMP_FRAME,
+  2,
+  {
+    {0x155b, ULONGEST_MAX},            /* cincoffset ca0, csp, #SF_UC  */
+    {0x0701, ULONGEST_MAX},
+    {0x0293, ULONGEST_MAX},            /* li   t0, #SYS_sigreturn  */
+    {0x1a10, ULONGEST_MAX},
+    {0x0073, ULONGEST_MAX},            /* ecall  */
+    {0x0000, ULONGEST_MAX},
+    {TRAMP_SENTINEL_INSN, ULONGEST_MAX}
+  },
+  riscv_fbsd_cheriabi_sigframe_init
+};
+
+/* Implement the core_read_description gdbarch method.
+
+   This is only really needed for hybrid binaries with a NT_CAPREGS
+   coredump note.  CheriABI binaries should already use the correct
+   description.  */
+
+static const struct target_desc *
+riscv_fbsd_core_read_description (struct gdbarch *gdbarch,
+                                 struct target_ops *target,
+                                 bfd *abfd)
+{
+  asection *capstate = bfd_get_section_by_name (abfd, ".reg-cap");
+
+  if (capstate == NULL)
+    return NULL;
+
+  struct riscv_gdbarch_features features = riscv_features_from_bfd (abfd);
+  features.clen = features.xlen * 2;
+  return riscv_lookup_target_description (features);
+}
+
 /* Implement the "get_thread_local_address" gdbarch method.  */
 
 static CORE_ADDR
@@ -167,14 +386,19 @@ riscv_fbsd_get_thread_local_address (struct gdbarch *gdbarch, ptid_t ptid,
                                     CORE_ADDR lm_addr, CORE_ADDR offset)
 {
   struct regcache *regcache;
+  int regnum;
 
   regcache = get_thread_arch_regcache (current_inferior ()->process_target (),
                                       ptid, gdbarch);
 
-  target_fetch_registers (regcache, RISCV_TP_REGNUM);
+  if (riscv_abi_clen (gdbarch) != 0)
+    regnum = RISCV_CTP_REGNUM;
+  else
+    regnum = RISCV_TP_REGNUM;
+  target_fetch_registers (regcache, regnum);
 
   ULONGEST tp;
-  if (regcache->cooked_read (RISCV_TP_REGNUM, &tp) != REG_VALID)
+  if (regcache->cooked_read (regnum, &tp) != REG_VALID)
     error (_("Unable to fetch %%tp"));
 
   /* %tp points to the end of the TCB which contains two pointers.
@@ -183,6 +407,64 @@ riscv_fbsd_get_thread_local_address (struct gdbarch *gdbarch, ptid_t ptid,
   return fbsd_get_thread_local_address (gdbarch, dtv_addr, lm_addr, offset);
 }
 
+static const char *scr_names[32] =
+{
+  "pcc", "ddc", NULL, NULL,
+  "utcc", "utdc", "uscratchc", "uepcc",
+  NULL, NULL, NULL, NULL,
+  "stcc", "stdc", "sscratchc", "sepcc",
+  NULL, NULL, NULL, NULL,
+  NULL, NULL, NULL, NULL,
+  NULL, NULL, NULL, NULL,
+  "mtcc", "mtdc", "mscratchc", "mepcc"
+};
+
+static void
+riscv_fbsd_report_signal_info (struct gdbarch *gdbarch,
+                              struct ui_out *uiout,
+                              enum gdb_signal siggnal)
+{
+  fbsd_report_signal_info (gdbarch, uiout, siggnal);
+
+  switch (siggnal)
+    {
+    case GDB_SIGNAL_PROT:
+      {
+       if (riscv_abi_clen (gdbarch) == 0)
+         return;
+
+       LONGEST capreg;
+
+       try
+         {
+           capreg = parse_and_eval_long ("$_siginfo._reason._fault.si_capreg");
+         }
+       catch (const gdb_exception_error &e)
+         {
+           return;
+         }
+
+       const char *name = NULL;
+       if (capreg >= 0 && capreg <= 31)
+         name = gdbarch_register_name (gdbarch, RISCV_CNULL_REGNUM + capreg);
+       else if (capreg >= 32 && capreg <= 63)
+         {
+           if (capreg - 32 < ARRAY_SIZE (scr_names))
+             name = scr_names[capreg - 32];
+         }
+       if (name == NULL)
+         return;
+
+       uiout->text (" caused by register ");
+       uiout->field_string ("cap-register", name);
+      }
+      break;
+
+    default:
+      break;
+    }
+}
+
 /* Implement the 'init_osabi' method of struct gdb_osabi_handler.  */
 
 static void
@@ -193,12 +475,21 @@ riscv_fbsd_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 
   set_gdbarch_software_single_step (gdbarch, riscv_software_single_step);
 
-  set_solib_svr4_fetch_link_map_offsets (gdbarch,
-                                        (riscv_isa_xlen (gdbarch) == 4
-                                         ? svr4_ilp32_fetch_link_map_offsets
-                                         : svr4_lp64_fetch_link_map_offsets));
+  if (riscv_abi_clen (gdbarch) != 0)
+    set_gdbarch_report_signal_info (gdbarch,
+                                   riscv_fbsd_report_signal_info);
 
-  tramp_frame_prepend_unwinder (gdbarch, &riscv_fbsd_sigframe);
+  set_solib_svr4_fetch_link_map_offsets
+    (gdbarch, (riscv_abi_clen (gdbarch) == 16
+              ? svr4_lp64_cheri_fetch_link_map_offsets
+              : (riscv_isa_xlen (gdbarch) == 4
+                 ? svr4_ilp32_fetch_link_map_offsets
+                 : svr4_lp64_fetch_link_map_offsets)));
+
+  if (riscv_abi_clen (gdbarch) != 0)
+    tramp_frame_prepend_unwinder (gdbarch, &riscv_fbsd_cheriabi_sigframe);
+  else
+    tramp_frame_prepend_unwinder (gdbarch, &riscv_fbsd_sigframe);
 
   set_gdbarch_iterate_over_regset_sections
     (gdbarch, riscv_fbsd_iterate_over_regset_sections);
@@ -207,6 +498,8 @@ riscv_fbsd_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
                                             svr4_fetch_objfile_link_map);
   set_gdbarch_get_thread_local_address (gdbarch,
                                        riscv_fbsd_get_thread_local_address);
+
+  set_gdbarch_core_read_description (gdbarch, riscv_fbsd_core_read_description);
 }
 
 void _initialize_riscv_fbsd_tdep ();
index ed9da4c149dbc37b8cd612021eaa1786874fe01a..99ad832d0092c1b178d5309f9fb4ab91f1d28a69 100644 (file)
    only the low 32-bits of each floating point register are valid.  */
 #define RISCV_FBSD_SIZEOF_FPREGSET (32 * 16 + 8)
 
+/* The capability regset consists of 33 capability registers plus the
+   tagmask psuedo-register.  The entire structure is padded to
+   capability alignment.  */
+#define        RISCV_FBSD_NUM_CAPREGS          34
+
 extern const struct regset riscv_fbsd_gregset;
 extern const struct regset riscv_fbsd_fpregset;
+extern const struct regset riscv_fbsd_capregset;
 
 #endif /* RISCV_FBSD_TDEP_H */