From patchwork Tue Oct 29 17:57:54 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Simon Marchi (Code Review)" X-Patchwork-Id: 35434 Received: (qmail 116203 invoked by alias); 29 Oct 2019 18:05:56 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 115314 invoked by uid 89); 29 Oct 2019 18:05:55 -0000 Authentication-Results: sourceware.org; auth=none X-Spam-SWARE-Status: No, score=-26.9 required=5.0 tests=BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3 autolearn=ham version=3.3.1 spammy=sk:softwar X-HELO: mx1.osci.io Received: from polly.osci.io (HELO mx1.osci.io) (8.43.85.229) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Tue, 29 Oct 2019 18:05:52 +0000 Received: by mx1.osci.io (Postfix, from userid 994) id C4467215AE; Tue, 29 Oct 2019 13:58:02 -0400 (EDT) Received: from gnutoolchain-gerrit.osci.io (gnutoolchain-gerrit.osci.io [IPv6:2620:52:3:1:5054:ff:fe06:16ca]) by mx1.osci.io (Postfix) with ESMTP id 0539721322 for ; Tue, 29 Oct 2019 13:57:55 -0400 (EDT) Received: from localhost (localhost [127.0.0.1]) by gnutoolchain-gerrit.osci.io (Postfix) with ESMTP id D546220AF6 for ; Tue, 29 Oct 2019 13:57:54 -0400 (EDT) X-Gerrit-PatchSet: 1 Date: Tue, 29 Oct 2019 13:57:54 -0400 From: "Tom Tromey (Code Review)" To: gdb-patches@sourceware.org Message-ID: Auto-Submitted: auto-generated X-Gerrit-MessageType: newchange Subject: [review] Handle pending stops from the Windows kernel X-Gerrit-Change-Id: I5d0dd64c3d2d8220b534d3e02aeaa6f6815264ab X-Gerrit-Change-Number: 414 X-Gerrit-ChangeURL: X-Gerrit-Commit: 6eb8880a7923ba771c112459bd4330603e06d8d3 References: Reply-To: tromey@sourceware.org, gdb-patches@sourceware.org MIME-Version: 1.0 Content-Disposition: inline User-Agent: Gerrit/3.0.3-74-g460fb0f7e9 Change URL: https://gnutoolchain-gerrit.osci.io/r/c/binutils-gdb/+/414 ...................................................................... Handle pending stops from the Windows kernel PR gdb/22992 concerns an assertion failure in gdb when debugging a certain inferior: int finish_step_over(execution_control_state*): Assertion `ecs->event_thread->control.trap_expected' failed. Initially the investigation centered on the discovery that gdb was not suspending other threads when attempting to single-step. This oversight is corrected in this patch: now, when stepping a thread, gdb will call SuspendThread on all other threads. However, the bug persisted even after this change. In particular, WaitForDebugEvent could see a stop for a thread that was ostensibly suspended. Our theory of what is happening here is that there are actually simultaneous breakpoint hits, and the Windows kernel queues the events, causing the second stop to be reported on a suspended thread. In Windows 10 or later gdb could use the DBG_REPLY_LATER flag to ContinueDebugEvent to request that such events be re-reported later. However, relying on that did not seem advisable, so this patch instead arranges to queue such "pending" stops, and then to report them later, once the step has completed. In the PR, Pedro pointed out that it's best in this scenario to implement the stopped_by_sw_breakpoint method, so this patch does this as well. gdb/ChangeLog 2019-10-29 Tom Tromey PR gdb/22992 * windows-nat.c (current_event): Update comment. (last_wait_event, desired_stop_thread_id): New globals. (struct pending_stop): New. (pending_stops, last_stop_was_breakpoint): New globals. (windows_nat_target) : New methods. (suspend_thread): New function. (thread_rec): Use suspend_thread. (windows_continue): Handle pending stops. Suspend other threads when stepping. Use last_wait_event (wait_for_debug_event): New function. (get_windows_debug_event): Use wait_for_debug_event. Handle pending stops. Queue spurious stops. (windows_nat_target::wait): Un-bias PC when needed. Set last_stop_was_breakpoint. (windows_nat_target::kill): Use wait_for_debug_event. Change-Id: I5d0dd64c3d2d8220b534d3e02aeaa6f6815264ab --- M gdb/ChangeLog M gdb/windows-nat.c 2 files changed, 184 insertions(+), 12 deletions(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 9e7aa7b..ecabb25 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,5 +1,25 @@ 2019-10-29 Tom Tromey + PR gdb/22992 + * windows-nat.c (current_event): Update comment. + (last_wait_event, desired_stop_thread_id): New globals. + (struct pending_stop): New. + (pending_stops, last_stop_was_breakpoint): New globals. + (windows_nat_target) + : New methods. + (suspend_thread): New function. + (thread_rec): Use suspend_thread. + (windows_continue): Handle pending stops. Suspend other threads + when stepping. Use last_wait_event + (wait_for_debug_event): New function. + (get_windows_debug_event): Use wait_for_debug_event. Handle + pending stops. Queue spurious stops. + (windows_nat_target::wait): Un-bias PC when needed. Set + last_stop_was_breakpoint. + (windows_nat_target::kill): Use wait_for_debug_event. + +2019-10-29 Tom Tromey + * windows-nat.c (enum thread_disposition_type): New. (thread_rec): Replace "get_context" parameter with "disposition"; change type. diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c index b07da3b..1249778 100644 --- a/gdb/windows-nat.c +++ b/gdb/windows-nat.c @@ -218,8 +218,17 @@ /* The process and thread handles for the above context. */ -static DEBUG_EVENT current_event; /* The current debug event from - WaitForDebugEvent */ +/* The current debug event from WaitForDebugEvent or from a pending + stop. */ +static DEBUG_EVENT current_event; + +/* The most recent event from WaitForDebugEvent. Unlike + current_event, this is guaranteed never to come from a pending + stop. This is important because only data from the most recent + event from WaitForDebugEvent can be used when calling + ContinueDebugEvent. */ +static DEBUG_EVENT last_wait_event; + static HANDLE current_process_handle; /* Currently executing process */ static windows_thread_info *current_thread; /* Info on currently selected thread */ @@ -289,6 +298,41 @@ #endif /* 0 */ +/* The ID of the thread for which we anticipate a stop event. + Normally this is -1, meaning we'll accept an event in any + thread. */ +static DWORD desired_stop_thread_id = -1; + +/* A single pending stop. See "pending_stops" for more + information. */ +struct pending_stop +{ + /* The thread id. */ + DWORD thread_id; + + /* The target waitstatus we computed. */ + target_waitstatus status; + + /* The event. A few fields of this can be referenced after a stop, + and it seemed simplest to store the entire event. */ + DEBUG_EVENT event; +}; + +/* A vector of pending stops. Sometimes, Windows will report a stop + on a thread that has been ostensibly suspended. We believe what + happens here is that two threads hit a breakpoint simultaneously, + and the Windows kernel queues the stop events. However, this can + result in the strange effect of trying to single step thread A -- + leaving all other threads suspended -- and then seeing a stop in + thread B. To handle this scenario, we queue all such "pending" + stops here, and then process them once the step has completed. See + PR gdb/22992. */ +static std::vector pending_stops; + +/* This is true if the most recently reported stop was due to a + breakpoint. */ +static bool last_stop_was_breakpoint = false; + struct windows_nat_target final : public x86_nat_target { void close () override; @@ -307,6 +351,16 @@ void fetch_registers (struct regcache *, int) override; void store_registers (struct regcache *, int) override; + bool stopped_by_sw_breakpoint () override + { + return last_stop_was_breakpoint; + } + + bool supports_stopped_by_sw_breakpoint () override + { + return true; + } + enum target_xfer_status xfer_partial (enum target_object object, const char *annex, gdb_byte *readbuf, @@ -1297,16 +1351,37 @@ { BOOL res; + desired_stop_thread_id = id; + + /* If there are pending stops, and we might plausibly hit one of + them, we don't want to actually continue the inferior -- we just + want to report the stop. In this case, we just pretend to + continue. See the comment by the definition of "pending_stops" + for details on why this is needed. */ + for (auto &item : pending_stops) + { + if (desired_stop_thread_id == -1 + || desired_stop_thread_id == item.thread_id) + { + DEBUG_EVENTS (("windows_continue - pending stop anticipated, " + "desired=0x%x, item=0x%x\n", + desired_stop_thread_id, item.thread_id)); + debug_registers_changed = 0; + return TRUE; + } + } + DEBUG_EVENTS (("ContinueDebugEvent (cpid=%d, ctid=0x%x, %s);\n", - (unsigned) current_event.dwProcessId, - (unsigned) current_event.dwThreadId, + (unsigned) last_wait_event.dwProcessId, + (unsigned) last_wait_event.dwThreadId, continue_status == DBG_CONTINUE ? "DBG_CONTINUE" : "DBG_EXCEPTION_NOT_HANDLED")); for (windows_thread_info *th : thread_list) - if ((id == -1 || id == (int) th->tid) - && th->suspended) + if (id == -1 || id == (int) th->tid) { + if (!th->suspended) + continue; if (debug_registers_changed) { th->context.ContextFlags |= CONTEXT_DEBUG_REGISTERS; @@ -1333,9 +1408,15 @@ } th->resume (); } + else + { + /* When single-stepping a specific thread, other threads must + be suspended. */ + th->suspend (); + } - res = ContinueDebugEvent (current_event.dwProcessId, - current_event.dwThreadId, + res = ContinueDebugEvent (last_wait_event.dwProcessId, + last_wait_event.dwThreadId, continue_status); if (!res) @@ -1487,6 +1568,17 @@ return TRUE; } +/* A wrapper for WaitForDebugEvent that sets "last_wait_event" + appropriately. */ +static BOOL +wait_for_debug_event (DEBUG_EVENT *event, DWORD timeout) +{ + BOOL result = WaitForDebugEvent (event, timeout); + if (result) + last_wait_event = *event; + return result; +} + /* Get the next event from the child. Returns a non-zero thread id if the event requires handling by WFI (or whatever). */ static int @@ -1499,9 +1591,36 @@ static windows_thread_info dummy_thread_info (0, 0, 0); DWORD thread_id = 0; + /* If there is a relevant pending stop, report it now. See the + comment by the definition of "pending_stops" for details on why + this is needed. */ + for (auto iter = pending_stops.begin (); + iter != pending_stops.end (); + ++iter) + { + if (desired_stop_thread_id == -1 + || desired_stop_thread_id == iter->thread_id) + { + thread_id = iter->thread_id; + *ourstatus = iter->status; + current_event = iter->event; + + inferior_ptid = ptid_t (current_event.dwProcessId, thread_id, 0); + current_thread = thread_rec (thread_id, INVALIDATE_CONTEXT); + current_thread->reload_context = 1; + + DEBUG_EVENTS (("get_windows_debug_event - " + "pending stop found in 0x%x (desired=0x%x)\n", + thread_id, desired_stop_thread_id)); + + pending_stops.erase (iter); + return thread_id; + } + } + last_sig = GDB_SIGNAL_0; - if (!(debug_event = WaitForDebugEvent (¤t_event, 1000))) + if (!(debug_event = wait_for_debug_event (¤t_event, 1000))) goto out; event_count++; @@ -1671,7 +1790,19 @@ if (!thread_id || saw_create != 1) { - CHECK (windows_continue (continue_status, -1, 0)); + CHECK (windows_continue (continue_status, desired_stop_thread_id, 0)); + } + else if (desired_stop_thread_id != -1 && desired_stop_thread_id != thread_id) + { + /* Pending stop. See the comment by the definition of + "pending_stops" for details on why this is needed. */ + DEBUG_EVENTS (("get_windows_debug_event - " + "unexpected stop in 0x%x (expecting 0x%x)\n", + thread_id, desired_stop_thread_id)); + + pending_stops.push_back ({thread_id, *ourstatus, current_event}); + thread_id = 0; + CHECK (windows_continue (continue_status, desired_stop_thread_id, 0)); } else { @@ -1733,7 +1864,28 @@ SetConsoleCtrlHandler (&ctrl_c_handler, FALSE); if (retval) - return ptid_t (current_event.dwProcessId, retval, 0); + { + ptid_t result = ptid_t (current_event.dwProcessId, retval, 0); + + last_stop_was_breakpoint = false; + if (current_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT + && (current_event.u.Exception.ExceptionRecord.ExceptionCode + == EXCEPTION_BREAKPOINT)) + { + struct regcache *regcache = get_thread_regcache (result); + gdbarch *gdbarch = regcache->arch (); + + CORE_ADDR pc = (regcache_read_pc (regcache) + - gdbarch_decr_pc_after_break (gdbarch)); + if (software_breakpoint_inserted_here_p (regcache->aspace (), pc)) + { + last_stop_was_breakpoint = true; + regcache_write_pc (regcache, pc); + } + } + + return result; + } else { int detach = 0; @@ -2872,7 +3024,7 @@ { if (!windows_continue (DBG_CONTINUE, -1, 1)) break; - if (!WaitForDebugEvent (¤t_event, INFINITE)) + if (!wait_for_debug_event (¤t_event, INFINITE)) break; if (current_event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) break;