diff options
-rw-r--r-- | gdb/nat/windows-nat.c | 17 | ||||
-rw-r--r-- | gdb/nat/windows-nat.h | 51 | ||||
-rw-r--r-- | gdb/windows-nat.c | 753 | ||||
-rw-r--r-- | gdbserver/win32-low.cc | 10 | ||||
-rw-r--r-- | 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<inf_child_target> void attach (const char *, int) override; bool attach_no_wait () override - { return true; } + { + /* In non-stop, after attach, we leave all threads running, like + other targets. */ + return !target_is_non_stop_p (); + } void detach (inferior *, int) override; @@ -312,8 +477,11 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target> std::string pid_to_str (ptid_t) override; void interrupt () override; + void stop (ptid_t) override; void pass_ctrlc () override; + void thread_events (int enable) override; + const char *pid_to_exec_file (int pid) override; ptid_t get_ada_task_ptid (long lwp, ULONGEST thread) override; @@ -343,6 +511,9 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target> return m_is_async; } + bool supports_non_stop () override; + bool always_non_stop_p () override; + void async (bool enable) override; int async_wait_fd () override @@ -357,6 +528,8 @@ 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<char> 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; |