This is the mail archive of the cygwin mailing list for the Cygwin 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]

[gdb] Data watchpoints in Windows weirdness. Call for testers.


Hi all,

I'm looking over watchpoint support in gdb, and I see something quite
weird here.  It seems something/someone is messing with the debug
registers.

When a watchpoint is triggered, the Dr6 register should have a few bits
set to tell the debugger which of the Dr[0-3] watchpoints triggered.
It happens that on all my machines many times it doesn't.  The
single step debug event comes through, but the output of the
GetThreadContext call with CONTEXT_DEBUG_REGISTERS shows Dr6 == 0.
This is quite unexpected, I don't see this happening on Linux, and
goes against every example of debug register usage on Windows I
could find - they all expect Dr6 to be set.

I suspected some app from TBLODA was messing up with GetThreadContext or
the NT native equivalent NtGetThreadContext or some such, so I uninstalled
all AV and anti-everything software on one of the machines and still the
problems shows up.  So, I bit the bullet and installed a Windows XP SP2
from scratch on a Virtual PC VM, installed Cygwin+gcc+gdb, nothing else,
and still, the problem persists.

By a long shot, Cygwin doesn't do anything funny like hooking
GetThreadContext, does it?

What's also funny is that I believed that watchpoints worked properly
at some point, but now I'm not so sure.

Maybe it is a specific to this Windows version I'm using.  All my machines
and the machines at work have XP SP2.

I have a workaround to it, which basicaly lies to gdb telling it that
every set watchpoint was hit, everytime.  It works quite nicelly, with
the only bad effect being a bit of (unnoticeable) unefficiency when a
breakpoint is hit - gdb will have to compare the watched regions
for changes - but that is getting into too much detail for this list.

Perhaps you, gentle reader, could ease my pain, by confirming the
behaviour on your machine.

Attached is a simple test main.c file you could use.

Here is a broken test run:

        >gcc main.c -o main.exe -g3 -O0
        >/usr/bin/gdb main.exe
GNU gdb 6.5.50.20060706-cvs (cygwin-special)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i686-pc-cygwin"...
(gdb) start
Breakpoint 1 at 0x401050: file main.c, line 8.
Starting program:
/cygdrive/d/cegccsf/cegcc/cegcc/src/build-gdb_server_cygwin_submit/main.exe
Loaded symbols for /cygdrive/c/WINDOWS/system32/ntdll.dll
Loaded symbols for /cygdrive/c/WINDOWS/system32/kernel32.dll
Loaded symbols for /usr/bin/cygwin1.dll
Loaded symbols for /cygdrive/c/WINDOWS/system32/advapi32.dll
Loaded symbols for /cygdrive/c/WINDOWS/system32/rpcrt4.dll
main () at main.c:8
8       {
(gdb) watch count
Hardware watchpoint 2: count
(gdb) display count
1: count = 0
(gdb) c
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
main () at main.c:11
11        printf ("count %d\n", count);
1: count = 999
(gdb) c
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
main () at main.c:16
16            printf ("count %d\n", count);
1: count = 1000
(gdb) c
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
main () at main.c:18
18            Sleep (1000);
1: count = 1001
(gdb) c
Continuing.
Hardware watchpoint 2: count

Old value = 0
New value = 1002
main () at main.c:18
18            Sleep (1000);
1: count = 1002

Notice that most of the times, gdb didn't display the "Old value",
and that when finally it does, it shows the wrong old value.


Doing:


set debug infrun 1
set debugevents 1
set debugexceptions 1
maint show-debug-regs 1

... helps to see why:

----------------------------------------

A bad run:

main () at main.c:8
8 {
(gdb) watch count
Hardware watchpoint 2: count
(gdb) c
Continuing.
infrun: proceed (addr=0xffffffff, signal=144, step=0)
insert_watchpoint (addr=403010, len=4, type=data-write):
CONTROL (DR7): 000d0101 STATUS (DR6): 00000000
DR0: addr=0x00403010, ref.count=1 DR1: addr=0x00000000, ref.count=0
DR2: addr=0x00000000, ref.count=0 DR3: addr=0x00000000, ref.count=0
infrun: resume (step=0, signal=0)
ContinueDebugEvent (cpid=4112, ctid=4740, DBG_CONTINUE);
infrun: wait_for_inferior
gdb: kernel event for pid=4112 tid=4740 code=EXCEPTION_DEBUG_EVENT)
gdb: Target exception EXCEPTION_SINGLE_STEP at 0x00401085
infrun: infwait_normal_state
infrun: TARGET_WAITKIND_STOPPED
infrun: stop_pc = 0x401085
stopped_data_addr:
CONTROL (DR7): 000d0101 STATUS (DR6): 00000000
^^^^^^^^
DR0: addr=0x00403010, ref.count=1 DR1: addr=0x00000000, ref.count=0
DR2: addr=0x00000000, ref.count=0 DR3: addr=0x00000000, ref.count=0
infrun: random signal 5
^^^^^^^^^^^^^^^^^^^^^^^


Program received signal SIGTRAP, Trace/breakpoint trap.
infrun: stop_stepping
remove_watchpoint (addr=403010, len=4, type=data-write):
CONTROL (DR7): 000d0100 STATUS (DR6): 00000000
DR0: addr=0x00000000, ref.count=0 DR1: addr=0x00000000, ref.count=0
DR2: addr=0x00000000, ref.count=0 DR3: addr=0x00000000, ref.count=0
main () at main.c:11
11 printf ("count %d\n", count);


----------------------------------------

A good run:

(gdb) c
Continuing.
infrun: proceed (addr=0xffffffff, signal=144, step=0)
insert_watchpoint (addr=403010, len=4, type=data-write):
CONTROL (DR7): 000d0101 STATUS (DR6): 00000000
DR0: addr=0x00403010, ref.count=1 DR1: addr=0x00000000, ref.count=0
DR2: addr=0x00000000, ref.count=0 DR3: addr=0x00000000, ref.count=0
infrun: resume (step=0, signal=0)
ContinueDebugEvent (cpid=4112, ctid=4740, DBG_CONTINUE);
infrun: wait_for_inferior
gdb: kernel event for pid=4112 tid=4740 code=EXCEPTION_DEBUG_EVENT)
gdb: Target exception EXCEPTION_SINGLE_STEP at 0x004010c9
infrun: infwait_normal_state
infrun: TARGET_WAITKIND_STOPPED
infrun: stop_pc = 0x4010c9
watchpoint_hit (addr=403010, len=-1, type=data-write):
CONTROL (DR7): 000d0101 STATUS (DR6): ffff0ff1
^^^^^^^^
DR0: addr=0x00403010, ref.count=1 DR1: addr=0x00000000, ref.count=0
DR2: addr=0x00000000, ref.count=0 DR3: addr=0x00000000, ref.count=0
infrun: BPSTATE_WHAT_STOP_NOISY
infrun: stop_stepping
remove_watchpoint (addr=403010, len=4, type=data-write):
CONTROL (DR7): 000d0100 STATUS (DR6): ffff0ff1
DR0: addr=0x00000000, ref.count=0 DR1: addr=0x00000000, ref.count=0
DR2: addr=0x00000000, ref.count=0 DR3: addr=0x00000000, ref.count=0
Hardware watchpoint 2: count


Old value = 0
New value = 1001
main () at main.c:18
18            Sleep (1000);


So, again, can anyone confirm me this behavior, preferably on different Windows versions, so I can either submit a workaround patch upstream, or fix it in a better way? You can test with stock Cygwin gcc and gdb.

For the brave souls that want to test a workaround, the fix_hwatch.diff
patch applies to gdb's CVS head.  It isn't cleaned up, but shows what
I've been testing if you're curious - the important change is in
cygwin_get_dr6.

Thanks!

Cheers,
Pedro Alves

#include <stdio.h>
#include <windows.h>

volatile int count = 0;

int
main ()
{
  printf ("count %d\n", count);
  count = 999;
  printf ("count %d\n", count);
  count++;

  while (1)
    {
      printf ("count %d\n", count);
      count++;
      Sleep (1000);
    }

  return 0;
}








---
 gdb/win32-nat.c |  136 +++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 126 insertions(+), 10 deletions(-)

Index: src/gdb/win32-nat.c
===================================================================
--- src.orig/gdb/win32-nat.c	2007-10-03 01:28:32.000000000 +0100
+++ src/gdb/win32-nat.c	2007-10-03 01:29:18.000000000 +0100
@@ -103,6 +103,15 @@ static int debug_registers_used;
 #define DEBUG_MEM(x)	if (debug_memory)	printf_unfiltered x
 #define DEBUG_EXCEPT(x)	if (debug_exceptions)	printf_unfiltered x
 
+//#define DO_DEBUG_DR
+
+#ifdef DO_DEBUG_DR
+# define DEBUG_DR(x) \
+   do { printf_unfiltered x; } while (0)
+#else
+#  define DEBUG_DR(x) do ; while (0)
+#endif
+
 static void win32_stop (void);
 static int win32_win32_thread_alive (ptid_t);
 static void win32_kill_inferior (void);
@@ -259,6 +268,25 @@ thread_rec (DWORD id, int get_context)
 	    else if (get_context < 0)
 	      th->suspend_count = -1;
 	    th->reload_context = 1;
+
+#ifdef DO_DEBUG_DR
+	    {
+	      DEBUG_DR (("thread_rec\n"));
+	      memset (&th->context, 0, sizeof th->context);
+	      th->context.ContextFlags = CONTEXT_DEBUGGER_DR;
+	      CHECK (GetThreadContext (th->h, &th->context));
+	      /* Copy dr values from that thread.  */
+	      dr[0] = th->context.Dr0;
+	      dr[1] = th->context.Dr1;
+	      dr[2] = th->context.Dr2;
+	      dr[3] = th->context.Dr3;
+	      dr[6] = th->context.Dr6;
+	      dr[7] = th->context.Dr7;
+	      DEBUG_DR (("dr0 = 0x%x\n", (int)dr[0]));
+	      DEBUG_DR (("dr6 = 0x%x\n", (int)dr[6]));
+	      DEBUG_DR (("dr7 = 0x%x\n", (int)dr[7]));
+	    }
+#endif
 	  }
 	return th;
       }
@@ -282,6 +310,7 @@ win32_add_thread (DWORD id, HANDLE h)
   thread_head.next = th;
   add_thread (pid_to_ptid (id));
   /* Set the debug registers for the new thread in they are used.  */
+  DEBUG_DR (("win32_add_thread dr6 = 0x%x\n", (int)dr[6]));
   if (debug_registers_used)
     {
       /* Only change the value of the debug registers.  */
@@ -291,8 +320,15 @@ win32_add_thread (DWORD id, HANDLE h)
       th->context.Dr1 = dr[1];
       th->context.Dr2 = dr[2];
       th->context.Dr3 = dr[3];
+      th->context.Dr6 = 0xffff4ff0;
+      th->context.Dr6 = 0;
       /* th->context.Dr6 = dr[6];
       FIXME: should we set dr6 also ?? */
+
+      /* DR6: Bits 4-11,16-31 reserved (set to 1).
+       *      Bit 12 reserved (set to 0).
+       */
+
       th->context.Dr7 = dr[7];
       CHECK (SetThreadContext (th->h, &th->context));
       th->context.ContextFlags = 0;
@@ -353,6 +389,9 @@ do_win32_fetch_inferior_registers (struc
     return;	/* Windows sometimes uses a non-existent thread id in its
 		   events */
 
+  DEBUG_DR (("do_win32_fetch_inferior_registers, reload_ctx = %d\n",
+	     current_thread->reload_context));
+
   if (current_thread->reload_context)
     {
 #ifdef __COPY_CONTEXT_SIZE
@@ -368,8 +407,9 @@ do_win32_fetch_inferior_registers (struc
 #endif
 	{
 	  thread_info *th = current_thread;
+	  memset (&th->context, 0, sizeof th->context);
 	  th->context.ContextFlags = CONTEXT_DEBUGGER_DR;
-	  GetThreadContext (th->h, &th->context);
+	  CHECK (GetThreadContext (th->h, &th->context));
 	  /* Copy dr values from that thread.  */
 	  dr[0] = th->context.Dr0;
 	  dr[1] = th->context.Dr1;
@@ -377,6 +417,9 @@ do_win32_fetch_inferior_registers (struc
 	  dr[3] = th->context.Dr3;
 	  dr[6] = th->context.Dr6;
 	  dr[7] = th->context.Dr7;
+	  DEBUG_DR (("dr0 = 0x%x\n", (int)dr[0]));
+	  DEBUG_DR (("dr6 = 0x%x\n", (int)dr[6]));
+	  DEBUG_DR (("dr7 = 0x%x\n", (int)dr[7]));
 	}
       current_thread->reload_context = 0;
     }
@@ -433,6 +476,7 @@ do_win32_store_inferior_registers (const
 static void
 win32_store_inferior_registers (struct regcache *regcache, int r)
 {
+  DEBUG_DR (("win32_store_inferior_registers\n"));
   current_thread = thread_rec (PIDGET (inferior_ptid), TRUE);
   /* Check if current_thread exists.  Windows sometimes uses a non-existent
      thread id in its events */
@@ -1119,24 +1163,27 @@ win32_continue (DWORD continue_status, i
     for (th = &thread_head; (th = th->next) != NULL;)
       if (((id == -1) || (id == (int) th->id)) && th->suspend_count)
 	{
-
-	  for (i = 0; i < th->suspend_count; i++)
-	    (void) ResumeThread (th->h);
-	  th->suspend_count = 0;
 	  if (debug_registers_changed)
 	    {
-	      /* Only change the value of the debug registers */
-	      th->context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
+	      /* Only change the value of the debug registers.  */
+	      th->context.ContextFlags |= CONTEXT_DEBUG_REGISTERS;
 	      th->context.Dr0 = dr[0];
 	      th->context.Dr1 = dr[1];
 	      th->context.Dr2 = dr[2];
 	      th->context.Dr3 = dr[3];
+	      th->context.Dr6 = 0xffff4ff0;
+	      th->context.Dr6 = 0;
 	      /* th->context.Dr6 = dr[6];
 		 FIXME: should we set dr6 also ?? */
 	      th->context.Dr7 = dr[7];
-	      CHECK (SetThreadContext (th->h, &th->context));
-	      th->context.ContextFlags = 0;
 	    }
+
+	  if (th->context.ContextFlags)
+	    CHECK (SetThreadContext (th->h, &th->context));
+	  th->context.ContextFlags = 0;
+	  for (i = 0; i < th->suspend_count; i++)
+	    (void) ResumeThread (th->h);
+	  th->suspend_count = 0;
 	}
 
   debug_registers_changed = 0;
@@ -1207,6 +1254,12 @@ win32_resume (ptid_t ptid, int step, enu
   th = thread_rec (current_event.dwThreadId, FALSE);
   if (th)
     {
+      DEBUG_DR (("resume: %d, %d, 0x%x, 0x%x\n",
+			 debug_registers_changed,
+			 step,
+			 (int)th->context.ContextFlags,
+			 dr[6]));
+
       if (step)
 	{
 	  /* Single step by setting t bit */
@@ -1215,6 +1268,11 @@ win32_resume (ptid_t ptid, int step, enu
 	  th->context.EFlags |= FLAG_TRACE_BIT;
 	}
 
+      DEBUG_DR (("eflags 0x%x\n", (int) th->context.EFlags));
+
+      if (!step)
+	  th->context.EFlags &= ~FLAG_TRACE_BIT;
+
       if (th->context.ContextFlags)
 	{
 	  if (debug_registers_changed)
@@ -1223,10 +1281,13 @@ win32_resume (ptid_t ptid, int step, enu
 	      th->context.Dr1 = dr[1];
 	      th->context.Dr2 = dr[2];
 	      th->context.Dr3 = dr[3];
+	      th->context.Dr6 = 0xffff4ff0;
+	      th->context.Dr6 = 0;
 	      /* th->context.Dr6 = dr[6];
 	       FIXME: should we set dr6 also ?? */
 	      th->context.Dr7 = dr[7];
 	    }
+	  DEBUG_DR (("SetThreadContext\n"));
 	  CHECK (SetThreadContext (th->h, &th->context));
 	  th->context.ContextFlags = 0;
 	}
@@ -2165,9 +2226,12 @@ cygwin_set_dr (int i, CORE_ADDR addr)
   if (i < 0 || i > 3)
     internal_error (__FILE__, __LINE__,
 		    _("Invalid register %d in cygwin_set_dr.\n"), i);
+
   dr[i] = (unsigned) addr;
   debug_registers_changed = 1;
   debug_registers_used = 1;
+
+  DEBUG_DR (("set_dr[%d] = 0x%x\n", i, (int)addr));
 }
 
 /* Pass the value VAL to the inferior in the DR7 debug control
@@ -2176,9 +2240,34 @@ cygwin_set_dr (int i, CORE_ADDR addr)
 void
 cygwin_set_dr7 (unsigned val)
 {
+  //  val &= 0xFFFF0155;
+
+  /* DR7: Bit 10 reserved (set to 1).
+     Bits 11-12,14-15 reserved (set to 0).
+     Privileged bits:
+     GD (bit 13): must be 0.
+     R/Wn (bits 16-17,20-21,24-25,28-29): mustn't be 10.
+     LENn (bits 18-19,22-23,26-27,30-31): mustn't be 10.
+
+     DR7 == 0 => debugging disabled for this domain.  */
+
+  if ( val != 0 )
+    {
+      int i;
+      val &= 0xffff27ff; /* reserved bits => 0 */
+      val |= 0x00000400; /* reserved bits => 1 */
+      if ((val & (1 << 13)) != 0)
+	return;
+      for ( i = 0; i < 16; i += 2 )
+	if (((val >> (i + 16)) & 3) == 2)
+	  return;
+    }
+
   dr[7] = val;
   debug_registers_changed = 1;
   debug_registers_used = 1;
+
+  DEBUG_DR (("set_dr7 = 0x%x\n", val));
 }
 
 /* Get the value of the DR6 debug status register from the inferior.
@@ -2187,7 +2276,34 @@ cygwin_set_dr7 (unsigned val)
 unsigned
 cygwin_get_dr6 (void)
 {
-  return dr[6];
+  if (dr[6])
+    return dr[6];
+  else
+    {
+      /* More often than not, the GetThreadContext call will report a
+	 Dr6 == 0 even if the inferior was stopped due to a data
+	 watchpoint triggering.  It seems that Windows is clearing dr6
+	 too soon, instead of relying on the debugger to clear it.  To
+	 work around it, we return as if the all the four watchpoints
+	 were triggered.  Since the values of the watched regions will
+	 be compared to old values, and gdb will only report the
+	 events when the values change, the worst this brings is a bit
+	 of inefficiency due to extra reading from the inferior, and
+	 extra expression evaluation.  */
+
+      unsigned val = 0;
+
+      if (dr[7] & 0x03)
+	val |= 1;
+      if (dr[7] & 0x0c)
+	val |= 2;
+      if (dr[7] & 0x30)
+	val |= 4;
+      if (dr[7] & 0xC0)
+	val |= 8;
+
+      return val;
+    }
 }
 
 /* Determine if the thread referenced by "pid" is alive




--
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple
Problem reports:       http://cygwin.com/problems.html
Documentation:         http://cygwin.com/docs.html
FAQ:                   http://cygwin.com/faq/

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