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>
This commit is contained in:
Ilya Leoshkevich 2025-02-07 15:31:10 +00:00 committed by Alex Bennée
parent a33dcfe771
commit d156d5d1df
3 changed files with 104 additions and 15 deletions

View file

@ -629,7 +629,6 @@ int main(int argc, char **argv)
if (gdbstub) { if (gdbstub) {
gdbserver_start(gdbstub, &error_fatal); gdbserver_start(gdbstub, &error_fatal);
gdb_handlesig(cpu, 0, NULL, NULL, 0);
} }
cpu_loop(env); cpu_loop(env);
/* never exits */ /* never exits */

View file

@ -22,6 +22,7 @@
#include "gdbstub/user.h" #include "gdbstub/user.h"
#include "gdbstub/enums.h" #include "gdbstub/enums.h"
#include "hw/core/cpu.h" #include "hw/core/cpu.h"
#include "user/signal.h"
#include "trace.h" #include "trace.h"
#include "internals.h" #include "internals.h"
@ -393,32 +394,122 @@ static int gdbserver_open_port(int port, Error **errp)
return fd; 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;
int gdb_fd;
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) { if (port > 0) {
gdb_fd = gdbserver_open_port(port, errp); gdb_fd = gdbserver_open_port(port, errp);
} else { } else {
gdb_fd = gdbserver_open_socket(port_or_path, errp); gdb_fd = gdbserver_open_socket(port_or_path, errp);
} }
if (gdb_fd < 0) { if (gdb_fd < 0) {
return false; return false;
} }
if (port > 0 && gdb_accept_tcp(gdb_fd)) { if (suspend) {
return true; if (gdbserver_accept(port, gdb_fd, port_or_path)) {
} else if (gdb_accept_socket(gdb_fd)) { gdb_handlesig(first_cpu, 0, NULL, NULL, 0);
gdbserver_user_state.socket_path = g_strdup(port_or_path); 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; return true;
} }
/* gone wrong */
close(gdb_fd);
error_setg(errp, "gdbstub: failed to accept connection");
return false;
} }
void gdbserver_fork_start(void) void gdbserver_fork_start(void)

View file

@ -1024,7 +1024,6 @@ int main(int argc, char **argv, char **envp)
if (gdbstub) { if (gdbstub) {
gdbserver_start(gdbstub, &error_fatal); gdbserver_start(gdbstub, &error_fatal);
gdb_handlesig(cpu, 0, NULL, NULL, 0);
} }
#ifdef CONFIG_SEMIHOSTING #ifdef CONFIG_SEMIHOSTING