]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdbserver: Add support for variable-size registers
authorThiago Jung Bauermann <thiago.bauermann@linaro.org>
Sat, 26 Apr 2025 19:22:53 +0000 (16:22 -0300)
committerThiago Jung Bauermann <thiago.bauermann@linaro.org>
Sun, 27 Apr 2025 05:38:03 +0000 (02:38 -0300)
They're limited to vector registers, and the size is given by a target
description parameter specifying the total size of the vector registers.
The number of elements is derived from the total size and the element
size.

gdb/regformats/regdef.h
gdbserver/ax.cc
gdbserver/regcache.cc
gdbserver/regcache.h
gdbserver/remote-utils.cc
gdbserver/server.cc
gdbserver/tdesc.cc
gdbserver/tdesc.h
gdbserver/tracepoint.cc

index 944e577f2c92660a4c95f04d879d4d639b644d0b..f2b4127fc55a67af31ecd109580dbbd0b004dc0f 100644 (file)
 
 namespace gdb {
 
+/* Copied from gdbsupport/tdesc.h because I couldn't find an appropriate
+   header that would work for gdbserver/regcache.cc.  */
+
+#define TDESC_REG_VARIABLE_SIZE -1
+
 struct reg
 {
   reg (int _offset)
@@ -32,7 +37,15 @@ struct reg
   reg (const char *_name, int _offset, int _size)
     : name (_name),
       offset (_offset),
-      size (_size)
+      size (_size),
+      bitsize_parameter (UINT_MAX)
+  {}
+
+  reg (const char *_name, int _offset, unsigned int _bitsize_parameter)
+    : name (_name),
+      offset (_offset),
+      size (TDESC_REG_VARIABLE_SIZE),
+      bitsize_parameter (_bitsize_parameter)
   {}
 
   /* The name of this register - NULL for pad entries.  */
@@ -46,14 +59,26 @@ struct reg
   /* The offset (in bits) of the value of this register in the buffer.  */
   int offset;
 
-  /* The size (in bits) of the value of this register, as transmitted.  */
+  /* The size (in bits) of the value of this register, as transmitted.  If
+     it's TDESC_REG_VARIABLE_SIZE then this register contains a vector with
+     total vector size given by BITSIZE_PARAMETER, and each element has a
+     size of ELEMENT_BITSIZE.  */
   int size;
 
+  /* Size of each element in the vector.  Only valid if
+     size == TDESC_REG_VARIABLE_SIZE.  */
+  unsigned int element_bitsize;
+
+  /* Target description parameter giving the bitsize of the vector.  Only
+     valid if size == TDESC_REG_VARIABLE_SIZE.  */
+  unsigned int bitsize_parameter;
+
   bool operator== (const reg &other) const
   {
     return (strcmp (name, other.name) == 0
            && offset == other.offset
-           && size == other.size);
+           && size == other.size
+           && bitsize_parameter == other.bitsize_parameter);
   }
 
   bool operator!= (const reg &other) const
index 567ef7fb4767869fe2a00336bf76db17466de9a8..4214873264672b1d4db1869f78a9b10095c44c36 100644 (file)
@@ -1191,7 +1191,7 @@ gdb_eval_agent_expr (struct eval_agent_expr_context *ctx,
            int regnum = arg;
            struct regcache *regcache = ctx->regcache;
 
-           switch (register_size (regcache->tdesc, regnum))
+           switch (regcache->register_size (regnum))
              {
              case 8:
                collect_register (regcache, regnum, cnv.u64.bytes);
index 1485d223c958e89b66aba44d0f335cf26d664629..63b23fb4cb602da1a26290c77539482e854158bd 100644 (file)
@@ -112,7 +112,7 @@ regcache_invalidate (void)
 #endif
 
 regcache::regcache (const target_desc *tdesc, unsigned char *regbuf)
-  : tdesc (tdesc), registers (regbuf)
+  : tdesc (tdesc), fixed_size_registers (regbuf)
 {
   gdb_assert (regbuf != nullptr);
 }
@@ -122,14 +122,14 @@ regcache::regcache (const target_desc *tdesc, unsigned char *regbuf)
 regcache::regcache (const target_desc *tdesc)
   : tdesc (tdesc), registers_owned (true)
 {
-  gdb_assert (tdesc->registers_size != 0);
+  gdb_assert (tdesc->fixed_registers_size != 0);
 
   /* Make sure to zero-initialize the register cache when it is
      created, in case there are registers the target never
      fetches.  This way they'll read as zero instead of
      garbage.  */
-  this->registers
-    = (unsigned char *) xmalloc (tdesc->registers_size);
+  this->fixed_size_registers
+    = (unsigned char *) xmalloc (tdesc->fixed_registers_size);
   size_t num_regs = tdesc->reg_defs.size ();
   m_register_status.reset (new enum register_status[num_regs]);
   reset (REG_UNKNOWN);
@@ -138,7 +138,7 @@ regcache::regcache (const target_desc *tdesc)
 regcache::~regcache ()
 {
   if (registers_owned)
-    free (registers);
+    free (fixed_size_registers);
 }
 
 #endif
@@ -149,10 +149,14 @@ regcache::reset (enum register_status status)
   /* Zero-initialize the register cache, in case there are registers
      the target never fetches.  This way they'll read as zero instead
      of garbage.  */
-  memset (this->registers, 0, this->tdesc->registers_size);
+  memset (this->fixed_size_registers, 0, this->tdesc->fixed_registers_size);
 #ifndef IN_PROCESS_AGENT
+  this->variable_size_registers.reset ();
+
   for (int i = 0; i < this->tdesc->reg_defs.size (); i++)
     set_register_status (i, status);
+
+  invalidate_variable_size_registers ();
 #endif
 }
 
@@ -163,7 +167,7 @@ regcache::copy_from (regcache *src)
   gdb_assert (src->tdesc == this->tdesc);
   gdb_assert (src != this);
 
-  memcpy (this->registers, src->registers, src->tdesc->registers_size);
+  memcpy (this->fixed_size_registers, src->fixed_size_registers, src->tdesc->fixed_registers_size);
 #ifndef IN_PROCESS_AGENT
   if (m_register_status != nullptr && src->m_register_status != nullptr)
     memcpy (m_register_status.get (), src->m_register_status.get (),
@@ -217,7 +221,7 @@ registers_to_string (struct regcache *regcache, char *buf)
   for (int i = 0; i < tdesc->reg_defs.size (); ++i)
     {
       collect_register_as_string (regcache, i, buf);
-      buf += register_size (tdesc, i) * 2;
+      buf += regcache->register_size (i) * 2;
     }
 }
 
@@ -225,17 +229,32 @@ void
 registers_from_string (struct regcache *regcache, char *buf)
 {
   int len = strlen (buf);
-  unsigned char *registers = regcache->registers;
+  unsigned char *registers = regcache->fixed_size_registers;
+  unsigned char *var_size_registers = regcache->variable_size_registers.get ();
   const struct target_desc *tdesc = regcache->tdesc;
+  int expected_len = (tdesc->fixed_registers_size + regcache->variable_registers_size) * 2;
 
-  if (len != tdesc->registers_size * 2)
+  if (len != expected_len)
     {
       warning ("Wrong sized register packet (expected %d bytes, got %d)",
-              2 * tdesc->registers_size, len);
-      if (len > tdesc->registers_size * 2)
-       len = tdesc->registers_size * 2;
+              expected_len, len);
+      if (len > expected_len)
+       len = expected_len;
+    }
+
+  for (int i = 0; i < tdesc->reg_defs.size (); ++i)
+    {
+      int reg_size = regcache->register_size (i);
+      unsigned char *regs;
+
+      if (tdesc->reg_defs[i].size == TDESC_REG_VARIABLE_SIZE)
+       regs = var_size_registers;
+      else
+       regs = registers;
+
+      hex2bin (buf, regs, reg_size);
+      buf += reg_size * 2;
     }
-  hex2bin (buf, registers, len / 2);
 }
 
 /* See regcache.h */
@@ -282,16 +301,77 @@ regcache_release (void)
 }
 #endif
 
+#ifndef IN_PROCESS_AGENT
+/* See regcache.h */
+
+void
+regcache::initialize_variable_size_registers ()
+{
+  const unsigned int num_regs = tdesc->reg_defs.size ();
+  int total_size = 0;
+
+  variable_size_sizes.resize (num_regs);
+  variable_size_offsets.resize (num_regs);
+
+  for (int i = 0; i < num_regs; i++)
+    {
+      const gdb::reg &reg = tdesc->reg_defs[i];
+      if (reg.size != TDESC_REG_VARIABLE_SIZE)
+       {
+         variable_size_sizes[i] = -1;
+         variable_size_offsets[i] = -1;
+         continue;
+       }
+
+      long size = target_desc_parameter_value (this, reg.bitsize_parameter);
+      variable_size_sizes[i] = size;
+      variable_size_offsets[i] = total_size;
+      total_size += size;
+    }
+
+  variable_size_registers.reset (new unsigned char[total_size]);
+  variable_registers_size = total_size;
+}
+
+/* See regcache.h */
+
+void
+regcache::invalidate_variable_size_registers ()
+{
+  /* There's nothing to do if there are already no variable-size register
+     contents.  */
+  if (variable_size_registers == nullptr)
+    return;
+
+  for (unsigned int i = 0; i < tdesc->reg_defs.size (); i++)
+    {
+      const gdb::reg &reg = tdesc->reg_defs[i];
+      if (reg.size == TDESC_REG_VARIABLE_SIZE)
+       set_register_status (i, REG_UNKNOWN);
+    }
+
+  variable_size_registers = nullptr;
+  variable_size_sizes.clear ();
+  variable_size_offsets.clear ();
+}
+#endif
+
 int
 register_cache_size (const struct target_desc *tdesc)
 {
-  return tdesc->registers_size;
+  /* Variable-size registers aren't considered here because this function is
+     only used for tracepoints.  */
+  return tdesc->fixed_registers_size;
 }
 
 int
 register_size (const struct target_desc *tdesc, int n)
 {
-  return find_register_by_number (tdesc, n).size / 8;
+  const gdb::reg &reg = find_register_by_number (tdesc, n);
+
+  gdb_assert (reg.size != TDESC_REG_VARIABLE_SIZE);
+
+  return reg.size / 8;
 }
 
 /* See gdbsupport/common-regcache.h.  */
@@ -299,15 +379,46 @@ register_size (const struct target_desc *tdesc, int n)
 int
 regcache::register_size (int regnum) const
 {
-  return ::register_size (tdesc, regnum);
+  const gdb::reg &reg = find_register_by_number (tdesc, regnum);
+
+  if (reg.size == TDESC_REG_VARIABLE_SIZE)
+    {
+#ifndef IN_PROCESS_AGENT
+      if (!variable_size_registers)
+       // FIXME: Remove cast.
+       const_cast<struct regcache *> (this)->initialize_variable_size_registers ();
+
+      return variable_size_sizes[regnum];
+#else
+      gdb_assert_not_reached ("Variable-size registers not supported.");
+#endif
+    }
+  else
+    return reg.size / 8;
 }
 
 static gdb::array_view<gdb_byte>
 register_data (const struct regcache *regcache, int n)
 {
   const gdb::reg &reg = find_register_by_number (regcache->tdesc, n);
-  return gdb::make_array_view (regcache->registers + reg.offset / 8,
-                              reg.size / 8);
+
+  if (reg.size == TDESC_REG_VARIABLE_SIZE)
+    {
+#ifndef IN_PROCESS_AGENT
+      if (!regcache->variable_size_registers)
+       // FIXME: Remove cast.
+       const_cast<struct regcache *> (regcache)->initialize_variable_size_registers ();
+
+      return gdb::make_array_view (regcache->variable_size_registers.get ()
+                                  + regcache->variable_size_offsets[n],
+                                  regcache->variable_size_sizes[n]);
+#else
+      gdb_assert_not_reached ("Variable-size registers not supported.");
+#endif
+    }
+  else
+    return gdb::make_array_view (regcache->fixed_size_registers + reg.offset / 8,
+                                reg.size / 8);
 }
 
 void
@@ -339,6 +450,12 @@ regcache::raw_supply (int n, gdb::array_view<const gdb_byte> src)
       set_register_status (n, REG_UNAVAILABLE);
 #endif
     }
+
+#ifndef IN_PROCESS_AGENT
+  if (target_is_register_relevant_for_tdesc_parameter (this->tdesc, n))
+    /* Invalidate variable-size registers.  */
+    this->invalidate_variable_size_registers ();
+#endif
 }
 
 /* Supply register N with value zero to REGCACHE.  */
@@ -385,7 +502,7 @@ supply_regblock (struct regcache *regcache, const void *buf)
   gdb_assert (buf != nullptr);
   const struct target_desc *tdesc = regcache->tdesc;
 
-  memcpy (regcache->registers, buf, tdesc->registers_size);
+  memcpy (regcache->fixed_size_registers, buf, tdesc->fixed_registers_size);
 #ifndef IN_PROCESS_AGENT
   for (int i = 0; i < tdesc->reg_defs.size (); i++)
     regcache->set_register_status (i, REG_VALID);
@@ -429,7 +546,7 @@ regcache_raw_read_unsigned (reg_buffer_common *reg_buf, int regnum,
 
   gdb_assert (regcache != NULL);
 
-  size = register_size (regcache->tdesc, regnum);
+  size = regcache->register_size (regnum);
 
   if (size > (int) sizeof (ULONGEST))
     error (_("That operation is not available on integers of more than"
@@ -457,7 +574,7 @@ regcache_raw_get_unsigned_by_name (struct regcache *regcache,
 void
 collect_register_as_string (struct regcache *regcache, int n, char *buf)
 {
-  int reg_size = register_size (regcache->tdesc, n);
+  int reg_size = regcache->register_size (n);
 
   if (regcache->get_register_status (n) == REG_VALID)
     bin2hex (register_data (regcache, n), buf);
index 03317bf9a2db1f603c1e045114fcde19497d8ff0..3b15a2d17281340602b04d9a46ec3ba9795cf0b7 100644 (file)
@@ -42,13 +42,31 @@ struct regcache : public reg_buffer_common
      below.  */
   bool registers_fetched = false;
   bool registers_owned = false;
-  unsigned char *registers = nullptr;
+  unsigned char *fixed_size_registers = nullptr;
 #ifndef IN_PROCESS_AGENT
+  /* The variable-size register buffers (if any).  */
+  std::unique_ptr<unsigned char[]> variable_size_registers;
+
+  /* Size of the variable_size_registers buffer, if there is one.  */
+  int variable_registers_size;
+
+  /* Offsets into variable_size_registers.  */
+  std::vector<long> variable_size_offsets;
+
+  /* Size of each variable-size register.  */
+  std::vector<long> variable_size_sizes;
+
   /* Construct a regcache using the register layout described by TDESC.
 
      The regcache dynamically allocates its register buffer.  */
   regcache (const target_desc *tdesc);
 
+  /* Initialize information about variable-size registers in this regcache.  */
+  void initialize_variable_size_registers ();
+
+  /* Reset information about variable-size registers in this regcache.  */
+  void invalidate_variable_size_registers ();
+
   /* Destructor.  */
   ~regcache ();
 #endif
index 15f073dd6bece9225e03c4a0ab47b17f64ca3b72..6de9ee1e4d40f69f456fabf5f475639100d338cd 100644 (file)
@@ -1049,7 +1049,7 @@ outreg (struct regcache *regcache, int regno, char *buf)
   *buf++ = tohex (regno & 0xf);
   *buf++ = ':';
   collect_register_as_string (regcache, regno, buf);
-  buf += 2 * register_size (regcache->tdesc, regno);
+  buf += 2 * regcache->register_size (regno);
   *buf++ = ';';
 
   return buf;
index 2900e55c730e3461876506d8c10f66e3af542271..47254f09af89ae88a60330e1e74f53e87b785e4b 100644 (file)
@@ -4080,7 +4080,7 @@ static void test_registers_raw_compare_zero_length ()
   target_desc dummy_tdesc;
 
   /* Make it 8 bytes long.  */
-  dummy_tdesc.registers_size = 8;
+  dummy_tdesc.fixed_registers_size = 8;
 
   /* Add a couple dummy 32-bit registers.  */
   dummy_tdesc.reg_defs.emplace_back ("r0", 0, 32);
index d0c8f99357da94d4ad28071b5de6e01ff8f61270..1afa462259f7fc625adb6b208ff252d0205bb0f9 100644 (file)
@@ -110,6 +110,40 @@ tdesc_parameter_size (const target_desc *tdesc, unsigned param_id)
   return tdesc->parameters[param_id].size;
 }
 
+/* Sets up REG's size information using TYPE.  */
+
+static void
+setup_variable_size_reg (const target_desc *tdesc, tdesc_type *type,
+                        gdb::reg &reg)
+{
+  /* We only support vector types or unions containing vector types as
+     variable-size.  */
+  gdb_assert (type->kind == TDESC_TYPE_UNION
+             || type->kind == TDESC_TYPE_VECTOR);
+
+  if (type->kind == TDESC_TYPE_VECTOR)
+    {
+      tdesc_type_vector *vec_type = static_cast<tdesc_type_vector *> (type);
+
+      reg.element_bitsize = tdesc_type_bitsize (vec_type->element_type);
+      std::optional<unsigned int> maybe_param_id
+       = tdesc_parameter_id (tdesc, vec_type->bitsize_parameter->feature.c_str (),
+                             vec_type->bitsize_parameter->name.c_str ());
+      gdb_assert (maybe_param_id.has_value ());
+
+      reg.bitsize_parameter = *maybe_param_id;
+    }
+  else
+    {
+      tdesc_type_with_fields *union_type
+       = static_cast<tdesc_type_with_fields *> (type);
+
+      /* We assume that all fields in the union have the same size, so
+        just get the first one.  */
+      setup_variable_size_reg (tdesc, union_type->fields.front ().type, reg);
+    }
+}
+
 void
 init_target_desc (struct target_desc *tdesc,
                  const char **expedite_regs,
@@ -136,15 +170,24 @@ init_target_desc (struct target_desc *tdesc,
 
          tdesc->reg_defs.emplace_back (treg->name.c_str (), offset,
                                        treg->bitsize);
-         offset += treg->bitsize;
+
+         if (treg->bitsize == TDESC_REG_VARIABLE_SIZE)
+           {
+             tdesc_type *type
+                 = tdesc_named_type (feature.get (), treg->type.c_str ());
+
+             setup_variable_size_reg (tdesc, type, tdesc->reg_defs.back ());
+           }
+         else
+           offset += treg->bitsize;
        }
     }
 
-  tdesc->registers_size = offset / 8;
+  tdesc->fixed_registers_size = offset / 8;
 
   /* Make sure PBUFSIZ is large enough to hold a full register
      packet.  */
-  gdb_assert (2 * tdesc->registers_size + 32 <= PBUFSIZ);
+  gdb_assert (2 * tdesc->fixed_registers_size + 32 <= PBUFSIZ);
 
 #ifndef IN_PROCESS_AGENT
   /* Drop the contents of the previous vector, if any.  */
@@ -185,7 +228,7 @@ copy_target_description (struct target_desc *dest,
 {
   dest->reg_defs = src->reg_defs;
   dest->expedite_regs = src->expedite_regs;
-  dest->registers_size = src->registers_size;
+  dest->fixed_registers_size = src->fixed_registers_size;
   dest->xmltarget = src->xmltarget;
 }
 
index 45b9b48c97e745c6a29cc74d8803b2f91fc92635..4e0a0913c9ba382645bf5356f89415232ad87d7b 100644 (file)
@@ -52,7 +52,7 @@ struct target_desc final : tdesc_element
   std::vector<struct gdb::reg> reg_defs;
 
   /* The register cache size, in bytes.  */
-  int registers_size;
+  int fixed_registers_size;
 
   /* XML features in this target description.  */
   std::vector<tdesc_feature_up> features;
@@ -82,7 +82,7 @@ struct target_desc final : tdesc_element
 
 public:
   target_desc ()
-    : registers_size (0)
+    : fixed_registers_size (0)
   {}
 
   bool operator== (const target_desc &other) const;
index 715cc6344c497a9d8ea16e664a5ceb1b2c2863a4..0f91e9cef9ad14d9c55544ddacbae2f6be214f15 100644 (file)
@@ -4739,7 +4739,7 @@ traceframe_walk_blocks (unsigned char *database, unsigned int datasize,
        {
        case 'R':
          /* Skip over the registers block.  */
-         dataptr += current_target_desc ()->registers_size;
+         dataptr += current_target_desc ()->fixed_registers_size;
          break;
        case 'M':
          /* Skip over the memory block.  */
@@ -5362,14 +5362,14 @@ gdb_collect (struct tracepoint *tpoint, unsigned char *regs)
 
   /* Wrap the regblock in a register cache (in the stack, we don't
      want to malloc here).  */
-  ctx.regspace = (unsigned char *) alloca (ipa_tdesc->registers_size);
+  ctx.regspace = (unsigned char *) alloca (ipa_tdesc->fixed_registers_size);
   if (ctx.regspace == NULL)
     {
       trace_debug ("Trace buffer block allocation failed, skipping");
       return;
     }
 
-  memset (ctx.regspace, 0, ipa_tdesc->registers_size);
+  memset (ctx.regspace, 0, ipa_tdesc->fixed_registers_size);
 
   for (ctx.tpoint = tpoint;
        ctx.tpoint != NULL && ctx.tpoint->address == tpoint->address;