gdbstub: move chunks of user code into own files

The process was pretty similar to the softmmu move except we take the
time to split stuff between user.c and user-target.c to avoid as much
target specific compilation as possible. We also start to make use of
our shiny new header scheme so the user-only helpers can be included
without the rest of the exec/gsbstub.h cruft.

As before we split some functions into user and softmmu versions

Reviewed-by: Fabiano Rosas <farosas@suse.de>
Signed-off-by: Alex Bennée <alex.bennee@linaro.org>

Message-Id: <20230302190846.2593720-12-alex.bennee@linaro.org>
Message-Id: <20230303025805.625589-12-richard.henderson@linaro.org>
This commit is contained in:
Alex Bennée 2023-03-02 18:57:47 -08:00
parent b6fa2ec238
commit d96bf49ba8
13 changed files with 790 additions and 696 deletions

View file

@ -2743,6 +2743,7 @@ S: Maintained
F: docs/system/gdb.rst
F: gdbstub/*
F: include/exec/gdbstub.h
F: include/gdbstub/*
F: gdb-xml/
F: tests/tcg/multiarch/gdbstub/
F: scripts/feature_to_c.sh

View file

@ -44,6 +44,7 @@
#include "trace/control.h"
#include "crypto/init.h"
#include "qemu/guest-random.h"
#include "gdbstub/user.h"
#include "host-os.h"
#include "target_arch_cpu.h"

View file

@ -21,6 +21,7 @@
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu.h"
#include "gdbstub/user.h"
#include "signal-common.h"
#include "trace.h"
#include "hw/core/tcg-cpu-ops.h"

View file

@ -30,13 +30,12 @@
#include "trace.h"
#include "exec/gdbstub.h"
#ifdef CONFIG_USER_ONLY
#include "qemu.h"
#include "gdbstub/user.h"
#else
#include "hw/cpu/cluster.h"
#include "hw/boards.h"
#endif
#include "qemu/sockets.h"
#include "sysemu/hw_accel.h"
#include "sysemu/runstate.h"
#include "semihosting/semihost.h"
@ -80,223 +79,6 @@ static inline int target_memory_rw_debug(CPUState *cpu, target_ulong addr,
return cpu_memory_rw_debug(cpu, addr, buf, len, is_write);
}
/*
* Return the GDB index for a given vCPU state.
*
* For user mode this is simply the thread id.
*/
#if defined(CONFIG_USER_ONLY)
int gdb_get_cpu_index(CPUState *cpu)
{
TaskState *ts = (TaskState *) cpu->opaque;
return ts ? ts->ts_tid : -1;
}
#endif
#ifdef CONFIG_USER_ONLY
/* Map target signal numbers to GDB protocol signal numbers and vice
* versa. For user emulation's currently supported systems, we can
* assume most signals are defined.
*/
static int gdb_signal_table[] = {
0,
TARGET_SIGHUP,
TARGET_SIGINT,
TARGET_SIGQUIT,
TARGET_SIGILL,
TARGET_SIGTRAP,
TARGET_SIGABRT,
-1, /* SIGEMT */
TARGET_SIGFPE,
TARGET_SIGKILL,
TARGET_SIGBUS,
TARGET_SIGSEGV,
TARGET_SIGSYS,
TARGET_SIGPIPE,
TARGET_SIGALRM,
TARGET_SIGTERM,
TARGET_SIGURG,
TARGET_SIGSTOP,
TARGET_SIGTSTP,
TARGET_SIGCONT,
TARGET_SIGCHLD,
TARGET_SIGTTIN,
TARGET_SIGTTOU,
TARGET_SIGIO,
TARGET_SIGXCPU,
TARGET_SIGXFSZ,
TARGET_SIGVTALRM,
TARGET_SIGPROF,
TARGET_SIGWINCH,
-1, /* SIGLOST */
TARGET_SIGUSR1,
TARGET_SIGUSR2,
#ifdef TARGET_SIGPWR
TARGET_SIGPWR,
#else
-1,
#endif
-1, /* SIGPOLL */
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
#ifdef __SIGRTMIN
__SIGRTMIN + 1,
__SIGRTMIN + 2,
__SIGRTMIN + 3,
__SIGRTMIN + 4,
__SIGRTMIN + 5,
__SIGRTMIN + 6,
__SIGRTMIN + 7,
__SIGRTMIN + 8,
__SIGRTMIN + 9,
__SIGRTMIN + 10,
__SIGRTMIN + 11,
__SIGRTMIN + 12,
__SIGRTMIN + 13,
__SIGRTMIN + 14,
__SIGRTMIN + 15,
__SIGRTMIN + 16,
__SIGRTMIN + 17,
__SIGRTMIN + 18,
__SIGRTMIN + 19,
__SIGRTMIN + 20,
__SIGRTMIN + 21,
__SIGRTMIN + 22,
__SIGRTMIN + 23,
__SIGRTMIN + 24,
__SIGRTMIN + 25,
__SIGRTMIN + 26,
__SIGRTMIN + 27,
__SIGRTMIN + 28,
__SIGRTMIN + 29,
__SIGRTMIN + 30,
__SIGRTMIN + 31,
-1, /* SIGCANCEL */
__SIGRTMIN,
__SIGRTMIN + 32,
__SIGRTMIN + 33,
__SIGRTMIN + 34,
__SIGRTMIN + 35,
__SIGRTMIN + 36,
__SIGRTMIN + 37,
__SIGRTMIN + 38,
__SIGRTMIN + 39,
__SIGRTMIN + 40,
__SIGRTMIN + 41,
__SIGRTMIN + 42,
__SIGRTMIN + 43,
__SIGRTMIN + 44,
__SIGRTMIN + 45,
__SIGRTMIN + 46,
__SIGRTMIN + 47,
__SIGRTMIN + 48,
__SIGRTMIN + 49,
__SIGRTMIN + 50,
__SIGRTMIN + 51,
__SIGRTMIN + 52,
__SIGRTMIN + 53,
__SIGRTMIN + 54,
__SIGRTMIN + 55,
__SIGRTMIN + 56,
__SIGRTMIN + 57,
__SIGRTMIN + 58,
__SIGRTMIN + 59,
__SIGRTMIN + 60,
__SIGRTMIN + 61,
__SIGRTMIN + 62,
__SIGRTMIN + 63,
__SIGRTMIN + 64,
__SIGRTMIN + 65,
__SIGRTMIN + 66,
__SIGRTMIN + 67,
__SIGRTMIN + 68,
__SIGRTMIN + 69,
__SIGRTMIN + 70,
__SIGRTMIN + 71,
__SIGRTMIN + 72,
__SIGRTMIN + 73,
__SIGRTMIN + 74,
__SIGRTMIN + 75,
__SIGRTMIN + 76,
__SIGRTMIN + 77,
__SIGRTMIN + 78,
__SIGRTMIN + 79,
__SIGRTMIN + 80,
__SIGRTMIN + 81,
__SIGRTMIN + 82,
__SIGRTMIN + 83,
__SIGRTMIN + 84,
__SIGRTMIN + 85,
__SIGRTMIN + 86,
__SIGRTMIN + 87,
__SIGRTMIN + 88,
__SIGRTMIN + 89,
__SIGRTMIN + 90,
__SIGRTMIN + 91,
__SIGRTMIN + 92,
__SIGRTMIN + 93,
__SIGRTMIN + 94,
__SIGRTMIN + 95,
-1, /* SIGINFO */
-1, /* UNKNOWN */
-1, /* DEFAULT */
-1,
-1,
-1,
-1,
-1,
-1
#endif
};
#else
/* In system mode we only need SIGINT and SIGTRAP; other signals
are not yet supported. */
enum {
TARGET_SIGINT = 2,
TARGET_SIGTRAP = 5
};
static int gdb_signal_table[] = {
-1,
-1,
TARGET_SIGINT,
-1,
-1,
TARGET_SIGTRAP
};
#endif
#ifdef CONFIG_USER_ONLY
static int target_signal_to_gdb (int sig)
{
int i;
for (i = 0; i < ARRAY_SIZE (gdb_signal_table); i++)
if (gdb_signal_table[i] == sig)
return i;
return GDB_SIGNAL_UNKNOWN;
}
#endif
static int gdb_signal_to_target (int sig)
{
if (sig < ARRAY_SIZE (gdb_signal_table))
return gdb_signal_table[sig];
else
return -1;
}
typedef struct GDBRegisterState {
int base_reg;
int num_regs;
@ -306,15 +88,6 @@ typedef struct GDBRegisterState {
struct GDBRegisterState *next;
} GDBRegisterState;
#ifdef CONFIG_USER_ONLY
typedef struct {
int fd;
char *socket_path;
int running_state;
} GDBUserState;
static GDBUserState gdbserver_user_state;
#endif
GDBState gdbserver_state;
void gdb_init_gdbserver_state(void)
@ -338,34 +111,6 @@ void gdb_init_gdbserver_state(void)
bool gdb_has_xml;
#ifdef CONFIG_USER_ONLY
static int get_char(void)
{
uint8_t ch;
int ret;
for(;;) {
ret = recv(gdbserver_user_state.fd, &ch, 1, 0);
if (ret < 0) {
if (errno == ECONNRESET) {
gdbserver_user_state.fd = -1;
}
if (errno != EINTR) {
return -1;
}
} else if (ret == 0) {
close(gdbserver_user_state.fd);
gdbserver_user_state.fd = -1;
return -1;
} else {
break;
}
}
return ch;
}
#endif
/*
* Return true if there is a GDB currently connected to the stub
* and attached to a CPU
@ -410,104 +155,6 @@ static bool stub_can_reverse(void)
#endif
}
/* Resume execution. */
static void gdb_continue(void)
{
#ifdef CONFIG_USER_ONLY
gdbserver_user_state.running_state = 1;
trace_gdbstub_op_continue();
#else
if (!runstate_needs_reset()) {
trace_gdbstub_op_continue();
vm_start();
}
#endif
}
/*
* Resume execution, per CPU actions. For user-mode emulation it's
* equivalent to gdb_continue.
*/
static int gdb_continue_partial(char *newstates)
{
CPUState *cpu;
int res = 0;
#ifdef CONFIG_USER_ONLY
/*
* This is not exactly accurate, but it's an improvement compared to the
* previous situation, where only one CPU would be single-stepped.
*/
CPU_FOREACH(cpu) {
if (newstates[cpu->cpu_index] == 's') {
trace_gdbstub_op_stepping(cpu->cpu_index);
cpu_single_step(cpu, gdbserver_state.sstep_flags);
}
}
gdbserver_user_state.running_state = 1;
#else
int flag = 0;
if (!runstate_needs_reset()) {
bool step_requested = false;
CPU_FOREACH(cpu) {
if (newstates[cpu->cpu_index] == 's') {
step_requested = true;
break;
}
}
if (vm_prepare_start(step_requested)) {
return 0;
}
CPU_FOREACH(cpu) {
switch (newstates[cpu->cpu_index]) {
case 0:
case 1:
break; /* nothing to do here */
case 's':
trace_gdbstub_op_stepping(cpu->cpu_index);
cpu_single_step(cpu, gdbserver_state.sstep_flags);
cpu_resume(cpu);
flag = 1;
break;
case 'c':
trace_gdbstub_op_continue_cpu(cpu->cpu_index);
cpu_resume(cpu);
flag = 1;
break;
default:
res = -1;
break;
}
}
}
if (flag) {
qemu_clock_enable(QEMU_CLOCK_VIRTUAL, true);
}
#endif
return res;
}
#ifdef CONFIG_USER_ONLY
void gdb_put_buffer(const uint8_t *buf, int len)
{
int ret;
while (len > 0) {
ret = send(gdbserver_user_state.fd, buf, len, 0);
if (ret < 0) {
if (errno != EINTR)
return;
} else {
buf += ret;
len -= ret;
}
}
}
#endif
/* writes 2*len+1 bytes in buf */
void gdb_memtohex(GString *buf, const uint8_t *mem, int len)
{
@ -593,7 +240,7 @@ int gdb_put_packet_binary(const char *buf, int len, bool dump)
gdbserver_state.last_packet->len);
#ifdef CONFIG_USER_ONLY
i = get_char();
i = gdb_get_char();
if (i < 0)
return -1;
if (i == '+')
@ -1950,23 +1597,6 @@ static void handle_query_thread_extra(GArray *params, void *user_ctx)
gdb_put_strbuf();
}
#ifdef CONFIG_USER_ONLY
static void handle_query_offsets(GArray *params, void *user_ctx)
{
TaskState *ts;
ts = gdbserver_state.c_cpu->opaque;
g_string_printf(gdbserver_state.str_buf,
"Text=" TARGET_ABI_FMT_lx
";Data=" TARGET_ABI_FMT_lx
";Bss=" TARGET_ABI_FMT_lx,
ts->info->code_offset,
ts->info->data_offset,
ts->info->data_offset);
gdb_put_strbuf();
}
#endif
static void handle_query_supported(GArray *params, void *user_ctx)
{
CPUClass *cc;
@ -2049,53 +1679,6 @@ static void handle_query_xfer_features(GArray *params, void *user_ctx)
gdbserver_state.str_buf->len, true);
}
#if defined(CONFIG_USER_ONLY) && defined(CONFIG_LINUX_USER)
static void handle_query_xfer_auxv(GArray *params, void *user_ctx)
{
TaskState *ts;
unsigned long offset, len, saved_auxv, auxv_len;
if (params->len < 2) {
gdb_put_packet("E22");
return;
}
offset = get_param(params, 0)->val_ul;
len = get_param(params, 1)->val_ul;
ts = gdbserver_state.c_cpu->opaque;
saved_auxv = ts->info->saved_auxv;
auxv_len = ts->info->auxv_len;
if (offset >= auxv_len) {
gdb_put_packet("E00");
return;
}
if (len > (MAX_PACKET_LENGTH - 5) / 2) {
len = (MAX_PACKET_LENGTH - 5) / 2;
}
if (len < auxv_len - offset) {
g_string_assign(gdbserver_state.str_buf, "m");
} else {
g_string_assign(gdbserver_state.str_buf, "l");
len = auxv_len - offset;
}
g_byte_array_set_size(gdbserver_state.mem_buf, len);
if (target_memory_rw_debug(gdbserver_state.g_cpu, saved_auxv + offset,
gdbserver_state.mem_buf->data, len, false)) {
gdb_put_packet("E14");
return;
}
gdb_memtox(gdbserver_state.str_buf,
(const char *)gdbserver_state.mem_buf->data, len);
gdb_put_packet_binary(gdbserver_state.str_buf->str,
gdbserver_state.str_buf->len, true);
}
#endif
static void handle_query_attached(GArray *params, void *user_ctx)
{
gdb_put_packet(GDB_ATTACHED);
@ -2173,7 +1756,7 @@ static const GdbCmdParseEntry gdb_gen_query_table[] = {
},
#ifdef CONFIG_USER_ONLY
{
.handler = handle_query_offsets,
.handler = gdb_handle_query_offsets,
.cmd = "Offsets",
},
#else
@ -2203,7 +1786,7 @@ static const GdbCmdParseEntry gdb_gen_query_table[] = {
},
#if defined(CONFIG_USER_ONLY) && defined(CONFIG_LINUX_USER)
{
.handler = handle_query_xfer_auxv,
.handler = gdb_handle_query_xfer_auxv,
.cmd = "Xfer:auxv:read::",
.cmd_startswith = 1,
.schema = "l,l0"
@ -2791,29 +2374,6 @@ void gdb_read_byte(uint8_t ch)
}
}
#ifdef CONFIG_USER_ONLY
/* Tell the remote gdb that the process has exited. */
void gdb_exit(int code)
{
char buf[4];
if (!gdbserver_state.init) {
return;
}
if (gdbserver_user_state.socket_path) {
unlink(gdbserver_user_state.socket_path);
}
if (gdbserver_user_state.fd < 0) {
return;
}
trace_gdbstub_op_exiting((uint8_t)code);
snprintf(buf, sizeof(buf), "W%02x", (uint8_t)code);
gdb_put_packet(buf);
}
#endif
/*
* Create the process that will contain all the "orphan" CPUs (that are not
* part of a CPU cluster). Note that if this process contains no CPUs, it won't
@ -2839,233 +2399,3 @@ void gdb_create_default_process(GDBState *s)
process->target_xml[0] = '\0';
}
#ifdef CONFIG_USER_ONLY
int
gdb_handlesig(CPUState *cpu, int sig)
{
char buf[256];
int n;
if (!gdbserver_state.init || gdbserver_user_state.fd < 0) {
return sig;
}
/* disable single step if it was enabled */
cpu_single_step(cpu, 0);
tb_flush(cpu);
if (sig != 0) {
gdb_set_stop_cpu(cpu);
g_string_printf(gdbserver_state.str_buf,
"T%02xthread:", target_signal_to_gdb(sig));
gdb_append_thread_id(cpu, gdbserver_state.str_buf);
g_string_append_c(gdbserver_state.str_buf, ';');
gdb_put_strbuf();
}
/*
* gdb_put_packet() might have detected that the peer terminated the
* connection.
*/
if (gdbserver_user_state.fd < 0) {
return sig;
}
sig = 0;
gdbserver_state.state = RS_IDLE;
gdbserver_user_state.running_state = 0;
while (gdbserver_user_state.running_state == 0) {
n = read(gdbserver_user_state.fd, buf, 256);
if (n > 0) {
int i;
for (i = 0; i < n; i++) {
gdb_read_byte(buf[i]);
}
} else {
/* XXX: Connection closed. Should probably wait for another
connection before continuing. */
if (n == 0) {
close(gdbserver_user_state.fd);
}
gdbserver_user_state.fd = -1;
return sig;
}
}
sig = gdbserver_state.signal;
gdbserver_state.signal = 0;
return sig;
}
/* Tell the remote gdb that the process has exited due to SIG. */
void gdb_signalled(CPUArchState *env, int sig)
{
char buf[4];
if (!gdbserver_state.init || gdbserver_user_state.fd < 0) {
return;
}
snprintf(buf, sizeof(buf), "X%02x", target_signal_to_gdb(sig));
gdb_put_packet(buf);
}
static void gdb_accept_init(int fd)
{
gdb_init_gdbserver_state();
gdb_create_default_process(&gdbserver_state);
gdbserver_state.processes[0].attached = true;
gdbserver_state.c_cpu = gdb_first_attached_cpu();
gdbserver_state.g_cpu = gdbserver_state.c_cpu;
gdbserver_user_state.fd = fd;
gdb_has_xml = false;
}
static bool gdb_accept_socket(int gdb_fd)
{
int fd;
for(;;) {
fd = accept(gdb_fd, NULL, NULL);
if (fd < 0 && errno != EINTR) {
perror("accept socket");
return false;
} else if (fd >= 0) {
qemu_set_cloexec(fd);
break;
}
}
gdb_accept_init(fd);
return true;
}
static int gdbserver_open_socket(const char *path)
{
struct sockaddr_un sockaddr = {};
int fd, ret;
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
perror("create socket");
return -1;
}
sockaddr.sun_family = AF_UNIX;
pstrcpy(sockaddr.sun_path, sizeof(sockaddr.sun_path) - 1, path);
ret = bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
if (ret < 0) {
perror("bind socket");
close(fd);
return -1;
}
ret = listen(fd, 1);
if (ret < 0) {
perror("listen socket");
close(fd);
return -1;
}
return fd;
}
static bool gdb_accept_tcp(int gdb_fd)
{
struct sockaddr_in sockaddr = {};
socklen_t len;
int fd;
for(;;) {
len = sizeof(sockaddr);
fd = accept(gdb_fd, (struct sockaddr *)&sockaddr, &len);
if (fd < 0 && errno != EINTR) {
perror("accept");
return false;
} else if (fd >= 0) {
qemu_set_cloexec(fd);
break;
}
}
/* set short latency */
if (socket_set_nodelay(fd)) {
perror("setsockopt");
close(fd);
return false;
}
gdb_accept_init(fd);
return true;
}
static int gdbserver_open_port(int port)
{
struct sockaddr_in sockaddr;
int fd, ret;
fd = socket(PF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
return -1;
}
qemu_set_cloexec(fd);
socket_set_fast_reuse(fd);
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(port);
sockaddr.sin_addr.s_addr = 0;
ret = bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
if (ret < 0) {
perror("bind");
close(fd);
return -1;
}
ret = listen(fd, 1);
if (ret < 0) {
perror("listen");
close(fd);
return -1;
}
return fd;
}
int gdbserver_start(const char *port_or_path)
{
int port = g_ascii_strtoull(port_or_path, NULL, 10);
int gdb_fd;
if (port > 0) {
gdb_fd = gdbserver_open_port(port);
} else {
gdb_fd = gdbserver_open_socket(port_or_path);
}
if (gdb_fd < 0) {
return -1;
}
if (port > 0 && gdb_accept_tcp(gdb_fd)) {
return 0;
} else if (gdb_accept_socket(gdb_fd)) {
gdbserver_user_state.socket_path = g_strdup(port_or_path);
return 0;
}
/* gone wrong */
close(gdb_fd);
return -1;
}
/* Disable gdb stub for child processes. */
void gdbserver_fork(CPUState *cpu)
{
if (!gdbserver_state.init || gdbserver_user_state.fd < 0) {
return;
}
close(gdbserver_user_state.fd);
gdbserver_user_state.fd = -1;
cpu_breakpoint_remove_all(cpu, BP_GDB);
cpu_watchpoint_remove_all(cpu, BP_GDB);
}
#endif

View file

@ -117,6 +117,22 @@ int gdb_get_cpu_index(CPUState *cpu);
void gdb_create_default_process(GDBState *s);
/* signal mapping, common for softmmu, specialised for user-mode */
int gdb_signal_to_target(int sig);
int gdb_target_signal_to_gdb(int sig);
int gdb_get_char(void); /* user only */
/**
* gdb_continue() - handle continue in mode specific way.
*/
void gdb_continue(void);
/**
* gdb_continue_partial() - handle partial continue in mode specific way.
*/
int gdb_continue_partial(char *newstates);
/*
* Helpers with separate softmmu and user implementations
*/
@ -149,6 +165,8 @@ typedef union GdbCmdVariant {
#define get_param(p, i) (&g_array_index(p, GdbCmdVariant, i))
void gdb_handle_query_rcmd(GArray *params, void *user_ctx); /* softmmu */
void gdb_handle_query_offsets(GArray *params, void *user_ctx); /* user */
void gdb_handle_query_xfer_auxv(GArray *params, void *user_ctx); /*user */
/*
* Break/Watch point support - there is an implementation for softmmu

View file

@ -7,3 +7,6 @@
specific_ss.add(files('gdbstub.c'))
softmmu_ss.add(files('softmmu.c'))
user_ss.add(files('user.c'))
# The user-target is specialised by the guest
specific_ss.add(when: 'CONFIG_USER_ONLY', if_true: files('user-target.c'))

View file

@ -434,6 +434,96 @@ void gdb_handle_query_rcmd(GArray *params, void *user_ctx)
gdb_put_packet("OK");
}
/*
* Execution state helpers
*/
void gdb_continue(void)
{
if (!runstate_needs_reset()) {
trace_gdbstub_op_continue();
vm_start();
}
}
/*
* Resume execution, per CPU actions.
*/
int gdb_continue_partial(char *newstates)
{
CPUState *cpu;
int res = 0;
int flag = 0;
if (!runstate_needs_reset()) {
bool step_requested = false;
CPU_FOREACH(cpu) {
if (newstates[cpu->cpu_index] == 's') {
step_requested = true;
break;
}
}
if (vm_prepare_start(step_requested)) {
return 0;
}
CPU_FOREACH(cpu) {
switch (newstates[cpu->cpu_index]) {
case 0:
case 1:
break; /* nothing to do here */
case 's':
trace_gdbstub_op_stepping(cpu->cpu_index);
cpu_single_step(cpu, gdbserver_state.sstep_flags);
cpu_resume(cpu);
flag = 1;
break;
case 'c':
trace_gdbstub_op_continue_cpu(cpu->cpu_index);
cpu_resume(cpu);
flag = 1;
break;
default:
res = -1;
break;
}
}
}
if (flag) {
qemu_clock_enable(QEMU_CLOCK_VIRTUAL, true);
}
return res;
}
/*
* Signal Handling - in system mode we only need SIGINT and SIGTRAP; other
* signals are not yet supported.
*/
enum {
TARGET_SIGINT = 2,
TARGET_SIGTRAP = 5
};
static int gdb_signal_table[] = {
-1,
-1,
TARGET_SIGINT,
-1,
-1,
TARGET_SIGTRAP
};
int gdb_signal_to_target(int sig)
{
if (sig < ARRAY_SIZE(gdb_signal_table)) {
return gdb_signal_table[sig];
} else {
return -1;
}
}
/*
* Break/Watch point helpers
*/

283
gdbstub/user-target.c Normal file
View file

@ -0,0 +1,283 @@
/*
* Target specific user-mode handling
*
* Copyright (c) 2003-2005 Fabrice Bellard
* Copyright (c) 2022 Linaro Ltd
*
* SPDX-License-Identifier: LGPL-2.0+
*/
#include "qemu/osdep.h"
#include "exec/gdbstub.h"
#include "qemu.h"
#include "internals.h"
/*
* Map target signal numbers to GDB protocol signal numbers and vice
* versa. For user emulation's currently supported systems, we can
* assume most signals are defined.
*/
static int gdb_signal_table[] = {
0,
TARGET_SIGHUP,
TARGET_SIGINT,
TARGET_SIGQUIT,
TARGET_SIGILL,
TARGET_SIGTRAP,
TARGET_SIGABRT,
-1, /* SIGEMT */
TARGET_SIGFPE,
TARGET_SIGKILL,
TARGET_SIGBUS,
TARGET_SIGSEGV,
TARGET_SIGSYS,
TARGET_SIGPIPE,
TARGET_SIGALRM,
TARGET_SIGTERM,
TARGET_SIGURG,
TARGET_SIGSTOP,
TARGET_SIGTSTP,
TARGET_SIGCONT,
TARGET_SIGCHLD,
TARGET_SIGTTIN,
TARGET_SIGTTOU,
TARGET_SIGIO,
TARGET_SIGXCPU,
TARGET_SIGXFSZ,
TARGET_SIGVTALRM,
TARGET_SIGPROF,
TARGET_SIGWINCH,
-1, /* SIGLOST */
TARGET_SIGUSR1,
TARGET_SIGUSR2,
#ifdef TARGET_SIGPWR
TARGET_SIGPWR,
#else
-1,
#endif
-1, /* SIGPOLL */
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
#ifdef __SIGRTMIN
__SIGRTMIN + 1,
__SIGRTMIN + 2,
__SIGRTMIN + 3,
__SIGRTMIN + 4,
__SIGRTMIN + 5,
__SIGRTMIN + 6,
__SIGRTMIN + 7,
__SIGRTMIN + 8,
__SIGRTMIN + 9,
__SIGRTMIN + 10,
__SIGRTMIN + 11,
__SIGRTMIN + 12,
__SIGRTMIN + 13,
__SIGRTMIN + 14,
__SIGRTMIN + 15,
__SIGRTMIN + 16,
__SIGRTMIN + 17,
__SIGRTMIN + 18,
__SIGRTMIN + 19,
__SIGRTMIN + 20,
__SIGRTMIN + 21,
__SIGRTMIN + 22,
__SIGRTMIN + 23,
__SIGRTMIN + 24,
__SIGRTMIN + 25,
__SIGRTMIN + 26,
__SIGRTMIN + 27,
__SIGRTMIN + 28,
__SIGRTMIN + 29,
__SIGRTMIN + 30,
__SIGRTMIN + 31,
-1, /* SIGCANCEL */
__SIGRTMIN,
__SIGRTMIN + 32,
__SIGRTMIN + 33,
__SIGRTMIN + 34,
__SIGRTMIN + 35,
__SIGRTMIN + 36,
__SIGRTMIN + 37,
__SIGRTMIN + 38,
__SIGRTMIN + 39,
__SIGRTMIN + 40,
__SIGRTMIN + 41,
__SIGRTMIN + 42,
__SIGRTMIN + 43,
__SIGRTMIN + 44,
__SIGRTMIN + 45,
__SIGRTMIN + 46,
__SIGRTMIN + 47,
__SIGRTMIN + 48,
__SIGRTMIN + 49,
__SIGRTMIN + 50,
__SIGRTMIN + 51,
__SIGRTMIN + 52,
__SIGRTMIN + 53,
__SIGRTMIN + 54,
__SIGRTMIN + 55,
__SIGRTMIN + 56,
__SIGRTMIN + 57,
__SIGRTMIN + 58,
__SIGRTMIN + 59,
__SIGRTMIN + 60,
__SIGRTMIN + 61,
__SIGRTMIN + 62,
__SIGRTMIN + 63,
__SIGRTMIN + 64,
__SIGRTMIN + 65,
__SIGRTMIN + 66,
__SIGRTMIN + 67,
__SIGRTMIN + 68,
__SIGRTMIN + 69,
__SIGRTMIN + 70,
__SIGRTMIN + 71,
__SIGRTMIN + 72,
__SIGRTMIN + 73,
__SIGRTMIN + 74,
__SIGRTMIN + 75,
__SIGRTMIN + 76,
__SIGRTMIN + 77,
__SIGRTMIN + 78,
__SIGRTMIN + 79,
__SIGRTMIN + 80,
__SIGRTMIN + 81,
__SIGRTMIN + 82,
__SIGRTMIN + 83,
__SIGRTMIN + 84,
__SIGRTMIN + 85,
__SIGRTMIN + 86,
__SIGRTMIN + 87,
__SIGRTMIN + 88,
__SIGRTMIN + 89,
__SIGRTMIN + 90,
__SIGRTMIN + 91,
__SIGRTMIN + 92,
__SIGRTMIN + 93,
__SIGRTMIN + 94,
__SIGRTMIN + 95,
-1, /* SIGINFO */
-1, /* UNKNOWN */
-1, /* DEFAULT */
-1,
-1,
-1,
-1,
-1,
-1
#endif
};
int gdb_signal_to_target(int sig)
{
if (sig < ARRAY_SIZE(gdb_signal_table)) {
return gdb_signal_table[sig];
} else {
return -1;
}
}
int gdb_target_signal_to_gdb(int sig)
{
int i;
for (i = 0; i < ARRAY_SIZE(gdb_signal_table); i++) {
if (gdb_signal_table[i] == sig) {
return i;
}
}
return GDB_SIGNAL_UNKNOWN;
}
int gdb_get_cpu_index(CPUState *cpu)
{
TaskState *ts = (TaskState *) cpu->opaque;
return ts ? ts->ts_tid : -1;
}
/*
* User-mode specific command helpers
*/
void gdb_handle_query_offsets(GArray *params, void *user_ctx)
{
TaskState *ts;
ts = gdbserver_state.c_cpu->opaque;
g_string_printf(gdbserver_state.str_buf,
"Text=" TARGET_ABI_FMT_lx
";Data=" TARGET_ABI_FMT_lx
";Bss=" TARGET_ABI_FMT_lx,
ts->info->code_offset,
ts->info->data_offset,
ts->info->data_offset);
gdb_put_strbuf();
}
#if defined(CONFIG_LINUX)
/* Partial user only duplicate of helper in gdbstub.c */
static inline int target_memory_rw_debug(CPUState *cpu, target_ulong addr,
uint8_t *buf, int len, bool is_write)
{
CPUClass *cc;
cc = CPU_GET_CLASS(cpu);
if (cc->memory_rw_debug) {
return cc->memory_rw_debug(cpu, addr, buf, len, is_write);
}
return cpu_memory_rw_debug(cpu, addr, buf, len, is_write);
}
void gdb_handle_query_xfer_auxv(GArray *params, void *user_ctx)
{
TaskState *ts;
unsigned long offset, len, saved_auxv, auxv_len;
if (params->len < 2) {
gdb_put_packet("E22");
return;
}
offset = get_param(params, 0)->val_ul;
len = get_param(params, 1)->val_ul;
ts = gdbserver_state.c_cpu->opaque;
saved_auxv = ts->info->saved_auxv;
auxv_len = ts->info->auxv_len;
if (offset >= auxv_len) {
gdb_put_packet("E00");
return;
}
if (len > (MAX_PACKET_LENGTH - 5) / 2) {
len = (MAX_PACKET_LENGTH - 5) / 2;
}
if (len < auxv_len - offset) {
g_string_assign(gdbserver_state.str_buf, "m");
} else {
g_string_assign(gdbserver_state.str_buf, "l");
len = auxv_len - offset;
}
g_byte_array_set_size(gdbserver_state.mem_buf, len);
if (target_memory_rw_debug(gdbserver_state.g_cpu, saved_auxv + offset,
gdbserver_state.mem_buf->data, len, false)) {
gdb_put_packet("E14");
return;
}
gdb_memtox(gdbserver_state.str_buf,
(const char *)gdbserver_state.mem_buf->data, len);
gdb_put_packet_binary(gdbserver_state.str_buf->str,
gdbserver_state.str_buf->len, true);
}
#endif

View file

@ -10,10 +10,354 @@
*/
#include "qemu/osdep.h"
#include "qemu/cutils.h"
#include "qemu/sockets.h"
#include "exec/hwaddr.h"
#include "exec/tb-flush.h"
#include "exec/gdbstub.h"
#include "gdbstub/user.h"
#include "hw/core/cpu.h"
#include "trace.h"
#include "internals.h"
/* User-mode specific state */
typedef struct {
int fd;
char *socket_path;
int running_state;
} GDBUserState;
static GDBUserState gdbserver_user_state;
int gdb_get_char(void)
{
uint8_t ch;
int ret;
for (;;) {
ret = recv(gdbserver_user_state.fd, &ch, 1, 0);
if (ret < 0) {
if (errno == ECONNRESET) {
gdbserver_user_state.fd = -1;
}
if (errno != EINTR) {
return -1;
}
} else if (ret == 0) {
close(gdbserver_user_state.fd);
gdbserver_user_state.fd = -1;
return -1;
} else {
break;
}
}
return ch;
}
void gdb_put_buffer(const uint8_t *buf, int len)
{
int ret;
while (len > 0) {
ret = send(gdbserver_user_state.fd, buf, len, 0);
if (ret < 0) {
if (errno != EINTR) {
return;
}
} else {
buf += ret;
len -= ret;
}
}
}
/* Tell the remote gdb that the process has exited. */
void gdb_exit(int code)
{
char buf[4];
if (!gdbserver_state.init) {
return;
}
if (gdbserver_user_state.socket_path) {
unlink(gdbserver_user_state.socket_path);
}
if (gdbserver_user_state.fd < 0) {
return;
}
trace_gdbstub_op_exiting((uint8_t)code);
snprintf(buf, sizeof(buf), "W%02x", (uint8_t)code);
gdb_put_packet(buf);
}
int gdb_handlesig(CPUState *cpu, int sig)
{
char buf[256];
int n;
if (!gdbserver_state.init || gdbserver_user_state.fd < 0) {
return sig;
}
/* disable single step if it was enabled */
cpu_single_step(cpu, 0);
tb_flush(cpu);
if (sig != 0) {
gdb_set_stop_cpu(cpu);
g_string_printf(gdbserver_state.str_buf,
"T%02xthread:", gdb_target_signal_to_gdb(sig));
gdb_append_thread_id(cpu, gdbserver_state.str_buf);
g_string_append_c(gdbserver_state.str_buf, ';');
gdb_put_strbuf();
}
/*
* gdb_put_packet() might have detected that the peer terminated the
* connection.
*/
if (gdbserver_user_state.fd < 0) {
return sig;
}
sig = 0;
gdbserver_state.state = RS_IDLE;
gdbserver_user_state.running_state = 0;
while (gdbserver_user_state.running_state == 0) {
n = read(gdbserver_user_state.fd, buf, 256);
if (n > 0) {
int i;
for (i = 0; i < n; i++) {
gdb_read_byte(buf[i]);
}
} else {
/*
* XXX: Connection closed. Should probably wait for another
* connection before continuing.
*/
if (n == 0) {
close(gdbserver_user_state.fd);
}
gdbserver_user_state.fd = -1;
return sig;
}
}
sig = gdbserver_state.signal;
gdbserver_state.signal = 0;
return sig;
}
/* Tell the remote gdb that the process has exited due to SIG. */
void gdb_signalled(CPUArchState *env, int sig)
{
char buf[4];
if (!gdbserver_state.init || gdbserver_user_state.fd < 0) {
return;
}
snprintf(buf, sizeof(buf), "X%02x", gdb_target_signal_to_gdb(sig));
gdb_put_packet(buf);
}
static void gdb_accept_init(int fd)
{
gdb_init_gdbserver_state();
gdb_create_default_process(&gdbserver_state);
gdbserver_state.processes[0].attached = true;
gdbserver_state.c_cpu = gdb_first_attached_cpu();
gdbserver_state.g_cpu = gdbserver_state.c_cpu;
gdbserver_user_state.fd = fd;
gdb_has_xml = false;
}
static bool gdb_accept_socket(int gdb_fd)
{
int fd;
for (;;) {
fd = accept(gdb_fd, NULL, NULL);
if (fd < 0 && errno != EINTR) {
perror("accept socket");
return false;
} else if (fd >= 0) {
qemu_set_cloexec(fd);
break;
}
}
gdb_accept_init(fd);
return true;
}
static int gdbserver_open_socket(const char *path)
{
struct sockaddr_un sockaddr = {};
int fd, ret;
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
perror("create socket");
return -1;
}
sockaddr.sun_family = AF_UNIX;
pstrcpy(sockaddr.sun_path, sizeof(sockaddr.sun_path) - 1, path);
ret = bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
if (ret < 0) {
perror("bind socket");
close(fd);
return -1;
}
ret = listen(fd, 1);
if (ret < 0) {
perror("listen socket");
close(fd);
return -1;
}
return fd;
}
static bool gdb_accept_tcp(int gdb_fd)
{
struct sockaddr_in sockaddr = {};
socklen_t len;
int fd;
for (;;) {
len = sizeof(sockaddr);
fd = accept(gdb_fd, (struct sockaddr *)&sockaddr, &len);
if (fd < 0 && errno != EINTR) {
perror("accept");
return false;
} else if (fd >= 0) {
qemu_set_cloexec(fd);
break;
}
}
/* set short latency */
if (socket_set_nodelay(fd)) {
perror("setsockopt");
close(fd);
return false;
}
gdb_accept_init(fd);
return true;
}
static int gdbserver_open_port(int port)
{
struct sockaddr_in sockaddr;
int fd, ret;
fd = socket(PF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
return -1;
}
qemu_set_cloexec(fd);
socket_set_fast_reuse(fd);
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(port);
sockaddr.sin_addr.s_addr = 0;
ret = bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
if (ret < 0) {
perror("bind");
close(fd);
return -1;
}
ret = listen(fd, 1);
if (ret < 0) {
perror("listen");
close(fd);
return -1;
}
return fd;
}
int gdbserver_start(const char *port_or_path)
{
int port = g_ascii_strtoull(port_or_path, NULL, 10);
int gdb_fd;
if (port > 0) {
gdb_fd = gdbserver_open_port(port);
} else {
gdb_fd = gdbserver_open_socket(port_or_path);
}
if (gdb_fd < 0) {
return -1;
}
if (port > 0 && gdb_accept_tcp(gdb_fd)) {
return 0;
} else if (gdb_accept_socket(gdb_fd)) {
gdbserver_user_state.socket_path = g_strdup(port_or_path);
return 0;
}
/* gone wrong */
close(gdb_fd);
return -1;
}
/* Disable gdb stub for child processes. */
void gdbserver_fork(CPUState *cpu)
{
if (!gdbserver_state.init || gdbserver_user_state.fd < 0) {
return;
}
close(gdbserver_user_state.fd);
gdbserver_user_state.fd = -1;
cpu_breakpoint_remove_all(cpu, BP_GDB);
/* no cpu_watchpoint_remove_all for user-mode */
}
/*
* Execution state helpers
*/
void gdb_continue(void)
{
gdbserver_user_state.running_state = 1;
trace_gdbstub_op_continue();
}
/*
* Resume execution, for user-mode emulation it's equivalent to
* gdb_continue.
*/
int gdb_continue_partial(char *newstates)
{
CPUState *cpu;
int res = 0;
/*
* This is not exactly accurate, but it's an improvement compared to the
* previous situation, where only one CPU would be single-stepped.
*/
CPU_FOREACH(cpu) {
if (newstates[cpu->cpu_index] == 's') {
trace_gdbstub_op_stepping(cpu->cpu_index);
cpu_single_step(cpu, gdbserver_state.sstep_flags);
}
}
gdbserver_user_state.running_state = 1;
return res;
}
/*
* Break/Watch point helpers
*/
bool gdb_supports_guest_debug(void)
{
/* user-mode == TCG == supported */

View file

@ -103,27 +103,6 @@ void gdb_do_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...);
void gdb_do_syscallv(gdb_syscall_complete_cb cb, const char *fmt, va_list va);
int use_gdb_syscalls(void);
#ifdef CONFIG_USER_ONLY
/**
* gdb_handlesig: yield control to gdb
* @cpu: CPU
* @sig: if non-zero, the signal number which caused us to stop
*
* This function yields control to gdb, when a user-mode-only target
* needs to stop execution. If @sig is non-zero, then we will send a
* stop packet to tell gdb that we have stopped because of this signal.
*
* This function will block (handling protocol requests from gdb)
* until gdb tells us to continue target execution. When it does
* return, the return value is a signal to deliver to the target,
* or 0 if no signal should be delivered, ie the signal that caused
* us to stop should be ignored.
*/
int gdb_handlesig(CPUState *, int);
void gdb_signalled(CPUArchState *, int);
void gdbserver_fork(CPUState *);
#endif
/* Get or set a register. Returns the size of the register. */
typedef int (*gdb_get_reg_cb)(CPUArchState *env, GByteArray *buf, int reg);
typedef int (*gdb_set_reg_cb)(CPUArchState *env, uint8_t *buf, int reg);

43
include/gdbstub/user.h Normal file
View file

@ -0,0 +1,43 @@
/*
* gdbstub user-mode only APIs
*
* Copyright (c) 2022 Linaro Ltd
*
* SPDX-License-Identifier: LGPL-2.0+
*/
#ifndef GDBSTUB_USER_H
#define GDBSTUB_USER_H
/**
* gdb_handlesig() - yield control to gdb
* @cpu: CPU
* @sig: if non-zero, the signal number which caused us to stop
*
* This function yields control to gdb, when a user-mode-only target
* needs to stop execution. If @sig is non-zero, then we will send a
* stop packet to tell gdb that we have stopped because of this signal.
*
* This function will block (handling protocol requests from gdb)
* until gdb tells us to continue target execution. When it does
* return, the return value is a signal to deliver to the target,
* or 0 if no signal should be delivered, ie the signal that caused
* us to stop should be ignored.
*/
int gdb_handlesig(CPUState *, int);
/**
* gdb_signalled() - inform remote gdb of sig exit
* @as: current CPUArchState
* @sig: signal number
*/
void gdb_signalled(CPUArchState *as, int sig);
/**
* gdbserver_fork() - disable gdb stub for child processes.
* @cs: CPU
*/
void gdbserver_fork(CPUState *cs);
#endif /* GDBSTUB_USER_H */

View file

@ -40,6 +40,7 @@
#include "qemu/plugin.h"
#include "exec/exec-all.h"
#include "exec/gdbstub.h"
#include "gdbstub/user.h"
#include "tcg/tcg.h"
#include "qemu/timer.h"
#include "qemu/envlist.h"

View file

@ -18,7 +18,7 @@
*/
#include "qemu/osdep.h"
#include "qemu/bitops.h"
#include "exec/gdbstub.h"
#include "gdbstub/user.h"
#include "hw/core/tcg-cpu-ops.h"
#include <sys/ucontext.h>