123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958 |
- /*
- * 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-or-later
- */
- #include "qemu/osdep.h"
- #include "qemu/bitops.h"
- #include "qemu/cutils.h"
- #include "qemu/sockets.h"
- #include "qapi/error.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 "user/signal.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, Error **errp)
- {
- g_autoptr(GString) buf = g_string_new("");
- char *pid_placeholder;
- pid_placeholder = strstr(path, "%d");
- if (pid_placeholder != NULL) {
- g_string_append_len(buf, path, pid_placeholder - path);
- g_string_append_printf(buf, "%d", qemu_get_thread_id());
- g_string_append(buf, pid_placeholder + 2);
- path = buf->str;
- }
- return unix_listen(path, errp);
- }
- 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, Error **errp)
- {
- struct sockaddr_in sockaddr;
- int fd, ret;
- fd = socket(PF_INET, SOCK_STREAM, 0);
- if (fd < 0) {
- error_setg_errno(errp, errno, "Failed to create 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) {
- error_setg_errno(errp, errno, "Failed to bind socket");
- close(fd);
- return -1;
- }
- ret = listen(fd, 1);
- if (ret < 0) {
- error_setg_errno(errp, errno, "Failed to listen to socket");
- close(fd);
- return -1;
- }
- return fd;
- }
- static bool gdbserver_accept(int port, int gdb_fd, const char *path)
- {
- bool ret;
- if (port > 0) {
- ret = gdb_accept_tcp(gdb_fd);
- } else {
- ret = gdb_accept_socket(gdb_fd);
- if (ret) {
- gdbserver_user_state.socket_path = g_strdup(path);
- }
- }
- if (!ret) {
- close(gdb_fd);
- }
- return ret;
- }
- struct {
- int port;
- int gdb_fd;
- char *path;
- } gdbserver_args;
- static void do_gdb_handlesig(CPUState *cs, run_on_cpu_data arg)
- {
- int sig;
- sig = target_to_host_signal(gdb_handlesig(cs, 0, NULL, NULL, 0));
- if (sig >= 1 && sig < NSIG) {
- qemu_kill_thread(gdb_get_cpu_index(cs), sig);
- }
- }
- static void *gdbserver_accept_thread(void *arg)
- {
- if (gdbserver_accept(gdbserver_args.port, gdbserver_args.gdb_fd,
- gdbserver_args.path)) {
- CPUState *cs = first_cpu;
- async_safe_run_on_cpu(cs, do_gdb_handlesig, RUN_ON_CPU_NULL);
- qemu_kill_thread(gdb_get_cpu_index(cs), host_interrupt_signal);
- }
- g_free(gdbserver_args.path);
- gdbserver_args.path = NULL;
- return NULL;
- }
- #define USAGE "\nUsage: -g {port|path}[,suspend={y|n}]"
- bool gdbserver_start(const char *args, Error **errp)
- {
- g_auto(GStrv) argv = g_strsplit(args, ",", 0);
- const char *port_or_path = NULL;
- bool suspend = true;
- int gdb_fd, port;
- GStrv arg;
- for (arg = argv; *arg; arg++) {
- g_auto(GStrv) tokens = g_strsplit(*arg, "=", 2);
- if (g_strcmp0(tokens[0], "suspend") == 0) {
- if (tokens[1] == NULL) {
- error_setg(errp,
- "gdbstub: missing \"suspend\" option value" USAGE);
- return false;
- } else if (!qapi_bool_parse(tokens[0], tokens[1],
- &suspend, errp)) {
- return false;
- }
- } else {
- if (port_or_path) {
- error_setg(errp, "gdbstub: unknown option \"%s\"" USAGE, *arg);
- return false;
- }
- port_or_path = *arg;
- }
- }
- if (!port_or_path) {
- error_setg(errp, "gdbstub: port or path not specified" USAGE);
- return false;
- }
- port = g_ascii_strtoull(port_or_path, NULL, 10);
- if (port > 0) {
- gdb_fd = gdbserver_open_port(port, errp);
- } else {
- gdb_fd = gdbserver_open_socket(port_or_path, errp);
- }
- if (gdb_fd < 0) {
- return false;
- }
- if (suspend) {
- if (gdbserver_accept(port, gdb_fd, port_or_path)) {
- gdb_handlesig(first_cpu, 0, NULL, NULL, 0);
- return true;
- } else {
- error_setg(errp, "gdbstub: failed to accept connection");
- return false;
- }
- } else {
- QemuThread thread;
- gdbserver_args.port = port;
- gdbserver_args.gdb_fd = gdb_fd;
- gdbserver_args.path = g_strdup(port_or_path);
- qemu_thread_create(&thread, "gdb-accept",
- &gdbserver_accept_thread, NULL,
- QEMU_THREAD_DETACHED);
- return true;
- }
- }
- 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);
- }
|