aboutsummaryrefslogtreecommitdiff
path: root/gdbstub/user.c
diff options
context:
space:
mode:
authorIlya Leoshkevich <iii@linux.ibm.com>2025-02-07 15:31:10 +0000
committerAlex Bennée <alex.bennee@linaro.org>2025-02-10 13:47:59 +0000
commitd156d5d1df12a73e06092dc5fa6595d6f16f8521 (patch)
treecf926ff4c17d703b50578e60efcd0ba071d2004f /gdbstub/user.c
parenta33dcfe771c7c2460f2a6809c3b73a5d0772396f (diff)
downloadqemu-d156d5d1df12a73e06092dc5fa6595d6f16f8521.zip
qemu-d156d5d1df12a73e06092dc5fa6595d6f16f8521.tar.gz
qemu-d156d5d1df12a73e06092dc5fa6595d6f16f8521.tar.bz2
gdbstub: Allow late attachment
Allow debugging individual processes in multi-process applications by starting them with export QEMU_GDB=/tmp/qemu-%d.sock,suspend=n. Currently one would have to attach to every process to ensure the app makes progress. In case suspend=n is not specified, the flow remains unchanged. If it is specified, then accepting the client connection is delegated to a thread. In the future this machinery may be reused for handling reconnections and interruptions. On accepting a connection, the thread schedules gdb_handlesig() on the first CPU and wakes it up with host_interrupt_signal. Note that the result of this gdb_handlesig() invocation is handled, as opposed to many other existing call sites. These other call sites probably need to be fixed separately. Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com> Message-Id: <20250117001542.8290-7-iii@linux.ibm.com> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-Id: <20250207153112.3939799-16-alex.bennee@linaro.org>
Diffstat (limited to 'gdbstub/user.c')
-rw-r--r--gdbstub/user.c115
1 files changed, 103 insertions, 12 deletions
diff --git a/gdbstub/user.c b/gdbstub/user.c
index 8225b70..3730f32 100644
--- a/gdbstub/user.c
+++ b/gdbstub/user.c
@@ -22,6 +22,7 @@
#include "gdbstub/user.h"
#include "gdbstub/enums.h"
#include "hw/core/cpu.h"
+#include "user/signal.h"
#include "trace.h"
#include "internals.h"
@@ -393,32 +394,122 @@ static int gdbserver_open_port(int port, Error **errp)
return fd;
}
-bool gdbserver_start(const char *port_or_path, Error **errp)
+static bool gdbserver_accept(int port, int gdb_fd, const char *path)
{
- int port = g_ascii_strtoull(port_or_path, NULL, 10);
+ bool ret;
+
+ if (port > 0) {
+ ret = gdb_accept_tcp(gdb_fd);
+ } else {
+ ret = gdb_accept_socket(gdb_fd);
+ if (ret) {
+ gdbserver_user_state.socket_path = g_strdup(path);
+ }
+ }
+
+ if (!ret) {
+ close(gdb_fd);
+ }
+
+ return ret;
+}
+
+struct {
+ int port;
int gdb_fd;
+ char *path;
+} gdbserver_args;
+
+static void do_gdb_handlesig(CPUState *cs, run_on_cpu_data arg)
+{
+ int sig;
+
+ sig = target_to_host_signal(gdb_handlesig(cs, 0, NULL, NULL, 0));
+ if (sig >= 1 && sig < NSIG) {
+ qemu_kill_thread(gdb_get_cpu_index(cs), sig);
+ }
+}
+
+static void *gdbserver_accept_thread(void *arg)
+{
+ if (gdbserver_accept(gdbserver_args.port, gdbserver_args.gdb_fd,
+ gdbserver_args.path)) {
+ CPUState *cs = first_cpu;
+
+ async_safe_run_on_cpu(cs, do_gdb_handlesig, RUN_ON_CPU_NULL);
+ qemu_kill_thread(gdb_get_cpu_index(cs), host_interrupt_signal);
+ }
+
+ g_free(gdbserver_args.path);
+ gdbserver_args.path = NULL;
+
+ return NULL;
+}
+#define USAGE "\nUsage: -g {port|path}[,suspend={y|n}]"
+
+bool gdbserver_start(const char *args, Error **errp)
+{
+ g_auto(GStrv) argv = g_strsplit(args, ",", 0);
+ const char *port_or_path = NULL;
+ bool suspend = true;
+ int gdb_fd, port;
+ GStrv arg;
+
+ for (arg = argv; *arg; arg++) {
+ g_auto(GStrv) tokens = g_strsplit(*arg, "=", 2);
+
+ if (g_strcmp0(tokens[0], "suspend") == 0) {
+ if (tokens[1] == NULL) {
+ error_setg(errp,
+ "gdbstub: missing \"suspend\" option value" USAGE);
+ return false;
+ } else if (!qapi_bool_parse(tokens[0], tokens[1],
+ &suspend, errp)) {
+ return false;
+ }
+ } else {
+ if (port_or_path) {
+ error_setg(errp, "gdbstub: unknown option \"%s\"" USAGE, *arg);
+ return false;
+ }
+ port_or_path = *arg;
+ }
+ }
+ if (!port_or_path) {
+ error_setg(errp, "gdbstub: port or path not specified" USAGE);
+ return false;
+ }
+
+ port = g_ascii_strtoull(port_or_path, NULL, 10);
if (port > 0) {
gdb_fd = gdbserver_open_port(port, errp);
} else {
gdb_fd = gdbserver_open_socket(port_or_path, errp);
}
-
if (gdb_fd < 0) {
return false;
}
- if (port > 0 && gdb_accept_tcp(gdb_fd)) {
- return true;
- } else if (gdb_accept_socket(gdb_fd)) {
- gdbserver_user_state.socket_path = g_strdup(port_or_path);
+ if (suspend) {
+ if (gdbserver_accept(port, gdb_fd, port_or_path)) {
+ gdb_handlesig(first_cpu, 0, NULL, NULL, 0);
+ return true;
+ } else {
+ error_setg(errp, "gdbstub: failed to accept connection");
+ return false;
+ }
+ } else {
+ QemuThread thread;
+
+ gdbserver_args.port = port;
+ gdbserver_args.gdb_fd = gdb_fd;
+ gdbserver_args.path = g_strdup(port_or_path);
+ qemu_thread_create(&thread, "gdb-accept",
+ &gdbserver_accept_thread, NULL,
+ QEMU_THREAD_DETACHED);
return true;
}
-
- /* gone wrong */
- close(gdb_fd);
- error_setg(errp, "gdbstub: failed to accept connection");
- return false;
}
void gdbserver_fork_start(void)