X-Recipient: archive-cygwin AT delorie DOT com X-Spam-Check-By: sourceware.org Message-ID: <4702E6B7.8020100@portugalmail.pt> Date: Wed, 03 Oct 2007 01:47:51 +0100 From: Pedro Alves User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-BR; rv:1.8.1.6) Gecko/20070728 Thunderbird/2.0.0.6 Mnenhy/0.7.5.0 MIME-Version: 1.0 To: cygwin AT cygwin DOT com Subject: [gdb] Data watchpoints in Windows weirdness. Call for testers. Content-Type: multipart/mixed; boundary="------------060604030207080403070709" X-IsSubscribed: yes Mailing-List: contact cygwin-help AT cygwin DOT com; run by ezmlm List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: cygwin-owner AT cygwin DOT com Mail-Followup-To: cygwin AT cygwin DOT com Delivered-To: mailing list cygwin AT cygwin DOT com --------------060604030207080403070709 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit 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 --------------060604030207080403070709 Content-Type: text/plain; name="main.c" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="main.c" #include #include 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; } --------------060604030207080403070709 Content-Type: text/x-diff; name="fix_hwatch.diff" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="fix_hwatch.diff" --- 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 --------------060604030207080403070709 Content-Type: text/plain; charset=us-ascii -- 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/ --------------060604030207080403070709--