On Thu, 2024-02-01 at 12:11 +0000, Alex Bennée wrote:
> Ilya Leoshkevich <[email protected]> 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 <[email protected]>
> > ---
> >  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/[email protected]/

Reply via email to