From 650daf18185de6d2e51da2fe5cd0b94cc7fb9174 Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Wed, 8 May 2024 00:13:08 +0100 Subject: Windows gdb: Add non-stop support This patch adds non-stop support to the native Windows target. This is made possible by the ContinueDebugEvent DBG_REPLY_LATER flag: https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-continuedebugevent Supported in Windows 10, version 1507 or above, 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. ^^^^^^^^^^^^ The patch adds a new comment section in gdb/windows-nat.c providing an overall picture of how all-stop / non-stop work. Without DBG_REPLY_LATER, if we SuspendThread the thread, and then immediately ContinueDebugThread(DBG_CONTINUE) before getting back to the prompt, we could still have non-stop mode working, however, then users wouldn't have a chance to decide whether to pass the signal to the inferior the next time they resume the program, as that is done by passing DBG_EXCEPTION_NOT_HANDLED to ContinueDebugEvent, and that has already been called. The patch adds that DBG_REPLY_LATER handling, and also adds support for target_stop, so the core can pause threads at its discretion. This pausing does not use the same mechanisms used in windows_nat_target::interrupt, as those inject a new thread in the inferior. Instead, for each thread the core wants paused, it uses SuspendThread, and enqueues a pending GDB_SIGNAL_0 stop on the thread. Since DBG_REPLY_LATER only exists on Windows 10 and later, we only enable non-stop mode on Windows 10 and later. Since having the target backend work in non-stop mode adds features compared to old all-stop mode (signal/exception passing/suppression is truly per-thread), this patch also switches the backend to do all-stop-on-top-of-non-stop, by having windows_nat_target::always_non_stop_p return true if non-stop mode is possible. To be clear, this just changes how the backend works in coordination with infrun. The user-visible mode default mode is still all-stop. The difference is that infrun is responsible for stopping all threads when needed, instead of the backend (actually the kernel) always doing that before reporting an event to infrun. There is no displaced stepping support, but that's "just" a missed optimization to be done later. Cygwin signals handling was a major headache, but I managed to get it working. See the "Cygwin signals" description section I added at the top of windows-nat.c. Change-Id: Id71aef461c43c244120635b5bedc638fe77c31fb --- gdb/nat/windows-nat.c | 17 +- gdb/nat/windows-nat.h | 51 +++- gdb/windows-nat.c | 753 ++++++++++++++++++++++++++++++++++++++++++------- gdbserver/win32-low.cc | 10 +- gdbserver/win32-low.h | 4 +- 5 files changed, 720 insertions(+), 115 deletions(-) diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c index 0fdfcba..33f6439 100644 --- a/gdb/nat/windows-nat.c +++ b/gdb/nat/windows-nat.c @@ -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. */ diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h index 96835b3..cf6889b 100644 --- a/gdb/nat/windows-nat.h +++ b/gdb/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 reply_later = 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. diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c index e11cc95..dfb5601 100644 --- a/gdb/windows-nat.c +++ b/gdb/windows-nat.c @@ -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::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. + +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 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 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 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 @@ private: 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 @@ private: 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 @@ -630,6 +806,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; } @@ -1049,12 +1232,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->reply_later != 0) + internal_error ("OutputDebugString thread 0x%x has reply-later set", + event_thr->tid); gdb::unique_xmalloc_ptr s = (target_read_string @@ -1091,15 +1279,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", @@ -1107,7 +1317,7 @@ windows_per_inferior::handle_output_debug_string } #endif - return thread_id; + return false; } static int @@ -1398,6 +1608,7 @@ windows_per_inferior::continue_one_thread (windows_thread_info *th, } th->resume (); + th->stopping = false; th->last_sig = GDB_SIGNAL_0; } @@ -1409,31 +1620,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; } @@ -1484,13 +1741,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 '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); } @@ -1526,33 +1797,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->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 (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); @@ -1601,12 +1879,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 @@ -1622,7 +1976,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); @@ -1633,7 +1987,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; } } @@ -1651,6 +2004,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 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", 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 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 + { + 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; + } + switch (event_code) { case CREATE_THREAD_DEBUG_EVENT: @@ -1683,21 +2080,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: @@ -1823,8 +2234,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: @@ -1859,22 +2286,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; } @@ -1905,6 +2345,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) @@ -1935,10 +2383,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->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 : 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 @@ -2020,7 +2504,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)); @@ -2165,7 +2649,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 @@ -2283,10 +2789,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); @@ -3038,13 +3545,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->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) { @@ -3099,14 +3626,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? */ @@ -3240,6 +3781,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 diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc index 65b01dc..b15d8d0 100644 --- a/gdbserver/win32-low.cc +++ b/gdbserver/win32-low.cc @@ -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 diff --git a/gdbserver/win32-low.h b/gdbserver/win32-low.h index ea2a9b4..2c1b3b9 100644 --- a/gdbserver/win32-low.h +++ b/gdbserver/win32-low.h @@ -175,8 +175,8 @@ public: 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; -- cgit v1.1