 133f202b19
			
		
	
	
		133f202b19
		
	
	
	
	
		
			
			Move GdbCmdParseEntry and its associated types into a separate header file to allow the use of GdbCmdParseEntry and other gdbstub command functions outside of gdbstub.c. Since GdbCmdParseEntry and get_param are now public, kdoc GdbCmdParseEntry and rename get_param to gdb_get_cmd_param. This commit also makes gdb_put_packet public since is used in gdbstub command handling. Signed-off-by: Gustavo Romero <gustavo.romero@linaro.org> Reviewed-by: Alex Bennée <alex.bennee@linaro.org> Message-Id: <20240628050850.536447-3-gustavo.romero@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-Id: <20240705084047.857176-32-alex.bennee@linaro.org>
		
			
				
	
	
		
			879 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			879 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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/commands.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 = gdb_get_cmd_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 = gdb_get_cmd_param(params, 0)->val_ul;
 | |
|     len = gdb_get_cmd_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);
 | |
| }
 |