Ilya Leoshkevich <i...@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 <i...@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/gdbstub.c b/gdbstub/gdbstub.c
> index 7e73e916bdc..46f5dd47e9e 100644
> --- a/gdbstub/gdbstub.c
> +++ b/gdbstub/gdbstub.c
> @@ -991,6 +991,12 @@ static void handle_detach(GArray *params, void *user_ctx)
>          pid = get_param(params, 0)->val_ul;
>      }
>  
> +#ifdef CONFIG_USER_ONLY
> +    if (gdb_handle_detach_user(pid)) {
> +        return;
> +    }
> +#endif
> +
>      process = gdb_get_process(pid);
>      gdb_process_breakpoint_remove_all(process);
>      process->attached = false;
> @@ -1066,6 +1072,7 @@ static void handle_cont_with_sig(GArray *params, void 
> *user_ctx)
>  
>  static void handle_set_thread(GArray *params, void *user_ctx)
>  {
> +    uint32_t pid, tid;
>      CPUState *cpu;
>  
>      if (params->len != 2) {
> @@ -1083,8 +1090,14 @@ static void handle_set_thread(GArray *params, void 
> *user_ctx)
>          return;
>      }
>  
> -    cpu = gdb_get_cpu(get_param(params, 1)->thread_id.pid,
> -                      get_param(params, 1)->thread_id.tid);
> +    pid = get_param(params, 1)->thread_id.pid;
> +    tid = get_param(params, 1)->thread_id.tid;
> +#ifdef CONFIG_USER_ONLY
> +    if (gdb_handle_set_thread_user(pid, tid)) {
> +        return;
> +    }
> +#endif
> +    cpu = gdb_get_cpu(pid, tid);
>      if (!cpu) {
>          gdb_put_packet("E22");
>          return;
> @@ -1599,6 +1612,7 @@ static void handle_query_thread_extra(GArray *params, 
> void *user_ctx)
>  
>  static void handle_query_supported(GArray *params, void *user_ctx)
>  {
> +    const char *gdb_supported;
>      CPUClass *cc;
>  
>      g_string_printf(gdbserver_state.str_buf, "PacketSize=%x", 
> MAX_PACKET_LENGTH);
> @@ -1622,9 +1636,14 @@ static void handle_query_supported(GArray *params, 
> void *user_ctx)
>      g_string_append(gdbserver_state.str_buf, ";qXfer:exec-file:read+");
>  #endif
>  
> -    if (params->len &&
> -        strstr(get_param(params, 0)->data, "multiprocess+")) {
> -        gdbserver_state.multiprocess = true;
> +    if (params->len) {
> +        gdb_supported = get_param(params, 0)->data;
> +        if (strstr(gdb_supported, "multiprocess+")) {
> +            gdbserver_state.multiprocess = true;
> +        }
> +#if defined(CONFIG_USER_ONLY)
> +        gdb_handle_query_supported_user(gdb_supported);
> +#endif
>      }
>  
>      g_string_append(gdbserver_state.str_buf, 
> ";vContSupported+;multiprocess+");
> diff --git a/gdbstub/internals.h b/gdbstub/internals.h
> index 56b7c13b750..b4724598384 100644
> --- a/gdbstub/internals.h
> +++ b/gdbstub/internals.h
> @@ -196,6 +196,9 @@ void gdb_handle_v_file_pread(GArray *params, void 
> *user_ctx); /* user */
>  void gdb_handle_v_file_readlink(GArray *params, void *user_ctx); /* user */
>  void gdb_handle_query_xfer_exec_file(GArray *params, void *user_ctx); /* 
> user */
>  void gdb_handle_set_catch_syscalls(GArray *params, void *user_ctx); /* user 
> */
> +void gdb_handle_query_supported_user(const char *gdb_supported); /* user */
> +bool gdb_handle_set_thread_user(uint32_t pid, uint32_t tid); /* user */
> +bool gdb_handle_detach_user(uint32_t pid); /* user */
>  
>  void gdb_handle_query_attached(GArray *params, void *user_ctx); /* both */
>  
> 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?

> +
> +enum GDBForkMessage {
> +    GDB_FORK_ACTIVATE = 'a',
> +    GDB_FORK_ENABLE = 'e',
> +    GDB_FORK_DISABLE = 'd',
> +};
> +
>  /* User-mode specific state */
>  typedef struct {
>      int fd;
> @@ -36,6 +72,10 @@ typedef struct {
>       */
>      bool catch_all_syscalls;
>      GDBSyscallsMask catch_syscalls_mask;
> +    bool fork_events;
> +    enum GDBForkState fork_state;
> +    int fork_sockets[2];
> +    pid_t fork_peer_pid, fork_peer_tid;
>  } GDBUserState;
>  
>  static GDBUserState gdbserver_user_state;
> @@ -358,6 +398,18 @@ int gdbserver_start(const char *port_or_path)
>  
>  void gdbserver_fork_start(void)
>  {
> +    if (!gdbserver_state.init || gdbserver_user_state.fd < 0) {
> +        return;
> +    }
> +    if (!gdbserver_user_state.fork_events ||
> +            qemu_socketpair(AF_UNIX, SOCK_STREAM, 0,
> +                            gdbserver_user_state.fork_sockets) < 0) {
> +        gdbserver_user_state.fork_state = GDB_FORK_DISABLED;
> +        return;
> +    }
> +    gdbserver_user_state.fork_state = GDB_FORK_INACTIVE;
> +    gdbserver_user_state.fork_peer_pid = getpid();
> +    gdbserver_user_state.fork_peer_tid = qemu_get_thread_id();
>  }
>  
>  static void disable_gdbstub(void)
> @@ -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?

-- 
Alex Bennée
Virtualisation Tech Lead @ Linaro

Reply via email to