Browse Source
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>pull/232/head
17 changed files with 387 additions and 288 deletions
@ -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(); |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
/*
|
|||
* GDB Syscall support |
|||
* |
|||
* Copyright (c) 2023 Linaro Ltd |
|||
* |
|||
* SPDX-License-Identifier: LGPL-2.0+ |
|||
*/ |
|||
|
|||
#ifndef _SYSCALLS_H_ |
|||
#define _SYSCALLS_H_ |
|||
|
|||
/* For gdb file i/o remote protocol open flags. */ |
|||
#define GDB_O_RDONLY 0 |
|||
#define GDB_O_WRONLY 1 |
|||
#define GDB_O_RDWR 2 |
|||
#define GDB_O_APPEND 8 |
|||
#define GDB_O_CREAT 0x200 |
|||
#define GDB_O_TRUNC 0x400 |
|||
#define GDB_O_EXCL 0x800 |
|||
|
|||
/* For gdb file i/o remote protocol errno values */ |
|||
#define GDB_EPERM 1 |
|||
#define GDB_ENOENT 2 |
|||
#define GDB_EINTR 4 |
|||
#define GDB_EBADF 9 |
|||
#define GDB_EACCES 13 |
|||
#define GDB_EFAULT 14 |
|||
#define GDB_EBUSY 16 |
|||
#define GDB_EEXIST 17 |
|||
#define GDB_ENODEV 19 |
|||
#define GDB_ENOTDIR 20 |
|||
#define GDB_EISDIR 21 |
|||
#define GDB_EINVAL 22 |
|||
#define GDB_ENFILE 23 |
|||
#define GDB_EMFILE 24 |
|||
#define GDB_EFBIG 27 |
|||
#define GDB_ENOSPC 28 |
|||
#define GDB_ESPIPE 29 |
|||
#define GDB_EROFS 30 |
|||
#define GDB_ENAMETOOLONG 91 |
|||
#define GDB_EUNKNOWN 9999 |
|||
|
|||
/* For gdb file i/o remote protocol lseek whence. */ |
|||
#define GDB_SEEK_SET 0 |
|||
#define GDB_SEEK_CUR 1 |
|||
#define GDB_SEEK_END 2 |
|||
|
|||
/* For gdb file i/o stat/fstat. */ |
|||
typedef uint32_t gdb_mode_t; |
|||
typedef uint32_t gdb_time_t; |
|||
|
|||
struct gdb_stat { |
|||
uint32_t gdb_st_dev; /* device */ |
|||
uint32_t gdb_st_ino; /* inode */ |
|||
gdb_mode_t gdb_st_mode; /* protection */ |
|||
uint32_t gdb_st_nlink; /* number of hard links */ |
|||
uint32_t gdb_st_uid; /* user ID of owner */ |
|||
uint32_t gdb_st_gid; /* group ID of owner */ |
|||
uint32_t gdb_st_rdev; /* device type (if inode device) */ |
|||
uint64_t gdb_st_size; /* total size, in bytes */ |
|||
uint64_t gdb_st_blksize; /* blocksize for filesystem I/O */ |
|||
uint64_t gdb_st_blocks; /* number of blocks allocated */ |
|||
gdb_time_t gdb_st_atime; /* time of last access */ |
|||
gdb_time_t gdb_st_mtime; /* time of last modification */ |
|||
gdb_time_t gdb_st_ctime; /* time of last change */ |
|||
} QEMU_PACKED; |
|||
|
|||
struct gdb_timeval { |
|||
gdb_time_t tv_sec; /* second */ |
|||
uint64_t tv_usec; /* microsecond */ |
|||
} QEMU_PACKED; |
|||
|
|||
typedef void (*gdb_syscall_complete_cb)(CPUState *cpu, uint64_t ret, int err); |
|||
|
|||
/**
|
|||
* gdb_do_syscall: |
|||
* @cb: function to call when the system call has completed |
|||
* @fmt: gdb syscall format string |
|||
* ...: list of arguments to interpolate into @fmt |
|||
* |
|||
* Send a GDB syscall request. This function will return immediately; |
|||
* the callback function will be called later when the remote system |
|||
* call has completed. |
|||
* |
|||
* @fmt should be in the 'call-id,parameter,parameter...' format documented |
|||
* for the F request packet in the GDB remote protocol. A limited set of |
|||
* printf-style format specifiers is supported: |
|||
* %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_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...); |
|||
|
|||
/**
|
|||
* gdb_do_syscallv: |
|||
* @cb: function to call when the system call has completed |
|||
* @fmt: gdb syscall format string |
|||
* @va: arguments to interpolate into @fmt |
|||
* |
|||
* As gdb_do_syscall, but taking a va_list rather than a variable |
|||
* argument list. |
|||
*/ |
|||
void gdb_do_syscallv(gdb_syscall_complete_cb cb, const char *fmt, va_list va); |
|||
|
|||
/**
|
|||
* use_gdb_syscalls() - report if GDB should be used for syscalls |
|||
* |
|||
* This is mostly driven by the semihosting mode the user configures |
|||
* but assuming GDB is allowed by that we report true if GDB is |
|||
* connected to the stub. |
|||
*/ |
|||
int use_gdb_syscalls(void); |
|||
|
|||
/**
|
|||
* gdb_exit: exit gdb session, reporting inferior status |
|||
* @code: exit code reported |
|||
* |
|||
* This closes the session and sends a final packet to GDB reporting |
|||
* the exit status of the program. It also cleans up any connections |
|||
* detritus before returning. |
|||
*/ |
|||
void gdb_exit(int code); |
|||
|
|||
#endif /* _SYSCALLS_H_ */ |
|||
Loading…
Reference in new issue