[v3] Support large registers in regcache transfer_regset

Message ID 20180622130819.1940-1-alan.hayward@arm.com
State New, archived
Headers

Commit Message

Alan Hayward June 22, 2018, 1:08 p.m. UTC
  This avoids assert failures when the register is bigger than the
slot size. This happens on Aarch64 when truncating Z registers
into an fpsimd structure. This can be triggered by running
gdb command "generate-core-file".

Also, when the register is smaller then the slot size, then
zero pad when writing to the slot, and truncate when writing
to the regcache. This happens on Aarch64 with the CPSR register.

Continue to ensure registers are invalidated when both buffers
are null.

Restore the removed asserts back to read_part and write_part.

Use gdb_byte for transfer_regset, moving the casting into
supply_regset/collect_regset. I didn't want to change the arguments
for those two functions as that requires changes to lots of other
files.

Tested by generating cores on aarch64 and aarch64 sve, both via gdb and the
kernel, then ensuring the cores load back on the both systems.
Checked cores on x86 still look ok.
Ran make check on x86 and aarch64.

2018-06-22  Alan Hayward  <alan.hayward@arm.com>

	* regcache.c (readable_regcache::read_part): Fix asserts.
	(reg_buffer::raw_collect_part): New function.
	(regcache::write_part): Fix asserts.
	(reg_buffer::raw_supply_part): New function.
	(regcache::transfer_regset_register): New helper function.
	(regcache::transfer_regset): Call new functions.
	(regcache_supply_regset): Use gdb_byte*.
	(regcache::supply_regset): Likewise.
	(regcache_collect_regset): Likewise.
	(regcache::collect_regset): Likewise.
	* regcache.h (reg_buffer::raw_collect_part): New declaration.
	(reg_buffer::raw_supply_part): Likewise.
	(regcache::transfer_regset_register): Likewise.
	(regcache::transfer_regset): Use gdb_byte*.
---
 gdb/regcache.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++----------
 gdb/regcache.h |  20 ++++++++-
 2 files changed, 126 insertions(+), 25 deletions(-)
  

Comments

Simon Marchi June 22, 2018, 2:25 p.m. UTC | #1
Hi Alan,

The patch LGTM.  I noted two small comments you might want to address before
pushing.  Sorry again for the extra work I made you do!

On 2018-06-22 09:08 AM, Alan Hayward wrote:
> @@ -868,6 +900,39 @@ regcache::write_part (int regnum, int offset, int len,
>  
>  /* See regcache.h.  */
>  
> +void
> +reg_buffer::raw_supply_part (int regnum, int offset, int len,
> +			     const gdb_byte *in)
> +{
> +  int reg_size = register_size (arch (), regnum);
> +
> +  gdb_assert (in != nullptr);
> +  gdb_assert (offset >= 0 && offset <= reg_size);
> +  gdb_assert (len >= 0 && offset + len <= reg_size);
> +
> +  if (offset == 0 && len == 0)
> +    {
> +      /* Nothing to do.  */
> +      return;
> +    }
> +
> +  if (offset == 0 && len == reg_size)
> +    {
> +      /* Supply the full register.  */
> +      return raw_supply (regnum, in);
> +    }
> +
> +  gdb_byte *reg = (gdb_byte *) alloca (reg_size);
> +
> +  /* Read when needed.  */
> +  if (offset > 0 || offset + len < reg_size)

I think this if is unneeded now, with the earlier check?  If we reach
this point, it will always be for a partial register.

> diff --git a/gdb/regcache.h b/gdb/regcache.h
> index 983137f6ad..e706d87b54 100644
> --- a/gdb/regcache.h
> +++ b/gdb/regcache.h
> @@ -166,6 +166,10 @@ public:
>    void raw_collect_integer (int regnum, gdb_byte *addr, int addr_len,
>  			    bool is_signed) const;
>  
> +  /* Collect register REGNUM from REGCACHE, starting at offset in REGCACHE,
> +     reading only LEN.  */

I think "starting at offset in REGCACHE" is wrong, you probably meant
"starting at offset in the register" or something like that?

> +  void raw_collect_part (int regnum, int offset, int len, gdb_byte *out) const;
> +
>    /* See common/common-regcache.h.  */
>    void raw_supply (int regnum, const void *buf) override;
>  
> @@ -187,6 +191,10 @@ public:
>       unavailable).  */
>    void raw_supply_zeroed (int regnum);
>  
> +  /* Supply register REGNUM to REGCACHE, starting at offset in REGCACHE, writing
> +     only LEN, without editing the rest of the register.  */

Here too.

Thanks,

Simon
  
Alan Hayward June 22, 2018, 3:29 p.m. UTC | #2
> On 22 Jun 2018, at 15:25, Simon Marchi <simon.marchi@ericsson.com> wrote:

> 

> Hi Alan,

> 

> The patch LGTM.  I noted two small comments you might want to address before

> pushing.  Sorry again for the extra work I made you do!


No probs!

> 

> On 2018-06-22 09:08 AM, Alan Hayward wrote:

>> @@ -868,6 +900,39 @@ regcache::write_part (int regnum, int offset, int len,

>> 

>> /* See regcache.h.  */

>> 

>> +void

>> +reg_buffer::raw_supply_part (int regnum, int offset, int len,

>> +			     const gdb_byte *in)

>> +{

>> +  int reg_size = register_size (arch (), regnum);

>> +

>> +  gdb_assert (in != nullptr);

>> +  gdb_assert (offset >= 0 && offset <= reg_size);

>> +  gdb_assert (len >= 0 && offset + len <= reg_size);

>> +

>> +  if (offset == 0 && len == 0)

>> +    {

>> +      /* Nothing to do.  */

>> +      return;

>> +    }

>> +

>> +  if (offset == 0 && len == reg_size)

>> +    {

>> +      /* Supply the full register.  */

>> +      return raw_supply (regnum, in);

>> +    }

>> +

>> +  gdb_byte *reg = (gdb_byte *) alloca (reg_size);

>> +

>> +  /* Read when needed.  */

>> +  if (offset > 0 || offset + len < reg_size)

> 

> I think this if is unneeded now, with the earlier check?  If we reach

> this point, it will always be for a partial register.


Yes. I had removed the same check from raw_collect_part, but didn’t
spot this.

> 

>> diff --git a/gdb/regcache.h b/gdb/regcache.h

>> index 983137f6ad..e706d87b54 100644

>> --- a/gdb/regcache.h

>> +++ b/gdb/regcache.h

>> @@ -166,6 +166,10 @@ public:

>>   void raw_collect_integer (int regnum, gdb_byte *addr, int addr_len,

>> 			    bool is_signed) const;

>> 

>> +  /* Collect register REGNUM from REGCACHE, starting at offset in REGCACHE,

>> +     reading only LEN.  */

> 

> I think "starting at offset in REGCACHE" is wrong, you probably meant

> "starting at offset in the register" or something like that?


Done.

> 

>> +  void raw_collect_part (int regnum, int offset, int len, gdb_byte *out) const;

>> +

>>   /* See common/common-regcache.h.  */

>>   void raw_supply (int regnum, const void *buf) override;

>> 

>> @@ -187,6 +191,10 @@ public:

>>      unavailable).  */

>>   void raw_supply_zeroed (int regnum);

>> 

>> +  /* Supply register REGNUM to REGCACHE, starting at offset in REGCACHE, writing

>> +     only LEN, without editing the rest of the register.  */

> 

> Here too.

> 


Done.


Pushed with changes as suggested.


Thanks,
Alan.
  

Patch

diff --git a/gdb/regcache.c b/gdb/regcache.c
index 1bc4f0de88..68267bd271 100644
--- a/gdb/regcache.c
+++ b/gdb/regcache.c
@@ -801,7 +801,8 @@  readable_regcache::read_part (int regnum, int offset, int len,
   int reg_size = register_size (arch (), regnum);
 
   gdb_assert (out != NULL);
-  gdb_assert (offset >= 0 && len >= 0 && offset + len <= reg_size);
+  gdb_assert (offset >= 0 && offset <= reg_size);
+  gdb_assert (len >= 0 && offset + len <= reg_size);
 
   if (offset == 0 && len == 0)
     {
@@ -830,6 +831,36 @@  readable_regcache::read_part (int regnum, int offset, int len,
 
 /* See regcache.h.  */
 
+void
+reg_buffer::raw_collect_part (int regnum, int offset, int len,
+			      gdb_byte *out) const
+{
+  int reg_size = register_size (arch (), regnum);
+
+  gdb_assert (out != nullptr);
+  gdb_assert (offset >= 0 && offset <= reg_size);
+  gdb_assert (len >= 0 && offset + len <= reg_size);
+
+  if (offset == 0 && len == 0)
+    {
+      /* Nothing to do.  */
+      return;
+    }
+
+  if (offset == 0 && len == reg_size)
+    {
+      /* Collect the full register.  */
+      return raw_collect (regnum, out);
+    }
+
+  /* Read to buffer, then write out.  */
+  gdb_byte *reg = (gdb_byte *) alloca (reg_size);
+  raw_collect (regnum, reg);
+  memcpy (out, reg + offset, len);
+}
+
+/* See regcache.h.  */
+
 enum register_status
 regcache::write_part (int regnum, int offset, int len,
 		      const gdb_byte *in, bool is_raw)
@@ -837,7 +868,8 @@  regcache::write_part (int regnum, int offset, int len,
   int reg_size = register_size (arch (), regnum);
 
   gdb_assert (in != NULL);
-  gdb_assert (offset >= 0 && len >= 0 && offset + len <= reg_size);
+  gdb_assert (offset >= 0 && offset <= reg_size);
+  gdb_assert (len >= 0 && offset + len <= reg_size);
 
   if (offset == 0 && len == 0)
     {
@@ -868,6 +900,39 @@  regcache::write_part (int regnum, int offset, int len,
 
 /* See regcache.h.  */
 
+void
+reg_buffer::raw_supply_part (int regnum, int offset, int len,
+			     const gdb_byte *in)
+{
+  int reg_size = register_size (arch (), regnum);
+
+  gdb_assert (in != nullptr);
+  gdb_assert (offset >= 0 && offset <= reg_size);
+  gdb_assert (len >= 0 && offset + len <= reg_size);
+
+  if (offset == 0 && len == 0)
+    {
+      /* Nothing to do.  */
+      return;
+    }
+
+  if (offset == 0 && len == reg_size)
+    {
+      /* Supply the full register.  */
+      return raw_supply (regnum, in);
+    }
+
+  gdb_byte *reg = (gdb_byte *) alloca (reg_size);
+
+  /* Read when needed.  */
+  if (offset > 0 || offset + len < reg_size)
+    raw_collect (regnum, reg);
+
+  /* Write to buffer, then write out.  */
+  memcpy (reg + offset, in, len);
+  raw_supply (regnum, reg);
+}
+
 enum register_status
 readable_regcache::raw_read_part (int regnum, int offset, int len,
 				  gdb_byte *buf)
@@ -1006,15 +1071,43 @@  reg_buffer::raw_collect_integer (int regnum, gdb_byte *addr, int addr_len,
 			byte_order);
 }
 
-/* Transfer a single or all registers belonging to a certain register
-   set to or from a buffer.  This is the main worker function for
-   regcache_supply_regset and regcache_collect_regset.  */
+/* See regcache.h.  */
+
+void
+regcache::transfer_regset_register (struct regcache *out_regcache, int regnum,
+				    const gdb_byte *in_buf, gdb_byte *out_buf,
+				    int slot_size, int offs) const
+{
+  struct gdbarch *gdbarch = arch ();
+  int reg_size = std::min (register_size (gdbarch, regnum), slot_size);
+
+  /* Use part versions and reg_size to prevent possible buffer overflows when
+     accessing the regcache.  */
+
+  if (out_buf != nullptr)
+    {
+      raw_collect_part (regnum, 0, reg_size, out_buf + offs);
+
+      /* Ensure any additional space is cleared.  */
+      if (slot_size > reg_size)
+	memset (out_buf + offs + reg_size, 0, slot_size - reg_size);
+    }
+  else if (in_buf != nullptr)
+    out_regcache->raw_supply_part (regnum, 0, reg_size, in_buf + offs);
+  else
+    {
+      /* Invalidate the register.  */
+      out_regcache->raw_supply (regnum, nullptr);
+    }
+}
+
+/* See regcache.h.  */
 
 void
 regcache::transfer_regset (const struct regset *regset,
 			   struct regcache *out_regcache,
-			   int regnum, const void *in_buf,
-			   void *out_buf, size_t size) const
+			   int regnum, const gdb_byte *in_buf,
+			   gdb_byte *out_buf, size_t size) const
 {
   const struct regcache_map_entry *map;
   int offs = 0, count;
@@ -1040,12 +1133,8 @@  regcache::transfer_regset (const struct regset *regset,
 	    if (offs + slot_size > size)
 	      break;
 
-	    if (out_buf)
-	      raw_collect (regno, (gdb_byte *) out_buf + offs);
-	    else
-	      out_regcache->raw_supply (regno, in_buf
-					? (const gdb_byte *) in_buf + offs
-					: NULL);
+	    transfer_regset_register (out_regcache, regno, in_buf, out_buf,
+				      slot_size, offs);
 	  }
       else
 	{
@@ -1054,12 +1143,8 @@  regcache::transfer_regset (const struct regset *regset,
 	  if (offs + slot_size > size)
 	    return;
 
-	  if (out_buf)
-	    raw_collect (regnum, (gdb_byte *) out_buf + offs);
-	  else
-	    out_regcache->raw_supply (regnum, in_buf
-				      ? (const gdb_byte *) in_buf + offs
-				      : NULL);
+	  transfer_regset_register (out_regcache, regnum, in_buf, out_buf,
+				    slot_size, offs);
 	  return;
 	}
     }
@@ -1074,14 +1159,14 @@  regcache_supply_regset (const struct regset *regset,
 			struct regcache *regcache,
 			int regnum, const void *buf, size_t size)
 {
-  regcache->supply_regset (regset, regnum, buf, size);
+  regcache->supply_regset (regset, regnum, (const gdb_byte *) buf, size);
 }
 
 void
 regcache::supply_regset (const struct regset *regset,
 			 int regnum, const void *buf, size_t size)
 {
-  transfer_regset (regset, this, regnum, buf, NULL, size);
+  transfer_regset (regset, this, regnum, (const gdb_byte *) buf, nullptr, size);
 }
 
 /* Collect register REGNUM from REGCACHE to BUF, using the register
@@ -1093,14 +1178,14 @@  regcache_collect_regset (const struct regset *regset,
 			 const struct regcache *regcache,
 			 int regnum, void *buf, size_t size)
 {
-  regcache->collect_regset (regset, regnum, buf, size);
+  regcache->collect_regset (regset, regnum, (gdb_byte *) buf, size);
 }
 
 void
 regcache::collect_regset (const struct regset *regset,
 			 int regnum, void *buf, size_t size) const
 {
-  transfer_regset (regset, NULL, regnum, NULL, buf, size);
+  transfer_regset (regset, nullptr, regnum, nullptr, (gdb_byte *) buf, size);
 }
 
 /* See common/common-regcache.h.  */
diff --git a/gdb/regcache.h b/gdb/regcache.h
index 983137f6ad..e706d87b54 100644
--- a/gdb/regcache.h
+++ b/gdb/regcache.h
@@ -166,6 +166,10 @@  public:
   void raw_collect_integer (int regnum, gdb_byte *addr, int addr_len,
 			    bool is_signed) const;
 
+  /* Collect register REGNUM from REGCACHE, starting at offset in REGCACHE,
+     reading only LEN.  */
+  void raw_collect_part (int regnum, int offset, int len, gdb_byte *out) const;
+
   /* See common/common-regcache.h.  */
   void raw_supply (int regnum, const void *buf) override;
 
@@ -187,6 +191,10 @@  public:
      unavailable).  */
   void raw_supply_zeroed (int regnum);
 
+  /* Supply register REGNUM to REGCACHE, starting at offset in REGCACHE, writing
+     only LEN, without editing the rest of the register.  */
+  void raw_supply_part (int regnum, int offset, int len, const gdb_byte *in);
+
   void invalidate (int regnum);
 
   virtual ~reg_buffer () = default;
@@ -358,10 +366,18 @@  protected:
 
 private:
 
+  /* Helper function for transfer_regset.  Copies across a single register.  */
+  void transfer_regset_register (struct regcache *out_regcache, int regnum,
+				 const gdb_byte *in_buf, gdb_byte *out_buf,
+				 int slot_size, int offs) const;
+
+  /* Transfer a single or all registers belonging to a certain register
+     set to or from a buffer.  This is the main worker function for
+     regcache_supply_regset and regcache_collect_regset.  */
   void transfer_regset (const struct regset *regset,
 			struct regcache *out_regcache,
-			int regnum, const void *in_buf,
-			void *out_buf, size_t size) const;
+			int regnum, const gdb_byte *in_buf,
+			gdb_byte *out_buf, size_t size) const;
 
   /* Perform a partial register transfer using a read, modify, write
      operation.  */