]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blobdiff - gdb/aarch64-linux-tdep.c
Support linkmap offsets for the AAPCS64-CAP ABI
[thirdparty/binutils-gdb.git] / gdb / aarch64-linux-tdep.c
index 7492437e97ebfe680052547a743c789824bbfc2e..d04fe8961edb12cec7960078086e22cd0bd2b876 100644 (file)
@@ -20,6 +20,7 @@
 
 #include "defs.h"
 
+#include "gdbcmd.h"
 #include "gdbarch.h"
 #include "glibc-tdep.h"
 #include "linux-tdep.h"
@@ -32,6 +33,7 @@
 #include "tramp-frame.h"
 #include "trad-frame.h"
 #include "target/target.h"
+#include "target.h"
 
 #include "regcache.h"
 #include "regset.h"
 
 #include "value.h"
 
+/* For aarch64_debug.  */
+#include "arch/aarch64-insn.h"
+
+#include "elf/common.h"
+
+#include "gdbsupport/capability.h"
+
 /* Signal frame handling.
 
       +------------+  ^
 #define AARCH64_EXTRA_MAGIC                    0x45585401
 #define AARCH64_FPSIMD_MAGIC                   0x46508001
 #define AARCH64_SVE_MAGIC                      0x53564501
+#define AARCH64_MORELLO_MAGIC                  0x4d524c01
 
 /* Defines for the extra_context that follows an AARCH64_EXTRA_MAGIC.  */
 #define AARCH64_EXTRA_DATAP_OFFSET             8
 #define AARCH64_SVE_CONTEXT_SIZE(vq) \
   (AARCH64_SVE_CONTEXT_FFR_OFFSET (vq) + (vq * 2))
 
+/* Defines for the Morello sigcontext data, which is define in the kernel like
+   so:
+
+   struct morello_context
+   {
+     struct _aarch64_ctx head;
+     __u64 __pad;
+     __kernel_uintcap_t cregs[31];
+     __kernel_uintcap_t csp;
+     __kernel_uintcap_t rcsp;
+     __kernel_uintcap_t pcc;
+   };
+
+*/
+
+#define AARCH64_MORELLO_SIGCONTEXT_SIZE              (8 + 8 + 34 * 16)
+#define AARCH64_MORELLO_SIGCONTEXT_C0_OFFSET  16
 
 /* Read an aarch64_ctx, returning the magic value, and setting *SIZE to the
    size, or return 0 on error.  */
@@ -205,6 +232,11 @@ aarch64_linux_sigframe_init (const struct tramp_frame *self,
   bool extra_found = false;
   int num_regs = gdbarch_num_regs (gdbarch);
 
+  if (aarch64_debug)
+    {
+      debug_printf ("\naarch64: Entering aarch64_linux_sigframe_init\n");
+    }
+
   /* Read in the integer registers.  */
 
   for (int i = 0; i < 31; i++)
@@ -261,6 +293,50 @@ aarch64_linux_sigframe_init (const struct tramp_frame *self,
            break;
          }
 
+       case AARCH64_MORELLO_MAGIC:
+         {
+           if (aarch64_debug)
+             debug_printf ("aarch64: Found Morello section at %s.\n",
+                           paddress (gdbarch, section));
+
+           /* Handle Morello sigcontext.  */
+           if (!tdep->has_capability ())
+             break;
+
+           int regno = tdep->cap_reg_base;
+           CORE_ADDR offset = section + AARCH64_MORELLO_SIGCONTEXT_C0_OFFSET;
+           int reg_size = C_REGISTER_SIZE;
+
+           if (aarch64_debug)
+             {
+               debug_printf ("aarch64: Reading C registers from sigreturn "
+                             "frame.\n");
+             }
+
+           for (int i = 0; i < AARCH64_C_REGS_NUM; i++)
+             {
+               trad_frame_set_reg_addr (this_cache, regno + i,
+                                        offset + i * reg_size);
+             }
+
+           if (aarch64_debug)
+             {
+               debug_printf ("aarch64: Reading PCC, CSP and RCSP registers "
+                             "from sigreturn frame at %s.\n",
+                             paddress (gdbarch, offset + 31 * reg_size));
+             }
+
+           trad_frame_set_reg_addr (this_cache, tdep->cap_reg_csp,
+                                    offset + 31 * reg_size);
+           trad_frame_set_reg_addr (this_cache, tdep->cap_reg_rcsp,
+                                    offset + 32 * reg_size);
+           trad_frame_set_reg_addr (this_cache, tdep->cap_reg_pcc,
+                                    offset + 33 * reg_size);
+
+           section += size;
+           break;
+         }
+
        case AARCH64_EXTRA_MAGIC:
          {
            /* Extra is always the last valid section in reserved and points to
@@ -359,6 +435,9 @@ aarch64_linux_sigframe_init (const struct tramp_frame *self,
     }
 
   trad_frame_set_id (this_cache, frame_id_build (sp, func));
+
+  if (aarch64_debug)
+    debug_printf ("aarch64: Exitting aarch64_linux_sigframe_init\n");
 }
 
 static const struct tramp_frame aarch64_linux_rt_sigframe =
@@ -397,6 +476,24 @@ static const struct regcache_map_entry aarch64_linux_fpregmap[] =
     { 0 }
   };
 
+/* Since the C register numbers are determined dynamically, we leave
+   placeholders so we can update the numbers later.  */
+static struct regcache_map_entry aarch64_linux_cregmap[] =
+  {
+    { 31, -1, 16 }, /* c0 ... c30 */
+    { 1, -1, 16 }, /* pcc */
+    { 1, -1, 16 }, /* csp */
+    { 1, -1, 16 }, /* ddc */
+    { 1, -1, 16 }, /* ctpidr */
+    { 1, -1, 16 }, /* rcsp */
+    { 1, -1, 16 }, /* rddc */
+    { 1, -1, 16 }, /* rctpidr */
+    { 1, -1, 16 }, /* cid */
+    { 1, -1, 8 },  /* tag_map */
+    { 1, -1, 8 },  /* cctlr */
+    { 0 }
+  };
+
 /* Register set definitions.  */
 
 const struct regset aarch64_linux_gregset =
@@ -411,6 +508,13 @@ const struct regset aarch64_linux_fpregset =
     regcache_supply_regset, regcache_collect_regset
   };
 
+/* The capability register set.  */
+const struct regset aarch64_linux_cregset =
+  {
+    aarch64_linux_cregmap,
+    regcache_supply_regset, regcache_collect_regset
+  };
+
 /* The fields in an SVE header at the start of a SVE regset.  */
 
 #define SVE_HEADER_SIZE_LENGTH         4
@@ -646,6 +750,14 @@ aarch64_linux_iterate_over_regset_sections (struct gdbarch *gdbarch,
          AARCH64_LINUX_SIZEOF_PAUTH, &aarch64_linux_pauth_regset,
          "pauth registers", cb_data);
     }
+
+  /* Morello capability registers.  */
+  if (tdep->has_capability ())
+    {
+      cb (".reg-aarch-morello", AARCH64_LINUX_CREGS_SIZE,
+         AARCH64_LINUX_CREGS_SIZE, &aarch64_linux_cregset,
+         NULL, cb_data);
+    }
 }
 
 /* Implement the "core_read_description" gdbarch method.  */
@@ -1507,7 +1619,223 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
   /* FIXME-Morello: Show more information about the faults.  */
   uiout->text (_(" while accessing address "));
   uiout->field_core_addr ("fault-addr", gdbarch, fault_addr);
-  uiout->text ("\n");
+}
+
+/* AArch64 Linux implementation of the get_cap_tag_from_address gdbarch
+   hook.  Returns the tag from the capability located at ADDR.  */
+
+static bool
+aarch64_linux_get_cap_tag_from_address (struct gdbarch *gdbarch, CORE_ADDR addr)
+{
+  gdb::byte_vector cap;
+
+  cap = target_read_capability (addr);
+
+  if (cap.size () == 0)
+    return false;
+
+  return cap[0] != 0;
+}
+
+/* Return the number of Morello memory tag granules contained in the memory
+   range [addr, addr + len).  */
+
+static size_t
+morello_get_tag_granules (CORE_ADDR addr, size_t len, size_t granule_size)
+{
+  /* An empty range has 0 tag granules.  */
+  if (len == 0)
+    return 0;
+
+  /* Start address */
+  CORE_ADDR s_addr = align_down (addr, granule_size);
+  /* End address */
+  CORE_ADDR e_addr = align_down (addr + len - 1, granule_size);
+
+  /* We always have at least 1 granule because len is non-zero at this
+     point.  */
+  return 1 + (e_addr - s_addr) / granule_size;
+}
+
+/* Maximum number of tags to request.  */
+#define MAX_TAGS_TO_TRANSFER 1024
+
+/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
+   gdbarch hook.  Create core file notes for memory tags.  */
+
+static std::vector<gdb::byte_vector>
+aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
+                                             CORE_ADDR start_address,
+                                             CORE_ADDR end_address)
+{
+  /* We only handle CHERI capability tags for now.  */
+
+  /* Figure out how many tags we need to store in this memory range.  */
+  int granules = morello_get_tag_granules (start_address,
+                                          end_address - start_address,
+                                          MORELLO_TAG_GRANULE_SIZE);
+
+  /* A vector to store multiple notes.  */
+  std::vector<gdb::byte_vector> notes;
+
+  /* Add the CHERI note.  */
+  notes.resize (1);
+  /* Resize to the number of bytes the tag granules will take.  */
+  notes[0].resize (sizeof (struct tag_dump_header) + granules);
+
+  /* Retrieve the tags and store them in the vector.  */
+  gdb::byte_vector tags;
+  CORE_ADDR address = start_address;
+  while (granules > 0)
+    {
+      /* Transfer tags in chunks.  */
+      gdb::byte_vector tags_read;
+      size_t xfer_len
+       = (granules >= MAX_TAGS_TO_TRANSFER)? MAX_TAGS_TO_TRANSFER : granules;
+
+      /* First clear the vector of tags.  */
+      tags_read.resize (0);
+
+      /* Copy tags in chunks.  */
+      while (tags_read.size () < xfer_len)
+       {
+         CORE_ADDR addr
+           = address + tags_read.size () * MORELLO_TAG_GRANULE_SIZE;
+
+         /* Always align the address to 16 bytes so we can read the
+            capability properly.  When we have a request to read only the
+            capability tags, then we won't need to do this.  */
+         CORE_ADDR aligned_addr = align_down (addr, MORELLO_TAG_GRANULE_SIZE);
+         bool tag
+           = aarch64_linux_get_cap_tag_from_address (gdbarch, aligned_addr);
+         gdb_byte tag_byte = (tag == false)? 0 : 1;
+         tags_read.push_back (tag_byte);
+       }
+
+      /* This process may take a while.  Make sure it is interruptible.  */
+      QUIT;
+
+      /* Transfer over the tags that have been read.  */
+      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
+
+      /* Adjust the remaining granules and starting address.  */
+      granules -= tags_read.size ();
+      address += tags_read.size () * MORELLO_TAG_GRANULE_SIZE;
+    }
+
+  /* Create the header.  Please note we don't yet compress the tag data.
+     We may do so in the future to save space, since a capability tag is only
+     1 bit in size.  */
+  struct tag_dump_header header;
+  header.format = ELF_CORE_TAG_CHERI;
+  header.start_vma = start_address;
+  header.end_vma = end_address;
+  header.u.cheri.granule_byte_size = MORELLO_TAG_GRANULE_SIZE;
+  header.u.cheri.tag_bit_size = MORELLO_TAG_BIT_SIZE;
+  header.u.cheri.__unused = 0;
+
+  /* Copy the tags to the note.  */
+  memcpy (notes[0].data (), &header, sizeof (header));
+  memcpy (notes[0].data () + sizeof (header), tags.data (), tags.size ());
+
+  return notes;
+}
+
+/* AArch64 Linux implementation of the aarch64_decode_memtag_note gdbarch
+   hook.  Decode a memory tag note and return the tag that it contains for
+   a particular address.  */
+
+static gdb_byte
+aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
+                                 gdb::array_view <const gdb_byte> note,
+                                 CORE_ADDR address)
+{
+  /* Read the header.  */
+  struct tag_dump_header header;
+  memcpy (&header, note.data (), sizeof (header));
+
+  /* Align the address to 16 bytes.  We assume the start_vma is already
+     aligned to the proper boundary, otherwise we would have tags dumped
+     into two different memory mappings.  */
+  address = align_down (address, MORELLO_TAG_GRANULE_SIZE);
+  CORE_ADDR offset = address - header.start_vma;
+  gdb_byte tag;
+
+  /* Read the tag.  */
+  memcpy (&tag, note.data () + sizeof (header)
+         + (offset / header.u.cheri.granule_byte_size), 1);
+
+  return tag;
+}
+
+/* Implement the maintenance print capability tag command.  */
+
+static void
+maint_print_cap_from_addr_cmd (const char *args, int from_tty)
+{
+  gdb::byte_vector cap;
+  CORE_ADDR addr = parse_and_eval_address (args);
+  cap = target_read_capability (addr);
+
+  if (cap.empty ())
+    {
+      fprintf_unfiltered (gdb_stdlog,
+                         "Could not read capability from address %s.\n",
+                         phex_nz (addr, 8));
+      return;
+    }
+
+  for (auto it : cap)
+    fprintf_unfiltered (gdb_stdlog, "%02x ", it);
+  fputs_unfiltered ("\n", gdb_stdlog);
+
+  bool tag = (cap[0] == 1);
+  uint128_t cap_128bits;
+  memcpy (&cap_128bits, &cap[1], 16);
+
+  capability capability (cap_128bits, tag);
+
+  fprintf_unfiltered (gdb_stdlog, "verbose: %s\n",
+                     capability.to_str (false).c_str ());
+  fputs_unfiltered ("\n", gdb_stdlog);
+  fprintf_unfiltered (gdb_stdlog, "compact: %s\n",
+                     capability.to_str (true).c_str ());
+}
+
+/* Implement the maintenance set capability in memory command.  */
+
+static void
+maint_set_capability_in_memory_cmd (const char *args, int from_tty)
+{
+  std::string addr_str, tag_str, upper_str, lower_str;
+  const char *args_ptr = args;
+
+  addr_str = extract_string_maybe_quoted (&args_ptr);
+  tag_str = extract_string_maybe_quoted (&args_ptr);
+  upper_str = extract_string_maybe_quoted (&args_ptr);
+  lower_str = extract_string_maybe_quoted (&args_ptr);
+
+  CORE_ADDR addr = parse_and_eval_address (addr_str.c_str ());
+  CORE_ADDR tag_part = parse_and_eval_address (tag_str.c_str ());
+  CORE_ADDR half_a = parse_and_eval_address (upper_str.c_str ());
+  CORE_ADDR half_b = parse_and_eval_address (lower_str.c_str ());
+
+  unsigned __int128 a, b;
+
+  a = half_a;
+  b = half_b;
+
+  a = (a << 64) | b;
+  bool tag = (tag_part != 0)? true : false;
+
+  gdb::byte_vector cap;
+
+  cap.resize (17);
+  memcpy (cap.data (), &tag, 1);
+  memcpy (cap.data () + 1, &a, 16);
+
+  if (!target_write_capability (addr, {cap.data (), cap.size ()}))
+    perror_with_name (_("Failed to set capability in memory."));
 }
 
 static void
@@ -1731,12 +2059,6 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
   set_xml_syscall_file_name (gdbarch, "syscalls/aarch64-linux.xml");
   set_gdbarch_get_syscall_number (gdbarch, aarch64_linux_get_syscall_number);
 
-  /* Displaced stepping.  */
-  set_gdbarch_max_insn_length (gdbarch, 4 * AARCH64_DISPLACED_MODIFIED_INSNS);
-  set_gdbarch_displaced_step_copy_insn (gdbarch,
-                                       aarch64_displaced_step_copy_insn);
-  set_gdbarch_displaced_step_fixup (gdbarch, aarch64_displaced_step_fixup);
-  set_gdbarch_displaced_step_location (gdbarch, linux_displaced_step_location);
   set_gdbarch_displaced_step_hw_singlestep (gdbarch,
                                            aarch64_displaced_step_hw_singlestep);
 
@@ -1744,8 +2066,63 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 
   if (tdep->has_capability ())
     {
+      /* Register CHERI-specific linkmap offsets for the AAPCS64_CAP ABI.  */
+      if (tdep->abi == AARCH64_ABI_AAPCS64_CAP)
+       set_solib_svr4_fetch_link_map_offsets (gdbarch,
+                                       svr4_lp64_cheri_fetch_link_map_offsets);
+
+      /* Initialize the register numbers for the core file register set.
+        Please note the PCC/CSP position in GDB's target description is
+        the inverse of the position in the Linux Kernel's user_morello_state
+        data structure.  This can cause some confusion.  */
+      aarch64_linux_cregmap[0].regno = tdep->cap_reg_base;
+      aarch64_linux_cregmap[1].regno = tdep->cap_reg_pcc;
+      aarch64_linux_cregmap[2].regno = tdep->cap_reg_csp;
+
+      /* Set the rest of the registers.  */
+      int next_regnum = tdep->cap_reg_base + 33;
+      for (int i = 3; i <= 10; i++)
+       {
+         aarch64_linux_cregmap[i].regno = next_regnum;
+         next_regnum++;
+       }
+
       set_gdbarch_report_signal_info (gdbarch,
                                      aarch64_linux_report_signal_info);
+      set_gdbarch_get_cap_tag_from_address (gdbarch,
+                                           aarch64_linux_get_cap_tag_from_address);
+
+      /* Core file helpers.  */
+
+      /* Core file helper to create memory tag notes for a particular range of
+        addresses.  */
+      set_gdbarch_create_memtag_notes_from_range (gdbarch,
+                                 aarch64_linux_create_memtag_notes_from_range);
+
+      /* Core file helper to decode a memory tag note.  */
+      set_gdbarch_decode_memtag_note (gdbarch,
+                                     aarch64_linux_decode_memtag_note);
+
+      add_cmd ("cap_from_addr", class_maintenance, maint_print_cap_from_addr_cmd,
+              _("Print the capability from addr."), &maintenanceprintlist);
+
+      add_cmd ("cap_in_memory", class_maintenance,
+              maint_set_capability_in_memory_cmd,
+              _("Print the capability from addr."), &maintenancelist);
+    }
+  else
+    {
+      /* Displaced stepping.  */
+      /* Note: Morello does not support displaced stepping yet because
+        adjustments to GPR's may not be correct.  This is because GDB can't
+        make adjustments to the upper 65 bits of the C registers.  */
+      set_gdbarch_max_insn_length (gdbarch,
+                                  4 * AARCH64_DISPLACED_MODIFIED_INSNS);
+      set_gdbarch_displaced_step_copy_insn (gdbarch,
+                                           aarch64_displaced_step_copy_insn);
+      set_gdbarch_displaced_step_fixup (gdbarch, aarch64_displaced_step_fixup);
+      set_gdbarch_displaced_step_location (gdbarch,
+                                          linux_displaced_step_location);
     }
 }