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>
This commit is contained in:
Alex Bennée 2023-03-02 18:57:57 -08:00
parent 4ea5fe997d
commit c566080cd3
17 changed files with 387 additions and 288 deletions

View file

@ -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;