| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| /* | ||
| * 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 "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 = 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(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,283 @@ | ||
| /* | ||
| * Target specific user-mode handling | ||
| * | ||
| * Copyright (c) 2003-2005 Fabrice Bellard | ||
| * Copyright (c) 2022 Linaro Ltd | ||
| * | ||
| * SPDX-License-Identifier: LGPL-2.0+ | ||
| */ | ||
|
|
||
| #include "qemu/osdep.h" | ||
| #include "exec/gdbstub.h" | ||
| #include "qemu.h" | ||
| #include "internals.h" | ||
|
|
||
| /* | ||
| * Map target signal numbers to GDB protocol signal numbers and vice | ||
| * versa. For user emulation's currently supported systems, we can | ||
| * assume most signals are defined. | ||
| */ | ||
|
|
||
| static int gdb_signal_table[] = { | ||
| 0, | ||
| TARGET_SIGHUP, | ||
| TARGET_SIGINT, | ||
| TARGET_SIGQUIT, | ||
| TARGET_SIGILL, | ||
| TARGET_SIGTRAP, | ||
| TARGET_SIGABRT, | ||
| -1, /* SIGEMT */ | ||
| TARGET_SIGFPE, | ||
| TARGET_SIGKILL, | ||
| TARGET_SIGBUS, | ||
| TARGET_SIGSEGV, | ||
| TARGET_SIGSYS, | ||
| TARGET_SIGPIPE, | ||
| TARGET_SIGALRM, | ||
| TARGET_SIGTERM, | ||
| TARGET_SIGURG, | ||
| TARGET_SIGSTOP, | ||
| TARGET_SIGTSTP, | ||
| TARGET_SIGCONT, | ||
| TARGET_SIGCHLD, | ||
| TARGET_SIGTTIN, | ||
| TARGET_SIGTTOU, | ||
| TARGET_SIGIO, | ||
| TARGET_SIGXCPU, | ||
| TARGET_SIGXFSZ, | ||
| TARGET_SIGVTALRM, | ||
| TARGET_SIGPROF, | ||
| TARGET_SIGWINCH, | ||
| -1, /* SIGLOST */ | ||
| TARGET_SIGUSR1, | ||
| TARGET_SIGUSR2, | ||
| #ifdef TARGET_SIGPWR | ||
| TARGET_SIGPWR, | ||
| #else | ||
| -1, | ||
| #endif | ||
| -1, /* SIGPOLL */ | ||
| -1, | ||
| -1, | ||
| -1, | ||
| -1, | ||
| -1, | ||
| -1, | ||
| -1, | ||
| -1, | ||
| -1, | ||
| -1, | ||
| -1, | ||
| #ifdef __SIGRTMIN | ||
| __SIGRTMIN + 1, | ||
| __SIGRTMIN + 2, | ||
| __SIGRTMIN + 3, | ||
| __SIGRTMIN + 4, | ||
| __SIGRTMIN + 5, | ||
| __SIGRTMIN + 6, | ||
| __SIGRTMIN + 7, | ||
| __SIGRTMIN + 8, | ||
| __SIGRTMIN + 9, | ||
| __SIGRTMIN + 10, | ||
| __SIGRTMIN + 11, | ||
| __SIGRTMIN + 12, | ||
| __SIGRTMIN + 13, | ||
| __SIGRTMIN + 14, | ||
| __SIGRTMIN + 15, | ||
| __SIGRTMIN + 16, | ||
| __SIGRTMIN + 17, | ||
| __SIGRTMIN + 18, | ||
| __SIGRTMIN + 19, | ||
| __SIGRTMIN + 20, | ||
| __SIGRTMIN + 21, | ||
| __SIGRTMIN + 22, | ||
| __SIGRTMIN + 23, | ||
| __SIGRTMIN + 24, | ||
| __SIGRTMIN + 25, | ||
| __SIGRTMIN + 26, | ||
| __SIGRTMIN + 27, | ||
| __SIGRTMIN + 28, | ||
| __SIGRTMIN + 29, | ||
| __SIGRTMIN + 30, | ||
| __SIGRTMIN + 31, | ||
| -1, /* SIGCANCEL */ | ||
| __SIGRTMIN, | ||
| __SIGRTMIN + 32, | ||
| __SIGRTMIN + 33, | ||
| __SIGRTMIN + 34, | ||
| __SIGRTMIN + 35, | ||
| __SIGRTMIN + 36, | ||
| __SIGRTMIN + 37, | ||
| __SIGRTMIN + 38, | ||
| __SIGRTMIN + 39, | ||
| __SIGRTMIN + 40, | ||
| __SIGRTMIN + 41, | ||
| __SIGRTMIN + 42, | ||
| __SIGRTMIN + 43, | ||
| __SIGRTMIN + 44, | ||
| __SIGRTMIN + 45, | ||
| __SIGRTMIN + 46, | ||
| __SIGRTMIN + 47, | ||
| __SIGRTMIN + 48, | ||
| __SIGRTMIN + 49, | ||
| __SIGRTMIN + 50, | ||
| __SIGRTMIN + 51, | ||
| __SIGRTMIN + 52, | ||
| __SIGRTMIN + 53, | ||
| __SIGRTMIN + 54, | ||
| __SIGRTMIN + 55, | ||
| __SIGRTMIN + 56, | ||
| __SIGRTMIN + 57, | ||
| __SIGRTMIN + 58, | ||
| __SIGRTMIN + 59, | ||
| __SIGRTMIN + 60, | ||
| __SIGRTMIN + 61, | ||
| __SIGRTMIN + 62, | ||
| __SIGRTMIN + 63, | ||
| __SIGRTMIN + 64, | ||
| __SIGRTMIN + 65, | ||
| __SIGRTMIN + 66, | ||
| __SIGRTMIN + 67, | ||
| __SIGRTMIN + 68, | ||
| __SIGRTMIN + 69, | ||
| __SIGRTMIN + 70, | ||
| __SIGRTMIN + 71, | ||
| __SIGRTMIN + 72, | ||
| __SIGRTMIN + 73, | ||
| __SIGRTMIN + 74, | ||
| __SIGRTMIN + 75, | ||
| __SIGRTMIN + 76, | ||
| __SIGRTMIN + 77, | ||
| __SIGRTMIN + 78, | ||
| __SIGRTMIN + 79, | ||
| __SIGRTMIN + 80, | ||
| __SIGRTMIN + 81, | ||
| __SIGRTMIN + 82, | ||
| __SIGRTMIN + 83, | ||
| __SIGRTMIN + 84, | ||
| __SIGRTMIN + 85, | ||
| __SIGRTMIN + 86, | ||
| __SIGRTMIN + 87, | ||
| __SIGRTMIN + 88, | ||
| __SIGRTMIN + 89, | ||
| __SIGRTMIN + 90, | ||
| __SIGRTMIN + 91, | ||
| __SIGRTMIN + 92, | ||
| __SIGRTMIN + 93, | ||
| __SIGRTMIN + 94, | ||
| __SIGRTMIN + 95, | ||
| -1, /* SIGINFO */ | ||
| -1, /* UNKNOWN */ | ||
| -1, /* DEFAULT */ | ||
| -1, | ||
| -1, | ||
| -1, | ||
| -1, | ||
| -1, | ||
| -1 | ||
| #endif | ||
| }; | ||
|
|
||
| int gdb_signal_to_target(int sig) | ||
| { | ||
| if (sig < ARRAY_SIZE(gdb_signal_table)) { | ||
| return gdb_signal_table[sig]; | ||
| } else { | ||
| return -1; | ||
| } | ||
| } | ||
|
|
||
| int gdb_target_signal_to_gdb(int sig) | ||
| { | ||
| int i; | ||
| for (i = 0; i < ARRAY_SIZE(gdb_signal_table); i++) { | ||
| if (gdb_signal_table[i] == sig) { | ||
| return i; | ||
| } | ||
| } | ||
| return GDB_SIGNAL_UNKNOWN; | ||
| } | ||
|
|
||
| int gdb_get_cpu_index(CPUState *cpu) | ||
| { | ||
| TaskState *ts = (TaskState *) cpu->opaque; | ||
| return ts ? ts->ts_tid : -1; | ||
| } | ||
|
|
||
| /* | ||
| * User-mode specific command helpers | ||
| */ | ||
|
|
||
| void gdb_handle_query_offsets(GArray *params, void *user_ctx) | ||
| { | ||
| TaskState *ts; | ||
|
|
||
| ts = gdbserver_state.c_cpu->opaque; | ||
| g_string_printf(gdbserver_state.str_buf, | ||
| "Text=" TARGET_ABI_FMT_lx | ||
| ";Data=" TARGET_ABI_FMT_lx | ||
| ";Bss=" TARGET_ABI_FMT_lx, | ||
| ts->info->code_offset, | ||
| ts->info->data_offset, | ||
| ts->info->data_offset); | ||
| gdb_put_strbuf(); | ||
| } | ||
|
|
||
| #if defined(CONFIG_LINUX) | ||
| /* Partial user only duplicate of helper in gdbstub.c */ | ||
| static inline int target_memory_rw_debug(CPUState *cpu, target_ulong addr, | ||
| uint8_t *buf, int len, bool is_write) | ||
| { | ||
| CPUClass *cc; | ||
| cc = CPU_GET_CLASS(cpu); | ||
| if (cc->memory_rw_debug) { | ||
| return cc->memory_rw_debug(cpu, addr, buf, len, is_write); | ||
| } | ||
| return cpu_memory_rw_debug(cpu, addr, buf, len, is_write); | ||
| } | ||
|
|
||
| void gdb_handle_query_xfer_auxv(GArray *params, void *user_ctx) | ||
| { | ||
| TaskState *ts; | ||
| unsigned long offset, len, saved_auxv, auxv_len; | ||
|
|
||
| if (params->len < 2) { | ||
| gdb_put_packet("E22"); | ||
| return; | ||
| } | ||
|
|
||
| offset = get_param(params, 0)->val_ul; | ||
| len = get_param(params, 1)->val_ul; | ||
| ts = gdbserver_state.c_cpu->opaque; | ||
| saved_auxv = ts->info->saved_auxv; | ||
| auxv_len = ts->info->auxv_len; | ||
|
|
||
| if (offset >= auxv_len) { | ||
| gdb_put_packet("E00"); | ||
| return; | ||
| } | ||
|
|
||
| if (len > (MAX_PACKET_LENGTH - 5) / 2) { | ||
| len = (MAX_PACKET_LENGTH - 5) / 2; | ||
| } | ||
|
|
||
| if (len < auxv_len - offset) { | ||
| g_string_assign(gdbserver_state.str_buf, "m"); | ||
| } else { | ||
| g_string_assign(gdbserver_state.str_buf, "l"); | ||
| len = auxv_len - offset; | ||
| } | ||
|
|
||
| g_byte_array_set_size(gdbserver_state.mem_buf, len); | ||
| if (target_memory_rw_debug(gdbserver_state.g_cpu, saved_auxv + offset, | ||
| gdbserver_state.mem_buf->data, len, false)) { | ||
| gdb_put_packet("E14"); | ||
| return; | ||
| } | ||
|
|
||
| gdb_memtox(gdbserver_state.str_buf, | ||
| (const char *)gdbserver_state.mem_buf->data, len); | ||
| gdb_put_packet_binary(gdbserver_state.str_buf->str, | ||
| gdbserver_state.str_buf->len, true); | ||
| } | ||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,24 +55,7 @@ | |
| # endif | ||
| #endif | ||
|
|
||
| #include "exec/target_long.h" | ||
|
|
||
| #if !defined(CONFIG_USER_ONLY) && defined(CONFIG_TCG) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| /* | ||
| * Target Long Definitions | ||
| * | ||
| * Copyright (c) 2003 Fabrice Bellard | ||
| * Copyright (c) 2023 Linaro Ltd | ||
| * | ||
| * SPDX-License-Identifier: GPL-2.0-or-later | ||
| */ | ||
|
|
||
| #ifndef _TARGET_LONG_H_ | ||
| #define _TARGET_LONG_H_ | ||
|
|
||
| /* | ||
| * Usually this should only be included via cpu-defs.h however for | ||
| * certain cases where we want to build only two versions of a binary | ||
| * object we can include directly. However the build-system must | ||
| * ensure TARGET_LONG_BITS is defined directly. | ||
| */ | ||
| #ifndef TARGET_LONG_BITS | ||
| #error TARGET_LONG_BITS not defined | ||
| #endif | ||
|
|
||
| #define TARGET_LONG_SIZE (TARGET_LONG_BITS / 8) | ||
|
|
||
| /* target_ulong is the type of a virtual address */ | ||
| #if TARGET_LONG_SIZE == 4 | ||
| typedef int32_t target_long; | ||
| typedef uint32_t target_ulong; | ||
| #define TARGET_FMT_lx "%08x" | ||
| #define TARGET_FMT_ld "%d" | ||
| #define TARGET_FMT_lu "%u" | ||
| #elif TARGET_LONG_SIZE == 8 | ||
| typedef int64_t target_long; | ||
| typedef uint64_t target_ulong; | ||
| #define TARGET_FMT_lx "%016" PRIx64 | ||
| #define TARGET_FMT_ld "%" PRId64 | ||
| #define TARGET_FMT_lu "%" PRIu64 | ||
| #else | ||
| #error TARGET_LONG_SIZE undefined | ||
| #endif | ||
|
|
||
| #endif /* _TARGET_LONG_H_ */ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| /* | ||
| * tb-flush prototype for use by the rest of the system. | ||
| * | ||
| * Copyright (c) 2022 Linaro Ltd | ||
| * | ||
| * SPDX-License-Identifier: GPL-2.0-or-later | ||
| */ | ||
| #ifndef _TB_FLUSH_H_ | ||
| #define _TB_FLUSH_H_ | ||
|
|
||
| /** | ||
| * tb_flush() - flush all translation blocks | ||
| * @cs: CPUState (must be valid, but treated as anonymous pointer) | ||
| * | ||
| * Used to flush all the translation blocks in the system. Sometimes | ||
| * it is simpler to flush everything than work out which individual | ||
| * translations are now invalid and ensure they are not called | ||
| * anymore. | ||
| * | ||
| * tb_flush() takes care of running the flush in an exclusive context | ||
| * if it is not already running in one. This means no guest code will | ||
| * run until this complete. | ||
| */ | ||
| void tb_flush(CPUState *cs); | ||
|
|
||
| #endif /* _TB_FLUSH_H_ */ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| /* | ||
| * gdbstub helpers | ||
| * | ||
| * These are all used by the various frontends and have to be host | ||
| * aware to ensure things are store in target order. | ||
| * | ||
| * Copyright (c) 2022 Linaro Ltd | ||
| * | ||
| * SPDX-License-Identifier: GPL-2.0-or-later | ||
| */ | ||
|
|
||
| #ifndef _GDBSTUB_HELPERS_H_ | ||
| #define _GDBSTUB_HELPERS_H_ | ||
|
|
||
| #ifdef NEED_CPU_H | ||
| #include "cpu.h" | ||
|
|
||
| /* | ||
| * The GDB remote protocol transfers values in target byte order. As | ||
| * the gdbstub may be batching up several register values we always | ||
| * append to the array. | ||
| */ | ||
|
|
||
| static inline int gdb_get_reg8(GByteArray *buf, uint8_t val) | ||
| { | ||
| g_byte_array_append(buf, &val, 1); | ||
| return 1; | ||
| } | ||
|
|
||
| static inline int gdb_get_reg16(GByteArray *buf, uint16_t val) | ||
| { | ||
| uint16_t to_word = tswap16(val); | ||
| g_byte_array_append(buf, (uint8_t *) &to_word, 2); | ||
| return 2; | ||
| } | ||
|
|
||
| static inline int gdb_get_reg32(GByteArray *buf, uint32_t val) | ||
| { | ||
| uint32_t to_long = tswap32(val); | ||
| g_byte_array_append(buf, (uint8_t *) &to_long, 4); | ||
| return 4; | ||
| } | ||
|
|
||
| static inline int gdb_get_reg64(GByteArray *buf, uint64_t val) | ||
| { | ||
| uint64_t to_quad = tswap64(val); | ||
| g_byte_array_append(buf, (uint8_t *) &to_quad, 8); | ||
| return 8; | ||
| } | ||
|
|
||
| static inline int gdb_get_reg128(GByteArray *buf, uint64_t val_hi, | ||
| uint64_t val_lo) | ||
| { | ||
| uint64_t to_quad; | ||
| #if TARGET_BIG_ENDIAN | ||
| to_quad = tswap64(val_hi); | ||
| g_byte_array_append(buf, (uint8_t *) &to_quad, 8); | ||
| to_quad = tswap64(val_lo); | ||
| g_byte_array_append(buf, (uint8_t *) &to_quad, 8); | ||
| #else | ||
| to_quad = tswap64(val_lo); | ||
| g_byte_array_append(buf, (uint8_t *) &to_quad, 8); | ||
| to_quad = tswap64(val_hi); | ||
| g_byte_array_append(buf, (uint8_t *) &to_quad, 8); | ||
| #endif | ||
| return 16; | ||
| } | ||
|
|
||
| static inline int gdb_get_zeroes(GByteArray *array, size_t len) | ||
| { | ||
| guint oldlen = array->len; | ||
| g_byte_array_set_size(array, oldlen + len); | ||
| memset(array->data + oldlen, 0, len); | ||
|
|
||
| return len; | ||
| } | ||
|
|
||
| /** | ||
| * gdb_get_reg_ptr: get pointer to start of last element | ||
| * @len: length of element | ||
| * | ||
| * This is a helper function to extract the pointer to the last | ||
| * element for additional processing. Some front-ends do additional | ||
| * dynamic swapping of the elements based on CPU state. | ||
| */ | ||
| static inline uint8_t *gdb_get_reg_ptr(GByteArray *buf, int len) | ||
| { | ||
| return buf->data + buf->len - len; | ||
| } | ||
|
|
||
| #if TARGET_LONG_BITS == 64 | ||
| #define gdb_get_regl(buf, val) gdb_get_reg64(buf, val) | ||
| #define ldtul_p(addr) ldq_p(addr) | ||
| #else | ||
| #define gdb_get_regl(buf, val) gdb_get_reg32(buf, val) | ||
| #define ldtul_p(addr) ldl_p(addr) | ||
| #endif | ||
|
|
||
| #else | ||
| #error "gdbstub helpers should only be included by target specific code" | ||
| #endif | ||
|
|
||
| #endif /* _GDBSTUB_HELPERS_H_ */ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| /* | ||
| * 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, ...); | ||
|
|
||
| /** | ||
| * 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_ */ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| /* | ||
| * gdbstub user-mode only APIs | ||
| * | ||
| * Copyright (c) 2022 Linaro Ltd | ||
| * | ||
| * SPDX-License-Identifier: LGPL-2.0+ | ||
| */ | ||
|
|
||
| #ifndef GDBSTUB_USER_H | ||
| #define GDBSTUB_USER_H | ||
|
|
||
| /** | ||
| * gdb_handlesig() - yield control to gdb | ||
| * @cpu: CPU | ||
| * @sig: if non-zero, the signal number which caused us to stop | ||
| * | ||
| * This function yields control to gdb, when a user-mode-only target | ||
| * needs to stop execution. If @sig is non-zero, then we will send a | ||
| * stop packet to tell gdb that we have stopped because of this signal. | ||
| * | ||
| * This function will block (handling protocol requests from gdb) | ||
| * until gdb tells us to continue target execution. When it does | ||
| * return, the return value is a signal to deliver to the target, | ||
| * or 0 if no signal should be delivered, ie the signal that caused | ||
| * us to stop should be ignored. | ||
| */ | ||
| int gdb_handlesig(CPUState *, int); | ||
|
|
||
| /** | ||
| * gdb_signalled() - inform remote gdb of sig exit | ||
| * @as: current CPUArchState | ||
| * @sig: signal number | ||
| */ | ||
| void gdb_signalled(CPUArchState *as, int sig); | ||
|
|
||
| /** | ||
| * gdbserver_fork() - disable gdb stub for child processes. | ||
| * @cs: CPU | ||
| */ | ||
| void gdbserver_fork(CPUState *cs); | ||
|
|
||
|
|
||
| #endif /* GDBSTUB_USER_H */ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| #!/usr/bin/env python3 | ||
| # coding: utf-8 | ||
| # | ||
| # Probe gdb for supported architectures. | ||
| # | ||
| # This is required to support testing of the gdbstub as its hard to | ||
| # handle errors gracefully during the test. Instead this script when | ||
| # passed a GDB binary will probe its architecture support and return a | ||
| # string of supported arches, stripped of guff. | ||
| # | ||
| # Copyright 2023 Linaro Ltd | ||
| # | ||
| # Author: Alex Bennée <alex.bennee@linaro.org> | ||
| # | ||
| # This work is licensed under the terms of the GNU GPL, version 2 or later. | ||
| # See the COPYING file in the top-level directory. | ||
| # | ||
| # SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| import argparse | ||
| import re | ||
| from subprocess import check_output, STDOUT | ||
|
|
||
| # mappings from gdb arch to QEMU target | ||
| mappings = { | ||
| "alpha" : "alpha", | ||
| "aarch64" : ["aarch64", "aarch64_be"], | ||
| "armv7": "arm", | ||
| "armv8-a" : ["aarch64", "aarch64_be"], | ||
| "avr" : "avr", | ||
| "cris" : "cris", | ||
| # no hexagon in upstream gdb | ||
| "hppa1.0" : "hppa", | ||
| "i386" : "i386", | ||
| "i386:x86-64" : "x86_64", | ||
| "Loongarch64" : "loongarch64", | ||
| "m68k" : "m68k", | ||
| "MicroBlaze" : "microblaze", | ||
| "mips:isa64" : ["mips64", "mips64el"], | ||
| "nios2" : "nios2", | ||
| "or1k" : "or1k", | ||
| "powerpc:common" : "ppc", | ||
| "powerpc:common64" : ["ppc64", "ppc64le"], | ||
| "riscv:rv32" : "riscv32", | ||
| "riscv:rv64" : "riscv64", | ||
| "s390:64-bit" : "s390x", | ||
| "sh4" : ["sh4", "sh4eb"], | ||
| "sparc": "sparc", | ||
| "sparc:v8plus": "sparc32plus", | ||
| "sparc:v9a" : "sparc64", | ||
| # no tricore in upstream gdb | ||
| "xtensa" : ["xtensa", "xtensaeb"] | ||
| } | ||
|
|
||
| def do_probe(gdb): | ||
| gdb_out = check_output([gdb, | ||
| "-ex", "set architecture", | ||
| "-ex", "quit"], stderr=STDOUT) | ||
|
|
||
| m = re.search(r"Valid arguments are (.*)", | ||
| gdb_out.decode("utf-8")) | ||
|
|
||
| valid_arches = set() | ||
|
|
||
| if m.group(1): | ||
| for arch in m.group(1).split(", "): | ||
| if arch in mappings: | ||
| mapping = mappings[arch] | ||
| if isinstance(mapping, str): | ||
| valid_arches.add(mapping) | ||
| else: | ||
| for entry in mapping: | ||
| valid_arches.add(entry) | ||
|
|
||
| return valid_arches | ||
|
|
||
| def main() -> None: | ||
| parser = argparse.ArgumentParser(description='Probe GDB Architectures') | ||
| parser.add_argument('gdb', help='Path to GDB binary.') | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
| supported = do_probe(args.gdb) | ||
|
|
||
| print(" ".join(supported)) | ||
|
|
||
| if __name__ == '__main__': | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| /* | ||
| * Semihosting Stubs for all targets | ||
| * | ||
| * Copyright (c) 2023 Linaro Ltd | ||
| * | ||
| * Stubs for all targets that don't actually do semihosting. | ||
| * | ||
| * SPDX-License-Identifier: GPL-2.0-or-later | ||
| */ | ||
|
|
||
| #include "qemu/osdep.h" | ||
| #include "semihosting/semihost.h" | ||
|
|
||
| SemihostingTarget semihosting_get_target(void) | ||
| { | ||
| return SEMIHOSTING_TARGET_AUTO; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,7 +16,7 @@ | |
| */ | ||
|
|
||
| #include "qemu/osdep.h" | ||
| #include "gdbstub/helpers.h" | ||
| #include "cpu.h" | ||
| #include "internal.h" | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,7 @@ | |
| */ | ||
|
|
||
| #include "qemu/osdep.h" | ||
| #include "gdbstub/helpers.h" | ||
|
|
||
|
|
||
| #define LCX_REGNUM 32 | ||
|
|
||