#include "ser-event.h"
#include "inf-loop.h"
+/* This comment documents high-level logic of this file.
+
+all-stop
+========
+
+In all-stop mode ("maint set target-non-stop off"), 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 (by unsuspending it),
+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 (the GDB thread responsible for calling
+WaitForDebugEvents) 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 (called when the reply-later event is re-reported).
+See windows_thread_info::reply_later 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.
+
+Thread and process exits
+========================
+
+When a process exits, Windows reports one EXIT_THREAD_DEBUG_EVENT
+event for each thread, except for the last thread that exits. That
+last thread reports a EXIT_PROCESS_DEBUG_EVENT event instead.
+
+The last thread that exits is not guaranteed to be the main thread of
+the process. In fact, it seldom is. E.g., if the main thread calls
+ExitProcess (or returns from main, which ends up calling ExitProcess),
+then we typically see a EXIT_THREAD_DEBUG_EVENT event for the main
+thread first, followed by more EXIT_THREAD_DEBUG_EVENT events for
+other threads, and then finaly the EXIT_PROCESS_DEBUG_EVENT for
+whatever thread happened to be the last one to exit.
+
+When a thread reports EXIT_THREAD_DEBUG_EVENT /
+EXIT_PROCESS_DEBUG_EVENT, our handle to the thread is still valid, and
+we can still read its registers. Windows only destroys the handle
+after ContinueDebugEvent.
+
+A thread that has exited CANNOT be suspended. So if a thread was
+previously suspended, and then something kills the whole process
+(which force-kills all threads), that suspended thread will
+automatically "unsuspend", and report a EXIT_THREAD_DEBUG_EVENT event.
+However, if we had previously used DBG_REPLY_LATER on the thread,
+Windows will first re-report the kernel-side-queued "reply-later"
+event, and only after that one is ContinueDebugEvent'ed, will we see
+the EXIT_THREAD_DEBUG_EVENT event.
+
+Detaching and DBG_REPLY_LATER
+=============================
+
+After we detach from a process that has threads that we had previously
+used DBG_REPLY_LATER on, the kernel re-raises the "reply-later"
+exceptions for those threads. This would most often kill the
+just-detached process, if we let it happen. To prevent it, we flush
+all the "reply-later" events from the kernel before detaching.
+
+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. */
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);
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;
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;
std::string pid_to_str (ptid_t) override;
void interrupt () override;
+ void stop (ptid_t) override;
void pass_ctrlc () override;
+ void thread_events (bool enable) override;
+
+ bool any_resumed_thread ();
+
const char *pid_to_exec_file (int pid) override;
ptid_t get_ada_task_ptid (long lwp, ULONGEST thread) override;
return m_is_async;
}
+ bool supports_non_stop () override;
+
void async (bool enable) override;
int async_wait_fd () override
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);
+
+ DWORD continue_status_for_event_detaching
+ (const DEBUG_EVENT &event, size_t *reply_later_events_left = nullptr);
+
+ DWORD prepare_resume (windows_thread_info *wth,
+ thread_info *tp,
+ int step, gdb_signal sig);
+
BOOL windows_continue (DWORD continue_status, int id,
windows_continue_flags cont_flags = 0);
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. */
+ bool m_report_thread_events = false;
};
/* Get the windows_thread_info object associated with THR. */
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_state (this, ptid, THREAD_RUNNING);
+ set_internal_state (this, ptid, THREAD_INT_RUNNING);
+
return th;
}
/* 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->reply_later != 0)
+ internal_error ("OutputDebugString thread 0x%x has reply-later set",
+ event_thr->tid);
gdb::unique_xmalloc_ptr<char> s
= (target_read_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",
}
#endif
- return thread_id;
+ return false;
}
static int
{
struct x86_debug_reg_state *state = x86_debug_reg_state (process_id);
+ /* If this thread is already gone, but the core doesn't know about
+ it yet, there's really nothing to resume. Such a thread will
+ have a pending exit status, so we won't try to resume it in the
+ normal resume path. But, we can still end up here in the
+ kill/detach/mourn paths, trying to resume the whole process to
+ collect the last debug event. */
+ if (th->h == nullptr)
+ return;
+
windows_process.with_context (th, [&] (auto *context)
{
if (th->debug_registers_changed)
});
th->resume ();
+ th->stopping = false;
th->last_sig = GDB_SIGNAL_0;
}
windows_nat_target::windows_continue (DWORD continue_status, int id,
windows_continue_flags cont_flags)
{
- for (auto *th : all_windows_threads ())
- {
- 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 : all_windows_threads ())
+ {
+ 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 : all_windows_threads ())
- if (id == -1 || id == (int) th->tid)
- windows_process.continue_one_thread (th, cont_flags);
+ if (th->suspended
+#ifdef __CYGWIN__
+ && th->signaled_thread == nullptr
+#endif
+ && (id == -1 || id == (int) th->tid))
+ {
+ windows_process.continue_one_thread (th, cont_flags);
+
+#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->internal_state () == THREAD_INT_RUNNING)
+ {
+ DEBUG_EVENTS ("\"sig\" thread %d (0x%x) meant to be running, "
+ "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
+ }
- continue_last_debug_event_main_thread
- (_("Failed to resume program execution"), continue_status,
- cont_flags & WCONT_LAST_CALL);
+ 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;
}
return current_event.dwThreadId;
}
-void
-windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
-{
- windows_thread_info *th;
- DWORD continue_status = DBG_CONTINUE;
-
- /* A specific PTID means `step only this thread id'. */
- int resume_all = ptid == minus_one_ptid;
-
- /* If we're continuing all threads, it's the current inferior that
- should be handled specially. */
- if (resume_all)
- ptid = inferior_ptid;
+/* Prepare TH to be resumed. TH and TP must point at the same thread.
+ Records the right dwContinueStatus for SIG in th->reply_later if we
+ used DBG_REPLY_LATER before on this thread, and sets of clears the
+ trace flag according to STEP. Also returns the dwContinueStatus
+ argument to pass to ContinueDebugEvent. The thread is still left
+ suspended -- a subsequent windows_continue/continue_one_thread call
+ is needed to flush the thread's register context and unsuspend. */
- DEBUG_EXEC ("pid=%d, tid=0x%x, step=%d, sig=%d",
- ptid.pid (), (unsigned) ptid.lwp (), step, sig);
+DWORD
+windows_nat_target::prepare_resume (windows_thread_info *th,
+ thread_info *tp,
+ int step, gdb_signal sig)
+{
+ gdb_assert (th->tid == tp->ptid.lwp ());
- /* Get currently selected thread. */
- th = windows_process.find_thread (inferior_ptid);
- gdb_assert (th != nullptr);
+ DWORD continue_status = DBG_CONTINUE;
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 ()
+ && tp->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 'reply_later', 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);
}
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->reply_later != 0)
+ th->reply_later = 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 (tp);
+ fetch_registers (regcache, gdbarch_ps_regnum (regcache->arch ()));
+
windows_process.with_context (th, [&] (auto *context)
{
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));
- context->EFlags |= FLAG_TRACE_BIT;
- }
+ context->EFlags |= FLAG_TRACE_BIT;
+ else
+ context->EFlags &= ~FLAG_TRACE_BIT;
});
- /* Allow continuing with the same signal that interrupted us.
- Otherwise complain. */
+ return continue_status;
+}
+
+void
+windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
+{
+ /* A specific PTID means `step only this thread id'. */
+ int resume_all = ptid == minus_one_ptid;
+
+ /* If we're continuing all threads, it's the current inferior that
+ should be handled specially. */
+ if (resume_all)
+ ptid = inferior_ptid;
+
+ DEBUG_EXEC ("pid=%d, tid=0x%x, step=%d, sig=%d",
+ ptid.pid (), (unsigned) ptid.lwp (), step, sig);
+
+ /* Get currently selected thread. */
+ windows_thread_info *th = windows_process.find_thread (inferior_ptid);
+ gdb_assert (th != nullptr);
+
+ DWORD continue_status = prepare_resume (th, inferior_thread (), step, sig);
if (resume_all)
windows_continue (continue_status, -1);
"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 ();
+
+ /* If suspension failed, it means the thread is exiting. Let
+ the thread exit event be reported instead of faking our own
+ stop. */
+ if (th->suspended == -1)
+ {
+ DEBUG_EVENTS ("suspension of %s failed, expect thread exit event",
+ thr_ptid.to_string ().c_str ());
+ return;
+ }
+
+ gdb_assert (th->suspended == 1);
+
+ 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 (thread_info *thr : all_non_exited_threads (this))
+ {
+ if (thr->ptid.matches (ptid))
+ stop_one_thread (as_windows_thread_info (thr));
+ }
+}
+
void
windows_nat_target::pass_ctrlc ()
{
interrupt ();
}
+/* Implementation of the target_ops::thread_events method. */
+
+void
+windows_nat_target::thread_events (bool enable)
+{
+ DEBUG_EVENTS ("windows_nat_target::thread_events(%d)", enable);
+ m_report_thread_events = enable;
+}
+
+/* True if there is any resumed thread. */
+
+bool
+windows_nat_target::any_resumed_thread ()
+{
+ for (thread_info *thread : all_non_exited_threads (this))
+ if (thread->internal_state () == THREAD_INT_RUNNING)
+ return true;
+ return false;
+}
+
+/* Called for both EXIT_THREAD_DEBUG_EVENT and
+ EXIT_PROCESS_DEBUG_EVENT to handle the fact that the event thread
+ has exited. */
+
+static void
+handle_thread_exit (const DEBUG_EVENT ¤t_event)
+{
+ ptid_t ptid (current_event.dwProcessId, current_event.dwThreadId);
+ windows_thread_info *th = windows_process.find_thread (ptid);
+ gdb_assert (th != nullptr);
+
+ /* The handle is still valid, but it is going to be automatically
+ closed by Windows when we next call ContinueDebugEvent. Fetch
+ the thread's registers while we still can. For EXIT_PROCESS,
+ ContinueDebugEvent only happens at target_mourn_inferior time,
+ but do this not too, for consistency with EXIT_THREAD time. */
+ windows_process.fill_thread_context (th);
+ th->h = nullptr;
+
+ /* The thread is gone, so no longer suspended from Windows's
+ perspective. */
+ th->suspended = -1;
+}
+
/* Get the next event from the child. Returns the thread ptid. */
ptid_t
/* If there is a relevant pending stop, report it now. See the
comment by the definition of "windows_thread_info::pending_status"
for details on why this is needed. */
- for (auto *th : all_windows_threads ())
+ for (thread_info *thread : all_threads_safe ())
{
- if (!th->suspended
+ if (thread->inf->process_target () != this)
+ continue;
+
+ auto *th = as_windows_thread_info (thread);
+ if (thread->internal_state () == THREAD_INT_RUNNING
+ && th->suspended
&& th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
{
- DEBUG_EVENTS ("reporting pending event for 0x%x", th->tid);
-
- thread_id = th->tid;
*ourstatus = th->pending_status;
th->pending_status.set_ignore ();
*current_event = th->last_event;
-
- ptid_t ptid (windows_process.process_id, thread_id);
- windows_process.invalidate_context (th);
- return ptid;
+ DEBUG_EVENTS ("reporting pending event for 0x%x", th->tid);
+ return thread->ptid;
}
}
+ /* If there are no resumed threads left, bail. */
+ if (windows_process.windows_initialization_done
+ && !any_resumed_thread ())
+ {
+ ourstatus->set_no_resumed ();
+ return minus_one_ptid;
+ }
+
if ((options & TARGET_WNOHANG) != 0 && !m_debug_event_pending)
{
ourstatus->set_ignore ();
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 REPLY_LATER 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->reply_later != 0)
+ {
+ DEBUG_EVENTS ("reply-later thread 0x%x, suspended=%d, dwDebugEventCode=%s",
+ result_th->tid, result_th->suspended,
+ event_code_to_string (event_code).c_str ());
+
+ gdb_assert (dbg_reply_later_available ());
+
+ /* We never ask to DBG_REPLY_LATER these two, so we shouldn't
+ see them here. If a thread is forced-exited when a
+ DBG_REPLY_LATER is in effect, then we will still see the
+ DBG_REPLY_LATER-ed event before the thread/process exit
+ event. */
+ gdb_assert (event_code != EXIT_THREAD_DEBUG_EVENT
+ && event_code != EXIT_PROCESS_DEBUG_EVENT);
+
+ if (result_th->suspended == 1)
+ {
+ /* Pending stop. See the comment by the definition of
+ "pending_status" for details on why this is needed. */
+ DEBUG_EVENTS ("unexpected reply-later 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 if (result_th->suspended == -1)
+ {
+ /* We resumed the thread expecting to get back a reply-later
+ event. Before we saw that event, we tried to suspend the
+ thread, but that failed, because the thread exited
+ (likely because the whole process has been killed). We
+ should get back an EXIT_THREAD_DEBUG_EVENT for this
+ thread, but only after getting past this reply-later
+ event. */
+ DEBUG_EVENTS ("reply-later stop in suspend-failed "
+ "thread 0x%x, ignoring",
+ result_th->tid);
+
+ /* Continue normally, and expect a
+ EXIT_THREAD_DEBUG_EVENT. */
+ continue_status = DBG_CONTINUE;
+ result_th->reply_later = 0;
+ }
+ else
+ {
+ continue_status = result_th->reply_later;
+ result_th->reply_later = 0;
+ }
+
+ /* Go back to waiting for the next event. */
+ continue_last_debug_event_main_thread
+ (_("Failed to continue reply-later event"), continue_status);
+
+ ourstatus->set_ignore ();
+ return null_ptid;
+ }
+
DEBUG_EVENTS ("kernel event for pid=%u tid=0x%x code=%s",
(unsigned) current_event->dwProcessId,
(unsigned) current_event->dwThreadId,
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:
- delete_thread (ptid_t (current_event->dwProcessId,
- current_event->dwThreadId, 0),
- current_event->u.ExitThread.dwExitCode,
- false /* main_thread_p */);
- thread_id = 0;
+ {
+ ourstatus->set_thread_exited
+ (current_event->u.ExitThread.dwExitCode);
+ thread_id = current_event->dwThreadId;
+
+ handle_thread_exit (*current_event);
+
+ /* Don't decide yet whether to report the event, or delete the
+ thread immediately, because we still need to check whether
+ the event should be left pending, depending on whether the
+ thread was running or not from the core's perspective. */
+ }
break;
case CREATE_PROCESS_DEBUG_EVENT:
}
else if (windows_process.saw_create == 1)
{
- delete_thread (ptid_t (current_event->dwProcessId,
- current_event->dwThreadId, 0),
- 0, true /* main_thread_p */);
DWORD exit_status = current_event->u.ExitProcess.dwExitCode;
/* If the exit status looks like a fatal exception, but we
don't recognize the exception's code, make the original
ourstatus->set_exited (exit_status);
else
ourstatus->set_signalled (gdb_signal_from_host (exit_signal));
- return ptid_t (current_event->dwProcessId);
+
+ thread_id = current_event->dwThreadId;
+
+ handle_thread_exit (*current_event);
}
break;
case OUTPUT_DEBUG_STRING_EVENT: /* Message from the kernel. */
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:
return null_ptid;
}
- const ptid_t ptid = ptid_t (current_event->dwProcessId, thread_id, 0);
- windows_thread_info *th = windows_process.find_thread (ptid);
+ const ptid_t ptid = ptid_t (current_event->dwProcessId, thread_id);
+ thread_info *thread = this->find_thread (ptid);
+ auto *th = as_windows_thread_info (thread);
th->last_event = *current_event;
- if (th->suspended)
+ if (thread->internal_state () == THREAD_INT_STOPPED)
{
+ gdb_assert (th->suspended != 0);
+
/* Pending stop. See the comment by the definition of
"pending_status" for details on why this is needed. */
DEBUG_EVENTS ("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)
+ /* Use DBG_REPLY_LATER to put the event back in the kernel queue
+ if possible. Don't do that with exit-thread or exit-process
+ events, because when a thread is dead, it can't be suspended
+ anymore, so the kernel would immediately re-report the
+ event. */
+ if (event_code != EXIT_THREAD_DEBUG_EVENT
+ && event_code != EXIT_PROCESS_DEBUG_EVENT
+ && 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. (Which is what
+ would happen if we used DBG_REPLY_LATER on an exit-thread
+ or exit-process event. See comment above.) */
+ 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;
+ th->last_event = {};
}
- th->pending_status = *ourstatus;
+ /* For exit-process, the debug event is continued later, at
+ mourn time. */
+ if (event_code != EXIT_PROCESS_DEBUG_EVENT)
+ {
+ continue_last_debug_event_main_thread
+ (_("Failed to resume program execution"), continue_status);
+ }
ourstatus->set_ignore ();
+ return null_ptid;
+ }
- continue_last_debug_event_main_thread
- (_("Failed to resume program execution"), continue_status);
+ gdb_assert (thread->internal_state () == THREAD_INT_RUNNING);
+
+ /* Now that we've handled exit events for suspended threads (above),
+ we can finally decide whether to report the thread exit event or
+ just delete the thread without bothering the core. */
+ if (ourstatus->kind () == TARGET_WAITKIND_THREAD_EXITED
+ && !m_report_thread_events)
+ {
+ delete_thread (ptid, ourstatus->exit_status (),
+ false /* main_thread_p */);
+ ourstatus->set_spurious ();
return null_ptid;
}
while (1)
{
- DEBUG_EVENT current_event;
+ DEBUG_EVENT current_event {};
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)
return result;
+ if (ourstatus->kind () == TARGET_WAITKIND_NO_RESUMED)
+ return result;
+
if (ourstatus->kind () == TARGET_WAITKIND_SPURIOUS)
{
continue_last_debug_event_main_thread
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)
+ {
+ gdb_assert (th->suspended == -1);
+ continue_last_debug_event_main_thread
+ (_("Init: Failed to DBG_CONTINUE after thread exit"),
+ DBG_CONTINUE);
+ }
+ else
+ {
+ th->suspend ();
+ th->reply_later = 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 : all_windows_threads ())
- thr->suspend ();
+ if (!target_is_non_stop_p ())
+ for (auto *thr : all_windows_threads ())
+ thr->suspend ();
+
+ th->stopping = false;
}
/* If something came out, assume there may be more. This is
ptid_t last_ptid;
+ /* Keep fetching events until we see the initial breakpoint (which
+ is planted by Windows itself) being reported. */
+
while (1)
{
struct target_waitstatus status;
last_ptid = this->wait (minus_one_ptid, &status, 0);
- /* Note windows_wait returns TARGET_WAITKIND_SPURIOUS for thread
- events. */
- if (status.kind () != TARGET_WAITKIND_LOADED
- && status.kind () != TARGET_WAITKIND_SPURIOUS)
+ /* These result in an error being thrown before we get here. */
+ gdb_assert (status.kind () != TARGET_WAITKIND_EXITED
+ && status.kind () != TARGET_WAITKIND_SIGNALLED);
+
+ /* We may also see TARGET_WAITKIND_THREAD_EXITED if
+ target_thread_events is active (because another thread was
+ stepping earlier, for example). Ignore such events until we
+ see the initial breakpoint. */
+
+ if (status.kind () == TARGET_WAITKIND_STOPPED)
break;
/* 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));
#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_state (this, minus_one_ptid, THREAD_STOPPED);
+ set_internal_state (this, minus_one_ptid, THREAD_INT_STOPPED);
+
+ target_terminal::ours ();
+ }
}
void
DEBUG_EVENTS ("got unrelated event, code %u",
current_event.dwDebugEventCode);
- windows_continue (DBG_CONTINUE, -1, 0);
+
+ DWORD continue_status
+ = continue_status_for_event_detaching (current_event);
+ windows_continue (continue_status, -1, WCONT_CONTINUE_DEBUG_EVENT);
}
if (injected_thread_handle != NULL)
CHECK (CloseHandle (injected_thread_handle));
}
+
+/* Used while detaching. Decide whether to pass the exception or not.
+ Returns the dwContinueStatus argument to pass to
+ ContinueDebugEvent. */
+
+DWORD
+windows_nat_target::continue_status_for_event_detaching
+ (const DEBUG_EVENT &event, size_t *reply_later_events_left)
+{
+ ptid_t ptid (event.dwProcessId, event.dwThreadId, 0);
+ windows_thread_info *th = windows_process.find_thread (ptid);
+
+ /* This can be a thread that we don't know about, as we're not
+ tracking thread creation events at this point. */
+ if (th != nullptr && th->reply_later != 0)
+ {
+ DWORD res = th->reply_later;
+ th->reply_later = 0;
+ if (reply_later_events_left != nullptr)
+ (*reply_later_events_left)--;
+ return res;
+ }
+ else if (event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
+ {
+ /* As the user asked to detach already, any new exception not
+ seen by infrun before, is passed down to the inferior without
+ considering "handle SIG pass/nopass". We can just pretend
+ the exception was raised after the inferior was detached. */
+ return DBG_EXCEPTION_NOT_HANDLED;
+ }
+ else
+ return DBG_CONTINUE;
+}
+
void
windows_nat_target::detach (inferior *inf, int from_tty)
{
+ DWORD continue_status = DBG_CONTINUE;
+
+ /* For any thread the core hasn't resumed, call prepare_resume with
+ the signal that the thread would be resumed with, so that we set
+ the right reply_later value, and also, so that we clear the trace
+ flag. */
+ for (thread_info *tp : inf->non_exited_threads ())
+ {
+ if (tp->internal_state () != THREAD_INT_RUNNING)
+ {
+ windows_thread_info *wth = windows_process.find_thread (tp->ptid);
+ gdb_signal signo = get_detach_signal (this, tp->ptid);
+
+ if (signo != wth->last_sig
+ || (signo != GDB_SIGNAL_0 && !signal_pass_state (signo)))
+ signo = GDB_SIGNAL_0;
+
+ DWORD cstatus = prepare_resume (wth, tp, 0, signo);
+
+ if (!m_continued && tp->ptid == get_last_debug_event_ptid ())
+ continue_status = cstatus;
+ }
+ }
+
/* If we see the process exit while unblocking the process_thread
helper thread, then we should skip the actual
DebugActiveProcessStop call. But don't report an error. Just
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);
+ {
+ break_out_process_thread (process_alive);
+
+ /* We're now either stopped at a thread exit event, or a process
+ exit event. */
+ continue_status = DBG_CONTINUE;
+ }
- windows_continue (DBG_CONTINUE, -1, WCONT_LAST_CALL);
+ windows_continue (continue_status, -1,
+ WCONT_LAST_CALL | WCONT_CONTINUE_DEBUG_EVENT);
std::optional<unsigned> err;
if (process_alive)
do_synchronously ([&] ()
{
- if (!DebugActiveProcessStop (windows_process.process_id))
+ /* The kernel re-raises any exception previously intercepted
+ and deferred with DBG_REPLY_LATER in the inferior after we
+ detach. We need to flush those, and suppress those which
+ aren't meant to be seen by the inferior (e.g., breakpoints,
+ single-steps, any with matching "handle SIG nopass", etc.),
+ otherwise the inferior dies immediately after the detach,
+ due to an unhandled exception. */
+ DEBUG_EVENT event;
+
+ /* Count how many threads have pending reply-later events. */
+ size_t reply_later_events_left = 0;
+ for (auto *th : all_windows_threads ())
+ if (th->reply_later != 0)
+ reply_later_events_left++;
+
+ DEBUG_EVENTS ("flushing %zu reply-later events",
+ reply_later_events_left);
+
+ /* Note we have to use a blocking wait (hence the need for the
+ counter). Just polling (timeout=0) until WaitForDebugEvent
+ returns false would be racy -- the kernel may take a little
+ bit to put the events in the pending queue. That has been
+ observed on Windows 11, where detaching would still very
+ occasionally result in the inferior dying after the detach
+ due to a reply-later event. */
+ while (reply_later_events_left > 0
+ && wait_for_debug_event (&event, INFINITE))
+ {
+ DEBUG_EVENTS ("flushed kernel event code %u",
+ event.dwDebugEventCode);
+
+ DWORD cstatus = (continue_status_for_event_detaching
+ (event, &reply_later_events_left));
+ if (!continue_last_debug_event (cstatus, debug_events))
+ {
+ err = (unsigned) GetLastError ();
+ return false;
+ }
+
+ if (event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
+ {
+ DEBUG_EVENTS ("got EXIT_PROCESS_DEBUG_EVENT, skipping detach");
+ process_alive = false;
+ break;
+ }
+ }
+
+ if (process_alive
+ && !DebugActiveProcessStop (windows_process.process_id))
err = (unsigned) GetLastError ();
else
DebugSetProcessKillOnExit (FALSE);
do_initial_windows_stuff (pi.dwProcessId, 0);
- /* windows_continue (DBG_CONTINUE, -1); */
+ /* Present the initial thread as stopped to the core. */
+ windows_thread_info *th = windows_process.find_thread (inferior_ptid);
+
+ th->suspend ();
+ set_state (this, inferior_ptid, THREAD_STOPPED);
+ set_internal_state (this, inferior_ptid, THREAD_INT_STOPPED);
+
+ 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->reply_later = 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)
{
return success ? TARGET_XFER_OK : TARGET_XFER_E_IO;
}
+/* Return true if all the threads of the process have already
+ exited. */
+
+static bool
+already_dead ()
+{
+ for (windows_thread_info *th : all_windows_threads ())
+ if (th->h != nullptr)
+ return false;
+ return true;
+}
+
void
windows_nat_target::kill ()
{
+ /* If all the threads of the process have already exited, there is
+ really nothing to kill. This can happen with e.g., scheduler
+ locking, where the thread exit events for all threads are still
+ pending to be processed by the core. */
+ if (already_dead ())
+ {
+ target_mourn_inferior (inferior_ptid);
+ return;
+ }
+
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? */
}
+/* 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 ();
+}
+
void _initialize_windows_nat ();
void
_initialize_windows_nat ()