| /* |
| * gdbstub user-mode helper routines. |
| * |
| * We know for user-mode we are using TCG so we can call stuff directly. |
| * |
| * Copyright (c) 2003-2005 Fabrice Bellard |
| * Copyright (c) 2022 Linaro Ltd |
| * |
| * SPDX-License-Identifier: LGPL-2.0+ |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "qemu/bitops.h" |
| #include "qemu/cutils.h" |
| #include "qemu/sockets.h" |
| #include "exec/hwaddr.h" |
| #include "exec/tb-flush.h" |
| #include "exec/gdbstub.h" |
| #include "gdbstub/syscalls.h" |
| #include "gdbstub/user.h" |
| #include "gdbstub/enums.h" |
| #include "hw/core/cpu.h" |
| #include "trace.h" |
| #include "internals.h" |
| |
| #define GDB_NR_SYSCALLS 1024 |
| typedef unsigned long GDBSyscallsMask[BITS_TO_LONGS(GDB_NR_SYSCALLS)]; |
| |
| /* |
| * Forked child talks to its parent in order to let GDB enforce the |
| * follow-fork-mode. This happens inside a start_exclusive() section, so that |
| * the other threads, which may be forking too, do not interfere. The |
| * implementation relies on GDB not sending $vCont until it has detached |
| * either from the parent (follow-fork-mode child) or from the child |
| * (follow-fork-mode parent). |
| * |
| * The parent and the child share the GDB socket; at any given time only one |
| * of them is allowed to use it, as is reflected in the respective fork_state. |
| * This is negotiated via the fork_sockets pair as a reaction to $Hg. |
| * |
| * Below is a short summary of the possible state transitions: |
| * |
| * ENABLED : Terminal state. |
| * DISABLED : Terminal state. |
| * ACTIVE : Parent initial state. |
| * INACTIVE : Child initial state. |
| * ACTIVE -> DEACTIVATING: On $Hg. |
| * ACTIVE -> ENABLING : On $D. |
| * ACTIVE -> DISABLING : On $D. |
| * ACTIVE -> DISABLED : On communication error. |
| * DEACTIVATING -> INACTIVE : On gdb_read_byte() return. |
| * DEACTIVATING -> DISABLED : On communication error. |
| * INACTIVE -> ACTIVE : On $Hg in the peer. |
| * INACTIVE -> ENABLE : On $D in the peer. |
| * INACTIVE -> DISABLE : On $D in the peer. |
| * INACTIVE -> DISABLED : On communication error. |
| * ENABLING -> ENABLED : On gdb_read_byte() return. |
| * ENABLING -> DISABLED : On communication error. |
| * DISABLING -> DISABLED : On gdb_read_byte() return. |
| */ |
| enum GDBForkState { |
| /* Fully owning the GDB socket. */ |
| GDB_FORK_ENABLED, |
| /* Working with the GDB socket; the peer is inactive. */ |
| GDB_FORK_ACTIVE, |
| /* Handing off the GDB socket to the peer. */ |
| GDB_FORK_DEACTIVATING, |
| /* The peer is working with the GDB socket. */ |
| GDB_FORK_INACTIVE, |
| /* Asking the peer to close its GDB socket fd. */ |
| GDB_FORK_ENABLING, |
| /* Asking the peer to take over, closing our GDB socket fd. */ |
| GDB_FORK_DISABLING, |
| /* The peer has taken over, our GDB socket fd is closed. */ |
| GDB_FORK_DISABLED, |
| }; |
| |
| enum GDBForkMessage { |
| GDB_FORK_ACTIVATE = 'a', |
| GDB_FORK_ENABLE = 'e', |
| GDB_FORK_DISABLE = 'd', |
| }; |
| |
| /* User-mode specific state */ |
| typedef struct { |
| int fd; |
| char *socket_path; |
| int running_state; |
| /* |
| * Store syscalls mask without memory allocation in order to avoid |
| * implementing synchronization. |
| */ |
| bool catch_all_syscalls; |
| GDBSyscallsMask catch_syscalls_mask; |
| bool fork_events; |
| enum GDBForkState fork_state; |
| int fork_sockets[2]; |
| pid_t fork_peer_pid, fork_peer_tid; |
| uint8_t siginfo[MAX_SIGINFO_LENGTH]; |
| unsigned long siginfo_len; |
| } GDBUserState; |
| |
| static GDBUserState gdbserver_user_state; |
| |
| int gdb_get_char(void) |
| { |
| uint8_t ch; |
| int ret; |
| |
| for (;;) { |
| ret = recv(gdbserver_user_state.fd, &ch, 1, 0); |
| if (ret < 0) { |
| if (errno == ECONNRESET) { |
| gdbserver_user_state.fd = -1; |
| } |
| if (errno != EINTR) { |
| return -1; |
| } |
| } else if (ret == 0) { |
| close(gdbserver_user_state.fd); |
| gdbserver_user_state.fd = -1; |
| return -1; |
| } else { |
| break; |
| } |
| } |
| return ch; |
| } |
| |
| bool gdb_got_immediate_ack(void) |
| { |
| int i; |
| |
| i = gdb_get_char(); |
| if (i < 0) { |
| /* no response, continue anyway */ |
| return true; |
| } |
| |
| if (i == '+') { |
| /* received correctly, continue */ |
| return true; |
| } |
| |
| /* anything else, including '-' then try again */ |
| return false; |
| } |
| |
| void gdb_put_buffer(const uint8_t *buf, int len) |
| { |
| int ret; |
| |
| while (len > 0) { |
| ret = send(gdbserver_user_state.fd, buf, len, 0); |
| if (ret < 0) { |
| if (errno != EINTR) { |
| return; |
| } |
| } else { |
| buf += ret; |
| len -= ret; |
| } |
| } |
| } |
| |
| /* Tell the remote gdb that the process has exited. */ |
| void gdb_exit(int code) |
| { |
| char buf[4]; |
| |
| if (!gdbserver_state.init) { |
| return; |
| } |
| if (gdbserver_user_state.socket_path) { |
| unlink(gdbserver_user_state.socket_path); |
| } |
| if (gdbserver_user_state.fd < 0) { |
| return; |
| } |
| |
| trace_gdbstub_op_exiting((uint8_t)code); |
| |
| if (gdbserver_state.allow_stop_reply) { |
| snprintf(buf, sizeof(buf), "W%02x", (uint8_t)code); |
| gdb_put_packet(buf); |
| gdbserver_state.allow_stop_reply = false; |
| } |
| |
| } |
| |
| void gdb_qemu_exit(int code) |
| { |
| exit(code); |
| } |
| |
| int gdb_handlesig(CPUState *cpu, int sig, const char *reason, void *siginfo, |
| int siginfo_len) |
| { |
| char buf[256]; |
| int n; |
| |
| if (!gdbserver_state.init || gdbserver_user_state.fd < 0) { |
| return sig; |
| } |
| |
| if (siginfo) { |
| /* |
| * Save target-specific siginfo. |
| * |
| * siginfo size, i.e. siginfo_len, is asserted at compile-time to fit in |
| * gdbserver_user_state.siginfo, usually in the source file calling |
| * gdb_handlesig. See, for instance, {linux,bsd}-user/signal.c. |
| */ |
| memcpy(gdbserver_user_state.siginfo, siginfo, siginfo_len); |
| gdbserver_user_state.siginfo_len = siginfo_len; |
| } |
| |
| /* disable single step if it was enabled */ |
| cpu_single_step(cpu, 0); |
| tb_flush(cpu); |
| |
| if (sig != 0) { |
| gdb_set_stop_cpu(cpu); |
| if (gdbserver_state.allow_stop_reply) { |
| g_string_printf(gdbserver_state.str_buf, |
| "T%02xthread:", gdb_target_signal_to_gdb(sig)); |
| gdb_append_thread_id(cpu, gdbserver_state.str_buf); |
| g_string_append_c(gdbserver_state.str_buf, ';'); |
| if (reason) { |
| g_string_append(gdbserver_state.str_buf, reason); |
| } |
| gdb_put_strbuf(); |
| gdbserver_state.allow_stop_reply = false; |
| } |
| } |
| /* |
| * gdb_put_packet() might have detected that the peer terminated the |
| * connection. |
| */ |
| if (gdbserver_user_state.fd < 0) { |
| return sig; |
| } |
| |
| sig = 0; |
| gdbserver_state.state = RS_IDLE; |
| gdbserver_user_state.running_state = 0; |
| while (gdbserver_user_state.running_state == 0) { |
| n = read(gdbserver_user_state.fd, buf, 256); |
| if (n > 0) { |
| int i; |
| |
| for (i = 0; i < n; i++) { |
| gdb_read_byte(buf[i]); |
| } |
| } else { |
| /* |
| * XXX: Connection closed. Should probably wait for another |
| * connection before continuing. |
| */ |
| if (n == 0) { |
| close(gdbserver_user_state.fd); |
| } |
| gdbserver_user_state.fd = -1; |
| return sig; |
| } |
| } |
| sig = gdbserver_state.signal; |
| gdbserver_state.signal = 0; |
| return sig; |
| } |
| |
| /* Tell the remote gdb that the process has exited due to SIG. */ |
| void gdb_signalled(CPUArchState *env, int sig) |
| { |
| char buf[4]; |
| |
| if (!gdbserver_state.init || gdbserver_user_state.fd < 0 || |
| !gdbserver_state.allow_stop_reply) { |
| return; |
| } |
| |
| snprintf(buf, sizeof(buf), "X%02x", gdb_target_signal_to_gdb(sig)); |
| gdb_put_packet(buf); |
| gdbserver_state.allow_stop_reply = false; |
| } |
| |
| static void gdb_accept_init(int fd) |
| { |
| gdb_init_gdbserver_state(); |
| gdb_create_default_process(&gdbserver_state); |
| gdbserver_state.processes[0].attached = true; |
| gdbserver_state.c_cpu = gdb_first_attached_cpu(); |
| gdbserver_state.g_cpu = gdbserver_state.c_cpu; |
| gdbserver_user_state.fd = fd; |
| } |
| |
| static bool gdb_accept_socket(int gdb_fd) |
| { |
| int fd; |
| |
| for (;;) { |
| fd = accept(gdb_fd, NULL, NULL); |
| if (fd < 0 && errno != EINTR) { |
| perror("accept socket"); |
| return false; |
| } else if (fd >= 0) { |
| qemu_set_cloexec(fd); |
| break; |
| } |
| } |
| |
| gdb_accept_init(fd); |
| return true; |
| } |
| |
| static int gdbserver_open_socket(const char *path) |
| { |
| struct sockaddr_un sockaddr = {}; |
| int fd, ret; |
| |
| fd = socket(AF_UNIX, SOCK_STREAM, 0); |
| if (fd < 0) { |
| perror("create socket"); |
| return -1; |
| } |
| |
| sockaddr.sun_family = AF_UNIX; |
| pstrcpy(sockaddr.sun_path, sizeof(sockaddr.sun_path) - 1, path); |
| ret = bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)); |
| if (ret < 0) { |
| perror("bind socket"); |
| close(fd); |
| return -1; |
| } |
| ret = listen(fd, 1); |
| if (ret < 0) { |
| perror("listen socket"); |
| close(fd); |
| return -1; |
| } |
| |
| return fd; |
| } |
| |
| static bool gdb_accept_tcp(int gdb_fd) |
| { |
| struct sockaddr_in sockaddr = {}; |
| socklen_t len; |
| int fd; |
| |
| for (;;) { |
| len = sizeof(sockaddr); |
| fd = accept(gdb_fd, (struct sockaddr *)&sockaddr, &len); |
| if (fd < 0 && errno != EINTR) { |
| perror("accept"); |
| return false; |
| } else if (fd >= 0) { |
| qemu_set_cloexec(fd); |
| break; |
| } |
| } |
| |
| /* set short latency */ |
| if (socket_set_nodelay(fd)) { |
| perror("setsockopt"); |
| close(fd); |
| return false; |
| } |
| |
| gdb_accept_init(fd); |
| return true; |
| } |
| |
| static int gdbserver_open_port(int port) |
| { |
| struct sockaddr_in sockaddr; |
| int fd, ret; |
| |
| fd = socket(PF_INET, SOCK_STREAM, 0); |
| if (fd < 0) { |
| perror("socket"); |
| return -1; |
| } |
| qemu_set_cloexec(fd); |
| |
| socket_set_fast_reuse(fd); |
| |
| sockaddr.sin_family = AF_INET; |
| sockaddr.sin_port = htons(port); |
| sockaddr.sin_addr.s_addr = 0; |
| ret = bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)); |
| if (ret < 0) { |
| perror("bind"); |
| close(fd); |
| return -1; |
| } |
| ret = listen(fd, 1); |
| if (ret < 0) { |
| perror("listen"); |
| close(fd); |
| return -1; |
| } |
| |
| return fd; |
| } |
| |
| int gdbserver_start(const char *port_or_path) |
| { |
| int port = g_ascii_strtoull(port_or_path, NULL, 10); |
| int gdb_fd; |
| |
| if (port > 0) { |
| gdb_fd = gdbserver_open_port(port); |
| } else { |
| gdb_fd = gdbserver_open_socket(port_or_path); |
| } |
| |
| if (gdb_fd < 0) { |
| return -1; |
| } |
| |
| if (port > 0 && gdb_accept_tcp(gdb_fd)) { |
| return 0; |
| } else if (gdb_accept_socket(gdb_fd)) { |
| gdbserver_user_state.socket_path = g_strdup(port_or_path); |
| return 0; |
| } |
| |
| /* gone wrong */ |
| close(gdb_fd); |
| return -1; |
| } |
| |
| void gdbserver_fork_start(void) |
| { |
| if (!gdbserver_state.init || gdbserver_user_state.fd < 0) { |
| return; |
| } |
| if (!gdbserver_user_state.fork_events || |
| qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, |
| gdbserver_user_state.fork_sockets) < 0) { |
| gdbserver_user_state.fork_state = GDB_FORK_DISABLED; |
| return; |
| } |
| gdbserver_user_state.fork_state = GDB_FORK_INACTIVE; |
| gdbserver_user_state.fork_peer_pid = getpid(); |
| gdbserver_user_state.fork_peer_tid = qemu_get_thread_id(); |
| } |
| |
| static void disable_gdbstub(CPUState *thread_cpu) |
| { |
| CPUState *cpu; |
| |
| close(gdbserver_user_state.fd); |
| gdbserver_user_state.fd = -1; |
| CPU_FOREACH(cpu) { |
| cpu_breakpoint_remove_all(cpu, BP_GDB); |
| /* no cpu_watchpoint_remove_all for user-mode */ |
| cpu_single_step(cpu, 0); |
| } |
| tb_flush(thread_cpu); |
| } |
| |
| void gdbserver_fork_end(CPUState *cpu, pid_t pid) |
| { |
| char b; |
| int fd; |
| |
| if (!gdbserver_state.init || gdbserver_user_state.fd < 0) { |
| return; |
| } |
| |
| if (pid == -1) { |
| if (gdbserver_user_state.fork_state != GDB_FORK_DISABLED) { |
| g_assert(gdbserver_user_state.fork_state == GDB_FORK_INACTIVE); |
| close(gdbserver_user_state.fork_sockets[0]); |
| close(gdbserver_user_state.fork_sockets[1]); |
| } |
| return; |
| } |
| |
| if (gdbserver_user_state.fork_state == GDB_FORK_DISABLED) { |
| if (pid == 0) { |
| disable_gdbstub(cpu); |
| } |
| return; |
| } |
| |
| if (pid == 0) { |
| close(gdbserver_user_state.fork_sockets[0]); |
| fd = gdbserver_user_state.fork_sockets[1]; |
| g_assert(gdbserver_state.process_num == 1); |
| g_assert(gdbserver_state.processes[0].pid == |
| gdbserver_user_state.fork_peer_pid); |
| g_assert(gdbserver_state.processes[0].attached); |
| gdbserver_state.processes[0].pid = getpid(); |
| } else { |
| close(gdbserver_user_state.fork_sockets[1]); |
| fd = gdbserver_user_state.fork_sockets[0]; |
| gdbserver_user_state.fork_state = GDB_FORK_ACTIVE; |
| gdbserver_user_state.fork_peer_pid = pid; |
| gdbserver_user_state.fork_peer_tid = pid; |
| |
| if (!gdbserver_state.allow_stop_reply) { |
| goto fail; |
| } |
| g_string_printf(gdbserver_state.str_buf, |
| "T%02xfork:p%02x.%02x;thread:p%02x.%02x;", |
| gdb_target_signal_to_gdb(gdb_target_sigtrap()), |
| pid, pid, (int)getpid(), qemu_get_thread_id()); |
| gdb_put_strbuf(); |
| } |
| |
| gdbserver_state.state = RS_IDLE; |
| gdbserver_state.allow_stop_reply = false; |
| gdbserver_user_state.running_state = 0; |
| for (;;) { |
| switch (gdbserver_user_state.fork_state) { |
| case GDB_FORK_ENABLED: |
| if (gdbserver_user_state.running_state) { |
| close(fd); |
| return; |
| } |
| QEMU_FALLTHROUGH; |
| case GDB_FORK_ACTIVE: |
| if (read(gdbserver_user_state.fd, &b, 1) != 1) { |
| goto fail; |
| } |
| gdb_read_byte(b); |
| break; |
| case GDB_FORK_DEACTIVATING: |
| b = GDB_FORK_ACTIVATE; |
| if (write(fd, &b, 1) != 1) { |
| goto fail; |
| } |
| gdbserver_user_state.fork_state = GDB_FORK_INACTIVE; |
| break; |
| case GDB_FORK_INACTIVE: |
| if (read(fd, &b, 1) != 1) { |
| goto fail; |
| } |
| switch (b) { |
| case GDB_FORK_ACTIVATE: |
| gdbserver_user_state.fork_state = GDB_FORK_ACTIVE; |
| break; |
| case GDB_FORK_ENABLE: |
| gdbserver_user_state.fork_state = GDB_FORK_ENABLED; |
| break; |
| case GDB_FORK_DISABLE: |
| gdbserver_user_state.fork_state = GDB_FORK_DISABLED; |
| break; |
| default: |
| g_assert_not_reached(); |
| } |
| break; |
| case GDB_FORK_ENABLING: |
| b = GDB_FORK_DISABLE; |
| if (write(fd, &b, 1) != 1) { |
| goto fail; |
| } |
| gdbserver_user_state.fork_state = GDB_FORK_ENABLED; |
| break; |
| case GDB_FORK_DISABLING: |
| b = GDB_FORK_ENABLE; |
| if (write(fd, &b, 1) != 1) { |
| goto fail; |
| } |
| gdbserver_user_state.fork_state = GDB_FORK_DISABLED; |
| break; |
| case GDB_FORK_DISABLED: |
| close(fd); |
| disable_gdbstub(cpu); |
| return; |
| default: |
| g_assert_not_reached(); |
| } |
| } |
| |
| fail: |
| close(fd); |
| if (pid == 0) { |
| disable_gdbstub(cpu); |
| } |
| } |
| |
| void gdb_handle_query_supported_user(const char *gdb_supported) |
| { |
| if (strstr(gdb_supported, "fork-events+")) { |
| gdbserver_user_state.fork_events = true; |
| } |
| g_string_append(gdbserver_state.str_buf, ";fork-events+"); |
| } |
| |
| bool gdb_handle_set_thread_user(uint32_t pid, uint32_t tid) |
| { |
| if (gdbserver_user_state.fork_state == GDB_FORK_ACTIVE && |
| pid == gdbserver_user_state.fork_peer_pid && |
| tid == gdbserver_user_state.fork_peer_tid) { |
| gdbserver_user_state.fork_state = GDB_FORK_DEACTIVATING; |
| gdb_put_packet("OK"); |
| return true; |
| } |
| return false; |
| } |
| |
| bool gdb_handle_detach_user(uint32_t pid) |
| { |
| bool enable; |
| |
| if (gdbserver_user_state.fork_state == GDB_FORK_ACTIVE) { |
| enable = pid == gdbserver_user_state.fork_peer_pid; |
| if (enable || pid == getpid()) { |
| gdbserver_user_state.fork_state = enable ? GDB_FORK_ENABLING : |
| GDB_FORK_DISABLING; |
| gdb_put_packet("OK"); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Execution state helpers |
| */ |
| |
| void gdb_handle_query_attached(GArray *params, void *user_ctx) |
| { |
| gdb_put_packet("0"); |
| } |
| |
| void gdb_continue(void) |
| { |
| gdbserver_user_state.running_state = 1; |
| trace_gdbstub_op_continue(); |
| } |
| |
| /* |
| * Resume execution, for user-mode emulation it's equivalent to |
| * gdb_continue. |
| */ |
| int gdb_continue_partial(char *newstates) |
| { |
| CPUState *cpu; |
| int res = 0; |
| /* |
| * This is not exactly accurate, but it's an improvement compared to the |
| * previous situation, where only one CPU would be single-stepped. |
| */ |
| CPU_FOREACH(cpu) { |
| if (newstates[cpu->cpu_index] == 's') { |
| trace_gdbstub_op_stepping(cpu->cpu_index); |
| cpu_single_step(cpu, gdbserver_state.sstep_flags); |
| } |
| } |
| gdbserver_user_state.running_state = 1; |
| return res; |
| } |
| |
| /* |
| * Memory access helpers |
| */ |
| int gdb_target_memory_rw_debug(CPUState *cpu, hwaddr 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); |
| } |
| |
| /* |
| * cpu helpers |
| */ |
| |
| unsigned int gdb_get_max_cpus(void) |
| { |
| CPUState *cpu; |
| unsigned int max_cpus = 1; |
| |
| CPU_FOREACH(cpu) { |
| max_cpus = max_cpus <= cpu->cpu_index ? cpu->cpu_index + 1 : max_cpus; |
| } |
| |
| return max_cpus; |
| } |
| |
| /* replay not supported for user-mode */ |
| bool gdb_can_reverse(void) |
| { |
| return false; |
| } |
| |
| /* |
| * Break/Watch point helpers |
| */ |
| |
| bool gdb_supports_guest_debug(void) |
| { |
| /* user-mode == TCG == supported */ |
| return true; |
| } |
| |
| int gdb_breakpoint_insert(CPUState *cs, int type, vaddr addr, vaddr len) |
| { |
| CPUState *cpu; |
| int err = 0; |
| |
| switch (type) { |
| case GDB_BREAKPOINT_SW: |
| case GDB_BREAKPOINT_HW: |
| CPU_FOREACH(cpu) { |
| err = cpu_breakpoint_insert(cpu, addr, BP_GDB, NULL); |
| if (err) { |
| break; |
| } |
| } |
| return err; |
| default: |
| /* user-mode doesn't support watchpoints */ |
| return -ENOSYS; |
| } |
| } |
| |
| int gdb_breakpoint_remove(CPUState *cs, int type, vaddr addr, vaddr len) |
| { |
| CPUState *cpu; |
| int err = 0; |
| |
| switch (type) { |
| case GDB_BREAKPOINT_SW: |
| case GDB_BREAKPOINT_HW: |
| CPU_FOREACH(cpu) { |
| err = cpu_breakpoint_remove(cpu, addr, BP_GDB); |
| if (err) { |
| break; |
| } |
| } |
| return err; |
| default: |
| /* user-mode doesn't support watchpoints */ |
| return -ENOSYS; |
| } |
| } |
| |
| void gdb_breakpoint_remove_all(CPUState *cs) |
| { |
| cpu_breakpoint_remove_all(cs, BP_GDB); |
| } |
| |
| /* |
| * For user-mode syscall support we send the system call immediately |
| * and then return control to gdb for it to process the syscall request. |
| * Since the protocol requires that gdb hands control back to us |
| * using a "here are the results" F packet, we don't need to check |
| * gdb_handlesig's return value (which is the signal to deliver if |
| * execution was resumed via a continue packet). |
| */ |
| void gdb_syscall_handling(const char *syscall_packet) |
| { |
| gdb_put_packet(syscall_packet); |
| gdb_handlesig(gdbserver_state.c_cpu, 0, NULL, NULL, 0); |
| } |
| |
| static bool should_catch_syscall(int num) |
| { |
| if (gdbserver_user_state.catch_all_syscalls) { |
| return true; |
| } |
| if (num < 0 || num >= GDB_NR_SYSCALLS) { |
| return false; |
| } |
| return test_bit(num, gdbserver_user_state.catch_syscalls_mask); |
| } |
| |
| void gdb_syscall_entry(CPUState *cs, int num) |
| { |
| if (should_catch_syscall(num)) { |
| g_autofree char *reason = g_strdup_printf("syscall_entry:%x;", num); |
| gdb_handlesig(cs, gdb_target_sigtrap(), reason, NULL, 0); |
| } |
| } |
| |
| void gdb_syscall_return(CPUState *cs, int num) |
| { |
| if (should_catch_syscall(num)) { |
| g_autofree char *reason = g_strdup_printf("syscall_return:%x;", num); |
| gdb_handlesig(cs, gdb_target_sigtrap(), reason, NULL, 0); |
| } |
| } |
| |
| void gdb_handle_set_catch_syscalls(GArray *params, void *user_ctx) |
| { |
| const char *param = get_param(params, 0)->data; |
| GDBSyscallsMask catch_syscalls_mask; |
| bool catch_all_syscalls; |
| unsigned int num; |
| const char *p; |
| |
| /* "0" means not catching any syscalls. */ |
| if (strcmp(param, "0") == 0) { |
| gdbserver_user_state.catch_all_syscalls = false; |
| memset(gdbserver_user_state.catch_syscalls_mask, 0, |
| sizeof(gdbserver_user_state.catch_syscalls_mask)); |
| gdb_put_packet("OK"); |
| return; |
| } |
| |
| /* "1" means catching all syscalls. */ |
| if (strcmp(param, "1") == 0) { |
| gdbserver_user_state.catch_all_syscalls = true; |
| gdb_put_packet("OK"); |
| return; |
| } |
| |
| /* |
| * "1;..." means catching only the specified syscalls. |
| * The syscall list must not be empty. |
| */ |
| if (param[0] == '1' && param[1] == ';') { |
| catch_all_syscalls = false; |
| memset(catch_syscalls_mask, 0, sizeof(catch_syscalls_mask)); |
| for (p = ¶m[2];; p++) { |
| if (qemu_strtoui(p, &p, 16, &num) || (*p && *p != ';')) { |
| goto err; |
| } |
| if (num >= GDB_NR_SYSCALLS) { |
| /* |
| * Fall back to reporting all syscalls. Reporting extra |
| * syscalls is inefficient, but the spec explicitly allows it. |
| * Keep parsing in case there is a syntax error ahead. |
| */ |
| catch_all_syscalls = true; |
| } else { |
| set_bit(num, catch_syscalls_mask); |
| } |
| if (!*p) { |
| break; |
| } |
| } |
| gdbserver_user_state.catch_all_syscalls = catch_all_syscalls; |
| if (!catch_all_syscalls) { |
| memcpy(gdbserver_user_state.catch_syscalls_mask, |
| catch_syscalls_mask, sizeof(catch_syscalls_mask)); |
| } |
| gdb_put_packet("OK"); |
| return; |
| } |
| |
| err: |
| gdb_put_packet("E00"); |
| } |
| |
| void gdb_handle_query_xfer_siginfo(GArray *params, void *user_ctx) |
| { |
| unsigned long offset, len; |
| uint8_t *siginfo_offset; |
| |
| offset = get_param(params, 0)->val_ul; |
| len = get_param(params, 1)->val_ul; |
| |
| if (offset + len > gdbserver_user_state.siginfo_len) { |
| /* Invalid offset and/or requested length. */ |
| gdb_put_packet("E01"); |
| return; |
| } |
| |
| siginfo_offset = (uint8_t *)gdbserver_user_state.siginfo + offset; |
| |
| /* Reply */ |
| g_string_assign(gdbserver_state.str_buf, "l"); |
| gdb_memtox(gdbserver_state.str_buf, (const char *)siginfo_offset, len); |
| gdb_put_packet_binary(gdbserver_state.str_buf->str, |
| gdbserver_state.str_buf->len, true); |
| } |