aboutsummaryrefslogtreecommitdiff
path: root/gdbstub/system.c
diff options
context:
space:
mode:
Diffstat (limited to 'gdbstub/system.c')
-rw-r--r--gdbstub/system.c658
1 files changed, 658 insertions, 0 deletions
diff --git a/gdbstub/system.c b/gdbstub/system.c
new file mode 100644
index 0000000..189975b
--- /dev/null
+++ b/gdbstub/system.c
@@ -0,0 +1,658 @@
+/*
+ * gdb server stub - softmmu specific bits
+ *
+ * Debug integration depends on support from the individual
+ * accelerators so most of this involves calling the ops helpers.
+ *
+ * Copyright (c) 2003-2005 Fabrice Bellard
+ * Copyright (c) 2022 Linaro Ltd
+ *
+ * SPDX-License-Identifier: LGPL-2.0+
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/cutils.h"
+#include "exec/gdbstub.h"
+#include "gdbstub/syscalls.h"
+#include "exec/hwaddr.h"
+#include "exec/tb-flush.h"
+#include "sysemu/cpus.h"
+#include "sysemu/runstate.h"
+#include "sysemu/replay.h"
+#include "hw/core/cpu.h"
+#include "hw/cpu/cluster.h"
+#include "hw/boards.h"
+#include "chardev/char.h"
+#include "chardev/char-fe.h"
+#include "monitor/monitor.h"
+#include "trace.h"
+#include "internals.h"
+
+/* System emulation specific state */
+typedef struct {
+ CharBackend chr;
+ Chardev *mon_chr;
+} GDBSystemState;
+
+GDBSystemState gdbserver_system_state;
+
+static void reset_gdbserver_state(void)
+{
+ g_free(gdbserver_state.processes);
+ gdbserver_state.processes = NULL;
+ gdbserver_state.process_num = 0;
+ gdbserver_state.allow_stop_reply = false;
+}
+
+/*
+ * Return the GDB index for a given vCPU state.
+ *
+ * In system mode GDB numbers CPUs from 1 as 0 is reserved as an "any
+ * cpu" index.
+ */
+int gdb_get_cpu_index(CPUState *cpu)
+{
+ return cpu->cpu_index + 1;
+}
+
+/*
+ * We check the status of the last message in the chardev receive code
+ */
+bool gdb_got_immediate_ack(void)
+{
+ return true;
+}
+
+/*
+ * GDB Connection management. For system emulation we do all of this
+ * via our existing Chardev infrastructure which allows us to support
+ * network and unix sockets.
+ */
+
+void gdb_put_buffer(const uint8_t *buf, int len)
+{
+ /*
+ * XXX this blocks entire thread. Rewrite to use
+ * qemu_chr_fe_write and background I/O callbacks
+ */
+ qemu_chr_fe_write_all(&gdbserver_system_state.chr, buf, len);
+}
+
+static void gdb_chr_event(void *opaque, QEMUChrEvent event)
+{
+ int i;
+ GDBState *s = (GDBState *) opaque;
+
+ switch (event) {
+ case CHR_EVENT_OPENED:
+ /* Start with first process attached, others detached */
+ for (i = 0; i < s->process_num; i++) {
+ s->processes[i].attached = !i;
+ }
+
+ s->c_cpu = gdb_first_attached_cpu();
+ s->g_cpu = s->c_cpu;
+
+ vm_stop(RUN_STATE_PAUSED);
+ replay_gdb_attached();
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * In system-mode we stop the VM and wait to send the syscall packet
+ * until notification that the CPU has stopped. This must be done
+ * because if the packet is sent now the reply from the syscall
+ * request could be received while the CPU is still in the running
+ * state, which can cause packets to be dropped and state transition
+ * 'T' packets to be sent while the syscall is still being processed.
+ */
+void gdb_syscall_handling(const char *syscall_packet)
+{
+ vm_stop(RUN_STATE_DEBUG);
+ qemu_cpu_kick(gdbserver_state.c_cpu);
+}
+
+static void gdb_vm_state_change(void *opaque, bool running, RunState state)
+{
+ CPUState *cpu = gdbserver_state.c_cpu;
+ g_autoptr(GString) buf = g_string_new(NULL);
+ g_autoptr(GString) tid = g_string_new(NULL);
+ const char *type;
+ int ret;
+
+ if (running || gdbserver_state.state == RS_INACTIVE) {
+ return;
+ }
+
+ /* Is there a GDB syscall waiting to be sent? */
+ if (gdb_handled_syscall()) {
+ return;
+ }
+
+ if (cpu == NULL) {
+ /* No process attached */
+ return;
+ }
+
+ if (!gdbserver_state.allow_stop_reply) {
+ return;
+ }
+
+ gdb_append_thread_id(cpu, tid);
+
+ switch (state) {
+ case RUN_STATE_DEBUG:
+ if (cpu->watchpoint_hit) {
+ switch (cpu->watchpoint_hit->flags & BP_MEM_ACCESS) {
+ case BP_MEM_READ:
+ type = "r";
+ break;
+ case BP_MEM_ACCESS:
+ type = "a";
+ break;
+ default:
+ type = "";
+ break;
+ }
+ trace_gdbstub_hit_watchpoint(type,
+ gdb_get_cpu_index(cpu),
+ cpu->watchpoint_hit->vaddr);
+ g_string_printf(buf, "T%02xthread:%s;%swatch:%" VADDR_PRIx ";",
+ GDB_SIGNAL_TRAP, tid->str, type,
+ cpu->watchpoint_hit->vaddr);
+ cpu->watchpoint_hit = NULL;
+ goto send_packet;
+ } else {
+ trace_gdbstub_hit_break();
+ }
+ tb_flush(cpu);
+ ret = GDB_SIGNAL_TRAP;
+ break;
+ case RUN_STATE_PAUSED:
+ trace_gdbstub_hit_paused();
+ ret = GDB_SIGNAL_INT;
+ break;
+ case RUN_STATE_SHUTDOWN:
+ trace_gdbstub_hit_shutdown();
+ ret = GDB_SIGNAL_QUIT;
+ break;
+ case RUN_STATE_IO_ERROR:
+ trace_gdbstub_hit_io_error();
+ ret = GDB_SIGNAL_IO;
+ break;
+ case RUN_STATE_WATCHDOG:
+ trace_gdbstub_hit_watchdog();
+ ret = GDB_SIGNAL_ALRM;
+ break;
+ case RUN_STATE_INTERNAL_ERROR:
+ trace_gdbstub_hit_internal_error();
+ ret = GDB_SIGNAL_ABRT;
+ break;
+ case RUN_STATE_SAVE_VM:
+ case RUN_STATE_RESTORE_VM:
+ return;
+ case RUN_STATE_FINISH_MIGRATE:
+ ret = GDB_SIGNAL_XCPU;
+ break;
+ default:
+ trace_gdbstub_hit_unknown(state);
+ ret = GDB_SIGNAL_UNKNOWN;
+ break;
+ }
+ gdb_set_stop_cpu(cpu);
+ g_string_printf(buf, "T%02xthread:%s;", ret, tid->str);
+
+send_packet:
+ gdb_put_packet(buf->str);
+ gdbserver_state.allow_stop_reply = false;
+
+ /* disable single step if it was enabled */
+ cpu_single_step(cpu, 0);
+}
+
+#ifndef _WIN32
+static void gdb_sigterm_handler(int signal)
+{
+ if (runstate_is_running()) {
+ vm_stop(RUN_STATE_PAUSED);
+ }
+}
+#endif
+
+static int gdb_monitor_write(Chardev *chr, const uint8_t *buf, int len)
+{
+ g_autoptr(GString) hex_buf = g_string_new("O");
+ gdb_memtohex(hex_buf, buf, len);
+ gdb_put_packet(hex_buf->str);
+ return len;
+}
+
+static void gdb_monitor_open(Chardev *chr, ChardevBackend *backend,
+ bool *be_opened, Error **errp)
+{
+ *be_opened = false;
+}
+
+static void char_gdb_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->internal = true;
+ cc->open = gdb_monitor_open;
+ cc->chr_write = gdb_monitor_write;
+}
+
+#define TYPE_CHARDEV_GDB "chardev-gdb"
+
+static const TypeInfo char_gdb_type_info = {
+ .name = TYPE_CHARDEV_GDB,
+ .parent = TYPE_CHARDEV,
+ .class_init = char_gdb_class_init,
+};
+
+static int gdb_chr_can_receive(void *opaque)
+{
+ /*
+ * We can handle an arbitrarily large amount of data.
+ * Pick the maximum packet size, which is as good as anything.
+ */
+ return MAX_PACKET_LENGTH;
+}
+
+static void gdb_chr_receive(void *opaque, const uint8_t *buf, int size)
+{
+ int i;
+
+ for (i = 0; i < size; i++) {
+ gdb_read_byte(buf[i]);
+ }
+}
+
+static int find_cpu_clusters(Object *child, void *opaque)
+{
+ if (object_dynamic_cast(child, TYPE_CPU_CLUSTER)) {
+ GDBState *s = (GDBState *) opaque;
+ CPUClusterState *cluster = CPU_CLUSTER(child);
+ GDBProcess *process;
+
+ s->processes = g_renew(GDBProcess, s->processes, ++s->process_num);
+
+ process = &s->processes[s->process_num - 1];
+
+ /*
+ * GDB process IDs -1 and 0 are reserved. To avoid subtle errors at
+ * runtime, we enforce here that the machine does not use a cluster ID
+ * that would lead to PID 0.
+ */
+ assert(cluster->cluster_id != UINT32_MAX);
+ process->pid = cluster->cluster_id + 1;
+ process->attached = false;
+ process->target_xml[0] = '\0';
+
+ return 0;
+ }
+
+ return object_child_foreach(child, find_cpu_clusters, opaque);
+}
+
+static int pid_order(const void *a, const void *b)
+{
+ GDBProcess *pa = (GDBProcess *) a;
+ GDBProcess *pb = (GDBProcess *) b;
+
+ if (pa->pid < pb->pid) {
+ return -1;
+ } else if (pa->pid > pb->pid) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+static void create_processes(GDBState *s)
+{
+ object_child_foreach(object_get_root(), find_cpu_clusters, s);
+
+ if (gdbserver_state.processes) {
+ /* Sort by PID */
+ qsort(gdbserver_state.processes,
+ gdbserver_state.process_num,
+ sizeof(gdbserver_state.processes[0]),
+ pid_order);
+ }
+
+ gdb_create_default_process(s);
+}
+
+int gdbserver_start(const char *device)
+{
+ Chardev *chr = NULL;
+ Chardev *mon_chr;
+ g_autoptr(GString) cs = g_string_new(device);
+
+ if (!first_cpu) {
+ error_report("gdbstub: meaningless to attach gdb to a "
+ "machine without any CPU.");
+ return -1;
+ }
+
+ if (!gdb_supports_guest_debug()) {
+ error_report("gdbstub: current accelerator doesn't "
+ "support guest debugging");
+ return -1;
+ }
+
+ if (cs->len == 0) {
+ return -1;
+ }
+
+ trace_gdbstub_op_start(cs->str);
+
+ if (g_strcmp0(cs->str, "none") != 0) {
+ if (g_str_has_prefix(cs->str, "tcp:")) {
+ /* enforce required TCP attributes */
+ g_string_append_printf(cs, ",wait=off,nodelay=on,server=on");
+ }
+#ifndef _WIN32
+ else if (strcmp(device, "stdio") == 0) {
+ struct sigaction act;
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = gdb_sigterm_handler;
+ sigaction(SIGINT, &act, NULL);
+ }
+#endif
+ /*
+ * FIXME: it's a bit weird to allow using a mux chardev here
+ * and implicitly setup a monitor. We may want to break this.
+ */
+ chr = qemu_chr_new_noreplay("gdb", cs->str, true, NULL);
+ if (!chr) {
+ return -1;
+ }
+ }
+
+ if (!gdbserver_state.init) {
+ gdb_init_gdbserver_state();
+
+ qemu_add_vm_change_state_handler(gdb_vm_state_change, NULL);
+
+ /* Initialize a monitor terminal for gdb */
+ mon_chr = qemu_chardev_new(NULL, TYPE_CHARDEV_GDB,
+ NULL, NULL, &error_abort);
+ monitor_init_hmp(mon_chr, false, &error_abort);
+ } else {
+ qemu_chr_fe_deinit(&gdbserver_system_state.chr, true);
+ mon_chr = gdbserver_system_state.mon_chr;
+ reset_gdbserver_state();
+ }
+
+ create_processes(&gdbserver_state);
+
+ if (chr) {
+ qemu_chr_fe_init(&gdbserver_system_state.chr, chr, &error_abort);
+ qemu_chr_fe_set_handlers(&gdbserver_system_state.chr,
+ gdb_chr_can_receive,
+ gdb_chr_receive, gdb_chr_event,
+ NULL, &gdbserver_state, NULL, true);
+ }
+ gdbserver_state.state = chr ? RS_IDLE : RS_INACTIVE;
+ gdbserver_system_state.mon_chr = mon_chr;
+ gdb_syscall_reset();
+
+ return 0;
+}
+
+static void register_types(void)
+{
+ type_register_static(&char_gdb_type_info);
+}
+
+type_init(register_types);
+
+/* Tell the remote gdb that the process has exited. */
+void gdb_exit(int code)
+{
+ char buf[4];
+
+ if (!gdbserver_state.init) {
+ 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;
+ }
+
+ qemu_chr_fe_deinit(&gdbserver_system_state.chr, true);
+}
+
+/*
+ * Memory access
+ */
+static int phy_memory_mode;
+
+int gdb_target_memory_rw_debug(CPUState *cpu, hwaddr addr,
+ uint8_t *buf, int len, bool is_write)
+{
+ CPUClass *cc;
+
+ if (phy_memory_mode) {
+ if (is_write) {
+ cpu_physical_memory_write(addr, buf, len);
+ } else {
+ cpu_physical_memory_read(addr, buf, len);
+ }
+ return 0;
+ }
+
+ 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)
+{
+ MachineState *ms = MACHINE(qdev_get_machine());
+ return ms->smp.max_cpus;
+}
+
+bool gdb_can_reverse(void)
+{
+ return replay_mode == REPLAY_MODE_PLAY;
+}
+
+/*
+ * Softmmu specific command helpers
+ */
+
+void gdb_handle_query_qemu_phy_mem_mode(GArray *params,
+ void *user_ctx)
+{
+ g_string_printf(gdbserver_state.str_buf, "%d", phy_memory_mode);
+ gdb_put_strbuf();
+}
+
+void gdb_handle_set_qemu_phy_mem_mode(GArray *params, void *user_ctx)
+{
+ if (!params->len) {
+ gdb_put_packet("E22");
+ return;
+ }
+
+ if (!get_param(params, 0)->val_ul) {
+ phy_memory_mode = 0;
+ } else {
+ phy_memory_mode = 1;
+ }
+ gdb_put_packet("OK");
+}
+
+void gdb_handle_query_rcmd(GArray *params, void *user_ctx)
+{
+ const guint8 zero = 0;
+ int len;
+
+ if (!params->len) {
+ gdb_put_packet("E22");
+ return;
+ }
+
+ len = strlen(get_param(params, 0)->data);
+ if (len % 2) {
+ gdb_put_packet("E01");
+ return;
+ }
+
+ g_assert(gdbserver_state.mem_buf->len == 0);
+ len = len / 2;
+ gdb_hextomem(gdbserver_state.mem_buf, get_param(params, 0)->data, len);
+ g_byte_array_append(gdbserver_state.mem_buf, &zero, 1);
+ qemu_chr_be_write(gdbserver_system_state.mon_chr,
+ gdbserver_state.mem_buf->data,
+ gdbserver_state.mem_buf->len);
+ gdb_put_packet("OK");
+}
+
+/*
+ * Execution state helpers
+ */
+
+void gdb_handle_query_attached(GArray *params, void *user_ctx)
+{
+ gdb_put_packet("1");
+}
+
+void gdb_continue(void)
+{
+ if (!runstate_needs_reset()) {
+ trace_gdbstub_op_continue();
+ vm_start();
+ }
+}
+
+/*
+ * Resume execution, per CPU actions.
+ */
+int gdb_continue_partial(char *newstates)
+{
+ CPUState *cpu;
+ int res = 0;
+ int flag = 0;
+
+ if (!runstate_needs_reset()) {
+ bool step_requested = false;
+ CPU_FOREACH(cpu) {
+ if (newstates[cpu->cpu_index] == 's') {
+ step_requested = true;
+ break;
+ }
+ }
+
+ if (vm_prepare_start(step_requested)) {
+ return 0;
+ }
+
+ CPU_FOREACH(cpu) {
+ switch (newstates[cpu->cpu_index]) {
+ case 0:
+ case 1:
+ break; /* nothing to do here */
+ case 's':
+ trace_gdbstub_op_stepping(cpu->cpu_index);
+ cpu_single_step(cpu, gdbserver_state.sstep_flags);
+ cpu_resume(cpu);
+ flag = 1;
+ break;
+ case 'c':
+ trace_gdbstub_op_continue_cpu(cpu->cpu_index);
+ cpu_resume(cpu);
+ flag = 1;
+ break;
+ default:
+ res = -1;
+ break;
+ }
+ }
+ }
+ if (flag) {
+ qemu_clock_enable(QEMU_CLOCK_VIRTUAL, true);
+ }
+ return res;
+}
+
+/*
+ * Signal Handling - in system mode we only need SIGINT and SIGTRAP; other
+ * signals are not yet supported.
+ */
+
+enum {
+ TARGET_SIGINT = 2,
+ TARGET_SIGTRAP = 5
+};
+
+int gdb_signal_to_target(int sig)
+{
+ switch (sig) {
+ case 2:
+ return TARGET_SIGINT;
+ case 5:
+ return TARGET_SIGTRAP;
+ default:
+ return -1;
+ }
+}
+
+/*
+ * Break/Watch point helpers
+ */
+
+bool gdb_supports_guest_debug(void)
+{
+ const AccelOpsClass *ops = cpus_get_accel();
+ if (ops->supports_guest_debug) {
+ return ops->supports_guest_debug();
+ }
+ return false;
+}
+
+int gdb_breakpoint_insert(CPUState *cs, int type, vaddr addr, vaddr len)
+{
+ const AccelOpsClass *ops = cpus_get_accel();
+ if (ops->insert_breakpoint) {
+ return ops->insert_breakpoint(cs, type, addr, len);
+ }
+ return -ENOSYS;
+}
+
+int gdb_breakpoint_remove(CPUState *cs, int type, vaddr addr, vaddr len)
+{
+ const AccelOpsClass *ops = cpus_get_accel();
+ if (ops->remove_breakpoint) {
+ return ops->remove_breakpoint(cs, type, addr, len);
+ }
+ return -ENOSYS;
+}
+
+void gdb_breakpoint_remove_all(CPUState *cs)
+{
+ const AccelOpsClass *ops = cpus_get_accel();
+ if (ops->remove_all_breakpoints) {
+ ops->remove_all_breakpoints(cs);
+ }
+}