aboutsummaryrefslogtreecommitdiff
path: root/gdbstub
diff options
context:
space:
mode:
authorAlex Bennée <alex.bennee@linaro.org>2023-03-02 18:57:57 -0800
committerAlex Bennée <alex.bennee@linaro.org>2023-03-07 20:44:08 +0000
commitc566080cd37fe328077a3c49d7fd248ce2a06bfe (patch)
tree356f3d4dd8d4e3e0303c250c57d1e5cf7f296e87 /gdbstub
parent4ea5fe997db4c5d893b69072f488880c07857a54 (diff)
gdbstub: move syscall handling to new file
Our GDB syscall support is the last chunk of code that needs target specific support so move it to a new file. We take the opportunity to move the syscall state into its own singleton instance and add in a few helpers for the main gdbstub to interact with the module. I also moved the gdb_exit() declaration into syscalls.h as it feels pretty related and most of the callers of it treat it as such. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-Id: <20230302190846.2593720-22-alex.bennee@linaro.org> Message-Id: <20230303025805.625589-22-richard.henderson@linaro.org>
Diffstat (limited to 'gdbstub')
-rw-r--r--gdbstub/gdbstub.c177
-rw-r--r--gdbstub/internals.h8
-rw-r--r--gdbstub/meson.build4
-rw-r--r--gdbstub/softmmu.c7
-rw-r--r--gdbstub/syscalls.c235
-rw-r--r--gdbstub/user.c1
6 files changed, 253 insertions, 179 deletions
diff --git a/gdbstub/gdbstub.c b/gdbstub/gdbstub.c
index f1504af44f..e264ed04e7 100644
--- a/gdbstub/gdbstub.c
+++ b/gdbstub/gdbstub.c
@@ -29,6 +29,7 @@
#include "qemu/module.h"
#include "trace.h"
#include "exec/gdbstub.h"
+#include "gdbstub/syscalls.h"
#ifdef CONFIG_USER_ONLY
#include "gdbstub/user.h"
#else
@@ -38,7 +39,6 @@
#include "sysemu/hw_accel.h"
#include "sysemu/runstate.h"
-#include "semihosting/semihost.h"
#include "exec/exec-all.h"
#include "exec/replay-core.h"
#include "exec/tb-flush.h"
@@ -78,41 +78,6 @@ void gdb_init_gdbserver_state(void)
bool gdb_has_xml;
-/*
- * Return true if there is a GDB currently connected to the stub
- * and attached to a CPU
- */
-static bool gdb_attached(void)
-{
- return gdbserver_state.init && gdbserver_state.c_cpu;
-}
-
-static enum {
- GDB_SYS_UNKNOWN,
- GDB_SYS_ENABLED,
- GDB_SYS_DISABLED,
-} gdb_syscall_mode;
-
-/* Decide if either remote gdb syscalls or native file IO should be used. */
-int use_gdb_syscalls(void)
-{
- SemihostingTarget target = semihosting_get_target();
- if (target == SEMIHOSTING_TARGET_NATIVE) {
- /* -semihosting-config target=native */
- return false;
- } else if (target == SEMIHOSTING_TARGET_GDB) {
- /* -semihosting-config target=gdb */
- return true;
- }
-
- /* -semihosting-config target=auto */
- /* On the first call check if gdb is connected and remember. */
- if (gdb_syscall_mode == GDB_SYS_UNKNOWN) {
- gdb_syscall_mode = gdb_attached() ? GDB_SYS_ENABLED : GDB_SYS_DISABLED;
- }
- return gdb_syscall_mode == GDB_SYS_ENABLED;
-}
-
/* writes 2*len+1 bytes in buf */
void gdb_memtohex(GString *buf, const uint8_t *mem, int len)
{
@@ -922,7 +887,7 @@ static void handle_detach(GArray *params, void *user_ctx)
if (!gdbserver_state.c_cpu) {
/* No more process attached */
- gdb_syscall_mode = GDB_SYS_DISABLED;
+ gdb_disable_syscalls();
gdb_continue();
}
gdb_put_packet("OK");
@@ -1235,60 +1200,6 @@ static void handle_read_all_regs(GArray *params, void *user_ctx)
gdb_put_strbuf();
}
-static void handle_file_io(GArray *params, void *user_ctx)
-{
- if (params->len >= 1 && gdbserver_state.current_syscall_cb) {
- uint64_t ret;
- int err;
-
- ret = get_param(params, 0)->val_ull;
- if (params->len >= 2) {
- err = get_param(params, 1)->val_ull;
- } else {
- err = 0;
- }
-
- /* Convert GDB error numbers back to host error numbers. */
-#define E(X) case GDB_E##X: err = E##X; break
- switch (err) {
- case 0:
- break;
- E(PERM);
- E(NOENT);
- E(INTR);
- E(BADF);
- E(ACCES);
- E(FAULT);
- E(BUSY);
- E(EXIST);
- E(NODEV);
- E(NOTDIR);
- E(ISDIR);
- E(INVAL);
- E(NFILE);
- E(MFILE);
- E(FBIG);
- E(NOSPC);
- E(SPIPE);
- E(ROFS);
- E(NAMETOOLONG);
- default:
- err = EINVAL;
- break;
- }
-#undef E
-
- gdbserver_state.current_syscall_cb(gdbserver_state.c_cpu, ret, err);
- gdbserver_state.current_syscall_cb = NULL;
- }
-
- if (params->len >= 3 && get_param(params, 2)->opcode == (uint8_t)'C') {
- gdb_put_packet("T02");
- return;
- }
-
- gdb_continue();
-}
static void handle_step(GArray *params, void *user_ctx)
{
@@ -1894,7 +1805,7 @@ static int gdb_handle_packet(const char *line_buf)
case 'F':
{
static const GdbCmdParseEntry file_io_cmd_desc = {
- .handler = handle_file_io,
+ .handler = gdb_handle_file_io,
.cmd = "F",
.cmd_startswith = 1,
.schema = "L,L,o0"
@@ -2062,88 +1973,6 @@ void gdb_set_stop_cpu(CPUState *cpu)
gdbserver_state.g_cpu = cpu;
}
-/* Send a gdb syscall request.
- This accepts limited printf-style format specifiers, specifically:
- %x - target_ulong argument printed in hex.
- %lx - 64-bit argument printed in hex.
- %s - string pointer (target_ulong) and length (int) pair. */
-void gdb_do_syscallv(gdb_syscall_complete_cb cb, const char *fmt, va_list va)
-{
- char *p;
- char *p_end;
- target_ulong addr;
- uint64_t i64;
-
- if (!gdb_attached()) {
- return;
- }
-
- gdbserver_state.current_syscall_cb = cb;
-#ifndef CONFIG_USER_ONLY
- vm_stop(RUN_STATE_DEBUG);
-#endif
- p = &gdbserver_state.syscall_buf[0];
- p_end = &gdbserver_state.syscall_buf[sizeof(gdbserver_state.syscall_buf)];
- *(p++) = 'F';
- while (*fmt) {
- if (*fmt == '%') {
- fmt++;
- switch (*fmt++) {
- case 'x':
- addr = va_arg(va, target_ulong);
- p += snprintf(p, p_end - p, TARGET_FMT_lx, addr);
- break;
- case 'l':
- if (*(fmt++) != 'x')
- goto bad_format;
- i64 = va_arg(va, uint64_t);
- p += snprintf(p, p_end - p, "%" PRIx64, i64);
- break;
- case 's':
- addr = va_arg(va, target_ulong);
- p += snprintf(p, p_end - p, TARGET_FMT_lx "/%x",
- addr, va_arg(va, int));
- break;
- default:
- bad_format:
- error_report("gdbstub: Bad syscall format string '%s'",
- fmt - 1);
- break;
- }
- } else {
- *(p++) = *(fmt++);
- }
- }
- *p = 0;
-#ifdef CONFIG_USER_ONLY
- gdb_put_packet(gdbserver_state.syscall_buf);
- /* Return control to gdb for it to process the syscall request.
- * Since the protocol requires that gdb hands control back to us
- * using a "here are the results" F packet, we don't need to check
- * gdb_handlesig's return value (which is the signal to deliver if
- * execution was resumed via a continue packet).
- */
- gdb_handlesig(gdbserver_state.c_cpu, 0);
-#else
- /* In this case wait to send the syscall packet until notification that
- the CPU has stopped. This must be done because if the packet is sent
- now the reply from the syscall request could be received while the CPU
- is still in the running state, which can cause packets to be dropped
- and state transition 'T' packets to be sent while the syscall is still
- being processed. */
- qemu_cpu_kick(gdbserver_state.c_cpu);
-#endif
-}
-
-void gdb_do_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...)
-{
- va_list va;
-
- va_start(va, fmt);
- gdb_do_syscallv(cb, fmt, va);
- va_end(va);
-}
-
void gdb_read_byte(uint8_t ch)
{
uint8_t reply;
diff --git a/gdbstub/internals.h b/gdbstub/internals.h
index be0eef4850..8db61f7fb4 100644
--- a/gdbstub/internals.h
+++ b/gdbstub/internals.h
@@ -61,8 +61,6 @@ typedef struct GDBState {
bool multiprocess;
GDBProcess *processes;
int process_num;
- char syscall_buf[256];
- gdb_syscall_complete_cb current_syscall_cb;
GString *str_buf;
GByteArray *mem_buf;
int sstep_flags;
@@ -191,6 +189,12 @@ void gdb_handle_query_attached(GArray *params, void *user_ctx); /* both */
void gdb_handle_query_qemu_phy_mem_mode(GArray *params, void *user_ctx);
void gdb_handle_set_qemu_phy_mem_mode(GArray *params, void *user_ctx);
+/* sycall handling */
+void gdb_handle_file_io(GArray *params, void *user_ctx);
+bool gdb_handled_syscall(void);
+void gdb_disable_syscalls(void);
+void gdb_syscall_reset(void);
+
/*
* Break/Watch point support - there is an implementation for softmmu
* and user mode.
diff --git a/gdbstub/meson.build b/gdbstub/meson.build
index 773bd4b9c9..c876222b9c 100644
--- a/gdbstub/meson.build
+++ b/gdbstub/meson.build
@@ -5,6 +5,10 @@
#
specific_ss.add(files('gdbstub.c'))
+
+# These have to built to the target ABI
+specific_ss.add(files('syscalls.c'))
+
softmmu_ss.add(files('softmmu.c'))
user_ss.add(files('user.c'))
diff --git a/gdbstub/softmmu.c b/gdbstub/softmmu.c
index d2863d0663..d3152fb6e7 100644
--- a/gdbstub/softmmu.c
+++ b/gdbstub/softmmu.c
@@ -15,6 +15,7 @@
#include "qemu/error-report.h"
#include "qemu/cutils.h"
#include "exec/gdbstub.h"
+#include "gdbstub/syscalls.h"
#include "exec/hwaddr.h"
#include "exec/tb-flush.h"
#include "sysemu/cpus.h"
@@ -113,9 +114,9 @@ static void gdb_vm_state_change(void *opaque, bool running, RunState state)
if (running || gdbserver_state.state == RS_INACTIVE) {
return;
}
+
/* Is there a GDB syscall waiting to be sent? */
- if (gdbserver_state.current_syscall_cb) {
- gdb_put_packet(gdbserver_state.syscall_buf);
+ if (gdb_handled_syscall()) {
return;
}
@@ -384,7 +385,7 @@ int gdbserver_start(const char *device)
}
gdbserver_state.state = chr ? RS_IDLE : RS_INACTIVE;
gdbserver_system_state.mon_chr = mon_chr;
- gdbserver_state.current_syscall_cb = NULL;
+ gdb_syscall_reset();
return 0;
}
diff --git a/gdbstub/syscalls.c b/gdbstub/syscalls.c
new file mode 100644
index 0000000000..46537938d5
--- /dev/null
+++ b/gdbstub/syscalls.c
@@ -0,0 +1,235 @@
+/*
+ * GDB Syscall Handling
+ *
+ * GDB can execute syscalls on the guests behalf, currently used by
+ * the various semihosting extensions. As this interfaces with a guest
+ * ABI we need to build it per-guest (although in reality its a 32 or
+ * 64 bit target_ulong that is the only difference).
+ *
+ * Copyright (c) 2003-2005 Fabrice Bellard
+ * Copyright (c) 2023 Linaro Ltd
+ *
+ * SPDX-License-Identifier: LGPL-2.0+
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "cpu.h"
+#include "semihosting/semihost.h"
+#include "sysemu/runstate.h"
+#include "gdbstub/user.h"
+#include "gdbstub/syscalls.h"
+#include "trace.h"
+#include "internals.h"
+
+/* Syscall specific state */
+typedef struct {
+ char syscall_buf[256];
+ gdb_syscall_complete_cb current_syscall_cb;
+} GDBSyscallState;
+
+static GDBSyscallState gdbserver_syscall_state;
+
+/*
+ * Return true if there is a GDB currently connected to the stub
+ * and attached to a CPU
+ */
+static bool gdb_attached(void)
+{
+ return gdbserver_state.init && gdbserver_state.c_cpu;
+}
+
+static enum {
+ GDB_SYS_UNKNOWN,
+ GDB_SYS_ENABLED,
+ GDB_SYS_DISABLED,
+} gdb_syscall_mode;
+
+/* Decide if either remote gdb syscalls or native file IO should be used. */
+int use_gdb_syscalls(void)
+{
+ SemihostingTarget target = semihosting_get_target();
+ if (target == SEMIHOSTING_TARGET_NATIVE) {
+ /* -semihosting-config target=native */
+ return false;
+ } else if (target == SEMIHOSTING_TARGET_GDB) {
+ /* -semihosting-config target=gdb */
+ return true;
+ }
+
+ /* -semihosting-config target=auto */
+ /* On the first call check if gdb is connected and remember. */
+ if (gdb_syscall_mode == GDB_SYS_UNKNOWN) {
+ gdb_syscall_mode = gdb_attached() ? GDB_SYS_ENABLED : GDB_SYS_DISABLED;
+ }
+ return gdb_syscall_mode == GDB_SYS_ENABLED;
+}
+
+/* called when the stub detaches */
+void gdb_disable_syscalls(void)
+{
+ gdb_syscall_mode = GDB_SYS_DISABLED;
+}
+
+void gdb_syscall_reset(void)
+{
+ gdbserver_syscall_state.current_syscall_cb = NULL;
+}
+
+bool gdb_handled_syscall(void)
+{
+ if (gdbserver_syscall_state.current_syscall_cb) {
+ gdb_put_packet(gdbserver_syscall_state.syscall_buf);
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Send a gdb syscall request.
+ * This accepts limited printf-style format specifiers, specifically:
+ * %x - target_ulong argument printed in hex.
+ * %lx - 64-bit argument printed in hex.
+ * %s - string pointer (target_ulong) and length (int) pair.
+ */
+void gdb_do_syscallv(gdb_syscall_complete_cb cb, const char *fmt, va_list va)
+{
+ char *p;
+ char *p_end;
+ target_ulong addr;
+ uint64_t i64;
+
+ if (!gdb_attached()) {
+ return;
+ }
+
+ gdbserver_syscall_state.current_syscall_cb = cb;
+#ifndef CONFIG_USER_ONLY
+ vm_stop(RUN_STATE_DEBUG);
+#endif
+ p = &gdbserver_syscall_state.syscall_buf[0];
+ p_end = &gdbserver_syscall_state.syscall_buf[sizeof(gdbserver_syscall_state.syscall_buf)];
+ *(p++) = 'F';
+ while (*fmt) {
+ if (*fmt == '%') {
+ fmt++;
+ switch (*fmt++) {
+ case 'x':
+ addr = va_arg(va, target_ulong);
+ p += snprintf(p, p_end - p, TARGET_FMT_lx, addr);
+ break;
+ case 'l':
+ if (*(fmt++) != 'x') {
+ goto bad_format;
+ }
+ i64 = va_arg(va, uint64_t);
+ p += snprintf(p, p_end - p, "%" PRIx64, i64);
+ break;
+ case 's':
+ addr = va_arg(va, target_ulong);
+ p += snprintf(p, p_end - p, TARGET_FMT_lx "/%x",
+ addr, va_arg(va, int));
+ break;
+ default:
+ bad_format:
+ error_report("gdbstub: Bad syscall format string '%s'",
+ fmt - 1);
+ break;
+ }
+ } else {
+ *(p++) = *(fmt++);
+ }
+ }
+ *p = 0;
+#ifdef CONFIG_USER_ONLY
+ gdb_put_packet(gdbserver_syscall_state.syscall_buf);
+ /*
+ * Return control to gdb for it to process the syscall request.
+ * Since the protocol requires that gdb hands control back to us
+ * using a "here are the results" F packet, we don't need to check
+ * gdb_handlesig's return value (which is the signal to deliver if
+ * execution was resumed via a continue packet).
+ */
+ gdb_handlesig(gdbserver_state.c_cpu, 0);
+#else
+ /*
+ * In this case wait to send the syscall packet until notification that
+ * the CPU has stopped. This must be done because if the packet is sent
+ * now the reply from the syscall request could be received while the CPU
+ * is still in the running state, which can cause packets to be dropped
+ * and state transition 'T' packets to be sent while the syscall is still
+ * being processed.
+ */
+ qemu_cpu_kick(gdbserver_state.c_cpu);
+#endif
+}
+
+void gdb_do_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ gdb_do_syscallv(cb, fmt, va);
+ va_end(va);
+}
+
+/*
+ * GDB Command Handlers
+ */
+
+void gdb_handle_file_io(GArray *params, void *user_ctx)
+{
+ if (params->len >= 1 && gdbserver_syscall_state.current_syscall_cb) {
+ uint64_t ret;
+ int err;
+
+ ret = get_param(params, 0)->val_ull;
+ if (params->len >= 2) {
+ err = get_param(params, 1)->val_ull;
+ } else {
+ err = 0;
+ }
+
+ /* Convert GDB error numbers back to host error numbers. */
+#define E(X) case GDB_E##X: err = E##X; break
+ switch (err) {
+ case 0:
+ break;
+ E(PERM);
+ E(NOENT);
+ E(INTR);
+ E(BADF);
+ E(ACCES);
+ E(FAULT);
+ E(BUSY);
+ E(EXIST);
+ E(NODEV);
+ E(NOTDIR);
+ E(ISDIR);
+ E(INVAL);
+ E(NFILE);
+ E(MFILE);
+ E(FBIG);
+ E(NOSPC);
+ E(SPIPE);
+ E(ROFS);
+ E(NAMETOOLONG);
+ default:
+ err = EINVAL;
+ break;
+ }
+#undef E
+
+ gdbserver_syscall_state.current_syscall_cb(gdbserver_state.c_cpu,
+ ret, err);
+ gdbserver_syscall_state.current_syscall_cb = NULL;
+ }
+
+ if (params->len >= 3 && get_param(params, 2)->opcode == (uint8_t)'C') {
+ gdb_put_packet("T02");
+ return;
+ }
+
+ gdb_continue();
+}
diff --git a/gdbstub/user.c b/gdbstub/user.c
index 3f6183e66a..3da410e221 100644
--- a/gdbstub/user.c
+++ b/gdbstub/user.c
@@ -15,6 +15,7 @@
#include "exec/hwaddr.h"
#include "exec/tb-flush.h"
#include "exec/gdbstub.h"
+#include "gdbstub/syscalls.h"
#include "gdbstub/user.h"
#include "hw/core/cpu.h"
#include "trace.h"