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;

View file

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

View file

@ -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'))

View file

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

235
gdbstub/syscalls.c Normal file
View file

@ -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();
}

View file

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