qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Ilya Leoshkevich <iii@linux.ibm.com>
To: "Alex Bennée" <alex.bennee@linaro.org>
Cc: "Laurent Vivier" <laurent@vivier.eu>,
	"Philippe Mathieu-Daudé" <philmd@linaro.org>,
	"Richard Henderson" <richard.henderson@linaro.org>,
	qemu-devel@nongnu.org
Subject: Re: [PATCH 2/3] gdbstub: Implement follow-fork-mode child
Date: Thu, 01 Feb 2024 17:22:07 +0100	[thread overview]
Message-ID: <bd14786900838155c9f1d2e82adc5e4d68207ee9.camel@linux.ibm.com> (raw)
In-Reply-To: <877cjomlk9.fsf@draig.linaro.org>

On Thu, 2024-02-01 at 12:11 +0000, Alex Bennée wrote:
> Ilya Leoshkevich <iii@linux.ibm.com> writes:
> 
> > Currently it's not possible to use gdbstub for debugging linux-user
> > code that runs in a forked child, which is normally done using the
> > `set
> > follow-fork-mode child` GDB command. Purely on the protocol level,
> > the
> > missing piece is the fork-events feature.
> > 
> > However, a deeper problem is supporting $Hg switching between
> > different
> > processes - right now it can do only threads. Implementing this for
> > the
> > general case would be quite complicated, but, fortunately, for the
> > follow-fork-mode case there are a few factors that greatly simplify
> > things: fork() happens in the exclusive section, there are only two
> > processes involved, and before one of them is resumed, the second
> > one
> > is detached.
> > 
> > This makes it possible to implement a simplified scheme: the parent
> > and
> > the child share the gdbserver socket, it's used only by one of them
> > at
> > any given time, which is coordinated through a separate socketpair.
> > The
> > processes can read from the gdbserver socket only one byte at a
> > time,
> > which is not great for performance, but, fortunately, the
> > follow-fork-mode involves only a few messages.
> > 
> > Add the hooks for the user-specific handling of $qSupported, $Hg,
> > and
> > $D. Advertise the fork-events support, and remember whether GDB has
> > it
> > as well. Implement the state machine that is initialized on fork(),
> > decides the current owner of the gdbserver socket, and is
> > terminated
> > when one of the two processes is detached. The logic for the parent
> > and
> > the child is the same, only the initial state is different.
> > 
> > Handle the `stepi` of a syscall corner case by disabling the
> > single-stepping in detached processes.
> > 
> > Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
> > ---
> >  gdbstub/gdbstub.c   |  29 ++++--
> >  gdbstub/internals.h |   3 +
> >  gdbstub/user.c      | 210
> > +++++++++++++++++++++++++++++++++++++++++++-
> >  3 files changed, 234 insertions(+), 8 deletions(-)

[...]
 
> > diff --git a/gdbstub/user.c b/gdbstub/user.c
> > index 120eb7fc117..962f4cb74e7 100644
> > --- a/gdbstub/user.c
> > +++ b/gdbstub/user.c
> > @@ -10,6 +10,7 @@
> >   */
> >  
> >  #include "qemu/osdep.h"
> > +#include <sys/syscall.h>
> >  #include "qemu/bitops.h"
> >  #include "qemu/cutils.h"
> >  #include "qemu/sockets.h"
> > @@ -25,6 +26,41 @@
> >  #define GDB_NR_SYSCALLS 1024
> >  typedef unsigned long
> > GDBSyscallsMask[BITS_TO_LONGS(GDB_NR_SYSCALLS)];
> >  
> > +/*
> > + * Forked child talks to its parent in order to let GDB enforce
> > the
> > + * follow-fork-mode. This happens inside a start_exclusive()
> > section, so that
> > + * the other threads, which may be forking too, do not interfere.
> > The
> > + * implementation relies on GDB not sending $vCont until it has
> > detached
> > + * either from the parent (follow-fork-mode child) or from the
> > child
> > + * (follow-fork-mode parent).
> > + *
> > + * The parent and the child share the GDB socket; at any given
> > time only one
> > + * of them is allowed to use it, as is reflected in the respective
> > fork_state.
> > + * This is negotiated via the fork_sockets pair as a reaction to
> > $Hg.
> > + */
> > +enum GDBForkState {
> > +    /* Fully owning the GDB socket. */
> > +    GDB_FORK_ENABLED,
> > +    /* Working with the GDB socket; the peer is inactive. */
> > +    GDB_FORK_ACTIVE,
> > +    /* Handing off the GDB socket to the peer. */
> > +    GDB_FORK_DEACTIVATING,
> > +    /* The peer is working with the GDB socket. */
> > +    GDB_FORK_INACTIVE,
> > +    /* Asking the peer to close its GDB socket fd. */
> > +    GDB_FORK_ENABLING,
> > +    /* Asking the peer to take over, closing our GDB socket fd. */
> > +    GDB_FORK_DISABLING,
> > +    /* The peer has taken over, our GDB socket fd is closed. */
> > +    GDB_FORK_DISABLED,
> > +};
> 
> gulp - thats a potentially fairly complex state diagram. Do we just
> work
> through the states sequentially?

Unfortunately no. I had less states at some point, but then realized
it was better to have these things laid out explicitly. Let me try to
summarize the possible transitions:

GDB_FORK_ENABLED: Terminal state; GDB follows the current process.
GDB_FORK_DISABLED: Terminal state; GDB follows the other process.
GDB_FORK_ACTIVE -> GDB_FORK_DEACTIVATING: On $Hg.
GDB_FORK_ACTIVE -> GDB_FORK_ENABLING: On $D.
GDB_FORK_ACTIVE -> GDB_FORK_DISABLING: On $D.
GDB_FORK_ACTIVE -> GDB_FORK_DISABLED: On communication error.
GDB_FORK_DEACTIVATING -> GDB_FORK_INACTIVE: On gdb_read_byte() return.
GDB_FORK_DEACTIVATING -> GDB_FORK_DISABLED: On communication error.
GDB_FORK_INACTIVE -> GDB_FORK_ACTIVE: On $Hg in peer.
GDB_FORK_INACTIVE -> GDB_FORK_ENABLE: On $D in peer.
GDB_FORK_INACTIVE -> GDB_FORK_DISABLE: On $D in peer.
GDB_FORK_INACTIVE -> GDB_FORK_DISABLED: On communication error.
GDB_FORK_ENABLING -> GDB_FORK_ENABLED: On gdb_read_byte() return.
GDB_FORK_ENABLING -> GDB_FORK_DISABLED: On communication error.
GDB_FORK_DISABLING -> GDB_FORK_DISABLED: On gdb_read_byte() return.

Some states have only one meaningful transition:

GDB_FORK_DEACTIVATING -> GDB_FORK_INACTIVE
GDB_FORK_ENABLING -> GDB_FORK_ENABLED

and can in theory be squashed, but then the socketpair communication
would have to be moved to the respective user-hook, which would
complicate the error handling.

[...]

> > @@ -369,16 +421,168 @@ static void disable_gdbstub(void)
> >      CPU_FOREACH(cpu) {
> >          cpu_breakpoint_remove_all(cpu, BP_GDB);
> >          /* no cpu_watchpoint_remove_all for user-mode */
> > +        cpu_single_step(cpu, 0);
> > +        tb_flush(cpu);
> >      }
> >  }
> >  
> > -/* Disable gdb stub for child processes.  */
> >  void gdbserver_fork_end(pid_t pid)
> >  {
> > -    if (pid != 0 || !gdbserver_state.init ||
> > gdbserver_user_state.fd < 0) {
> > +    char b;
> > +    int fd;
> > +
> > +    if (!gdbserver_state.init || gdbserver_user_state.fd < 0) {
> > +        return;
> > +    }
> > +
> > +    if (pid == -1) {
> > +        if (gdbserver_user_state.fork_state != GDB_FORK_DISABLED)
> > {
> > +            g_assert(gdbserver_user_state.fork_state ==
> > GDB_FORK_INACTIVE);
> > +            close(gdbserver_user_state.fork_sockets[0]);
> > +            close(gdbserver_user_state.fork_sockets[1]);
> > +        }
> >          return;
> >      }
> > -    disable_gdbstub();
> > +
> > +    if (gdbserver_user_state.fork_state == GDB_FORK_DISABLED) {
> > +        if (pid == 0) {
> > +            disable_gdbstub();
> > +        }
> > +        return;
> > +    }
> > +
> > +    if (pid == 0) {
> > +        close(gdbserver_user_state.fork_sockets[0]);
> > +        fd = gdbserver_user_state.fork_sockets[1];
> > +        g_assert(gdbserver_state.process_num == 1);
> > +        g_assert(gdbserver_state.processes[0].pid ==
> > +                     gdbserver_user_state.fork_peer_pid);
> > +        g_assert(gdbserver_state.processes[0].attached);
> > +        gdbserver_state.processes[0].pid = getpid();
> > +    } else {
> > +        close(gdbserver_user_state.fork_sockets[1]);
> > +        fd = gdbserver_user_state.fork_sockets[0];
> > +        gdbserver_user_state.fork_state = GDB_FORK_ACTIVE;
> > +        gdbserver_user_state.fork_peer_pid = pid;
> > +        gdbserver_user_state.fork_peer_tid = pid;
> > +
> > +        if (!gdbserver_state.allow_stop_reply) {
> > +            goto fail;
> > +        }
> > +        g_string_printf(gdbserver_state.str_buf,
> > +                        "T%02xfork:p%02x.%02x;thread:p%02x.%02x;",
> > +                       
> > gdb_target_signal_to_gdb(gdb_target_sigtrap()),
> > +                        pid, pid, (int)getpid(),
> > qemu_get_thread_id());
> 
> I don't think I messed up the merge but:
> 
> ../../gdbstub/user.c: In function ‘gdbserver_fork_end’:
> ../../gdbstub/user.c:461:50: error: implicit declaration of function
> ‘gdb_target_sigtrap’ [-Werror=implicit-function-declaration]
>   461 |                        
> gdb_target_signal_to_gdb(gdb_target_sigtrap()),
>       |                                                 
> ^~~~~~~~~~~~~~~~~~
> ../../gdbstub/user.c:461:50: error: nested extern declaration of
> ‘gdb_target_sigtrap’ [-Werror=nested-externs]
> cc1: all warnings being treated as errors
> 
> I cant see where gdb_target_sigtrap is from?

This is from [1], which this series is Based-on. I can make one series
with both features if it's more convenient to review.

[1] https://patchew.org/QEMU/20240116094411.216665-1-iii@linux.ibm.com/


  reply	other threads:[~2024-02-01 16:26 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-01-31 20:43 [PATCH 0/3] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
2024-01-31 20:43 ` [PATCH 1/3] gdbstub: Refactor fork() handling Ilya Leoshkevich
2024-02-01 12:05   ` Alex Bennée
2024-01-31 20:43 ` [PATCH 2/3] gdbstub: Implement follow-fork-mode child Ilya Leoshkevich
2024-02-01 12:11   ` Alex Bennée
2024-02-01 16:22     ` Ilya Leoshkevich [this message]
2024-02-01 16:48       ` Alex Bennée
2024-01-31 20:43 ` [PATCH 3/3] tests/tcg: Add two follow-fork-mode tests Ilya Leoshkevich
2024-02-01 11:59 ` [PATCH 0/3] gdbstub: Implement follow-fork-mode child Alex Bennée

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=bd14786900838155c9f1d2e82adc5e4d68207ee9.camel@linux.ibm.com \
    --to=iii@linux.ibm.com \
    --cc=alex.bennee@linaro.org \
    --cc=laurent@vivier.eu \
    --cc=philmd@linaro.org \
    --cc=qemu-devel@nongnu.org \
    --cc=richard.henderson@linaro.org \
    /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 NNTP newsgroup(s).