|  | /* | 
|  | * 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/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 "hw/core/cpu.h" | 
|  | #include "trace.h" | 
|  | #include "internals.h" | 
|  |  | 
|  | /* User-mode specific state */ | 
|  | typedef struct { | 
|  | int fd; | 
|  | char *socket_path; | 
|  | int running_state; | 
|  | } 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | int gdb_handlesig(CPUState *cpu, int sig) | 
|  | { | 
|  | char buf[256]; | 
|  | int n; | 
|  |  | 
|  | if (!gdbserver_state.init || gdbserver_user_state.fd < 0) { | 
|  | return sig; | 
|  | } | 
|  |  | 
|  | /* 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, ';'); | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /* Disable gdb stub for child processes.  */ | 
|  | void gdbserver_fork(CPUState *cpu) | 
|  | { | 
|  | if (!gdbserver_state.init || gdbserver_user_state.fd < 0) { | 
|  | return; | 
|  | } | 
|  | close(gdbserver_user_state.fd); | 
|  | gdbserver_user_state.fd = -1; | 
|  | cpu_breakpoint_remove_all(cpu, BP_GDB); | 
|  | /* no cpu_watchpoint_remove_all for user-mode */ | 
|  | } | 
|  |  | 
|  | /* | 
|  | * 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); | 
|  | } |