From patchwork Tue Mar 5 12:09:48 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alex_Benn=C3=A9e?= X-Patchwork-Id: 1908182 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=qE5e/VwA; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=patchwork.ozlabs.org) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4Tpvj65ZmDz23cm for ; Tue, 5 Mar 2024 23:18:02 +1100 (AEDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1rhTdm-0002ni-Ke; Tue, 05 Mar 2024 07:11:26 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rhTd4-0001iU-K3 for qemu-devel@nongnu.org; Tue, 05 Mar 2024 07:10:53 -0500 Received: from mail-wm1-x32c.google.com ([2a00:1450:4864:20::32c]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1rhTcg-0001T8-DJ for qemu-devel@nongnu.org; Tue, 05 Mar 2024 07:10:40 -0500 Received: by mail-wm1-x32c.google.com with SMTP id 5b1f17b1804b1-412e8c63f23so10146655e9.0 for ; Tue, 05 Mar 2024 04:10:17 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1709640617; x=1710245417; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=FBFGDsBcYD+JP9yYVswciAAzuKJ4opwX8kvXoDHdqq0=; b=qE5e/VwAT8kONqORRKFnYNJUHP0ghVUts/F9GxCp8d4KXkpACvADST+02ffd4OfHR2 4q/gSblilrIJHw4sDpenoukNgQXVaccmh7RR7pwlbsjFrHvtoSDLbkxSv4fao7KCYJ7+ wihU8HVd+YjhGuaA1E2N/eSPTFV+0MzmfHU87COOJalqTlMcaLszp3aCeAocAHWyDZce dYhBXcP5VFNKakt4b4gOM6p0Ng10f75viQW3Ro67MAhJl9i1Rwk2OT3p9+/dhuPn1KFP IPmgst+YGORKI/TKiPYu2TGACVPOHoVGcyA7fMo1POWLc7fuXhb0WQX66tybcml4ysDg qBjw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1709640617; x=1710245417; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=FBFGDsBcYD+JP9yYVswciAAzuKJ4opwX8kvXoDHdqq0=; b=RylM2vLFiDWSK7ASVdim/S/x11rSX18Hyvk3bdIyb5oqW8eHxxJkKsY/7+x7t7uF0b n5pdI5c3AD1ovMze/GS+Ba8uchMH5AMYdM+DBuUMNh1u2zbSpgOEWr3HsWed1wnFdhCj S6+pRPv5YTtnDTSI06tiMShkFPr46xc+/Qhi7X/ed+2HcdiiRoaqwlZSKZiGIrYhqDX9 BN2SxcUCXY0wF01oMIoi1yqoyucKC4eFI0VhjVsoTxD67oS2kbVyvttyMS8SOFvjLXZu AjG0iQ213YbtGVEhpzTy1tinAbZvc1V13CQ6GI1XmQsdHRgoegSnnqA3bDrU0WTdKjlx hi3A== X-Gm-Message-State: AOJu0YxSeSyy0KzIqmkkHHruFeFv8618XWyHfIBNZTyi9/MgYBV/akNN mXnZSrhg17yNhGgJRjFxztY8mFegxUkuVQMWpEb3OXtxqfPptifJ6eqpvXuaZ0o= X-Google-Smtp-Source: AGHT+IHims0YVu0GdqE5LnWHhikrSCK/ucLVqfvhymbwIXB42724DOEZ1ysUvwWkLQ7sPlQRaafPEg== X-Received: by 2002:a05:600c:1c9c:b0:412:e675:f807 with SMTP id k28-20020a05600c1c9c00b00412e675f807mr2966432wms.31.1709640616665; Tue, 05 Mar 2024 04:10:16 -0800 (PST) Received: from draig.lan ([85.9.250.243]) by smtp.gmail.com with ESMTPSA id u20-20020adfcb14000000b0033b7ce8b496sm14732205wrh.108.2024.03.05.04.10.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 05 Mar 2024 04:10:12 -0800 (PST) Received: from draig.lan (localhost [IPv6:::1]) by draig.lan (Postfix) with ESMTP id 9FDDD5F9D3; Tue, 5 Mar 2024 12:10:06 +0000 (GMT) From: =?utf-8?q?Alex_Benn=C3=A9e?= To: qemu-devel@nongnu.org Cc: Wainer dos Santos Moschetta , Alistair Francis , Nicholas Piggin , Liu Zhiwei , Richard Henderson , Yanan Wang , qemu-s390x@nongnu.org, "Edgar E. Iglesias" , Eduardo Habkost , Daniel Henrique Barboza , Michael Rolnik , Daniel Henrique Barboza , =?utf-8?q?Alex_Benn=C3=A9e?= , Laurent Vivier , Yoshinori Sato , Laurent Vivier , Thomas Huth , Peter Maydell , Mahmoud Mandour , Brad Smith , Alistair Francis , Brian Cain , Cleber Rosa , John Snow , Marcel Apfelbaum , Pierrick Bouvier , Palmer Dabbelt , Riku Voipio , qemu-arm@nongnu.org, qemu-ppc@nongnu.org, Weiwei Li , Bin Meng , =?utf-8?q?C=C3=A9dric_Le_Goater?= , Beraldo Leal , Kyle Evans , David Hildenbrand , Song Gao , =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= , Paolo Bonzini , Alexandre Iooss , Warner Losh , qemu-riscv@nongnu.org, Ilya Leoshkevich Subject: [PATCH 12/29] gdbstub: Implement follow-fork-mode child Date: Tue, 5 Mar 2024 12:09:48 +0000 Message-Id: <20240305121005.3528075-13-alex.bennee@linaro.org> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240305121005.3528075-1-alex.bennee@linaro.org> References: <20240305121005.3528075-1-alex.bennee@linaro.org> MIME-Version: 1.0 Received-SPF: pass client-ip=2a00:1450:4864:20::32c; envelope-from=alex.bennee@linaro.org; helo=mail-wm1-x32c.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org From: Ilya Leoshkevich 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 Message-Id: <20240219141628.246823-12-iii@linux.ibm.com> Signed-off-by: Alex Bennée --- 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; }