aboutsummaryrefslogtreecommitdiff
path: root/gdbserver
diff options
context:
space:
mode:
authorPedro Alves <pedro@palves.net>2024-05-07 20:41:37 +0100
committerPedro Alves <pedro@palves.net>2024-05-10 11:26:16 +0100
commitaf7fc7ff9e4c7081cc7b163111c3db2d4b9f46b8 (patch)
tree608893a1cb8b4acbcc882ae6bf183534c4b801ce /gdbserver
parentacd3803fa94f0f9cc5e47751d183320e12a2c90b (diff)
downloadfsf-binutils-gdb-af7fc7ff9e4c7081cc7b163111c3db2d4b9f46b8.zip
fsf-binutils-gdb-af7fc7ff9e4c7081cc7b163111c3db2d4b9f46b8.tar.gz
fsf-binutils-gdb-af7fc7ff9e4c7081cc7b163111c3db2d4b9f46b8.tar.bz2
Windows gdb: Avoid writing debug registers if watchpoint hit pending
Several watchpoint-related testcases, such as gdb.threads/watchthreads.exp for example, when tested with the backend in non-stop mode, exposed an interesting detail of the Windows debug API that wasn't considered before. The symptom observed is spurious SIGTRAPs, like: Thread 1 "watchthreads" received signal SIGTRAP, Trace/breakpoint trap. 0x00000001004010b1 in main () at .../src/gdb/testsuite/gdb.threads/watchthreads.c:48 48 args[i] = 1; usleep (1); /* Init value. */ After a good amount of staring at logs and headscratching, I realized the problem: #0 - It all starts in the fact that multiple threads can hit an event at the same time. Say, a watchpoint for thread A, and a breakpoint for thread B. #1 - Say, WaitForDebugEvent reports the breakpoint hit for thread B first, then GDB for some reason decides to update debug registers, and continue. Updating debug registers means writing the debug registers to _all_ threads, with SetThreadContext. #2 - WaitForDebugEvent reports the watchpoint hit for thread A. Watchpoint hits are reported as EXCEPTION_SINGLE_STEP. #3 - windows-nat checks the Dr6 debug register to check if the step was a watchpoint or hardware breakpoint stop, and finds that Dr6 is completely cleared. So windows-nat reports a plain SIGTRAP (given EXCEPTION_SINGLE_STEP) to the core. #4 - Thread A was not supposed to be stepping, so infrun reports the SIGTRAP to the user as a random signal. The strange part is #3 above. Why was Dr6 cleared? Turns out what (at least in Windows 10 & 11), writing to _any_ debug register has the side effect of clearing Dr6, even if you write the same values the registers already had, back to the registers. I confirmed it clearly by adding this hack to GDB: if (th->context.ContextFlags == 0) { th->context.ContextFlags = CONTEXT_DEBUGGER_DR; /* Get current values of debug registers. */ CHECK (GetThreadContext (th->h, &th->context)); DEBUG_EVENTS ("For 0x%x (once), Dr6=0x%llx", th->tid, th->context.Dr6); /* Write debug registers back to thread, same values, and re-read them. */ CHECK (SetThreadContext (th->h, &th->context)); CHECK (GetThreadContext (th->h, &th->context)); DEBUG_EVENTS ("For 0x%x (twice), Dr6=0x%llx", th->tid, th->context.Dr6); } Which showed Dr6=0 after the write + re-read: [windows events] fill_thread_context: For 0x6a0 (once), Dr6=0xffff0ff1 [windows events] fill_thread_context: For 0x6a0 (twice), Dr6=0x0 This commit fixes the issue by detecting that a thread has a pending watchpoint hit to report (Dr6 has interesting bits set), and if so, avoid mofiying any debug register. Instead, let the pending watchpoint hit be reported by WaitForDebugEvent. If infrun did want to modify watchpoints, it will still be done when the thread is eventually re-resumed after the pending watchpoint hit is reported. (infrun knows how to gracefully handle the case of a watchpoint hit for a watchpoint that has since been deleted.) Change-Id: I21a3daa9e34eecfa054f0fea706e5ab40aabe70a
Diffstat (limited to 'gdbserver')
-rw-r--r--gdbserver/win32-low.cc8
-rw-r--r--gdbserver/win32-low.h2
2 files changed, 10 insertions, 0 deletions
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 004bf94..65b01dc 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -141,6 +141,14 @@ win32_require_context (windows_thread_info *th)
/* See nat/windows-nat.h. */
+void
+gdbserver_windows_process::fill_thread_context (windows_thread_info *th)
+{
+ win32_require_context (th);
+}
+
+/* See nat/windows-nat.h. */
+
windows_thread_info *
gdbserver_windows_process::find_thread (ptid_t ptid)
{
diff --git a/gdbserver/win32-low.h b/gdbserver/win32-low.h
index e99e47e..ea2a9b4 100644
--- a/gdbserver/win32-low.h
+++ b/gdbserver/win32-low.h
@@ -181,6 +181,8 @@ struct gdbserver_windows_process : public windows_nat::windows_process_info
void handle_unload_dll (const DEBUG_EVENT &current_event) override;
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;
+ void fill_thread_context (windows_nat::windows_thread_info *th) override;
+
int attaching = 0;
/* A status that hasn't been reported to the core yet, and so