@@ -727,17 +727,20 @@ get_last_debug_event_ptid ()
/* See nat/windows-nat.h. */
BOOL
-continue_last_debug_event (DWORD continue_status, bool debug_events)
+continue_last_debug_event (DWORD cont_status, bool debug_events)
{
- DEBUG_EVENTS ("ContinueDebugEvent (cpid=%d, ctid=0x%x, %s)",
- (unsigned) last_wait_event.dwProcessId,
- (unsigned) last_wait_event.dwThreadId,
- continue_status == DBG_CONTINUE ?
- "DBG_CONTINUE" : "DBG_EXCEPTION_NOT_HANDLED");
+ DEBUG_EVENTS
+ ("ContinueDebugEvent (cpid=%d, ctid=0x%x, %s)",
+ (unsigned) last_wait_event.dwProcessId,
+ (unsigned) last_wait_event.dwThreadId,
+ cont_status == DBG_CONTINUE ? "DBG_CONTINUE" :
+ cont_status == DBG_EXCEPTION_NOT_HANDLED ? "DBG_EXCEPTION_NOT_HANDLED" :
+ cont_status == DBG_REPLY_LATER ? "DBG_REPLY_LATER" :
+ "DBG_???");
return ContinueDebugEvent (last_wait_event.dwProcessId,
last_wait_event.dwThreadId,
- continue_status);
+ cont_status);
}
/* See nat/windows-nat.h. */
@@ -81,12 +81,55 @@ struct windows_thread_info
/* Thread Information Block address. */
CORE_ADDR thread_local_base;
+#ifdef __CYGWIN__
+ /* These two fields are used to handle Cygwin signals. When a
+ thread is signaled, the "sig" thread inside the Cygwin runtime
+ reports the fact to us via a special OutputDebugString message.
+ In order to make stepping into a signal handler work, we can only
+ resume the "sig" thread when we also resume the target signaled
+ thread. When we intercept a Cygwin signal, we set up a cross
+ link between the two threads using the two fields below, so we
+ can always identify one from the other. See the "Cygwin signals"
+ description in gdb/windows-nat.c for more. */
+
+ /* If this thread received a signal, then 'cygwin_sig_thread' points
+ to the "sig" thread within the Cygwin runtime. */
+ windows_thread_info *cygwin_sig_thread = nullptr;
+
+ /* If this thread is the Cygwin runtime's "sig" thread, then
+ 'signaled_thread' points at the thread that received a
+ signal. */
+ windows_thread_info *signaled_thread = nullptr;
+#endif
+
+ /* If the thread had its event postponed with DBG_REPLY_LATER, when
+ we later ResumeThread this thread, WaitForDebugEvent will
+ re-report the postponed event. This field holds the continue
+ status value to be automatically passed to ContinueDebugEvent
+ when we encounter this re-reported event. 0 if the thread has
+ not had its event postponed with DBG_REPLY_LATER. */
+ DWORD auto_cont = 0;
+
/* This keeps track of whether SuspendThread was called on this
thread. -1 means there was a failure or that the thread was
explicitly not suspended, 1 means it was called, and 0 means it
was not. */
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.
+
+ 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
+ this flag to true. However, if the thread has had its event
+ previously postponed with DBG_REPLY_LATER, it means that it
+ 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;
+
/* Info about a potential pending stop.
Sometimes, Windows will report a stop on a thread that has been
@@ -167,15 +210,15 @@ struct windows_process_info
virtual windows_thread_info *find_thread (ptid_t ptid) = 0;
/* Handle OUTPUT_DEBUG_STRING_EVENT from child process. Updates
- OURSTATUS and returns the thread id if this represents a thread
- change (this is specific to Cygwin), otherwise 0.
+ OURSTATUS and returns true if this represents a Cygwin signal,
+ otherwise false.
Cygwin prepends its messages with a "cygwin:". Interpret this as
a Cygwin signal. Otherwise just print the string as a warning.
This function must be supplied by the embedding application. */
- virtual DWORD handle_output_debug_string (const DEBUG_EVENT ¤t_event,
- struct target_waitstatus *ourstatus) = 0;
+ virtual bool handle_output_debug_string (const DEBUG_EVENT ¤t_event,
+ struct target_waitstatus *ourstatus) = 0;
/* Handle a DLL load event.
@@ -415,6 +458,11 @@ extern DeleteProcThreadAttributeList_ftype *DeleteProcThreadAttributeList;
extern bool disable_randomization_available ();
+/* This is available starting with Windows 10. */
+#ifndef DBG_REPLY_LATER
+# define DBG_REPLY_LATER 0x40010001L
+#endif
+
/* Return true if it's possible to use DBG_REPLY_LATER with
ContinueDebugEvent on this host. */
extern bool dbg_reply_later_available ();
@@ -74,6 +74,162 @@
#include "ser-event.h"
#include "inf-loop.h"
+/* This comment documents high-level logic of this file.
+
+all-stop
+========
+
+In all-stop mode, there is only ever one Windows debug event in
+flight. When we receive an event from WaitForDebugEvent, the kernel
+has already implicitly suspended all the threads of the process. We
+report the breaking event to the core. When the core decides to
+resume the inferior, it calls windows_nat_target:resume, which
+triggers a ContinueDebugEvent call. This call makes all unsuspended
+threads schedulable again, and we go back to waiting for the next
+event in WaitForDebugEvent.
+
+non-stop
+========
+
+For non-stop mode, we utilize the DBG_REPLY_LATER flag in the
+ContinueDebugEvent function. According to Microsoft:
+
+ "This flag causes dwThreadId to replay the existing breaking event
+ after the target continues. By calling the SuspendThread API against
+ dwThreadId, a debugger can resume other threads in the process and
+ later return to the breaking."
+
+To enable non-stop mode, windows_nat_target::wait suspends the thread,
+calls 'ContinueForDebugEvent(..., DBG_REPLY_LATER)', and sets the
+process_thread thread to wait for the next event using
+WaitForDebugEvent, all before returning the original breaking event to
+the core.
+
+When the user/core finally decides to resume the inferior thread that
+reported the event, we unsuspend it using ResumeThread. Unlike in
+all-stop mode, we don't call ContinueDebugEvent then, as it has
+already been called when the event was first encountered. By making
+the inferior thread schedulable again, WaitForDebugEvent re-reports
+the same event (due to the earlier DBG_REPLY_LATER). In
+windows_nat_target::wait, we detect this delayed re-report and call
+ContinueDebugEvent on the thread, instructing the process_thread
+thread to continue waiting for the next event.
+
+During the initial thread resumption in windows_nat_target::resume, we
+recorded the dwContinueStatus argument to be passed to the last
+ContinueDebugEvent. See windows_thread_info::auto_cont for details.
+
+Note that with this setup, in non-stop mode, every stopped thread has
+its own independent last-reported Windows debug event. Therefore, we
+can decide on a per-thread basis whether to pass the thread's
+exception (DBG_EXCEPTION_NOT_HANDLED / DBG_CONTINUE) to the inferior.
+This per-thread decision is not possible in all-stop mode, where we
+only call ContinueDebugEvent for the thread that last reported a stop,
+at windows_nat_target::resume time.
+
+Cygwin signals
+==============
+
+The Cygwin runtime always spawns a "sig" thread, which is responsible
+for receiving signal delivery requests, and hijacking the signaled
+thread's execution to make it run the signal handler. This is all
+explained here:
+
+ https://sourceware.org/cgit/newlib-cygwin/tree/winsup/cygwin/DevDocs/how-signals-work.txt
+
+There's a custom debug api protocol between GDB and Cygwin to be able
+to intercept Cygwin signals before they're seen by the signaled
+thread, just like the debugger intercepts signals with ptrace on
+Linux. This Cygwin debugger protocol isn't well documented, though.
+Here's what happens: when the special "sig" thread in the Cygwin
+runtime is about to deliver a signal to the target thread, it calls
+OutputDebugString with a special message:
+
+ https://sourceware.org/cgit/newlib-cygwin/tree/winsup/cygwin/exceptions.cc?id=4becae7bd833e183c789821a477f25898ed0db1f#n1866
+
+OutputDebugString is a function that is part of the Windows debug API.
+It generates an OUTPUT_DEBUG_STRING_EVENT event out of
+WaitForDebugEvent in the debugger, which freezes the inferior, like
+any other event.
+
+GDB recognizes the special Cygwin signal marker string, and is able to
+report the intercepted Cygwin signal to the user.
+
+With the windows-nat backend in all-stop mode, if the user decides to
+single-step the signaled thread, GDB will set the trace flag in the
+signaled thread to force it to single-step, and then re-resume the
+program with ContinueDebugEvent. This resumes both the signaled
+thread, and the special "sig" thread. The special "sig" thread
+decides to make the signaled thread run the signal handler, so it
+suspends it with SuspendThread, does a read-modify-write operation
+with GetThreadContext/SetThreadContext, and then re-resumes it with
+ResumeThread. This is all done here:
+
+ https://sourceware.org/cgit/newlib-cygwin/tree/winsup/cygwin/exceptions.cc?id=4becae7bd833e183c789821a477f25898ed0db1f#n1011
+
+That resulting register context will still have its trace flag set, so
+the signaled thread ends up single-stepping the signal handler and
+reporting the trace stop to GDB, which reports the stop where the
+thread is now stopped, inside the signal handler.
+
+That is the intended behavior; stepping into a signal handler is a
+feature that works on other ports as well, including x86 GNU/Linux,
+for example. This is exercised by the gdb.base/sigstep.exp testcase.
+
+Now, making that work with the backend in non-stop mode (the default
+on Windows 10 and above) is tricker. In that case, when GDB sees the
+magic OUTPUT_DEBUG_STRING_EVENT event mentioned above, reported for
+the "sig" thread, GDB reports the signal stop for the target signaled
+thread to the user (leaving that thread stopped), but, unlike with an
+all-stop backend, in non-stop, only the evented/signaled thread should
+be stopped, so the backend would normally want to re-resume the Cygwin
+runtime's "sig" thread after handling the OUTPUT_DEBUG_STRING_EVENT
+event, like it does with any other event out of WaitForDebugEvent that
+is not reported to the core. If it did that (resume the "sig" thread)
+however, at that point, the signaled thread would be stopped,
+suspended with SuspendThread by GDB (while the user is inspecting it),
+but, unlike in all-stop, the "sig" thread would be set running free.
+The "sig" thread would reach the code that wants to redirect the
+signaled thread's execution to the signal handler (by hacking the
+registers context, as described above), but unlike in the all-stop
+case, the "sig" thread would notice that the signaled thread is
+suspended, and so would decide to defer the signal handler until a
+later time. It's the same code as described above for the all-stop
+case, except it would take the "then" branch:
+
+ https://sourceware.org/cgit/newlib-cygwin/tree/winsup/cygwin/exceptions.cc?id=4becae7bd833e183c789821a477f25898ed0db1f#n1019
+
+ // Just set pending if thread is already suspended
+ if (res)
+ {
+ tls->unlock ();
+ ResumeThread (hth);
+ goto out;
+ }
+
+The result would be that when the GDB user later finally decides to
+step the signaled thread, the signaled thread would just single step
+the mainline code, instead of stepping into the signal handler.
+
+To avoid this difference of behavior in non-stop mode compared to
+all-stop mode, we use a trick -- whenever we see that magic
+OUTPUT_DEBUG_STRING_EVENT event reported for the "sig" thread, we
+report a stop for the target signaled thread, _and_ leave the "sig"
+thread suspended as well, for as long as the target signaled thread is
+suspended. I.e., we don't let the "sig" thread run before the user
+decides what to do with the signaled thread's signal. Only when the
+user re-resumes the signaled thread, will we resume the "sig" thread
+as well. The trick is that all this is done here in the Windows
+backend, while providing the illusion to the core of GDB (and the
+user) that the "sig" thread is "running", for as long as the core
+wants the "sig" thread to be running.
+
+This isn't ideal, since this means that with user-visible non-stop,
+the inferior will only be able to process and report one signal at a
+time (as the "sig" thread is responsible for that), but that seems
+like an acceptible compromise, better than not being able to have the
+target work in non-stop by default on Cygwin. */
+
using namespace windows_nat;
/* Maintain a linked list of "so" information. */
@@ -100,6 +256,11 @@ enum windows_continue_flag
call to continue the inferior -- we are either mourning it or
detaching. */
WCONT_LAST_CALL = 2,
+
+ /* By default, windows_continue only calls ContinueDebugEvent in
+ all-stop mode. This flag indicates that windows_continue
+ should call ContinueDebugEvent even in non-stop mode. */
+ WCONT_CONTINUE_DEBUG_EVENT = 4,
};
DEF_ENUM_FLAGS_TYPE (windows_continue_flag, windows_continue_flags);
@@ -107,8 +268,8 @@ DEF_ENUM_FLAGS_TYPE (windows_continue_flag, windows_continue_flags);
struct windows_per_inferior : public windows_process_info
{
windows_thread_info *find_thread (ptid_t ptid) override;
- DWORD handle_output_debug_string (const DEBUG_EVENT ¤t_event,
- struct target_waitstatus *ourstatus) override;
+ bool handle_output_debug_string (const DEBUG_EVENT ¤t_event,
+ struct target_waitstatus *ourstatus) override;
void handle_load_dll (const char *dll_name, LPVOID base) override;
void handle_unload_dll (const DEBUG_EVENT ¤t_event) override;
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;
@@ -269,7 +430,11 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
void attach (const char *, int) override;
bool attach_no_wait () override
- { return true; }
+ {
+ /* In non-stop, after attach, we leave all threads running, like
+ other targets. */
+ return !target_is_non_stop_p ();
+ }
void detach (inferior *, int) override;
@@ -312,8 +477,11 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
std::string pid_to_str (ptid_t) override;
void interrupt () override;
+ void stop (ptid_t) override;
void pass_ctrlc () override;
+ void thread_events (int enable) override;
+
const char *pid_to_exec_file (int pid) override;
ptid_t get_ada_task_ptid (long lwp, ULONGEST thread) override;
@@ -343,6 +511,9 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
return m_is_async;
}
+ bool supports_non_stop () override;
+ bool always_non_stop_p () override;
+
void async (bool enable) override;
int async_wait_fd () override
@@ -357,6 +528,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);
+
BOOL windows_continue (DWORD continue_status, int id,
windows_continue_flags cont_flags = 0);
@@ -420,6 +593,9 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
already returned an event, and we need to ContinueDebugEvent
again to restart the inferior. */
bool m_continued = false;
+
+ /* Whether target_thread_events is in effect. */
+ int m_report_thread_events = 0;
};
static void
@@ -629,6 +805,13 @@ windows_nat_target::add_thread (ptid_t ptid, HANDLE h, void *tlb,
registers. */
th->debug_registers_changed = true;
+ /* Even if we're stopping the thread for some reason internal to
+ this module, from the perspective of infrun and the
+ user/frontend, this new thread is running until it next reports a
+ stop. */
+ set_running (this, ptid, true);
+ set_executing (this, ptid, true);
+
return th;
}
@@ -1048,12 +1231,17 @@ signal_event_command (const char *args, int from_tty)
/* See nat/windows-nat.h. */
-DWORD
+bool
windows_per_inferior::handle_output_debug_string
(const DEBUG_EVENT ¤t_event,
struct target_waitstatus *ourstatus)
{
- DWORD thread_id = 0;
+ windows_thread_info *event_thr
+ = windows_process.find_thread (ptid_t (current_event.dwProcessId,
+ current_event.dwThreadId));
+ if (event_thr->auto_cont)
+ internal_error ("OutputDebugString thread 0x%x has auto-cont set",
+ event_thr->tid);
gdb::unique_xmalloc_ptr<char> s
= (target_read_string
@@ -1090,15 +1278,37 @@ windows_per_inferior::handle_output_debug_string
int sig = strtol (s.get () + sizeof (_CYGWIN_SIGNAL_STRING) - 1, &p, 0);
gdb_signal gotasig = gdb_signal_from_host (sig);
LPCVOID x = 0;
+ DWORD thread_id = 0;
- if (gotasig)
+ if (gotasig != GDB_SIGNAL_0)
{
- ourstatus->set_stopped (gotasig);
thread_id = strtoul (p, &p, 0);
- if (thread_id == 0)
- thread_id = current_event.dwThreadId;
- else
- x = (LPCVOID) (uintptr_t) strtoull (p, NULL, 0);
+ if (thread_id != 0)
+ {
+ x = (LPCVOID) (uintptr_t) strtoull (p, NULL, 0);
+
+ ptid_t ptid (current_event.dwProcessId, thread_id, 0);
+ windows_thread_info *th = find_thread (ptid);
+
+ /* Suspend the signaled thread, and leave the signal as
+ a pending event. It will be picked up by
+ windows_nat_target::wait. */
+ th->suspend ();
+ th->stopping = true;
+ th->last_event = {};
+ th->pending_status.set_stopped (gotasig);
+
+ /* Link the "sig" thread and the signaled threads, so we
+ can keep the "sig" thread suspended until we resume
+ the signaled thread. See "Cygwin signals" at the
+ top. */
+ event_thr->signaled_thread = th;
+ th->cygwin_sig_thread = event_thr;
+
+ /* Leave the "sig" thread suspended. */
+ event_thr->suspend ();
+ return true;
+ }
}
DEBUG_EVENTS ("gdb: cygwin signal %d, thread 0x%x, CONTEXT @ %p",
@@ -1106,7 +1316,7 @@ windows_per_inferior::handle_output_debug_string
}
#endif
- return thread_id;
+ return false;
}
static int
@@ -1395,6 +1605,7 @@ windows_per_inferior::continue_one_thread (windows_thread_info *th,
}
th->resume ();
+ th->stopping = false;
th->last_sig = GDB_SIGNAL_0;
}
@@ -1406,31 +1617,77 @@ BOOL
windows_nat_target::windows_continue (DWORD continue_status, int id,
windows_continue_flags cont_flags)
{
- for (auto &th : windows_process.thread_list)
- {
- if ((id == -1 || id == (int) th->tid)
- && !th->suspended
- && th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
- {
- DEBUG_EVENTS ("got matching pending stop event "
- "for 0x%x, not resuming",
- th->tid);
-
- /* There's no need to really continue, because there's already
- another event pending. However, we do need to inform the
- event loop of this. */
- serial_event_set (m_wait_event);
- return TRUE;
- }
- }
+ if ((cont_flags & (WCONT_LAST_CALL | WCONT_KILLED)) == 0)
+ for (auto &th : windows_process.thread_list)
+ {
+ if ((id == -1 || id == (int) th->tid)
+ && th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
+ {
+ DEBUG_EVENTS ("got matching pending stop event "
+ "for 0x%x, not resuming",
+ th->tid);
+
+ /* There's no need to really continue, because there's already
+ another event pending. However, we do need to inform the
+ event loop of this. */
+ serial_event_set (m_wait_event);
+ return TRUE;
+ }
+ }
+ /* Resume any suspended thread whose ID matches "ID". Skip the
+ Cygwin "sig" thread in the main iteration, though. That one is
+ only resumed when the target signaled thread is resumed. See
+ "Cygwin signals" in the intro section. */
for (auto &th : windows_process.thread_list)
- if (id == -1 || id == (int) th->tid)
- windows_process.continue_one_thread (th.get (), cont_flags);
+ if (th->suspended
+#ifdef __CYGWIN__
+ && th->signaled_thread == nullptr
+#endif
+ && (id == -1 || id == (int) th->tid))
+ {
+ windows_process.continue_one_thread (th.get (), cont_flags);
- continue_last_debug_event_main_thread
- (_("Failed to resume program execution"), continue_status,
- cont_flags & WCONT_LAST_CALL);
+#ifdef __CYGWIN__
+ /* See if we're resuming a thread that caught a Cygwin signal.
+ If so, also resume the Cygwin runtime's "sig" thread. */
+ if (th->cygwin_sig_thread != nullptr)
+ {
+ DEBUG_EVENTS ("\"sig\" thread %d (0x%x) blocked by "
+ "just-resumed thread %d (0x%x)",
+ th->cygwin_sig_thread->tid,
+ th->cygwin_sig_thread->tid,
+ th->tid, th->tid);
+
+ inferior *inf = find_inferior_pid (this,
+ windows_process.process_id);
+ thread_info *sig_thr
+ = inf->find_thread (ptid_t (windows_process.process_id,
+ th->cygwin_sig_thread->tid));
+ if (sig_thr->executing ())
+ {
+ DEBUG_EVENTS ("\"sig\" thread %d (0x%x) meant to be executing, "
+ "continuing it now",
+ th->cygwin_sig_thread->tid,
+ th->cygwin_sig_thread->tid);
+ windows_process.continue_one_thread (th->cygwin_sig_thread,
+ cont_flags);
+ }
+ /* Break the chain. */
+ th->cygwin_sig_thread->signaled_thread = nullptr;
+ th->cygwin_sig_thread = nullptr;
+ }
+#endif
+ }
+
+ if (!target_is_non_stop_p ()
+ || (cont_flags & WCONT_CONTINUE_DEBUG_EVENT) != 0)
+ {
+ DEBUG_EVENTS ("windows_continue -> continue_last_debug_event");
+ continue_last_debug_event_main_thread
+ (_("Failed to resume program execution"), continue_status,
+ cont_flags & WCONT_LAST_CALL);
+ }
return TRUE;
}
@@ -1481,13 +1738,27 @@ windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
if (sig != GDB_SIGNAL_0)
{
+ /* Allow continuing with the same signal that interrupted us.
+ Otherwise complain. */
+
/* Note it is OK to call get_last_debug_event_ptid() from the
- main thread here, because we know the process_thread thread
- isn't waiting for an event at this point, so there's no data
- race. */
- if (inferior_ptid != get_last_debug_event_ptid ())
+ main thread here in all-stop, because we know the
+ process_thread thread is not waiting for an event at this
+ point, so there is no data race. We cannot call it in
+ non-stop mode, as the process_thread thread _is_ waiting for
+ events right now in that case. However, the restriction does
+ not exist in non-stop mode, so we don't even call it in that
+ mode. */
+ if (!target_is_non_stop_p ()
+ && inferior_ptid != get_last_debug_event_ptid ())
{
- /* ContinueDebugEvent will be for a different thread. */
+ /* In all-stop, ContinueDebugEvent will be for a different
+ thread. For non-stop, we've called ContinueDebugEvent
+ with DBG_REPLY_LATER for this thread, so we just set the
+ intended continue status in 'auto_cont', which is later
+ passed to ContinueDebugEvent in windows_nat_target::wait
+ after we resume the thread and we get the replied-later
+ (repeated) event out of WaitForDebugEvent. */
DEBUG_EXCEPT ("Cannot continue with signal %d here. "
"Not last-event thread", sig);
}
@@ -1523,33 +1794,40 @@ windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
th->last_sig);
}
+ /* If DBG_REPLY_LATER was used on the thread, we override the
+ continue status that will be passed to ContinueDebugEvent later
+ with the continue status we've just determined fulfils the
+ caller's resumption request. Note that DBG_REPLY_LATER is only
+ used in non-stop mode, and in that mode, windows_continue (called
+ below) does not call ContinueDebugEvent. */
+ if (th->auto_cont != 0)
+ th->auto_cont = continue_status;
+
+ /* Single step by setting t bit (trap flag). The trap flag is
+ automatically reset as soon as the single-step exception arrives,
+ however, it's possible to suspend/stop a thread before it
+ executes any instruction, leaving the trace flag set. If we
+ subsequently decide to continue such a thread instead of stepping
+ it, and we didn't clear the trap flag, the thread would step, and
+ we'd end up reporting a SIGTRAP to the core which the core
+ couldn't explain (because the thread wasn't supposed to be
+ stepping), and end up reporting a spurious SIGTRAP to the
+ user. */
+ regcache *regcache = get_thread_regcache (inferior_thread ());
+ fetch_registers (regcache, gdbarch_ps_regnum (regcache->arch ()));
+
+ DWORD *eflags;
#ifdef __x86_64__
if (windows_process.wow64_process)
- {
- if (step)
- {
- /* Single step by setting t bit. */
- regcache *regcache = get_thread_regcache (inferior_thread ());
- struct gdbarch *gdbarch = regcache->arch ();
- fetch_registers (regcache, gdbarch_ps_regnum (gdbarch));
- th->wow64_context.EFlags |= FLAG_TRACE_BIT;
- }
- }
+ eflags = &th->wow64_context.EFlags;
else
#endif
- {
- if (step)
- {
- /* Single step by setting t bit. */
- regcache *regcache = get_thread_regcache (inferior_thread ());
- struct gdbarch *gdbarch = regcache->arch ();
- fetch_registers (regcache, gdbarch_ps_regnum (gdbarch));
- th->context.EFlags |= FLAG_TRACE_BIT;
- }
- }
+ eflags = &th->context.EFlags;
- /* Allow continuing with the same signal that interrupted us.
- Otherwise complain. */
+ if (step)
+ *eflags |= FLAG_TRACE_BIT;
+ else
+ *eflags &= ~FLAG_TRACE_BIT;
if (resume_all)
windows_continue (continue_status, -1);
@@ -1598,12 +1876,88 @@ 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. */
+
+void
+windows_nat_target::stop_one_thread (windows_thread_info *th)
+{
+ ptid_t thr_ptid (windows_process.process_id, th->tid);
+
+ if (th->suspended == -1)
+ {
+ /* Already known to be stopped; and suspension failed, most
+ probably because the thread is exiting. Do nothing, and let
+ the thread exit event be reported. */
+ DEBUG_EVENTS ("already suspended %s: suspended=%d, stopping=%d",
+ thr_ptid.to_string ().c_str (),
+ th->suspended, th->stopping);
+ }
+#ifdef __CYGWIN__
+ else if (th->suspended
+ && th->signaled_thread != nullptr
+ && th->pending_status.kind () == TARGET_WAITKIND_IGNORE)
+ {
+ DEBUG_EVENTS ("explict stop for \"sig\" thread %s held for signal",
+ thr_ptid.to_string ().c_str ());
+
+ th->stopping = true;
+ th->pending_status.set_stopped (GDB_SIGNAL_0);
+ th->last_event = {};
+ serial_event_set (m_wait_event);
+ }
+#endif
+ else if (th->suspended)
+ {
+ /* Already known to be stopped; do nothing. */
+
+ DEBUG_EVENTS ("already suspended %s: suspended=%d, stopping=%d",
+ thr_ptid.to_string ().c_str (),
+ th->suspended, th->stopping);
+
+ th->stopping = true;
+ }
+ else
+ {
+ DEBUG_EVENTS ("stop request for %s", thr_ptid.to_string ().c_str ());
+
+ th->suspend ();
+ gdb_assert (th->suspended);
+
+ th->stopping = true;
+ th->pending_status.set_stopped (GDB_SIGNAL_0);
+ th->last_event = {};
+ serial_event_set (m_wait_event);
+ }
+}
+
+/* Implementation of target_ops::stop. */
+
+void
+windows_nat_target::stop (ptid_t ptid)
+{
+ for (auto &th : windows_process.thread_list)
+ {
+ ptid_t thr_ptid (windows_process.process_id, th->tid);
+ if (thr_ptid.matches (ptid))
+ stop_one_thread (th.get ());
+ }
+}
+
void
windows_nat_target::pass_ctrlc ()
{
interrupt ();
}
+/* Implementation of the target_ops::thread_events method. */
+
+void
+windows_nat_target::thread_events (int enable)
+{
+ m_report_thread_events = enable;
+}
+
/* Get the next event from the child. Returns the thread ptid. */
ptid_t
@@ -1619,7 +1973,7 @@ windows_nat_target::get_windows_debug_event
for details on why this is needed. */
for (auto &th : windows_process.thread_list)
{
- if (!th->suspended
+ if ((!th->suspended || th->stopping)
&& th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
{
DEBUG_EVENTS ("reporting pending event for 0x%x", th->tid);
@@ -1630,7 +1984,6 @@ windows_nat_target::get_windows_debug_event
*current_event = th->last_event;
ptid_t ptid (windows_process.process_id, thread_id);
- windows_process.invalidate_context (th.get ());
return ptid;
}
}
@@ -1648,6 +2001,50 @@ windows_nat_target::get_windows_debug_event
event_code = current_event->dwDebugEventCode;
ourstatus->set_spurious ();
+ ptid_t result_ptid (current_event->dwProcessId,
+ current_event->dwThreadId, 0);
+ windows_thread_info *result_th = windows_process.find_thread (result_ptid);
+
+ /* If we previously used DBG_REPLY_LATER on this thread, and we're
+ seeing an event for it, it means we've already processed the
+ event, and then subsequently resumed the thread [1], intending to
+ pass AUTO_CONT to ContinueDebugEvent. Do that now, before the
+ switch table below, which may have side effects that don't make
+ sense for a delayed event.
+
+ [1] - with the caveat that sometimes Windows reports an event for
+ a suspended thread. Also handled below. */
+ if (result_th != nullptr && result_th->auto_cont != 0)
+ {
+ DEBUG_EVENTS ("auto-cont thread 0x%x", result_th->tid);
+
+ gdb_assert (dbg_reply_later_available ());
+
+ if (result_th->suspended)
+ {
+ /* Pending stop. See the comment by the definition of
+ "pending_status" for details on why this is needed. */
+ DEBUG_EVENTS ("unexpected auto-cont stop in suspended thread 0x%x",
+ result_th->tid);
+
+ /* Put the event back in the kernel queue. We haven't yet
+ decided which reply to use. */
+ continue_status = DBG_REPLY_LATER;
+ }
+ else
+ {
+ continue_status = result_th->auto_cont;
+ result_th->auto_cont = 0;
+ }
+
+ /* Go back to waiting for the next event. */
+ continue_last_debug_event_main_thread
+ (_("Failed to auto-continue thread"), continue_status);
+
+ ourstatus->set_ignore ();
+ return null_ptid;
+ }
+
switch (event_code)
{
case CREATE_THREAD_DEBUG_EVENT:
@@ -1680,21 +2077,35 @@ windows_nat_target::get_windows_debug_event
current_event->u.CreateThread.lpThreadLocalBase,
false /* main_thread_p */));
- /* This updates debug registers if necessary. */
- windows_process.continue_one_thread (th, 0);
+ /* Update the debug registers if we're not reporting the stop.
+ If we are (reporting the stop), the debug registers will be
+ updated when the thread is eventually re-resumed. */
+ if (m_report_thread_events)
+ ourstatus->set_thread_created ();
+ else
+ windows_process.continue_one_thread (th, 0);
}
break;
case EXIT_THREAD_DEBUG_EVENT:
- DEBUG_EVENTS ("kernel event for pid=%u tid=0x%x code=%s",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId,
- "EXIT_THREAD_DEBUG_EVENT");
- delete_thread (ptid_t (current_event->dwProcessId,
- current_event->dwThreadId, 0),
- current_event->u.ExitThread.dwExitCode,
- false /* main_thread_p */);
- thread_id = 0;
+ {
+ DEBUG_EVENTS ("kernel event for pid=%u tid=0x%x code=%s",
+ (unsigned) current_event->dwProcessId,
+ (unsigned) current_event->dwThreadId,
+ "EXIT_THREAD_DEBUG_EVENT");
+ ptid_t thr_ptid (current_event->dwProcessId,
+ current_event->dwThreadId, 0);
+ if (m_report_thread_events)
+ {
+ ourstatus->set_thread_exited
+ (current_event->u.ExitThread.dwExitCode);
+ return thr_ptid;
+ }
+ delete_thread (thr_ptid,
+ current_event->u.ExitThread.dwExitCode,
+ false /* main_thread_p */);
+ thread_id = 0;
+ }
break;
case CREATE_PROCESS_DEBUG_EVENT:
@@ -1820,8 +2231,24 @@ windows_nat_target::get_windows_debug_event
"OUTPUT_DEBUG_STRING_EVENT");
if (windows_process.saw_create != 1)
break;
- thread_id = windows_process.handle_output_debug_string (*current_event,
- ourstatus);
+ if (windows_process.handle_output_debug_string (*current_event,
+ ourstatus))
+ {
+ /* We caught a Cygwin signal for a thread. That thread now
+ has a pending event, and the "sig" thread is
+ suspended. */
+ serial_event_set (m_wait_event);
+
+ /* In all-stop, return now to avoid reaching
+ ContinueDebugEvent further below. In all-stop, it's
+ always windows_nat_target::resume that does the
+ ContinueDebugEvent call. */
+ if (!target_is_non_stop_p ())
+ {
+ ourstatus->set_ignore ();
+ return null_ptid;
+ }
+ }
break;
default:
@@ -1856,22 +2283,35 @@ windows_nat_target::get_windows_debug_event
"unexpected stop in suspended thread 0x%x",
thread_id);
- if (current_event->dwDebugEventCode == EXCEPTION_DEBUG_EVENT
- && ((current_event->u.Exception.ExceptionRecord.ExceptionCode
- == EXCEPTION_BREAKPOINT)
- || (current_event->u.Exception.ExceptionRecord.ExceptionCode
- == STATUS_WX86_BREAKPOINT))
- && windows_process.windows_initialization_done)
+ if (dbg_reply_later_available ())
{
- th->stopped_at_software_breakpoint = true;
- th->pc_adjusted = false;
+ /* Thankfully, the Windows kernel doesn't immediately
+ re-report the unexpected event for a suspended thread
+ when we defer it with DBG_REPLY_LATER, otherwise this
+ would get us stuck in an infinite loop re-processing the
+ same unexpected event over and over. */
+ continue_status = DBG_REPLY_LATER;
}
+ else
+ {
+ if (current_event->dwDebugEventCode == EXCEPTION_DEBUG_EVENT
+ && ((current_event->u.Exception.ExceptionRecord.ExceptionCode
+ == EXCEPTION_BREAKPOINT)
+ || (current_event->u.Exception.ExceptionRecord.ExceptionCode
+ == STATUS_WX86_BREAKPOINT))
+ && windows_process.windows_initialization_done)
+ {
+ th->stopped_at_software_breakpoint = true;
+ th->pc_adjusted = false;
+ }
- th->pending_status = *ourstatus;
- ourstatus->set_ignore ();
+ th->pending_status = *ourstatus;
+ th->last_event = {};
+ }
continue_last_debug_event_main_thread
(_("Failed to resume program execution"), continue_status);
+ ourstatus->set_ignore ();
return null_ptid;
}
@@ -1902,6 +2342,14 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
ptid_t result = get_windows_debug_event (pid, ourstatus, options,
¤t_event);
+ /* True if this is a pending event that we injected ourselves,
+ instead of a real event out of WaitForDebugEvent. */
+ bool fake = current_event.dwDebugEventCode == 0;
+
+ DEBUG_EVENTS ("get_windows_debug_event returned [%s : %s, fake=%d]",
+ result.to_string ().c_str (),
+ ourstatus->to_string ().c_str(),
+ fake);
if ((options & TARGET_WNOHANG) != 0
&& ourstatus->kind () == TARGET_WAITKIND_IGNORE)
@@ -1932,10 +2380,46 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
th->pc_adjusted = false;
}
+ /* If non-stop, suspend the event thread, and continue
+ it with DBG_REPLY_LATER, so the other threads go back
+ to running as soon as possible. Don't do this if
+ stopping the thread, as in that case the thread was
+ already suspended, and also there's no real Windows
+ debug event to continue in that case. */
+ if (windows_process.windows_initialization_done
+ && target_is_non_stop_p ()
+ && !fake)
+ {
+ if (ourstatus->kind () == TARGET_WAITKIND_THREAD_EXITED)
+ {
+ /* If we failed to suspend the thread in
+ windows_nat_target::stop, then 'suspended'
+ will be -1 here. */
+ gdb_assert (th->suspended < 1);
+ delete_thread (result,
+ ourstatus->exit_status (),
+ false /* main_thread_p */);
+ continue_last_debug_event_main_thread
+ (_("Init: Failed to DBG_CONTINUE after thread exit"),
+ DBG_CONTINUE);
+ }
+ else
+ {
+ th->suspend ();
+ th->auto_cont = DBG_CONTINUE;
+ continue_last_debug_event_main_thread
+ (_("Init: Failed to defer event with DBG_REPLY_LATER"),
+ DBG_REPLY_LATER);
+ }
+ }
+
/* All-stop, suspend all threads until they are
explicitly resumed. */
- for (auto &thr : windows_process.thread_list)
- thr->suspend ();
+ if (!target_is_non_stop_p ())
+ for (auto &thr : windows_process.thread_list)
+ thr->suspend ();
+
+ th->stopping = false;
}
/* If something came out, assume there may be more. This is
@@ -2017,7 +2501,7 @@ windows_nat_target::do_initial_windows_stuff (DWORD pid, bool attaching)
/* Don't use windows_nat_target::resume here because that
assumes that inferior_ptid points at a valid thread, and we
haven't switched to any thread yet. */
- windows_continue (DBG_CONTINUE, -1);
+ windows_continue (DBG_CONTINUE, -1, WCONT_CONTINUE_DEBUG_EVENT);
}
switch_to_thread (this->find_thread (last_ptid));
@@ -2162,7 +2646,29 @@ windows_nat_target::attach (const char *args, int from_tty)
#endif
do_initial_windows_stuff (pid, 1);
- target_terminal::ours ();
+
+ if (target_is_non_stop_p ())
+ {
+ /* Leave all threads running. */
+
+ continue_last_debug_event_main_thread
+ (_("Failed to DBG_CONTINUE after attach"),
+ DBG_CONTINUE);
+
+ /* The thread that reports the initial breakpoint, and thus ends
+ up as selected thread here, was injected by Windows into the
+ program for the attach, and it exits as soon as we resume it.
+ Switch to the first thread in the inferior, otherwise the
+ user will be left with an exited thread selected. */
+ switch_to_thread (first_thread_of_inferior (current_inferior ()));
+ }
+ else
+ {
+ set_running (this, minus_one_ptid, false);
+ set_executing (this, minus_one_ptid, false);
+
+ target_terminal::ours ();
+ }
}
void
@@ -2280,10 +2786,11 @@ windows_nat_target::detach (inferior *inf, int from_tty)
bool process_alive = true;
/* The process_thread helper thread will be blocked in
- WaitForDebugEvent waiting for events if we've resumed the target
- before we get here, e.g., with "attach&" or "c&". We need to
- unblock it so that we can have it call DebugActiveProcessStop
- below, in the do_synchronously block. */
+ WaitForDebugEvent waiting for events if we're in non-stop mode,
+ or if in all-stop and we've resumed the target before we get
+ here, e.g., with "attach&" or "c&". We need to unblock it so
+ that we can have it call DebugActiveProcessStop below, in the
+ do_synchronously block. */
if (m_continued)
break_out_process_thread (process_alive);
@@ -3035,13 +3542,33 @@ windows_nat_target::create_inferior (const char *exec_file,
do_initial_windows_stuff (pi.dwProcessId, 0);
- /* windows_continue (DBG_CONTINUE, -1); */
+ /* There is one thread in the process now, and inferior_ptid points
+ to it. Present it as stopped to the core. */
+ windows_thread_info *th = windows_process.find_thread (inferior_ptid);
+
+ th->suspend ();
+ set_running (this, inferior_ptid, false);
+ set_executing (this, inferior_ptid, false);
+
+ if (target_is_non_stop_p ())
+ {
+ /* In non-stop mode, we always immediately use DBG_REPLY_LATER
+ on threads as soon as they report an event. However, during
+ the initial startup, windows_nat_target::wait does not do
+ this, so we need to handle it here for the initial
+ thread. */
+ th->auto_cont = DBG_CONTINUE;
+ continue_last_debug_event_main_thread
+ (_("Failed to defer event with DBG_REPLY_LATER"),
+ DBG_REPLY_LATER);
+ }
}
void
windows_nat_target::mourn_inferior ()
{
- windows_continue (DBG_CONTINUE, -1, WCONT_LAST_CALL);
+ windows_continue (DBG_CONTINUE, -1,
+ WCONT_LAST_CALL | WCONT_CONTINUE_DEBUG_EVENT);
x86_cleanup_dregs();
if (windows_process.open_process_used)
{
@@ -3096,14 +3623,28 @@ windows_nat_target::kill ()
{
CHECK (TerminateProcess (windows_process.handle, 0));
+ /* In non-stop mode, windows_continue does not call
+ ContinueDebugEvent by default. This behavior is appropriate for
+ the first call to windows_continue because any thread that is
+ stopped has already been ContinueDebugEvent'ed with
+ DBG_REPLY_LATER. However, after the first
+ wait_for_debug_event_main_thread call in the loop, this will no
+ longer be true.
+
+ In all-stop mode, the WCONT_CONTINUE_DEBUG_EVENT flag has no
+ effect, so writing the code in this way ensures that the code is
+ the same for both modes. */
+ windows_continue_flags flags = WCONT_KILLED;
+
for (;;)
{
- if (!windows_continue (DBG_CONTINUE, -1, WCONT_KILLED))
+ if (!windows_continue (DBG_CONTINUE, -1, flags))
break;
DEBUG_EVENT current_event;
wait_for_debug_event_main_thread (¤t_event);
if (current_event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
break;
+ flags |= WCONT_CONTINUE_DEBUG_EVENT;
}
target_mourn_inferior (inferior_ptid); /* Or just windows_mourn_inferior? */
@@ -3237,6 +3778,24 @@ windows_nat_target::thread_name (struct thread_info *thr)
return th->thread_name ();
}
+/* Implementation of the target_ops::supports_non_stop method. */
+
+bool
+windows_nat_target::supports_non_stop ()
+{
+ /* Non-stop support requires DBG_REPLY_LATER, which only exists on
+ Windows 10 and later. */
+ return dbg_reply_later_available ();
+}
+
+/* Implementation of the target_ops::always_non_stop_p method. */
+
+bool
+windows_nat_target::always_non_stop_p ()
+{
+ /* If we can do non-stop, prefer it. */
+ return supports_non_stop ();
+}
void _initialize_windows_nat ();
void
@@ -625,7 +625,7 @@ win32_process_target::attach (unsigned long pid)
/* See nat/windows-nat.h. */
-DWORD
+bool
gdbserver_windows_process::handle_output_debug_string
(const DEBUG_EVENT ¤t_event,
struct target_waitstatus *ourstatus)
@@ -636,7 +636,7 @@ gdbserver_windows_process::handle_output_debug_string
DWORD nbytes = current_event.u.DebugString.nDebugStringLength;
if (nbytes == 0)
- return 0;
+ return false;
if (nbytes > READ_BUFFER_LEN)
nbytes = READ_BUFFER_LEN;
@@ -655,7 +655,7 @@ gdbserver_windows_process::handle_output_debug_string
else
{
if (read_inferior_memory (addr, (unsigned char *) s, nbytes) != 0)
- return 0;
+ return false;
}
if (!startswith (s, "cYg"))
@@ -663,14 +663,14 @@ gdbserver_windows_process::handle_output_debug_string
if (!server_waiting)
{
OUTMSG2(("%s", s));
- return 0;
+ return false;
}
monitor_output (s);
}
#undef READ_BUFFER_LEN
- return 0;
+ return false;
}
static void
@@ -175,8 +175,8 @@ class win32_process_target : public process_stratum_target
struct gdbserver_windows_process : public windows_nat::windows_process_info
{
windows_nat::windows_thread_info *find_thread (ptid_t ptid) override;
- DWORD handle_output_debug_string (const DEBUG_EVENT ¤t_event,
- struct target_waitstatus *ourstatus) override;
+ bool handle_output_debug_string (const DEBUG_EVENT ¤t_event,
+ struct target_waitstatus *ourstatus) override;
void handle_load_dll (const char *dll_name, LPVOID base) override;
void handle_unload_dll (const DEBUG_EVENT ¤t_event) override;
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;