| /* | 
 |  * 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 "system/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(); | 
 | } |