From: Pedro Alves <pedro@palves.net>
To: gdb-patches@sourceware.org
Subject: [PATCH 33/34] Windows gdb: Watchpoints while running (internal vs external stops)
Date: Wed, 8 May 2024 00:42:32 +0100 [thread overview]
Message-ID: <20240507234233.371123-34-pedro@palves.net> (raw)
In-Reply-To: <20240507234233.371123-1-pedro@palves.net>
Teach the Windows target to temporarily pause all threads when we
change the debug registers for a watchpoint. Implements the same
logic as Linux uses:
~~~
/* (...) if threads are running when the
mirror changes, a temporary and transparent stop on all threads
is forced so they can get their copy of the debug registers
updated on re-resume. (...) */
~~~
On Linux, we send each thread a SIGSTOP to step them. On Windows,
SuspendThread itself doesn't cause any asynchronous debug event to be
reported. However, we've implemented windows_nat_target::stop such
that it uses SuspendThread, and then queues a pending GDB_SIGNAL_0
stop on the thread. That results in a user-visible stop, while here
we want a non-user-visible stop. So what we do is re-use that
windows_nat_target::stop stopping mechanism, but add an external vs
internal stopping kind distinction. An internal stop results in
windows_nat_target::wait immediately re-resuming the thread.
Note we don't make the debug registers poking code SuspendThread ->
write debug registers -> ContinueThread itself, because SuspendThread
is actually asynchronous and may take a bit to stop the thread (a
following GetThreadContext blocks until the thread is actually
suspended), and, there will be several debug register writes when a
watchpoint is set, because we have to set all of DR0, DR1, DR2, DR3,
and DR7. Defering the actualy writes to ::wait avoids a bunch of
SuspendThread/ResumeThread sequences, so in principle should be
faster.
Change-Id: I39c2492c7aac06d23ef8f287f4afe3747b7bc53f
---
gdb/nat/windows-nat.h | 27 ++++++++--
gdb/windows-nat.c | 116 ++++++++++++++++++++++++++++++++++--------
2 files changed, 119 insertions(+), 24 deletions(-)
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index c6d0c4e98dd..8399ed7b3b7 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -34,6 +34,24 @@ namespace windows_nat
struct windows_process_info;
+/* The reason for explicitly stopping a thread. Note the enumerators
+ are ordered such that when comparing two stopping_kind's numerical
+ value, the highest should prevail. */
+enum stopping_kind
+ {
+ /* Not really stopping the thread. */
+ SK_NOT_STOPPING = 0,
+
+ /* We're stopping the thread for internal reasons, the stop should
+ not be reported as an event to the core. */
+ SK_INTERNAL = 1,
+
+ /* We're stopping the thread for external reasons, meaning, the
+ core/user asked us to stop the thread, so we must report a stop
+ event to the core. */
+ SK_EXTERNAL = 2,
+ };
+
/* Thread information structure used to track extra information about
each thread. */
struct windows_thread_info
@@ -117,9 +135,10 @@ struct windows_thread_info
int suspended = 0;
/* This flag indicates whether we are explicitly stopping this
- thread in response to a target_stop request. This allows
- distinguishing between threads that are explicitly stopped by the
- debugger and threads that are stopped due to other reasons.
+ thread in response to a target_stop request or for
+ backend-internal reasons. This allows distinguishing between
+ threads that are explicitly stopped by the debugger and threads
+ that are stopped due to other reasons.
Typically, when we want to stop a thread, we suspend it, enqueue
a pending GDB_SIGNAL_0 stop status on the thread, and then set
@@ -128,7 +147,7 @@ struct windows_thread_info
already has an event to report. In such case, we simply set the
'stopping' flag without suspending the thread or enqueueing a
pending stop. See stop_one_thread. */
- bool stopping = false;
+ stopping_kind stopping = SK_NOT_STOPPING;
/* Info about a potential pending stop.
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index a33ef868566..cb1d030d12b 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -261,6 +261,10 @@ enum windows_continue_flag
all-stop mode. This flag indicates that windows_continue
should call ContinueDebugEvent even in non-stop mode. */
WCONT_CONTINUE_DEBUG_EVENT = 4,
+
+ /* Skip calling ContinueDebugEvent even in all-stop mode. This is
+ the default in non-stop mode. */
+ WCONT_DONT_CONTINUE_DEBUG_EVENT = 8,
};
DEF_ENUM_FLAGS_TYPE (windows_continue_flag, windows_continue_flags);
@@ -521,6 +525,8 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
return serial_event_fd (m_wait_event);
}
+ void debug_registers_changed_all_threads ();
+
private:
windows_thread_info *add_thread (ptid_t ptid, HANDLE h, void *tlb,
@@ -528,7 +534,8 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
void delete_thread (ptid_t ptid, DWORD exit_code, bool main_thread_p);
DWORD fake_create_process (const DEBUG_EVENT ¤t_event);
- void stop_one_thread (windows_thread_info *th);
+ void stop_one_thread (windows_thread_info *th,
+ enum stopping_kind stopping_kind);
BOOL windows_continue (DWORD continue_status, int id,
windows_continue_flags cont_flags = 0);
@@ -1294,7 +1301,7 @@ windows_per_inferior::handle_output_debug_string
a pending event. It will be picked up by
windows_nat_target::wait. */
th->suspend ();
- th->stopping = true;
+ th->stopping = SK_EXTERNAL;
th->last_event = {};
th->pending_status.set_stopped (gotasig);
@@ -1605,7 +1612,7 @@ windows_per_inferior::continue_one_thread (windows_thread_info *th,
}
th->resume ();
- th->stopping = false;
+ th->stopping = SK_NOT_STOPPING;
th->last_sig = GDB_SIGNAL_0;
}
@@ -1680,8 +1687,19 @@ windows_nat_target::windows_continue (DWORD continue_status, int id,
#endif
}
- if (!target_is_non_stop_p ()
- || (cont_flags & WCONT_CONTINUE_DEBUG_EVENT) != 0)
+ /* WCONT_DONT_CONTINUE_DEBUG_EVENT and WCONT_CONTINUE_DEBUG_EVENT
+ can't both be enabled at the same time. */
+ gdb_assert ((cont_flags & WCONT_DONT_CONTINUE_DEBUG_EVENT) == 0
+ || (cont_flags & WCONT_CONTINUE_DEBUG_EVENT) == 0);
+
+ bool continue_debug_event;
+ if ((cont_flags & WCONT_CONTINUE_DEBUG_EVENT) != 0)
+ continue_debug_event = true;
+ else if ((cont_flags & WCONT_DONT_CONTINUE_DEBUG_EVENT) != 0)
+ continue_debug_event = false;
+ else
+ continue_debug_event = !target_is_non_stop_p ();
+ if (continue_debug_event)
{
DEBUG_EVENTS ("windows_continue -> continue_last_debug_event");
continue_last_debug_event_main_thread
@@ -1876,11 +1894,13 @@ windows_nat_target::interrupt ()
"Press Ctrl-c in the program console."));
}
-/* Stop thread TH. This leaves a GDB_SIGNAL_0 pending in the thread,
- which is later consumed by windows_nat_target::wait. */
+/* Stop thread TH, for STOPPING_KIND reason. This leaves a
+ GDB_SIGNAL_0 pending in the thread, which is later consumed by
+ windows_nat_target::wait. */
void
-windows_nat_target::stop_one_thread (windows_thread_info *th)
+windows_nat_target::stop_one_thread (windows_thread_info *th,
+ enum stopping_kind stopping_kind)
{
ptid_t thr_ptid (windows_process.process_id, th->tid);
@@ -1896,12 +1916,18 @@ windows_nat_target::stop_one_thread (windows_thread_info *th)
#ifdef __CYGWIN__
else if (th->suspended
&& th->signaled_thread != nullptr
- && th->pending_status.kind () == TARGET_WAITKIND_IGNORE)
+ && th->pending_status.kind () == TARGET_WAITKIND_IGNORE
+ /* If doing an internal stop to update debug registers,
+ then just leave the "sig" thread suspended. Otherwise
+ windows_nat_target::wait would incorrectly break the
+ signaled_thread lock when it later processes the pending
+ stop and calls windows_continue on this thread. */
+ && stopping_kind == SK_EXTERNAL)
{
DEBUG_EVENTS ("explict stop for \"sig\" thread %s held for signal",
thr_ptid.to_string ().c_str ());
- th->stopping = true;
+ th->stopping = stopping_kind;
th->pending_status.set_stopped (GDB_SIGNAL_0);
th->last_event = {};
serial_event_set (m_wait_event);
@@ -1915,7 +1941,9 @@ windows_nat_target::stop_one_thread (windows_thread_info *th)
thr_ptid.to_string ().c_str (),
th->suspended, th->stopping);
- th->stopping = true;
+ /* Upgrade stopping. */
+ if (stopping_kind > th->stopping)
+ th->stopping = stopping_kind;
}
else
{
@@ -1924,9 +1952,13 @@ windows_nat_target::stop_one_thread (windows_thread_info *th)
th->suspend ();
gdb_assert (th->suspended);
- th->stopping = true;
- th->pending_status.set_stopped (GDB_SIGNAL_0);
- th->last_event = {};
+ if (stopping_kind > th->stopping)
+ {
+ th->stopping = stopping_kind;
+ th->pending_status.set_stopped (GDB_SIGNAL_0);
+ th->last_event = {};
+ }
+
serial_event_set (m_wait_event);
}
}
@@ -1940,7 +1972,7 @@ windows_nat_target::stop (ptid_t ptid)
{
ptid_t thr_ptid (windows_process.process_id, th->tid);
if (thr_ptid.matches (ptid))
- stop_one_thread (th.get ());
+ stop_one_thread (th.get (), SK_EXTERNAL);
}
}
@@ -2367,6 +2399,17 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
{
windows_thread_info *th = windows_process.find_thread (result);
+ /* If this thread was temporarily stopped just so we
+ could update its debug registers on the next
+ resumption, do it now. */
+ if (th->stopping == SK_INTERNAL)
+ {
+ gdb_assert (fake);
+ windows_continue (DBG_CONTINUE, th->tid,
+ WCONT_DONT_CONTINUE_DEBUG_EVENT);
+ continue;
+ }
+
th->stopped_at_software_breakpoint = false;
if (current_event.dwDebugEventCode
== EXCEPTION_DEBUG_EVENT
@@ -2419,7 +2462,7 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
for (auto &thr : windows_process.thread_list)
thr->suspend ();
- th->stopping = false;
+ th->stopping = SK_NOT_STOPPING;
}
/* If something came out, assume there may be more. This is
@@ -3902,6 +3945,41 @@ Use \"file\" or \"dll\" command to load executable/libraries directly."));
}
}
+/* For each thread, set the debug_registers_changed flag, and
+ temporarily stop it so we can update its debug registers. */
+
+void
+windows_nat_target::debug_registers_changed_all_threads ()
+{
+ for (auto &th : windows_process.thread_list)
+ {
+ th->debug_registers_changed = true;
+
+ /* Note we don't SuspendThread => update debug regs =>
+ ResumeThread, because SuspendThread is actually asynchronous
+ (and GetThreadContext blocks until the thread really
+ suspends), and doing that for all threads may take a bit.
+ Also, the core does one call per DR register update, so that
+ would result in a lot of suspend-resumes. So instead, we
+ suspend the thread if it wasn't already suspended, and queue
+ a pending stop to be handled by windows_nat_target::wait.
+ This means we only stop each thread once, and, we don't block
+ waiting for each individual thread stop. */
+ stop_one_thread (th.get (), SK_INTERNAL);
+ }
+}
+
+/* Trampoline helper to get at the
+ windows_nat_target::debug_registers_changed_all_threads method in
+ the native target. */
+
+static void
+debug_registers_changed_all_threads ()
+{
+ auto *win_tgt = static_cast<windows_nat_target *> (get_native_target ());
+ win_tgt->debug_registers_changed_all_threads ();
+}
+
/* Hardware watchpoint support, adapted from go32-nat.c code. */
/* Pass the address ADDR to the inferior in the I'th debug register.
@@ -3913,8 +3991,7 @@ windows_set_dr (int i, CORE_ADDR addr)
if (i < 0 || i > 3)
internal_error (_("Invalid register %d in windows_set_dr.\n"), i);
- for (auto &th : windows_process.thread_list)
- th->debug_registers_changed = true;
+ debug_registers_changed_all_threads ();
}
/* Pass the value VAL to the inferior in the DR7 debug control
@@ -3923,8 +4000,7 @@ windows_set_dr (int i, CORE_ADDR addr)
static void
windows_set_dr7 (unsigned long val)
{
- for (auto &th : windows_process.thread_list)
- th->debug_registers_changed = true;
+ debug_registers_changed_all_threads ();
}
/* Get the value of debug register I from the inferior. */
--
2.43.2
next prev parent reply other threads:[~2024-05-07 23:44 UTC|newest]
Thread overview: 64+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-05-07 23:41 [PATCH 00/34] Windows non-stop mode Pedro Alves
2024-05-07 23:42 ` [PATCH 01/34] Windows gdb: Dead code in windows_nat_target::do_initial_windows_stuff Pedro Alves
2024-05-08 14:39 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 02/34] Windows gdb: Eliminate global current_process.dr[8] global Pedro Alves
2024-05-08 15:02 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 03/34] Windows gdb+gdbserver: New find_thread, replaces thread_rec(DONT_INVALIDATE_CONTEXT) Pedro Alves
2024-05-08 15:03 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 04/34] Windows gdb: handle_output_debug_string return type Pedro Alves
2024-05-08 14:43 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 05/34] Windows gdb: Eliminate reload_context Pedro Alves
2024-05-08 14:45 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 06/34] Windows gdb+gdbserver: Eliminate thread_rec(INVALIDATE_CONTEXT) calls Pedro Alves
2024-05-08 15:08 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 07/34] Windows gdb+gdbserver: Eliminate DONT_SUSPEND Pedro Alves
2024-05-08 15:12 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 08/34] Windows gdb+gdbserver: Eliminate windows_process_info::thread_rec Pedro Alves
2024-05-08 15:12 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 09/34] Windows gdb: Simplify windows_nat_target::wait Pedro Alves
2024-05-07 23:42 ` [PATCH 10/34] Windows gdb+gdbserver: Move suspending thread to when returning event Pedro Alves
2024-05-07 23:42 ` [PATCH 11/34] Windows gdb: Introduce continue_last_debug_event_main_thread Pedro Alves
2024-05-08 14:53 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 12/34] Windows gdb: Introduce windows_continue_flags Pedro Alves
2024-05-08 15:16 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 13/34] Windows gdb: Factor code out of windows_nat_target::windows_continue Pedro Alves
2024-05-08 15:18 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 14/34] Windows gdb: Pending stop and current_event Pedro Alves
2024-05-08 15:18 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 15/34] Windows gdb+gdbserver: Elim desired_stop_thread_id / rework pending_stops Pedro Alves
2024-05-07 23:42 ` [PATCH 16/34] Windows gdb+gdbserver: Introduce get_last_debug_event_ptid Pedro Alves
2024-05-07 23:42 ` [PATCH 17/34] Windows gdb: Can't pass signal to thread other than last stopped thread Pedro Alves
2024-05-07 23:42 ` [PATCH 18/34] Windows gdbserver: Fix scheduler-locking Pedro Alves
2024-05-07 23:42 ` [PATCH 19/34] Windows gdb: Enable "set scheduler-locking on" Pedro Alves
2024-05-08 15:25 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 20/34] Windows gdbserver: Eliminate soft-interrupt mechanism Pedro Alves
2024-05-08 15:26 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 21/34] Windows gdb+gdbserver: Make current_event per-thread state Pedro Alves
2024-05-07 23:42 ` [PATCH 22/34] Windows gdb+gdbserver: Make last_sig " Pedro Alves
2024-05-07 23:42 ` [PATCH 23/34] Windows gdb+gdbserver: Make siginfo_er " Pedro Alves
2024-05-07 23:42 ` [PATCH 24/34] Add backpointer from windows_thread_info to windows_process_info Pedro Alves
2024-05-08 15:28 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 25/34] Windows gdb+gdbserver: Share $_siginfo reading code Pedro Alves
2024-05-08 15:29 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 26/34] Windows gdb+gdbserver: Eliminate struct pending_stop Pedro Alves
2024-05-07 23:42 ` [PATCH 27/34] Windows gdb: Change serial_event management Pedro Alves
2024-05-07 23:42 ` [PATCH 28/34] Windows gdb: cygwin_set_dr => windows_set_dr, etc Pedro Alves
2024-05-08 14:46 ` Tom Tromey
2024-05-07 23:42 ` [PATCH 29/34] windows_per_inferior::continue_one_thread, unify WoW64/non-WoW64 paths Pedro Alves
2024-05-07 23:42 ` [PATCH 30/34] windows-nat.c: Avoid writing debug registers if watchpoint hit pending Pedro Alves
2024-05-07 23:42 ` [PATCH 31/34] Windows gdb+gdbserver: Check whether DBG_REPLY_LATER is available Pedro Alves
2024-05-08 12:45 ` Eli Zaretskii
2024-05-08 21:33 ` [PATCH 31/34 v1.2] " Pedro Alves
2024-05-09 10:07 ` Hannes Domani
[not found] ` <86zfsz5kly.fsf@gnu.org>
2024-05-09 11:11 ` Pedro Alves
2024-05-09 11:47 ` [PATCH 31/34 v1.3] " Pedro Alves
2024-05-09 12:28 ` Eli Zaretskii
2024-05-09 14:17 ` Tom Tromey
[not found] ` <86r0eb5g2n.fsf@gnu.org>
2024-05-09 13:27 ` [PATCH 31/34 v1.2] " Pedro Alves
2024-05-07 23:42 ` [PATCH 32/34] Windows gdb: Add non-stop support Pedro Alves
2024-05-07 23:42 ` Pedro Alves [this message]
2024-05-07 23:42 ` [PATCH 34/34] Mention Windows non-stop support in NEWS Pedro Alves
2024-05-08 15:40 ` [PATCH 00/34] Windows non-stop mode Tom Tromey
2024-05-15 17:35 ` Tom Tromey
2024-05-15 20:39 ` Pedro Alves
2024-05-16 15:53 ` Tom Tromey
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240507234233.371123-34-pedro@palves.net \
--to=pedro@palves.net \
--cc=gdb-patches@sourceware.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).