public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
From: Hannes Domani <ssbssa@yahoo.de>
To: "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>,
	 Pedro Alves <pedro@palves.net>
Subject: Re: [PATCH] gdb/Windows: Fix detach while running
Date: Fri, 12 Apr 2024 10:56:45 +0000 (UTC)	[thread overview]
Message-ID: <178084166.332205.1712919405648@mail.yahoo.com> (raw)
In-Reply-To: <20240411200356.270360-1-pedro@palves.net>

 Am Donnerstag, 11. April 2024 um 22:04:34 MESZ hat Pedro Alves <pedro@palves.net> Folgendes geschrieben:

> While testing a WIP Cygwin GDB that supports non-stop, I noticed that
> gdb.threads/attach-non-stop.exp exposes that this:
>
> (gdb) attach PID&
> ...
> (gdb) detach
>
> ... hangs.
>
> And it turns out that it hangs in all-stop as well.  This commits
> fixes that.
>
> After "attach &", the target is set running, we've called
> ContinueDebugEvent and the process_thread thread is waiting for
> WaitForDebugEvent events.  It is the equivalent of "attach; c&".
>
> In windows_nat_target::detach, the first thing we do is
> unconditionally call windows_continue (for ContinueDebugEvent), which
> blocks in do_synchronously, until the process_thread sees an event out
> of WaitForDebugEvent.  Unless the inferior happens to run into a
> breakpoint, etc., then this hangs indefinitely.
>
> If we've already called ContinueDebugEvent earlier, then we shouldn't
> be calling it again in ::detach.
>
> Still in windows_nat_target::detach, we have an interesting issue that
> ends up being the bulk of the patch -- only the process_thread thread
> can call DebugActiveProcessStop, but if it is blocked in
> WaitForDebugEvent, we need to somehow force it to break out of it.
> The only way to do that, is to force the inferior to do something that
> causes WaitForDebugEvent to return some event.
>
> This patch uses CreateRemoteThread to do it, which results in
> WaitForDebugEvent reporting CREATE_THREAD_DEBUG_EVENT.  We then
> terminate the injected thread before it has a chance to run any
> userspace code.
>
> Note that Win32 functions like DebugBreakProcess and
> GenerateConsoleCtrlEvent would also inject a new thread in the
> inferior.  I first used DebugBreakProcess, but that is actually more
> complicated to use, because we'd have to be sure to consume the
> breakpoint event before detaching, otherwise the inferior would likely
> die due a breakpoint exception being raised with no debugger around to
> intercept it.
>
> See the new break_out_process_thread method.
>
> So the fix has two parts:
>
> - Keep track of whether we've called ContinueDebugEvent and the
>   process_thread thread is waiting for events, or whether
>   WaitForDebugEvent already returned an event.
>
> - In windows_nat_target::detach, if the process_thread thread is
>   waiting for events, unblock out of its WaitForDebugEvent, before
>   proceeding with the actual detach.
>
> New test included.  Passes cleanly on GNU/Linux native and gdbserver,
> and also passes cleanly on Cygwin, with the fix.  Before the fix, it
> would hang and fail with a timeout.

I tried this, and it works for me too for Windows native.

Tested-By: Hannes Domani <ssbssa@yahoo.de>

I have 2 comments below.


> Change-Id: Ifb91c58c08af1a9bcbafecedc93dfce001040905
> ---
> .../gdb.threads/detach-while-running.c        |  30 ++++
> .../gdb.threads/detach-while-running.exp      |  77 ++++++++++
> gdb/windows-nat.c                            | 131 ++++++++++++++++--
> 3 files changed, 230 insertions(+), 8 deletions(-)
> create mode 100644 gdb/testsuite/gdb.threads/detach-while-running.c
> create mode 100644 gdb/testsuite/gdb.threads/detach-while-running.exp
>
> diff --git a/gdb/testsuite/gdb.threads/detach-while-running.c b/gdb/testsuite/gdb.threads/detach-while-running.c
> new file mode 100644
> index 00000000000..19cc3b5761c
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/detach-while-running.c
> @@ -0,0 +1,30 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +  Copyright 2024 Free Software Foundation, Inc.
> +
> +  This program is free software; you can redistribute it and/or modify
> +  it under the terms of the GNU General Public License as published by
> +  the Free Software Foundation; either version 3 of the License, or
> +  (at your option) any later version.
> +
> +  This program is distributed in the hope that it will be useful,
> +  but WITHOUT ANY WARRANTY; without even the implied warranty of
> +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +  GNU General Public License for more details.
> +
> +  You should have received a copy of the GNU General Public License
> +  along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +#include <assert.h>
> +#include <unistd.h>
> +
> +int
> +main (int argc, char **argv)
> +{
> +  alarm (30);
> +
> +  while (1)
> +    sleep (1);
> +
> +  return 0;
> +}

The test passes for me, but I had to #ifdef the alarm() function out,
since it doesn't exist on Windows.


> diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
> index b123a66ef0f..7e571f281cb 100644
> --- a/gdb/windows-nat.c
> +++ b/gdb/windows-nat.c
> @@ -2072,20 +2089,118 @@ windows_nat_target::attach (const char *args, int from_tty)
>   target_terminal::ours ();
> }
>
> +void
> +windows_nat_target::break_out_process_thread (bool &process_alive)
> +{
> +  /* This is called when the process_thread thread is blocked in
> +    WaitForDebugEvent (unless it already returned some event we
> +    haven't consumed yet), and we need to unblock it so that we can
> +    have it call DebugActiveProcessStop.
> +
> +    To make WaitForDebugEvent return, we need to force some event in
> +    the inferior.  Any method that lets us do that (without
> +    disturbing the other threads), injects a new thread in the
> +    inferior.
> +
> +    We don't use DebugBreakProcess for this, because that injects a
> +    thread that ends up executing a breakpoint instruction.  We can't
> +    let the injected thread hit that breakpoint _after_ we've
> +    detached.  Consuming events until we see a breakpoint trap isn't
> +    100% reliable, because we can't distinguish it from some other
> +    thread itself deciding to call int3 while we're detaching, unless
> +    we temporarily suspend all threads.  It's just a lot of
> +    complication, and there's an easier way.
> +
> +    Important observation: the thread creation event for the newly
> +    injected thread is sufficient to unblock WaitForDebugEvent.
> +
> +    Instead of DebugBreakProcess, we can instead use
> +    CreateRemoteThread to control the code that the injected thread
> +    runs ourselves.  We could consider pointing the injected thread
> +    at some side-effect-free Win32 function as entry point.  However,
> +    finding the address of such a function requires having at least
> +    minimal symbols loaded for ntdll.dll.  Having a way that avoids
> +    that is better, so that detach always works correctly even when
> +    we don't have any symbols loaded.
> +
> +    So what we do is inject a thread that doesn't actually run ANY
> +    userspace code, because we force-terminate it as soon as we see
> +    its corresponding thread creation event.  CreateRemoteThread
> +    gives us the new thread's ID, which we can match with the thread
> +    associated with the CREATE_THREAD_DEBUG_EVENT event.  */
> +
> +  DWORD injected_thread_id = 0;
> +  HANDLE injected_thread_handle
> +    = CreateRemoteThread (windows_process.handle, NULL,
> +              0, (LPTHREAD_START_ROUTINE) 0,
> +              NULL, 0, &injected_thread_id);
> +
> +  process_alive = true;
> +
> +  /* At this point, the user has declared that they want to detach, so
> +    any event that happens from this point on should be forwarded to
> +    the inferior.  */
> +
> +  for (;;)
> +    {
> +      DEBUG_EVENT current_event;
> +      wait_for_debug_event_main_thread (&current_event);
> +
> +      if (current_event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
> +    {
> +      DEBUG_EVENTS ("got EXIT_PROCESS_DEBUG_EVENT");
> +      process_alive = false;
> +      break;

Not sure how likely it is to get here, but injected_thread_handle
leaks in this case.


> +    }
> +
> +      if (current_event.dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT
> +      && current_event.dwThreadId == injected_thread_id)
> +    {
> +      DEBUG_EVENTS ("got CREATE_THREAD_DEBUG_EVENT for injected thread");
> +
> +      /* Terminate the injected thread, so it doesn't run any code
> +        at all.  All we wanted was some event, and
> +        CREATE_THREAD_DEBUG_EVENT is sufficient.  */
> +      CHECK (TerminateThread (injected_thread_handle, 0));
> +      CHECK (CloseHandle (injected_thread_handle));
> +      break;
> +    }
> +
> +      DEBUG_EVENTS ("got unrelated event, code %u",
> +            current_event.dwDebugEventCode);
> +      windows_continue (DBG_CONTINUE, -1, 0);
> +    }
> +}


Hannes

  reply	other threads:[~2024-04-12 10:56 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-04-11 20:03 Pedro Alves
2024-04-12 10:56 ` Hannes Domani [this message]
2024-04-12 13:41   ` [PATCH v2] " Pedro Alves
2024-04-15 17:02     ` Tom Tromey
2024-04-17 17:57       ` Pedro Alves
2024-04-17 18:18       ` Pedro Alves

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178084166.332205.1712919405648@mail.yahoo.com \
    --to=ssbssa@yahoo.de \
    --cc=gdb-patches@sourceware.org \
    --cc=pedro@palves.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).