mirror of https://gitlab.com/qemu-project/qemu
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
207 lines
5.1 KiB
C
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-or-later
|
|
*/
|
|
|
|
#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();
|
|
}
|