This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: x86 watchpoints bug (Re: ping: Re: PATCH : allow to set length of hw watchpoints (e.g. for Valgrind gdbserver))


Hi Philippe.

I'm very sorry in the time it is taking to get this over with...

On Thursday 09 June 2011 23:16:35, Philippe Waroquiers wrote:
> Thanks for the feedback, behaviour looks to be better
> when adding the missing assignment. But I suspect I found another
> bug in the area of "high level" to "low level" to "hw level" watchpoints.
> 
> >> I suspect the problem might be in the following piece of code:
> >> static void
> >> update_inferior (struct i386_debug_reg_state *inf_state,
> >>    struct i386_debug_reg_state *new_state)
> >> {
> >>   int i;
> >> 
> >>   ALL_DEBUG_REGISTERS (i)
> >>     {
> >>       if (new_state->dr_mirror[i] != inf_state->dr_mirror[i]
> >>    || (new_state->dr_ref_count[i] != 0
> >>        && inf_state->dr_ref_count[i] == 0))
> >>  {
> >> 
> >> The dr_mirror is the address being watched.
> >> But if address being watched is 0x0, then a 'busy' register
> >> watching 0x0 and a non-busy register will have equal dr_mirror.
> >> Then the || condition is bizarre as the ref.count will be updated
> >> only if the current inf_state ref.count is 0.
> > 
> > Not the ref.count.  The address to watch, DR[0-3].
> 
> Without the  *inf_state = *new_state,
> I had some difficulties to understand the above code.
> 
> From what I understand now, the idea of this piece of code
> is (only) to change the real value of the hw register.
> But if inf_state->dr_mirror properly mirrors the value of the hw
> register, then the inequality of the dr_mirror[i] should
> be good enough to detect the need to change the hw register.
> 
> And if setting the address is only to be done when activating
> the watchpoint, then the inequality on the ref count should be
> good enough (and the assert new_state->dr_ref_count[i] == 1
> should hold when changing the hw addr value).
> 
> Well it seems I still have difficulty to understand the code :).

You are right, that wasn't clear.  This version of the function
is modelled on what you wrote above, with a twist -- we just check
if the register is transitioning from used or not used, a bit
easier to read than the refcounts checks.

The gdb native version now also better mirrors what the unpatched
code is doing, so is a bit different (gdbserver doesn't have the
unset_status or reset_addr callbacks).

I've renamed the function i386_update_inferior_debug_regs, btw.

This centralization opens the possibility of replacing most of
the i386_dr_low interface callbacks for a single callback that takes
a i386_debug_reg_state pointer (or an argument for each debug register)
afterwards, given that we now inform the target about changes all
in a single place -- that may save a few ptrace calls per watchpoint.

> >> +# registers were available to cover a single (low level) watchpoint.
> > watchpoint.  So the comment was correct if you think of high and
> > low level watchpoints like I was thinking.  Maybe you were thinking
> > of a high level watchpoint as what the target sees?
> 
> Yes, I was interpreting "high level" being a Z2 packet, and "low level"
> being the "hw" watchpoint.
> Now, I understand that we have:
>    high level watchpoints = "user defined watchpoints in gdb" = watched expressions
>    low level watchpoints = "memory region watchpoints needed to implement the high level" = Z2 packets
>    hw level  watchpoints =  "hw watchpoint(s) needed to implement the low level watchpoint(s)"
> 
> Thanks for the clarification

Ah, now it's clear why we were talking past each other.  I think I might
as well stop avoiding GDB's internal terminology for "low level
watchpoints" -- it's a watchpoint location.  I've tweaked the comment
accordingly.

I think nothing else changed in the patch.

> Doing some additional checks, I found something else slightly strange, but it seems
> to be wrong at the mapping between "high level watchpoints" and "low level watchpoints".
> 
> In the below, you see that 3 identical watchpoints results in 1 single Z2
> packet, but disabling the 3 watchpoints gives 3 z2 packets (sent
> when the last watchpoint is disabled).
> The reason for this assymetry looks not very clear to me, but that might
> just be an implementation detail.
> I however suspect there is still a bug, as after, when sharing a debug register
> between two non-identical watchpoints, we are losing a part of the to be watched
> zone : we still have a user level watchpoint of 16 bytes at 0x0, but the hw registers
> are only watching 8 bytes at 0x8.
> (the below is done with a patched gdb/gdbserver containing the "dr busy" fix
> + the missing assignment + the "set length" patch to allow watching 16 bytes
> with gdbserver).
> Note that the bug seems also present in native debugging (see native gdb session at the end).

Very nice catch.  Yes, pushing out 3 z2 packets is very wrong, and
is most certainly what is messing up the 16 bytes watchpoint -- that
watchpoint should have got spread out across two debug registers (which
can only watch 8 byte long regions), those extra z2 packets messed
up the refcount of the first register.  

I've debugged this a little, and it all looks like the bug is
within breakpoint.c:update_global_location_list, which isn't
handling duplicate locations across "disabled" breakpoints
correctly.  This not watchpoint specific, and, triggers
on 7.2 as well.  E.g., on 7.2, with breakpoints always inserted
on, triggering the bug with regular breakpoints:

...
Sending packet: $z0,4004b8,1#95...Packet received: OK
(gdb) b main
Breakpoint 2 at 0x4004b8: file s.c, line 22.
Sending packet: $Z0,4004b8,1#75...Packet received: OK
(gdb) b main
Note: breakpoint 2 also set at pc 0x4004b8.
Breakpoint 3 at 0x4004b8: file s.c, line 22.
(gdb) disable 2
(gdb) disable 3
Sending packet: $z0,4004b8,1#95...Packet received: OK
Sending packet: $z0,4004b8,1#95...Packet received: E01
warning: Error removing breakpoint 3
(gdb) 

GDB failed to realize that the locations were duplicate,
and that only one should have been removed --- there should
have been one z0 only...  Bummer.

Given that's an unrelated issue, this patch stands on its
own.  Could you run your sharp eye through it one more
time, please?

-- 
Pedro Alves

2011-07-21  Pedro Alves  <pedro@codesourcery.com>

	gdb/testsuite/
	* gdb.arch/i386-dr3-watch.exp: Test that the i386 watchpoints
	backend doesn't leave used debug registers behind.

	gdb/gdbserver/
	* i386-low.c (i386_insert_aligned_watchpoint): Don't pass the info
	to the inferior here.
	(i386_remove_aligned_watchpoint): Ditto.
	(i386_handle_nonaligned_watchpoint): Return immediate on fail to
	fit part of the watchpoint in the debug registers.
	(i386_update_inferior_debug_regs): New.
	(i386_low_insert_watchpoint): Work on a local mirror of the debug
	registers, and only update the inferior on success.
	(i386_low_remove_watchpoint): Ditto.

	gdb/
	* i386-nat.c (I386_DR_VACANT, I386_DR_LOCAL_ENABLE)
	(I386_DR_GLOBAL_ENABLE, I386_DR_DISABLE, I386_DR_SET_RW_LEN)
	(I386_DR_GET_RW_LEN, I386_DR_WATCH_HIT): Add state parameter and
	adjust.
	(dr_mirror, dr_status_mirror, dr_control_mirror): Delete.
	(struct i386_debug_reg_state): New.
	(i386_init_dregs): New.
	(dr_mirror): New.
	(i386_cleanup_dregs): Use i386_init_dregs.
	(i386_show_dr): Add state parameter and adjust.
	(i386_insert_aligned_watchpoint): Ditto.  Don't pass the info to
	the inferior here.
	(i386_remove_aligned_watchpoint): Likewise.
	(i386_handle_nonaligned_watchpoint): Add state parameter and adjust.
	(i386_update_inferior_debug_regs): New.
	(i386_insert_watchpoint): Work on a local mirror of the debug
	registers, and only update the inferior on success.
	(i386_remove_watchpoint): Ditto.
	(i386_region_ok_for_watchpoint): Adjust.
	(i386_stopped_data_address): Adjust.
	(i386_insert_hw_breakpoint): Adjust.
	(i386_remove_hw_breakpoint): Adjust.

---
 gdb/gdbserver/i386-low.c                  |   69 ++++---
 gdb/i386-nat.c                            |  283 +++++++++++++++++++-----------
 gdb/testsuite/gdb.arch/i386-dr3-watch.exp |   44 ++++
 3 files changed, 272 insertions(+), 124 deletions(-)

Index: src/gdb/testsuite/gdb.arch/i386-dr3-watch.exp
===================================================================
--- src.orig/gdb/testsuite/gdb.arch/i386-dr3-watch.exp	2011-07-21 16:21:25.000000000 +0100
+++ src/gdb/testsuite/gdb.arch/i386-dr3-watch.exp	2011-07-21 16:56:08.773673926 +0100
@@ -38,6 +38,8 @@ if ![runto_main] then {
 
 gdb_test_no_output "set breakpoint always-inserted on"
 
+# Test that we handle watchpoints in all of DR0-DR3.
+
 gdb_test "watch i1" "Hardware watchpoint .*: i1"
 gdb_test "watch i2" "Hardware watchpoint .*: i2"
 gdb_test "watch i3" "Hardware watchpoint .*: i3"
@@ -47,3 +49,45 @@ gdb_test "c" "Hardware watchpoint.*: i1.
 gdb_test "c" "Hardware watchpoint.*: i2.*" "continue to i2 watchpoint"
 gdb_test "c" "Hardware watchpoint.*: i3.*" "continue to i3 watchpoint"
 gdb_test "c" "Hardware watchpoint.*: i4.*" "continue to i4 watchpoint"
+
+delete_breakpoints
+
+# Regression test for a bug where the i386 watchpoints support backend
+# would leave some debug registers occupied even if not enough debug
+# registers were available to cover a single watchpoint location.
+
+gdb_test "watch i1" \
+    "Hardware watchpoint .*: i1" \
+    "set watchpoint occuping one debug register"
+
+# gap1 too long to fit the 3 left over debug registers (but would fit
+# 4 if all were available).
+set test "watchpoint on gap1 does not fit debug registers"
+gdb_test_multiple "watch gap1" "$test" {
+    -re "Hardware watchpoint .*: gap1.*Warning:.*Could not insert hardware watchpoint.*You may have requested too many.*" {
+	pass $test
+    }
+    -re "Hardware watchpoint .*: gap1\r\n$gdb_prompt $" {
+	pass "$test (target emulates hardware watchpoints)"
+	return
+    }
+    -re "Watchpoint .*: gap1\r\n$gdb_prompt $" {
+	pass "$test (gdb figured out itself the watchpoint does not fit)"
+	return
+    }
+}
+
+# Start over.
+gdb_test "delete" \
+    "" \
+    "delete all watchpoints" \
+    "Delete all breakpoints.*$" \
+    "y"
+
+# If debug registers were left occupied by mistake, we'll fail to set
+# some of these watchpoints.  Each watchpoint should fit in one of the
+# 4 debug registers available.
+gdb_test "watch i1" "Hardware watchpoint .*: i1" "watch i1 still fits"
+gdb_test "watch i2" "Hardware watchpoint .*: i2" "watch i2 still fits"
+gdb_test "watch i3" "Hardware watchpoint .*: i3" "watch i3 still fits"
+gdb_test "watch i4" "Hardware watchpoint .*: i4" "watch i4 still fits"
Index: src/gdb/gdbserver/i386-low.c
===================================================================
--- src.orig/gdb/gdbserver/i386-low.c	2011-07-21 16:21:25.000000000 +0100
+++ src/gdb/gdbserver/i386-low.c	2011-07-21 16:56:08.773673926 +0100
@@ -307,10 +307,6 @@ i386_insert_aligned_watchpoint (struct i
   state->dr_control_mirror |= DR_LOCAL_SLOWDOWN;
   state->dr_control_mirror &= I386_DR_CONTROL_MASK;
 
-  /* Finally, actually pass the info to the inferior.  */
-  i386_dr_low_set_addr (state, i);
-  i386_dr_low_set_control (state);
-
   return 0;
 }
 
@@ -337,9 +333,6 @@ i386_remove_aligned_watchpoint (struct i
 	      /* Reset our mirror.  */
 	      state->dr_mirror[i] = 0;
 	      I386_DR_DISABLE (state, i);
-	      /* Reset it in the inferior.  */
-	      i386_dr_low_set_control (state);
-	      i386_dr_low_set_addr (state, i);
 	    }
 	  retval = 0;
 	}
@@ -360,7 +353,7 @@ i386_handle_nonaligned_watchpoint (struc
 				   i386_wp_op_t what, CORE_ADDR addr, int len,
 				   enum target_hw_bp_type type)
 {
-  int retval = 0, status = 0;
+  int retval = 0;
   int max_wp_len = TARGET_HAS_DR_LEN_8 ? 8 : 4;
 
   static const int size_try_array[8][8] =
@@ -398,25 +391,16 @@ i386_handle_nonaligned_watchpoint (struc
 	  unsigned len_rw = i386_length_and_rw_bits (size, type);
 
 	  if (what == WP_INSERT)
-	    status = i386_insert_aligned_watchpoint (state, addr, len_rw);
+	    retval = i386_insert_aligned_watchpoint (state, addr, len_rw);
 	  else if (what == WP_REMOVE)
-	    status = i386_remove_aligned_watchpoint (state, addr, len_rw);
+	    retval = i386_remove_aligned_watchpoint (state, addr, len_rw);
 	  else
 	    fatal ("\
 Invalid value %d of operation in i386_handle_nonaligned_watchpoint.\n",
 		   (int) what);
 
-	  /* We keep the loop going even after a failure, because some
-	     of the other aligned watchpoints might still succeed
-	     (e.g. if they watch addresses that are already watched,
-	     in which case we just increment the reference counts of
-	     occupied debug registers).  If we break out of the loop
-	     too early, we could cause those addresses watched by
-	     other watchpoints to be disabled when breakpoint.c reacts
-	     to our failure to insert this watchpoint and tries to
-	     remove it.  */
-	  if (status)
-	    retval = status;
+	  if (retval)
+	    break;
 	}
 
       addr += size;
@@ -448,6 +432,29 @@ Z_packet_to_hw_type (char type)
     }
 }
 
+/* Update the inferior debug registers state, in INF_STATE, with the
+   new debug registers state, in NEW_STATE.  */
+
+static void
+i386_update_inferior_debug_regs (struct i386_debug_reg_state *inf_state,
+				 struct i386_debug_reg_state *new_state)
+{
+  int i;
+
+  ALL_DEBUG_REGISTERS (i)
+    {
+      if (I386_DR_VACANT (new_state, i) != I386_DR_VACANT (inf_state, i))
+	i386_dr_low_set_addr (new_state, i);
+      else
+	gdb_assert (new_state->dr_mirror[i] == inf_state->dr_mirror[i]);
+    }
+
+  if (new_state->dr_control_mirror != inf_state->dr_control_mirror)
+    i386_dr_low_set_control (new_state);
+
+  *inf_state = *new_state;
+}
+
 /* Insert a watchpoint to watch a memory region which starts at
    address ADDR and whose length is LEN bytes.  Watch memory accesses
    of the type TYPE_FROM_PACKET.  Return 0 on success, -1 on failure.  */
@@ -458,6 +465,9 @@ i386_low_insert_watchpoint (struct i386_
 {
   int retval;
   enum target_hw_bp_type type = Z_packet_to_hw_type (type_from_packet);
+  /* Work on a local copy of the debug registers, and on success,
+     commit the change back to the inferior.  */
+  struct i386_debug_reg_state local_state = *state;
 
   if (type == hw_read)
     return 1; /* unsupported */
@@ -466,16 +476,19 @@ i386_low_insert_watchpoint (struct i386_
        && !(TARGET_HAS_DR_LEN_8 && len == 8))
       || addr % len != 0)
     {
-      retval = i386_handle_nonaligned_watchpoint (state, WP_INSERT,
+      retval = i386_handle_nonaligned_watchpoint (&local_state, WP_INSERT,
 						  addr, len, type);
     }
   else
     {
       unsigned len_rw = i386_length_and_rw_bits (len, type);
 
-      retval = i386_insert_aligned_watchpoint (state, addr, len_rw);
+      retval = i386_insert_aligned_watchpoint (&local_state, addr, len_rw);
     }
 
+  if (retval == 0)
+    i386_update_inferior_debug_regs (state, &local_state);
+
   if (debug_hw_points)
     i386_show_dr (state, "insert_watchpoint", addr, len, type);
 
@@ -492,21 +505,27 @@ i386_low_remove_watchpoint (struct i386_
 {
   int retval;
   enum target_hw_bp_type type = Z_packet_to_hw_type (type_from_packet);
+  /* Work on a local copy of the debug registers, and on success,
+     commit the change back to the inferior.  */
+  struct i386_debug_reg_state local_state = *state;
 
   if (((len != 1 && len != 2 && len != 4)
        && !(TARGET_HAS_DR_LEN_8 && len == 8))
       || addr % len != 0)
     {
-      retval = i386_handle_nonaligned_watchpoint (state, WP_REMOVE,
+      retval = i386_handle_nonaligned_watchpoint (&local_state, WP_REMOVE,
 						  addr, len, type);
     }
   else
     {
       unsigned len_rw = i386_length_and_rw_bits (len, type);
 
-      retval = i386_remove_aligned_watchpoint (state, addr, len_rw);
+      retval = i386_remove_aligned_watchpoint (&local_state, addr, len_rw);
     }
 
+  if (retval == 0)
+    i386_update_inferior_debug_regs (state, &local_state);
+
   if (debug_hw_points)
     i386_show_dr (state, "remove_watchpoint", addr, len, type);
 
Index: src/gdb/i386-nat.c
===================================================================
--- src.orig/gdb/i386-nat.c	2011-07-21 16:21:25.000000000 +0100
+++ src/gdb/i386-nat.c	2011-07-21 16:56:08.773673926 +0100
@@ -111,45 +111,88 @@ struct i386_dr_low_type i386_dr_low;
 
 /* The I'th debug register is vacant if its Local and Global Enable
    bits are reset in the Debug Control register.  */
-#define I386_DR_VACANT(i) \
-  ((dr_control_mirror & (3 << (DR_ENABLE_SIZE * (i)))) == 0)
+#define I386_DR_VACANT(state, i)					\
+  (((state)->dr_control_mirror & (3 << (DR_ENABLE_SIZE * (i)))) == 0)
 
 /* Locally enable the break/watchpoint in the I'th debug register.  */
-#define I386_DR_LOCAL_ENABLE(i) \
-  dr_control_mirror |= (1 << (DR_LOCAL_ENABLE_SHIFT + DR_ENABLE_SIZE * (i)))
+#define I386_DR_LOCAL_ENABLE(state, i) \
+  do { \
+    (state)->dr_control_mirror |= \
+      (1 << (DR_LOCAL_ENABLE_SHIFT + DR_ENABLE_SIZE * (i))); \
+  } while (0)
 
 /* Globally enable the break/watchpoint in the I'th debug register.  */
-#define I386_DR_GLOBAL_ENABLE(i) \
-  dr_control_mirror |= (1 << (DR_GLOBAL_ENABLE_SHIFT + DR_ENABLE_SIZE * (i)))
+#define I386_DR_GLOBAL_ENABLE(state, i) \
+  do { \
+    (state)->dr_control_mirror |= \
+      (1 << (DR_GLOBAL_ENABLE_SHIFT + DR_ENABLE_SIZE * (i))); \
+  } while (0)
 
 /* Disable the break/watchpoint in the I'th debug register.  */
-#define I386_DR_DISABLE(i) \
-  dr_control_mirror &= ~(3 << (DR_ENABLE_SIZE * (i)))
+#define I386_DR_DISABLE(state, i) \
+  do { \
+    (state)->dr_control_mirror &= \
+      ~(3 << (DR_ENABLE_SIZE * (i))); \
+  } while (0)
 
 /* Set in DR7 the RW and LEN fields for the I'th debug register.  */
-#define I386_DR_SET_RW_LEN(i,rwlen) \
+#define I386_DR_SET_RW_LEN(state, i, rwlen) \
   do { \
-    dr_control_mirror &= ~(0x0f << (DR_CONTROL_SHIFT+DR_CONTROL_SIZE*(i)));   \
-    dr_control_mirror |= ((rwlen) << (DR_CONTROL_SHIFT+DR_CONTROL_SIZE*(i))); \
+    (state)->dr_control_mirror &= \
+      ~(0x0f << (DR_CONTROL_SHIFT + DR_CONTROL_SIZE * (i))); \
+    (state)->dr_control_mirror |= \
+      ((rwlen) << (DR_CONTROL_SHIFT + DR_CONTROL_SIZE * (i))); \
   } while (0)
 
 /* Get from DR7 the RW and LEN fields for the I'th debug register.  */
-#define I386_DR_GET_RW_LEN(i) \
-  ((dr_control_mirror >> (DR_CONTROL_SHIFT + DR_CONTROL_SIZE * (i))) & 0x0f)
+#define I386_DR_GET_RW_LEN(dr7, i) \
+  (((dr7) \
+    >> (DR_CONTROL_SHIFT + DR_CONTROL_SIZE * (i))) & 0x0f)
 
 /* Mask that this I'th watchpoint has triggered.  */
 #define I386_DR_WATCH_MASK(i)	(1 << (i))
 
 /* Did the watchpoint whose address is in the I'th register break?  */
-#define I386_DR_WATCH_HIT(i)	(dr_status_mirror & I386_DR_WATCH_MASK (i))
+#define I386_DR_WATCH_HIT(dr6, i) ((dr6) & (1 << (i)))
 
 /* A macro to loop over all debug registers.  */
 #define ALL_DEBUG_REGISTERS(i)	for (i = 0; i < DR_NADDR; i++)
 
-/* Mirror the inferior's DRi registers.  We keep the status and
-   control registers separated because they don't hold addresses.  */
-static CORE_ADDR dr_mirror[DR_NADDR];
-static unsigned long dr_status_mirror, dr_control_mirror;
+
+/* Global state needed to track h/w watchpoints.  */
+
+struct i386_debug_reg_state
+{
+  /* Mirror the inferior's DRi registers.  We keep the status and
+     control registers separated because they don't hold addresses.
+     Note that since we can change these mirrors while threads are
+     running, we never trust them to explain a cause of a trap.
+     For that, we need to peek directly in the inferior registers.  */
+  CORE_ADDR dr_mirror[DR_NADDR];
+  unsigned dr_status_mirror, dr_control_mirror;
+
+  /* Reference counts for each debug register.  */
+  int dr_ref_count[DR_NADDR];
+};
+
+/* Clear the reference counts and forget everything we knew about the
+   debug registers.  */
+
+static void
+i386_init_dregs (struct i386_debug_reg_state *state)
+{
+  int i;
+
+  ALL_DEBUG_REGISTERS (i)
+    {
+      state->dr_mirror[i] = 0;
+      state->dr_ref_count[i] = 0;
+    }
+  state->dr_control_mirror = 0;
+  state->dr_status_mirror  = 0;
+}
+
+static struct i386_debug_reg_state dr_mirror;
 
 /* Reference counts for each debug register.  */
 static int dr_ref_count[DR_NADDR];
@@ -172,7 +215,8 @@ static unsigned i386_length_and_rw_bits
    value of the bit-field from DR7 which describes the length and
    access type of the region to be watched by this watchpoint.  Return
    0 on success, -1 on failure.  */
-static int i386_insert_aligned_watchpoint (CORE_ADDR addr,
+static int i386_insert_aligned_watchpoint (struct i386_debug_reg_state *state,
+					   CORE_ADDR addr,
 					   unsigned len_rw_bits);
 
 /* Remove a watchpoint at address ADDR, which is assumed to be aligned
@@ -180,7 +224,8 @@ static int i386_insert_aligned_watchpoin
    value of the bits from DR7 which describes the length and access
    type of the region watched by this watchpoint.  Return 0 on
    success, -1 on failure.  */
-static int i386_remove_aligned_watchpoint (CORE_ADDR addr,
+static int i386_remove_aligned_watchpoint (struct i386_debug_reg_state *state,
+					   CORE_ADDR addr,
 					   unsigned len_rw_bits);
 
 /* Insert or remove a (possibly non-aligned) watchpoint, or count the
@@ -189,7 +234,8 @@ static int i386_remove_aligned_watchpoin
    successful insertion or removal, a positive number when queried
    about the number of registers, or -1 on failure.  If WHAT is not a
    valid value, bombs through internal_error.  */
-static int i386_handle_nonaligned_watchpoint (i386_wp_op_t what,
+static int i386_handle_nonaligned_watchpoint (struct i386_debug_reg_state *state,
+					      i386_wp_op_t what,
 					      CORE_ADDR addr, int len,
 					      enum target_hw_bp_type type);
 
@@ -201,15 +247,7 @@ static int i386_handle_nonaligned_watchp
 void
 i386_cleanup_dregs (void)
 {
-  int i;
-
-  ALL_DEBUG_REGISTERS(i)
-    {
-      dr_mirror[i] = 0;
-      dr_ref_count[i] = 0;
-    }
-  dr_control_mirror = 0;
-  dr_status_mirror  = 0;
+  i386_init_dregs (&dr_mirror);
 }
 
 /* Print the values of the mirrored debug registers.  This is called
@@ -217,7 +255,8 @@ i386_cleanup_dregs (void)
    show-debug-regs" at GDB's prompt.  */
 
 static void
-i386_show_dr (const char *func, CORE_ADDR addr,
+i386_show_dr (struct i386_debug_reg_state *state,
+	      const char *func, CORE_ADDR addr,
 	      int len, enum target_hw_bp_type type)
 {
   int addr_size = gdbarch_addr_bit (target_gdbarch) / 8;
@@ -239,13 +278,16 @@ i386_show_dr (const char *func, CORE_ADD
 				: "??unknown??"))));
   puts_unfiltered (":\n");
   printf_unfiltered ("\tCONTROL (DR7): %s          STATUS (DR6): %s\n",
-		     phex (dr_control_mirror, 8), phex (dr_status_mirror, 8));
+		     phex (state->dr_control_mirror, 8),
+		     phex (state->dr_status_mirror, 8));
   ALL_DEBUG_REGISTERS(i)
     {
       printf_unfiltered ("\
 \tDR%d: addr=0x%s, ref.count=%d  DR%d: addr=0x%s, ref.count=%d\n",
-		 i, phex (dr_mirror[i], addr_size), dr_ref_count[i],
-		 i+1, phex (dr_mirror[i+1], addr_size), dr_ref_count[i+1]);
+			 i, phex (state->dr_mirror[i], addr_size),
+			 state->dr_ref_count[i],
+			 i + 1, phex (state->dr_mirror[i + 1], addr_size),
+			 state->dr_ref_count[i+1]);
       i++;
     }
 }
@@ -311,7 +353,8 @@ Invalid hardware breakpoint length %d in
    success, -1 on failure.  */
 
 static int
-i386_insert_aligned_watchpoint (CORE_ADDR addr, unsigned len_rw_bits)
+i386_insert_aligned_watchpoint (struct i386_debug_reg_state *state,
+				CORE_ADDR addr, unsigned len_rw_bits)
 {
   int i;
 
@@ -323,11 +366,11 @@ i386_insert_aligned_watchpoint (CORE_ADD
      reuse it for this watchpoint as well (and save a register).  */
   ALL_DEBUG_REGISTERS(i)
     {
-      if (!I386_DR_VACANT (i)
-	  && dr_mirror[i] == addr
-	  && I386_DR_GET_RW_LEN (i) == len_rw_bits)
+      if (!I386_DR_VACANT (state, i)
+	  && state->dr_mirror[i] == addr
+	  && I386_DR_GET_RW_LEN (state->dr_control_mirror, i) == len_rw_bits)
 	{
-	  dr_ref_count[i]++;
+	  state->dr_ref_count[i]++;
 	  return 0;
 	}
     }
@@ -335,7 +378,7 @@ i386_insert_aligned_watchpoint (CORE_ADD
   /* Next, look for a vacant debug register.  */
   ALL_DEBUG_REGISTERS(i)
     {
-      if (I386_DR_VACANT (i))
+      if (I386_DR_VACANT (state, i))
 	break;
     }
 
@@ -346,9 +389,9 @@ i386_insert_aligned_watchpoint (CORE_ADD
   /* Now set up the register I to watch our region.  */
 
   /* Record the info in our local mirrored array.  */
-  dr_mirror[i] = addr;
-  dr_ref_count[i] = 1;
-  I386_DR_SET_RW_LEN (i, len_rw_bits);
+  state->dr_mirror[i] = addr;
+  state->dr_ref_count[i] = 1;
+  I386_DR_SET_RW_LEN (state, i, len_rw_bits);
   /* Note: we only enable the watchpoint locally, i.e. in the current
      task.  Currently, no i386 target allows or supports global
      watchpoints; however, if any target would want that in the
@@ -356,17 +399,9 @@ i386_insert_aligned_watchpoint (CORE_ADD
      to enable watchpoints globally or locally, and the code below
      should use global or local enable and slow-down flags as
      appropriate.  */
-  I386_DR_LOCAL_ENABLE (i);
-  dr_control_mirror |= DR_LOCAL_SLOWDOWN;
-  dr_control_mirror &= I386_DR_CONTROL_MASK;
-
-  /* Finally, actually pass the info to the inferior.  */
-  i386_dr_low.set_addr (i, addr);
-  i386_dr_low.set_control (dr_control_mirror);
-
-  /* Only a sanity check for leftover bits (set possibly only by inferior).  */
-  if (i386_dr_low.unset_status)
-    i386_dr_low.unset_status (I386_DR_WATCH_MASK (i));
+  I386_DR_LOCAL_ENABLE (state, i);
+  state->dr_control_mirror |= DR_LOCAL_SLOWDOWN;
+  state->dr_control_mirror &= I386_DR_CONTROL_MASK;
 
   return 0;
 }
@@ -378,25 +413,22 @@ i386_insert_aligned_watchpoint (CORE_ADD
    success, -1 on failure.  */
 
 static int
-i386_remove_aligned_watchpoint (CORE_ADDR addr, unsigned len_rw_bits)
+i386_remove_aligned_watchpoint (struct i386_debug_reg_state *state,
+				CORE_ADDR addr, unsigned len_rw_bits)
 {
   int i, retval = -1;
 
   ALL_DEBUG_REGISTERS(i)
     {
-      if (!I386_DR_VACANT (i)
-	  && dr_mirror[i] == addr
-	  && I386_DR_GET_RW_LEN (i) == len_rw_bits)
+      if (!I386_DR_VACANT (state, i)
+	  && state->dr_mirror[i] == addr
+	  && I386_DR_GET_RW_LEN (state->dr_control_mirror, i) == len_rw_bits)
 	{
-	  if (--dr_ref_count[i] == 0) /* no longer in use?  */
+	  if (--state->dr_ref_count[i] == 0) /* no longer in use?  */
 	    {
 	      /* Reset our mirror.  */
-	      dr_mirror[i] = 0;
-	      I386_DR_DISABLE (i);
-	      /* Reset it in the inferior.  */
-	      i386_dr_low.set_control (dr_control_mirror);
-	      if (i386_dr_low.reset_addr)
-		i386_dr_low.reset_addr (i);
+	      state->dr_mirror[i] = 0;
+	      I386_DR_DISABLE (state, i);
 	    }
 	  retval = 0;
 	}
@@ -413,10 +445,11 @@ i386_remove_aligned_watchpoint (CORE_ADD
    valid value, bombs through internal_error.  */
 
 static int
-i386_handle_nonaligned_watchpoint (i386_wp_op_t what, CORE_ADDR addr, int len,
+i386_handle_nonaligned_watchpoint (struct i386_debug_reg_state *state,
+				   i386_wp_op_t what, CORE_ADDR addr, int len,
 				   enum target_hw_bp_type type)
 {
-  int retval = 0, status = 0;
+  int retval = 0;
   int max_wp_len = TARGET_HAS_DR_LEN_8 ? 8 : 4;
 
   static int size_try_array[8][8] =
@@ -454,24 +487,15 @@ i386_handle_nonaligned_watchpoint (i386_
 	  unsigned len_rw = i386_length_and_rw_bits (size, type);
 
 	  if (what == WP_INSERT)
-	    status = i386_insert_aligned_watchpoint (addr, len_rw);
+	    retval = i386_insert_aligned_watchpoint (state, addr, len_rw);
 	  else if (what == WP_REMOVE)
-	    status = i386_remove_aligned_watchpoint (addr, len_rw);
+	    retval = i386_remove_aligned_watchpoint (state, addr, len_rw);
 	  else
 	    internal_error (__FILE__, __LINE__, _("\
 Invalid value %d of operation in i386_handle_nonaligned_watchpoint.\n"),
 			    (int)what);
-	  /* We keep the loop going even after a failure, because some
-	     of the other aligned watchpoints might still succeed
-	     (e.g. if they watch addresses that are already watched,
-	     in which case we just increment the reference counts of
-	     occupied debug registers).  If we break out of the loop
-	     too early, we could cause those addresses watched by
-	     other watchpoints to be disabled when breakpoint.c reacts
-	     to our failure to insert this watchpoint and tries to
-	     remove it.  */
-	  if (status)
-	    retval = status;
+	  if (retval)
+	    break;
 	}
 
       addr += size;
@@ -481,6 +505,43 @@ Invalid value %d of operation in i386_ha
   return retval;
 }
 
+/* Update the inferior debug registers state, in INF_STATE, with the
+   new debug registers state, in NEW_STATE.  */
+
+static void
+i386_update_inferior_debug_regs (struct i386_debug_reg_state *new_state)
+{
+  int i;
+
+  ALL_DEBUG_REGISTERS (i)
+    {
+      if (I386_DR_VACANT (new_state, i) != I386_DR_VACANT (&dr_mirror, i))
+	{
+	  if (!I386_DR_VACANT (new_state, i))
+	    {
+	      i386_dr_low.set_addr (i, new_state->dr_mirror[i]);
+
+	      /* Only a sanity check for leftover bits (set possibly only
+		 by inferior).  */
+	      if (i386_dr_low.unset_status)
+		i386_dr_low.unset_status (I386_DR_WATCH_MASK (i));
+	    }
+	  else
+	    {
+	      if (i386_dr_low.reset_addr)
+		i386_dr_low.reset_addr (i);
+	    }
+	}
+      else
+	gdb_assert (new_state->dr_mirror[i] == dr_mirror.dr_mirror[i]);
+    }
+
+  if (new_state->dr_control_mirror != dr_mirror.dr_control_mirror)
+    i386_dr_low.set_control (new_state->dr_control_mirror);
+
+  dr_mirror = *new_state;
+}
+
 /* Insert a watchpoint to watch a memory region which starts at
    address ADDR and whose length is LEN bytes.  Watch memory accesses
    of the type TYPE.  Return 0 on success, -1 on failure.  */
@@ -490,22 +551,30 @@ i386_insert_watchpoint (CORE_ADDR addr,
 			struct expression *cond)
 {
   int retval;
+  /* Work on a local copy of the debug registers, and on success,
+     commit the change back to the inferior.  */
+  struct i386_debug_reg_state local_state = dr_mirror;
 
   if (type == hw_read)
     return 1; /* unsupported */
 
   if (((len != 1 && len !=2 && len !=4) && !(TARGET_HAS_DR_LEN_8 && len == 8))
       || addr % len != 0)
-    retval = i386_handle_nonaligned_watchpoint (WP_INSERT, addr, len, type);
+    retval = i386_handle_nonaligned_watchpoint (&local_state,
+						WP_INSERT, addr, len, type);
   else
     {
       unsigned len_rw = i386_length_and_rw_bits (len, type);
 
-      retval = i386_insert_aligned_watchpoint (addr, len_rw);
+      retval = i386_insert_aligned_watchpoint (&local_state,
+					       addr, len_rw);
     }
 
+  if (retval == 0)
+    i386_update_inferior_debug_regs (&local_state);
+
   if (maint_show_dr)
-    i386_show_dr ("insert_watchpoint", addr, len, type);
+    i386_show_dr (&dr_mirror, "insert_watchpoint", addr, len, type);
 
   return retval;
 }
@@ -518,19 +587,27 @@ i386_remove_watchpoint (CORE_ADDR addr,
 			struct expression *cond)
 {
   int retval;
+  /* Work on a local copy of the debug registers, and on success,
+     commit the change back to the inferior.  */
+  struct i386_debug_reg_state local_state = dr_mirror;
 
   if (((len != 1 && len !=2 && len !=4) && !(TARGET_HAS_DR_LEN_8 && len == 8))
       || addr % len != 0)
-    retval = i386_handle_nonaligned_watchpoint (WP_REMOVE, addr, len, type);
+    retval = i386_handle_nonaligned_watchpoint (&local_state,
+						WP_REMOVE, addr, len, type);
   else
     {
       unsigned len_rw = i386_length_and_rw_bits (len, type);
 
-      retval = i386_remove_aligned_watchpoint (addr, len_rw);
+      retval = i386_remove_aligned_watchpoint (&local_state,
+					       addr, len_rw);
     }
 
+  if (retval == 0)
+    i386_update_inferior_debug_regs (&local_state);
+
   if (maint_show_dr)
-    i386_show_dr ("remove_watchpoint", addr, len, type);
+    i386_show_dr (&dr_mirror, "remove_watchpoint", addr, len, type);
 
   return retval;
 }
@@ -545,7 +622,8 @@ i386_region_ok_for_watchpoint (CORE_ADDR
 
   /* Compute how many aligned watchpoints we would need to cover this
      region.  */
-  nregs = i386_handle_nonaligned_watchpoint (WP_COUNT, addr, len, hw_write);
+  nregs = i386_handle_nonaligned_watchpoint (&dr_mirror,
+					     WP_COUNT, addr, len, hw_write);
   return nregs <= DR_NADDR ? 1 : 0;
 }
 
@@ -559,30 +637,35 @@ i386_stopped_data_address (struct target
   CORE_ADDR addr = 0;
   int i;
   int rc = 0;
-
-  dr_status_mirror = i386_dr_low.get_status ();
+  unsigned status;
+  unsigned control;
+  struct i386_debug_reg_state *state = &dr_mirror;
+
+  dr_mirror.dr_status_mirror = i386_dr_low.get_status ();
+  status = dr_mirror.dr_status_mirror;
+  control = dr_mirror.dr_control_mirror;
 
   ALL_DEBUG_REGISTERS(i)
     {
-      if (I386_DR_WATCH_HIT (i)
+      if (I386_DR_WATCH_HIT (status, i)
 	  /* This second condition makes sure DRi is set up for a data
 	     watchpoint, not a hardware breakpoint.  The reason is
 	     that GDB doesn't call the target_stopped_data_address
 	     method except for data watchpoints.  In other words, I'm
 	     being paranoiac.  */
-	  && I386_DR_GET_RW_LEN (i) != 0
+	  && I386_DR_GET_RW_LEN (control, i) != 0
 	  /* This third condition makes sure DRi is not vacant, this
 	     avoids false positives in windows-nat.c.  */
-	  && !I386_DR_VACANT (i))
+	  && !I386_DR_VACANT (state, i))
 	{
-	  addr = dr_mirror[i];
+	  addr = state->dr_mirror[i];
 	  rc = 1;
 	  if (maint_show_dr)
-	    i386_show_dr ("watchpoint_hit", addr, -1, hw_write);
+	    i386_show_dr (&dr_mirror, "watchpoint_hit", addr, -1, hw_write);
 	}
     }
   if (maint_show_dr && addr == 0)
-    i386_show_dr ("stopped_data_addr", 0, 0, hw_write);
+    i386_show_dr (&dr_mirror, "stopped_data_addr", 0, 0, hw_write);
 
   if (rc)
     *addr_p = addr;
@@ -604,10 +687,11 @@ i386_insert_hw_breakpoint (struct gdbarc
 {
   unsigned len_rw = i386_length_and_rw_bits (1, hw_execute);
   CORE_ADDR addr = bp_tgt->placed_address;
-  int retval = i386_insert_aligned_watchpoint (addr, len_rw) ? EBUSY : 0;
+  int retval = i386_insert_aligned_watchpoint (&dr_mirror,
+					       addr, len_rw) ? EBUSY : 0;
 
   if (maint_show_dr)
-    i386_show_dr ("insert_hwbp", addr, 1, hw_execute);
+    i386_show_dr (&dr_mirror, "insert_hwbp", addr, 1, hw_execute);
 
   return retval;
 }
@@ -621,10 +705,11 @@ i386_remove_hw_breakpoint (struct gdbarc
 {
   unsigned len_rw = i386_length_and_rw_bits (1, hw_execute);
   CORE_ADDR addr = bp_tgt->placed_address;
-  int retval = i386_remove_aligned_watchpoint (addr, len_rw);
+  int retval = i386_remove_aligned_watchpoint (&dr_mirror,
+					       addr, len_rw);
 
   if (maint_show_dr)
-    i386_show_dr ("remove_hwbp", addr, 1, hw_execute);
+    i386_show_dr (&dr_mirror, "remove_hwbp", addr, 1, hw_execute);
 
   return retval;
 }


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]