diff options
author | Alex Bennée <alex.bennee@linaro.org> | 2023-03-02 18:57:57 -0800 |
---|---|---|
committer | Alex Bennée <alex.bennee@linaro.org> | 2023-03-07 20:44:08 +0000 |
commit | c566080cd37fe328077a3c49d7fd248ce2a06bfe (patch) | |
tree | 356f3d4dd8d4e3e0303c250c57d1e5cf7f296e87 /gdbstub | |
parent | 4ea5fe997db4c5d893b69072f488880c07857a54 (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.c | 177 | ||||
-rw-r--r-- | gdbstub/internals.h | 8 | ||||
-rw-r--r-- | gdbstub/meson.build | 4 | ||||
-rw-r--r-- | gdbstub/softmmu.c | 7 | ||||
-rw-r--r-- | gdbstub/syscalls.c | 235 | ||||
-rw-r--r-- | gdbstub/user.c | 1 |
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" |