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/
next prev parent 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).