mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-07-27 04:13:53 -06:00
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:
parent
a33dcfe771
commit
d156d5d1df
3 changed files with 104 additions and 15 deletions
|
@ -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 */
|
||||||
|
|
115
gdbstub/user.c
115
gdbstub/user.c
|
@ -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,33 +394,123 @@ 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) {
|
||||||
|
if (gdbserver_accept(port, gdb_fd, port_or_path)) {
|
||||||
|
gdb_handlesig(first_cpu, 0, NULL, NULL, 0);
|
||||||
return true;
|
return true;
|
||||||
} else if (gdb_accept_socket(gdb_fd)) {
|
} else {
|
||||||
gdbserver_user_state.socket_path = g_strdup(port_or_path);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* gone wrong */
|
|
||||||
close(gdb_fd);
|
|
||||||
error_setg(errp, "gdbstub: failed to accept connection");
|
error_setg(errp, "gdbstub: failed to accept connection");
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void gdbserver_fork_start(void)
|
void gdbserver_fork_start(void)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue