From: "Alex Bennée" <alex.bennee@linaro.org>
To: qemu-devel@nongnu.org
Cc: "Wainer dos Santos Moschetta" <wainersm@redhat.com>,
"Alistair Francis" <Alistair.Francis@wdc.com>,
"Nicholas Piggin" <npiggin@gmail.com>,
"Liu Zhiwei" <zhiwei_liu@linux.alibaba.com>,
"Richard Henderson" <richard.henderson@linaro.org>,
"Yanan Wang" <wangyanan55@huawei.com>,
qemu-s390x@nongnu.org,
"Edgar E. Iglesias" <edgar.iglesias@gmail.com>,
"Eduardo Habkost" <eduardo@habkost.net>,
"Daniel Henrique Barboza" <dbarboza@ventanamicro.com>,
"Michael Rolnik" <mrolnik@gmail.com>,
"Daniel Henrique Barboza" <danielhb413@gmail.com>,
"Alex Bennée" <alex.bennee@linaro.org>,
"Laurent Vivier" <lvivier@redhat.com>,
"Yoshinori Sato" <ysato@users.sourceforge.jp>,
"Laurent Vivier" <laurent@vivier.eu>,
"Thomas Huth" <thuth@redhat.com>,
"Peter Maydell" <peter.maydell@linaro.org>,
"Mahmoud Mandour" <ma.mandourr@gmail.com>,
"Brad Smith" <brad@comstyle.com>,
"Alistair Francis" <alistair.francis@wdc.com>,
"Brian Cain" <bcain@quicinc.com>,
"Cleber Rosa" <crosa@redhat.com>, "John Snow" <jsnow@redhat.com>,
"Marcel Apfelbaum" <marcel.apfelbaum@gmail.com>,
"Pierrick Bouvier" <pierrick.bouvier@linaro.org>,
"Palmer Dabbelt" <palmer@dabbelt.com>,
"Riku Voipio" <riku.voipio@iki.fi>,
qemu-arm@nongnu.org, qemu-ppc@nongnu.org,
"Weiwei Li" <liwei1518@gmail.com>,
"Bin Meng" <bin.meng@windriver.com>,
"Cédric Le Goater" <clg@kaod.org>,
"Beraldo Leal" <bleal@redhat.com>,
"Kyle Evans" <kevans@freebsd.org>,
"David Hildenbrand" <david@redhat.com>,
"Song Gao" <gaosong@loongson.cn>,
"Philippe Mathieu-Daudé" <philmd@linaro.org>,
"Paolo Bonzini" <pbonzini@redhat.com>,
"Alexandre Iooss" <erdnaxe@crans.org>,
"Warner Losh" <imp@bsdimp.com>,
qemu-riscv@nongnu.org, "Ilya Leoshkevich" <iii@linux.ibm.com>
Subject: [PATCH 12/29] gdbstub: Implement follow-fork-mode child
Date: Tue, 5 Mar 2024 12:09:48 +0000 [thread overview]
Message-ID: <20240305121005.3528075-13-alex.bennee@linaro.org> (raw)
In-Reply-To: <20240305121005.3528075-1-alex.bennee@linaro.org>
From: Ilya Leoshkevich <iii@linux.ibm.com>
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 handling involves only a few messages.
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.
Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
Message-Id: <20240219141628.246823-12-iii@linux.ibm.com>
Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
---
gdbstub/user.c | 212 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 210 insertions(+), 2 deletions(-)
diff --git a/gdbstub/user.c b/gdbstub/user.c
index 1a7b582a40d..7f9f19a1249 100644
--- a/gdbstub/user.c
+++ b/gdbstub/user.c
@@ -25,6 +25,61 @@
#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.
+ *
+ * Below is a short summary of the possible state transitions:
+ *
+ * ENABLED : Terminal state.
+ * DISABLED : Terminal state.
+ * ACTIVE : Parent initial state.
+ * INACTIVE : Child initial state.
+ * ACTIVE -> DEACTIVATING: On $Hg.
+ * ACTIVE -> ENABLING : On $D.
+ * ACTIVE -> DISABLING : On $D.
+ * ACTIVE -> DISABLED : On communication error.
+ * DEACTIVATING -> INACTIVE : On gdb_read_byte() return.
+ * DEACTIVATING -> DISABLED : On communication error.
+ * INACTIVE -> ACTIVE : On $Hg in the peer.
+ * INACTIVE -> ENABLE : On $D in the peer.
+ * INACTIVE -> DISABLE : On $D in the peer.
+ * INACTIVE -> DISABLED : On communication error.
+ * ENABLING -> ENABLED : On gdb_read_byte() return.
+ * ENABLING -> DISABLED : On communication error.
+ * DISABLING -> DISABLED : On gdb_read_byte() return.
+ */
+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,
+};
+
+enum GDBForkMessage {
+ GDB_FORK_ACTIVATE = 'a',
+ GDB_FORK_ENABLE = 'e',
+ GDB_FORK_DISABLE = 'd',
+};
+
/* User-mode specific state */
typedef struct {
int fd;
@@ -36,6 +91,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 +417,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(CPUState *thread_cpu)
@@ -376,23 +447,160 @@ static void disable_gdbstub(CPUState *thread_cpu)
void gdbserver_fork_end(CPUState *cpu, 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;
}
- disable_gdbstub(cpu);
+
+ 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;
+ }
+
+ if (gdbserver_user_state.fork_state == GDB_FORK_DISABLED) {
+ if (pid == 0) {
+ disable_gdbstub(cpu);
+ }
+ 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());
+ gdb_put_strbuf();
+ }
+
+ gdbserver_state.state = RS_IDLE;
+ gdbserver_state.allow_stop_reply = false;
+ gdbserver_user_state.running_state = 0;
+ for (;;) {
+ switch (gdbserver_user_state.fork_state) {
+ case GDB_FORK_ENABLED:
+ if (gdbserver_user_state.running_state) {
+ return;
+ }
+ QEMU_FALLTHROUGH;
+ case GDB_FORK_ACTIVE:
+ if (read(gdbserver_user_state.fd, &b, 1) != 1) {
+ goto fail;
+ }
+ gdb_read_byte(b);
+ break;
+ case GDB_FORK_DEACTIVATING:
+ b = GDB_FORK_ACTIVATE;
+ if (write(fd, &b, 1) != 1) {
+ goto fail;
+ }
+ gdbserver_user_state.fork_state = GDB_FORK_INACTIVE;
+ break;
+ case GDB_FORK_INACTIVE:
+ if (read(fd, &b, 1) != 1) {
+ goto fail;
+ }
+ switch (b) {
+ case GDB_FORK_ACTIVATE:
+ gdbserver_user_state.fork_state = GDB_FORK_ACTIVE;
+ break;
+ case GDB_FORK_ENABLE:
+ close(fd);
+ gdbserver_user_state.fork_state = GDB_FORK_ENABLED;
+ break;
+ case GDB_FORK_DISABLE:
+ gdbserver_user_state.fork_state = GDB_FORK_DISABLED;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case GDB_FORK_ENABLING:
+ b = GDB_FORK_DISABLE;
+ if (write(fd, &b, 1) != 1) {
+ goto fail;
+ }
+ close(fd);
+ gdbserver_user_state.fork_state = GDB_FORK_ENABLED;
+ break;
+ case GDB_FORK_DISABLING:
+ b = GDB_FORK_ENABLE;
+ if (write(fd, &b, 1) != 1) {
+ goto fail;
+ }
+ gdbserver_user_state.fork_state = GDB_FORK_DISABLED;
+ break;
+ case GDB_FORK_DISABLED:
+ close(fd);
+ disable_gdbstub(cpu);
+ return;
+ default:
+ g_assert_not_reached();
+ }
+ }
+
+fail:
+ close(fd);
+ if (pid == 0) {
+ disable_gdbstub(cpu);
+ }
}
void gdb_handle_query_supported_user(const char *gdb_supported)
{
+ if (strstr(gdb_supported, "fork-events+")) {
+ gdbserver_user_state.fork_events = true;
+ }
+ g_string_append(gdbserver_state.str_buf, ";fork-events+");
}
bool gdb_handle_set_thread_user(uint32_t pid, uint32_t tid)
{
+ if (gdbserver_user_state.fork_state == GDB_FORK_ACTIVE &&
+ pid == gdbserver_user_state.fork_peer_pid &&
+ tid == gdbserver_user_state.fork_peer_tid) {
+ gdbserver_user_state.fork_state = GDB_FORK_DEACTIVATING;
+ gdb_put_packet("OK");
+ return true;
+ }
return false;
}
bool gdb_handle_detach_user(uint32_t pid)
{
+ bool enable;
+
+ if (gdbserver_user_state.fork_state == GDB_FORK_ACTIVE) {
+ enable = pid == gdbserver_user_state.fork_peer_pid;
+ if (enable || pid == getpid()) {
+ gdbserver_user_state.fork_state = enable ? GDB_FORK_ENABLING :
+ GDB_FORK_DISABLING;
+ gdb_put_packet("OK");
+ return true;
+ }
+ }
return false;
}
--
2.39.2
next prev parent reply other threads:[~2024-03-05 12:18 UTC|newest]
Thread overview: 35+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-03-05 12:09 [PATCH 00/29] maintainer/next for 9.0 (testing, gdbstub, plugins, disas) Alex Bennée
2024-03-05 12:09 ` [PATCH 01/29] tests: bump QOS_PATH_MAX_ELEMENT_SIZE again Alex Bennée
2024-03-05 12:09 ` [PATCH 02/29] gdbstub: Support disablement in a multi-threaded process Alex Bennée
2024-03-05 12:09 ` [PATCH 03/29] {linux,bsd}-user: Introduce get_task_state() Alex Bennée
2024-03-05 12:09 ` [PATCH 04/29] {linux,bsd}-user: Update ts_tid after fork() Alex Bennée
2024-03-05 20:44 ` Richard Henderson
2024-03-05 12:09 ` [PATCH 05/29] gdbstub: Introduce gdbserver_fork_start() Alex Bennée
2024-03-05 12:09 ` [PATCH 06/29] {linux,bsd}-user: Pass pid to fork_end() Alex Bennée
2024-03-05 12:09 ` [PATCH 07/29] {linux,bsd}-user: Pass pid to gdbserver_fork() Alex Bennée
2024-03-05 12:09 ` [PATCH 08/29] gdbstub: Call gdbserver_fork() both in parent and in child Alex Bennée
2024-03-05 12:09 ` [PATCH 09/29] gdbstub: Introduce gdb_handle_query_supported_user() Alex Bennée
2024-03-05 12:09 ` [PATCH 10/29] gdbstub: Introduce gdb_handle_set_thread_user() Alex Bennée
2024-03-05 12:09 ` [PATCH 11/29] gdbstub: Introduce gdb_handle_detach_user() Alex Bennée
2024-03-05 12:09 ` Alex Bennée [this message]
2024-03-05 12:09 ` [PATCH 13/29] tests/tcg: Add two follow-fork-mode tests Alex Bennée
2024-03-05 12:09 ` [PATCH 14/29] plugins: scoreboard API Alex Bennée
2024-03-05 12:09 ` [PATCH 15/29] plugins: define qemu_plugin_u64 Alex Bennée
2024-03-05 12:09 ` [PATCH 16/29] plugins: implement inline operation relative to cpu_index Alex Bennée
2024-03-05 12:09 ` [PATCH 17/29] plugins: add inline operation per vcpu Alex Bennée
2024-03-05 12:09 ` [PATCH 18/29] tests/plugin: add test plugin for inline operations Alex Bennée
2024-03-05 12:09 ` [PATCH 19/29] tests/plugin/mem: migrate to new per_vcpu API Alex Bennée
2024-03-05 12:09 ` [PATCH 20/29] tests/plugin/insn: " Alex Bennée
2024-03-05 12:09 ` [PATCH 21/29] tests/plugin/bb: " Alex Bennée
2024-03-05 12:09 ` [PATCH 22/29] contrib/plugins/hotblocks: " Alex Bennée
2024-03-05 12:09 ` [PATCH 23/29] contrib/plugins/howvec: " Alex Bennée
2024-03-05 12:10 ` [PATCH 24/29] plugins: remove non per_vcpu inline operation from API Alex Bennée
2024-03-05 12:10 ` [PATCH 25/29] plugins: cleanup codepath for previous inline operation Alex Bennée
2024-03-05 12:10 ` [PATCH 26/29] disas: introduce show_opcodes Alex Bennée
2024-03-05 20:46 ` Richard Henderson
2024-03-05 12:10 ` [PATCH 27/29] disas/hppa: honour show_opcodes Alex Bennée
2024-03-05 20:46 ` Richard Henderson
2024-03-05 12:10 ` [PATCH 28/29] target/loongarch: honour show_opcodes when disassembling Alex Bennée
2024-03-05 20:47 ` Richard Henderson
2024-03-05 12:10 ` [PATCH 29/29] target/riscv: " Alex Bennée
2024-03-05 20:48 ` Richard Henderson
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=20240305121005.3528075-13-alex.bennee@linaro.org \
--to=alex.bennee@linaro.org \
--cc=Alistair.Francis@wdc.com \
--cc=bcain@quicinc.com \
--cc=bin.meng@windriver.com \
--cc=bleal@redhat.com \
--cc=brad@comstyle.com \
--cc=clg@kaod.org \
--cc=crosa@redhat.com \
--cc=danielhb413@gmail.com \
--cc=david@redhat.com \
--cc=dbarboza@ventanamicro.com \
--cc=edgar.iglesias@gmail.com \
--cc=eduardo@habkost.net \
--cc=erdnaxe@crans.org \
--cc=gaosong@loongson.cn \
--cc=iii@linux.ibm.com \
--cc=imp@bsdimp.com \
--cc=jsnow@redhat.com \
--cc=kevans@freebsd.org \
--cc=laurent@vivier.eu \
--cc=liwei1518@gmail.com \
--cc=lvivier@redhat.com \
--cc=ma.mandourr@gmail.com \
--cc=marcel.apfelbaum@gmail.com \
--cc=mrolnik@gmail.com \
--cc=npiggin@gmail.com \
--cc=palmer@dabbelt.com \
--cc=pbonzini@redhat.com \
--cc=peter.maydell@linaro.org \
--cc=philmd@linaro.org \
--cc=pierrick.bouvier@linaro.org \
--cc=qemu-arm@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=qemu-ppc@nongnu.org \
--cc=qemu-riscv@nongnu.org \
--cc=qemu-s390x@nongnu.org \
--cc=richard.henderson@linaro.org \
--cc=riku.voipio@iki.fi \
--cc=thuth@redhat.com \
--cc=wainersm@redhat.com \
--cc=wangyanan55@huawei.com \
--cc=ysato@users.sourceforge.jp \
--cc=zhiwei_liu@linux.alibaba.com \
/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).