qemu/gdbstub/syscalls.c
Gustavo Romero 133f202b19 gdbstub: Move GdbCmdParseEntry into a new header file
Move GdbCmdParseEntry and its associated types into a separate header
file to allow the use of GdbCmdParseEntry and other gdbstub command
functions outside of gdbstub.c.

Since GdbCmdParseEntry and get_param are now public, kdoc
GdbCmdParseEntry and rename get_param to gdb_get_cmd_param.

This commit also makes gdb_put_packet public since is used in gdbstub
command handling.

Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org>
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Message-Id: <20240628050850.536447-3-gustavo.romero@linaro.org>
Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
Message-Id: <20240705084047.857176-32-alex.bennee@linaro.org>
2024-07-05 12:35:01 +01:00

207 lines
5.1 KiB
C

/*
* GDB Syscall Handling
*
* GDB can execute syscalls on the guests behalf, currently used by
* the various semihosting extensions.
*
* 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 "semihosting/semihost.h"
#include "sysemu/runstate.h"
#include "gdbstub/user.h"
#include "gdbstub/syscalls.h"
#include "gdbstub/commands.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_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...)
{
char *p, *p_end;
va_list va;
if (!gdb_attached()) {
return;
}
gdbserver_syscall_state.current_syscall_cb = cb;
va_start(va, fmt);
p = gdbserver_syscall_state.syscall_buf;
p_end = p + sizeof(gdbserver_syscall_state.syscall_buf);
*(p++) = 'F';
while (*fmt) {
if (*fmt == '%') {
uint64_t i64;
uint32_t i32;
fmt++;
switch (*fmt++) {
case 'x':
i32 = va_arg(va, uint32_t);
p += snprintf(p, p_end - p, "%" PRIx32, i32);
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':
i64 = va_arg(va, uint64_t);
i32 = va_arg(va, uint32_t);
p += snprintf(p, p_end - p, "%" PRIx64 "/%x" PRIx32, i64, i32);
break;
default:
bad_format:
error_report("gdbstub: Bad syscall format string '%s'",
fmt - 1);
break;
}
} else {
*(p++) = *(fmt++);
}
}
*p = 0;
va_end(va);
gdb_syscall_handling(gdbserver_syscall_state.syscall_buf);
}
/*
* 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 = gdb_get_cmd_param(params, 0)->val_ull;
if (params->len >= 2) {
err = gdb_get_cmd_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 && gdb_get_cmd_param(params, 2)->opcode == (uint8_t)'C') {
gdb_put_packet("T02");
return;
}
gdb_continue();
}