| /* |
| * 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" |
| #ifdef CONFIG_LINUX |
| #include "linux-user/loader.h" |
| #include "linux-user/qemu.h" |
| #endif |
| |
| /* |
| * 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 = get_task_state(cpu); |
| return ts ? ts->ts_tid : -1; |
| } |
| |
| /* |
| * User-mode specific command helpers |
| */ |
| |
| void gdb_handle_query_offsets(GArray *params, void *user_ctx) |
| { |
| TaskState *ts; |
| |
| ts = get_task_state(gdbserver_state.c_cpu); |
| 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 = get_task_state(gdbserver_state.c_cpu); |
| 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 |
| |
| static const char *get_filename_param(GArray *params, int i) |
| { |
| const char *hex_filename = get_param(params, i)->data; |
| gdb_hextomem(gdbserver_state.mem_buf, hex_filename, |
| strlen(hex_filename) / 2); |
| g_byte_array_append(gdbserver_state.mem_buf, (const guint8 *)"", 1); |
| return (const char *)gdbserver_state.mem_buf->data; |
| } |
| |
| static void hostio_reply_with_data(const void *buf, size_t n) |
| { |
| g_string_printf(gdbserver_state.str_buf, "F%zx;", n); |
| gdb_memtox(gdbserver_state.str_buf, buf, n); |
| gdb_put_packet_binary(gdbserver_state.str_buf->str, |
| gdbserver_state.str_buf->len, true); |
| } |
| |
| void gdb_handle_v_file_open(GArray *params, void *user_ctx) |
| { |
| const char *filename = get_filename_param(params, 0); |
| uint64_t flags = get_param(params, 1)->val_ull; |
| uint64_t mode = get_param(params, 2)->val_ull; |
| |
| #ifdef CONFIG_LINUX |
| int fd = do_guest_openat(cpu_env(gdbserver_state.g_cpu), 0, filename, |
| flags, mode, false); |
| #else |
| int fd = open(filename, flags, mode); |
| #endif |
| if (fd < 0) { |
| g_string_printf(gdbserver_state.str_buf, "F-1,%d", errno); |
| } else { |
| g_string_printf(gdbserver_state.str_buf, "F%d", fd); |
| } |
| gdb_put_strbuf(); |
| } |
| |
| void gdb_handle_v_file_close(GArray *params, void *user_ctx) |
| { |
| int fd = get_param(params, 0)->val_ul; |
| |
| if (close(fd) == -1) { |
| g_string_printf(gdbserver_state.str_buf, "F-1,%d", errno); |
| gdb_put_strbuf(); |
| return; |
| } |
| |
| gdb_put_packet("F00"); |
| } |
| |
| void gdb_handle_v_file_pread(GArray *params, void *user_ctx) |
| { |
| int fd = get_param(params, 0)->val_ul; |
| size_t count = get_param(params, 1)->val_ull; |
| off_t offset = get_param(params, 2)->val_ull; |
| |
| size_t bufsiz = MIN(count, BUFSIZ); |
| g_autofree char *buf = g_try_malloc(bufsiz); |
| if (buf == NULL) { |
| gdb_put_packet("E12"); |
| return; |
| } |
| |
| ssize_t n = pread(fd, buf, bufsiz, offset); |
| if (n < 0) { |
| g_string_printf(gdbserver_state.str_buf, "F-1,%d", errno); |
| gdb_put_strbuf(); |
| return; |
| } |
| hostio_reply_with_data(buf, n); |
| } |
| |
| void gdb_handle_v_file_readlink(GArray *params, void *user_ctx) |
| { |
| const char *filename = get_filename_param(params, 0); |
| |
| g_autofree char *buf = g_try_malloc(BUFSIZ); |
| if (buf == NULL) { |
| gdb_put_packet("E12"); |
| return; |
| } |
| |
| #ifdef CONFIG_LINUX |
| ssize_t n = do_guest_readlink(filename, buf, BUFSIZ); |
| #else |
| ssize_t n = readlink(filename, buf, BUFSIZ); |
| #endif |
| if (n < 0) { |
| g_string_printf(gdbserver_state.str_buf, "F-1,%d", errno); |
| gdb_put_strbuf(); |
| return; |
| } |
| hostio_reply_with_data(buf, n); |
| } |
| |
| void gdb_handle_query_xfer_exec_file(GArray *params, void *user_ctx) |
| { |
| uint32_t pid = get_param(params, 0)->val_ul; |
| uint32_t offset = get_param(params, 1)->val_ul; |
| uint32_t length = get_param(params, 2)->val_ul; |
| |
| GDBProcess *process = gdb_get_process(pid); |
| if (!process) { |
| gdb_put_packet("E00"); |
| return; |
| } |
| |
| CPUState *cpu = gdb_get_first_cpu_in_process(process); |
| if (!cpu) { |
| gdb_put_packet("E00"); |
| return; |
| } |
| |
| TaskState *ts = get_task_state(cpu); |
| if (!ts || !ts->bprm || !ts->bprm->filename) { |
| gdb_put_packet("E00"); |
| return; |
| } |
| |
| size_t total_length = strlen(ts->bprm->filename); |
| if (offset > total_length) { |
| gdb_put_packet("E00"); |
| return; |
| } |
| if (offset + length > total_length) { |
| length = total_length - offset; |
| } |
| |
| g_string_printf(gdbserver_state.str_buf, "l%.*s", length, |
| ts->bprm->filename + offset); |
| gdb_put_strbuf(); |
| } |
| |
| int gdb_target_sigtrap(void) |
| { |
| return TARGET_SIGTRAP; |
| } |