From: Thiago Jung Bauermann Date: Sat, 26 Apr 2025 19:22:53 +0000 (-0300) Subject: gdbserver: Add support for variable-size registers X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6a46b0e7d181136690c53e8d7a73c926b171510e;p=thirdparty%2Fbinutils-gdb.git gdbserver: Add support for variable-size registers 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. --- diff --git a/gdb/regformats/regdef.h b/gdb/regformats/regdef.h index 944e577f2c9..f2b4127fc55 100644 --- a/gdb/regformats/regdef.h +++ b/gdb/regformats/regdef.h @@ -21,6 +21,11 @@ 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 diff --git a/gdbserver/ax.cc b/gdbserver/ax.cc index 567ef7fb476..42148732646 100644 --- a/gdbserver/ax.cc +++ b/gdbserver/ax.cc @@ -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); diff --git a/gdbserver/regcache.cc b/gdbserver/regcache.cc index 1485d223c95..63b23fb4cb6 100644 --- a/gdbserver/regcache.cc +++ b/gdbserver/regcache.cc @@ -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 ® = 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 ® = 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 ® = 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 ® = 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 (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 register_data (const struct regcache *regcache, int n) { const gdb::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 (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 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); diff --git a/gdbserver/regcache.h b/gdbserver/regcache.h index 03317bf9a2d..3b15a2d1728 100644 --- a/gdbserver/regcache.h +++ b/gdbserver/regcache.h @@ -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 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 variable_size_offsets; + + /* Size of each variable-size register. */ + std::vector 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 diff --git a/gdbserver/remote-utils.cc b/gdbserver/remote-utils.cc index 15f073dd6be..6de9ee1e4d4 100644 --- a/gdbserver/remote-utils.cc +++ b/gdbserver/remote-utils.cc @@ -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; diff --git a/gdbserver/server.cc b/gdbserver/server.cc index 2900e55c730..47254f09af8 100644 --- a/gdbserver/server.cc +++ b/gdbserver/server.cc @@ -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); diff --git a/gdbserver/tdesc.cc b/gdbserver/tdesc.cc index d0c8f99357d..1afa462259f 100644 --- a/gdbserver/tdesc.cc +++ b/gdbserver/tdesc.cc @@ -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 ®) +{ + /* 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 (type); + + reg.element_bitsize = tdesc_type_bitsize (vec_type->element_type); + std::optional 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 (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; } diff --git a/gdbserver/tdesc.h b/gdbserver/tdesc.h index 45b9b48c97e..4e0a0913c9b 100644 --- a/gdbserver/tdesc.h +++ b/gdbserver/tdesc.h @@ -52,7 +52,7 @@ struct target_desc final : tdesc_element std::vector reg_defs; /* The register cache size, in bytes. */ - int registers_size; + int fixed_registers_size; /* XML features in this target description. */ std::vector 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; diff --git a/gdbserver/tracepoint.cc b/gdbserver/tracepoint.cc index 715cc6344c4..0f91e9cef9a 100644 --- a/gdbserver/tracepoint.cc +++ b/gdbserver/tracepoint.cc @@ -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;