#include "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/cutils.h"
-#include "cpu.h"
+#include "trace-root.h"
#ifdef CONFIG_USER_ONLY
#include "qemu.h"
#else
#include "monitor/monitor.h"
-#include "sysemu/char.h"
+#include "chardev/char.h"
+#include "chardev/char-fe.h"
#include "sysemu/sysemu.h"
#include "exec/gdbstub.h"
+#include "hw/cpu/cluster.h"
#endif
#define MAX_PACKET_LENGTH 4096
return cpu_memory_rw_debug(cpu, addr, buf, len, is_write);
}
+/* Return the GDB index for a given vCPU state.
+ *
+ * For user mode this is simply the thread id. In system mode GDB
+ * numbers CPUs from 1 as 0 is reserved as an "any cpu" index.
+ */
+static inline int cpu_gdb_index(CPUState *cpu)
+{
+#if defined(CONFIG_USER_ONLY)
+ TaskState *ts = (TaskState *) cpu->opaque;
+ return ts->ts_tid;
+#else
+ return cpu->cpu_index + 1;
+#endif
+}
+
enum {
GDB_SIGNAL_0 = 0,
GDB_SIGNAL_INT = 2,
return -1;
}
-//#define DEBUG_GDB
-
typedef struct GDBRegisterState {
int base_reg;
int num_regs;
struct GDBRegisterState *next;
} GDBRegisterState;
+typedef struct GDBProcess {
+ uint32_t pid;
+ bool attached;
+
+ char target_xml[1024];
+} GDBProcess;
+
enum RSState {
RS_INACTIVE,
RS_IDLE,
RS_GETLINE,
+ RS_GETLINE_ESC,
+ RS_GETLINE_RLE,
RS_CHKSUM1,
RS_CHKSUM2,
};
enum RSState state; /* parsing state */
char line_buf[MAX_PACKET_LENGTH];
int line_buf_index;
- int line_csum;
+ int line_sum; /* running checksum */
+ int line_csum; /* checksum at the end of the packet */
uint8_t last_packet[MAX_PACKET_LENGTH + 4];
int last_packet_len;
int signal;
int running_state;
#else
CharBackend chr;
- CharDriverState *mon_chr;
+ Chardev *mon_chr;
#endif
+ bool multiprocess;
+ GDBProcess *processes;
+ int process_num;
char syscall_buf[256];
gdb_syscall_complete_cb current_syscall_cb;
} GDBState;
/* Resume execution. */
static inline void gdb_continue(GDBState *s)
{
+
#ifdef CONFIG_USER_ONLY
s->running_state = 1;
+ trace_gdbstub_op_continue();
#else
if (!runstate_needs_reset()) {
+ trace_gdbstub_op_continue();
vm_start();
}
#endif
}
+/*
+ * Resume execution, per CPU actions. For user-mode emulation it's
+ * equivalent to gdb_continue.
+ */
+static int gdb_continue_partial(GDBState *s, char *newstates)
+{
+ CPUState *cpu;
+ int res = 0;
+#ifdef CONFIG_USER_ONLY
+ /*
+ * 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, sstep_flags);
+ }
+ }
+ s->running_state = 1;
+#else
+ int flag = 0;
+
+ if (!runstate_needs_reset()) {
+ if (vm_prepare_start()) {
+ 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, 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);
+ }
+#endif
+ return res;
+}
+
static void put_buffer(GDBState *s, const uint8_t *buf, int len)
{
#ifdef CONFIG_USER_ONLY
return v - 10 + 'a';
}
+/* writes 2*len+1 bytes in buf */
static void memtohex(char *buf, const uint8_t *mem, int len)
{
int i, c;
}
}
+static void hexdump(const char *buf, int len,
+ void (*trace_fn)(size_t ofs, char const *text))
+{
+ char line_buffer[3 * 16 + 4 + 16 + 1];
+
+ size_t i;
+ for (i = 0; i < len || (i & 0xF); ++i) {
+ size_t byte_ofs = i & 15;
+
+ if (byte_ofs == 0) {
+ memset(line_buffer, ' ', 3 * 16 + 4 + 16);
+ line_buffer[3 * 16 + 4 + 16] = 0;
+ }
+
+ size_t col_group = (i >> 2) & 3;
+ size_t hex_col = byte_ofs * 3 + col_group;
+ size_t txt_col = 3 * 16 + 4 + byte_ofs;
+
+ if (i < len) {
+ char value = buf[i];
+
+ line_buffer[hex_col + 0] = tohex((value >> 4) & 0xF);
+ line_buffer[hex_col + 1] = tohex((value >> 0) & 0xF);
+ line_buffer[txt_col + 0] = (value >= ' ' && value < 127)
+ ? value
+ : '.';
+ }
+
+ if (byte_ofs == 0xF)
+ trace_fn(i & -16, line_buffer);
+ }
+}
+
/* return -1 if error, 0 if OK */
-static int put_packet_binary(GDBState *s, const char *buf, int len)
+static int put_packet_binary(GDBState *s, const char *buf, int len, bool dump)
{
int csum, i;
uint8_t *p;
+ if (dump && trace_event_get_state_backends(TRACE_GDBSTUB_IO_BINARYREPLY)) {
+ hexdump(buf, len, trace_gdbstub_io_binaryreply);
+ }
+
for(;;) {
p = s->last_packet;
*(p++) = '$';
/* return -1 if error, 0 if OK */
static int put_packet(GDBState *s, const char *buf)
{
-#ifdef DEBUG_GDB
- printf("reply='%s'\n", buf);
-#endif
+ trace_gdbstub_io_reply(buf);
- return put_packet_binary(s, buf, strlen(buf));
+ return put_packet_binary(s, buf, strlen(buf), false);
}
/* Encode data using the encoding for 'x' packets. */
return p - buf;
}
-static const char *get_feature_xml(const char *p, const char **newp,
- CPUClass *cc)
+static uint32_t gdb_get_cpu_pid(const GDBState *s, CPUState *cpu)
+{
+#ifndef CONFIG_USER_ONLY
+ gchar *path, *name = NULL;
+ Object *obj;
+ CPUClusterState *cluster;
+ uint32_t ret;
+
+ path = object_get_canonical_path(OBJECT(cpu));
+
+ if (path == NULL) {
+ /* Return the default process' PID */
+ ret = s->processes[s->process_num - 1].pid;
+ goto out;
+ }
+
+ name = object_get_canonical_path_component(OBJECT(cpu));
+ assert(name != NULL);
+
+ /*
+ * Retrieve the CPU parent path by removing the last '/' and the CPU name
+ * from the CPU canonical path.
+ */
+ path[strlen(path) - strlen(name) - 1] = '\0';
+
+ obj = object_resolve_path_type(path, TYPE_CPU_CLUSTER, NULL);
+
+ if (obj == NULL) {
+ /* Return the default process' PID */
+ ret = s->processes[s->process_num - 1].pid;
+ goto out;
+ }
+
+ cluster = CPU_CLUSTER(obj);
+ ret = cluster->cluster_id + 1;
+
+out:
+ g_free(name);
+ g_free(path);
+
+ return ret;
+
+#else
+ /* TODO: In user mode, we should use the task state PID */
+ return s->processes[s->process_num - 1].pid;
+#endif
+}
+
+static GDBProcess *gdb_get_process(const GDBState *s, uint32_t pid)
+{
+ int i;
+
+ if (!pid) {
+ /* 0 means any process, we take the first one */
+ return &s->processes[0];
+ }
+
+ for (i = 0; i < s->process_num; i++) {
+ if (s->processes[i].pid == pid) {
+ return &s->processes[i];
+ }
+ }
+
+ return NULL;
+}
+
+static GDBProcess *gdb_get_cpu_process(const GDBState *s, CPUState *cpu)
+{
+ return gdb_get_process(s, gdb_get_cpu_pid(s, cpu));
+}
+
+static CPUState *find_cpu(uint32_t thread_id)
+{
+ CPUState *cpu;
+
+ CPU_FOREACH(cpu) {
+ if (cpu_gdb_index(cpu) == thread_id) {
+ return cpu;
+ }
+ }
+
+ return NULL;
+}
+
+static CPUState *get_first_cpu_in_process(const GDBState *s,
+ GDBProcess *process)
+{
+ CPUState *cpu;
+
+ CPU_FOREACH(cpu) {
+ if (gdb_get_cpu_pid(s, cpu) == process->pid) {
+ return cpu;
+ }
+ }
+
+ return NULL;
+}
+
+static CPUState *gdb_next_cpu_in_process(const GDBState *s, CPUState *cpu)
+{
+ uint32_t pid = gdb_get_cpu_pid(s, cpu);
+ cpu = CPU_NEXT(cpu);
+
+ while (cpu) {
+ if (gdb_get_cpu_pid(s, cpu) == pid) {
+ break;
+ }
+
+ cpu = CPU_NEXT(cpu);
+ }
+
+ return cpu;
+}
+
+static CPUState *gdb_get_cpu(const GDBState *s, uint32_t pid, uint32_t tid)
+{
+ GDBProcess *process;
+ CPUState *cpu;
+
+ if (!tid) {
+ /* 0 means any thread, we take the first one */
+ tid = 1;
+ }
+
+ cpu = find_cpu(tid);
+
+ if (cpu == NULL) {
+ return NULL;
+ }
+
+ process = gdb_get_cpu_process(s, cpu);
+
+ if (process->pid != pid) {
+ return NULL;
+ }
+
+ if (!process->attached) {
+ return NULL;
+ }
+
+ return cpu;
+}
+
+/* Return the cpu following @cpu, while ignoring unattached processes. */
+static CPUState *gdb_next_attached_cpu(const GDBState *s, CPUState *cpu)
+{
+ cpu = CPU_NEXT(cpu);
+
+ while (cpu) {
+ if (gdb_get_cpu_process(s, cpu)->attached) {
+ break;
+ }
+
+ cpu = CPU_NEXT(cpu);
+ }
+
+ return cpu;
+}
+
+/* Return the first attached cpu */
+static CPUState *gdb_first_attached_cpu(const GDBState *s)
+{
+ CPUState *cpu = first_cpu;
+ GDBProcess *process = gdb_get_cpu_process(s, cpu);
+
+ if (!process->attached) {
+ return gdb_next_attached_cpu(s, cpu);
+ }
+
+ return cpu;
+}
+
+static const char *get_feature_xml(const GDBState *s, const char *p,
+ const char **newp, GDBProcess *process)
{
size_t len;
int i;
const char *name;
- static char target_xml[1024];
+ CPUState *cpu = get_first_cpu_in_process(s, process);
+ CPUClass *cc = CPU_GET_CLASS(cpu);
len = 0;
while (p[len] && p[len] != ':')
name = NULL;
if (strncmp(p, "target.xml", len) == 0) {
+ char *buf = process->target_xml;
+ const size_t buf_sz = sizeof(process->target_xml);
+
/* Generate the XML description for this CPU. */
- if (!target_xml[0]) {
+ if (!buf[0]) {
GDBRegisterState *r;
- CPUState *cpu = first_cpu;
- pstrcat(target_xml, sizeof(target_xml),
+ pstrcat(buf, buf_sz,
"<?xml version=\"1.0\"?>"
"<!DOCTYPE target SYSTEM \"gdb-target.dtd\">"
"<target>");
if (cc->gdb_arch_name) {
gchar *arch = cc->gdb_arch_name(cpu);
- pstrcat(target_xml, sizeof(target_xml), "<architecture>");
- pstrcat(target_xml, sizeof(target_xml), arch);
- pstrcat(target_xml, sizeof(target_xml), "</architecture>");
+ pstrcat(buf, buf_sz, "<architecture>");
+ pstrcat(buf, buf_sz, arch);
+ pstrcat(buf, buf_sz, "</architecture>");
g_free(arch);
}
- pstrcat(target_xml, sizeof(target_xml), "<xi:include href=\"");
- pstrcat(target_xml, sizeof(target_xml), cc->gdb_core_xml_file);
- pstrcat(target_xml, sizeof(target_xml), "\"/>");
+ pstrcat(buf, buf_sz, "<xi:include href=\"");
+ pstrcat(buf, buf_sz, cc->gdb_core_xml_file);
+ pstrcat(buf, buf_sz, "\"/>");
for (r = cpu->gdb_regs; r; r = r->next) {
- pstrcat(target_xml, sizeof(target_xml), "<xi:include href=\"");
- pstrcat(target_xml, sizeof(target_xml), r->xml);
- pstrcat(target_xml, sizeof(target_xml), "\"/>");
+ pstrcat(buf, buf_sz, "<xi:include href=\"");
+ pstrcat(buf, buf_sz, r->xml);
+ pstrcat(buf, buf_sz, "\"/>");
}
- pstrcat(target_xml, sizeof(target_xml), "</target>");
+ pstrcat(buf, buf_sz, "</target>");
+ }
+ return buf;
+ }
+ if (cc->gdb_get_dynamic_xml) {
+ char *xmlname = g_strndup(p, len);
+ const char *xml = cc->gdb_get_dynamic_xml(cpu, xmlname);
+
+ g_free(xmlname);
+ if (xml) {
+ return xml;
}
- return target_xml;
}
for (i = 0; ; i++) {
name = xml_builtin[i][0];
}
}
+static inline void gdb_cpu_breakpoint_remove_all(CPUState *cpu)
+{
+ cpu_breakpoint_remove_all(cpu, BP_GDB);
+#ifndef CONFIG_USER_ONLY
+ cpu_watchpoint_remove_all(cpu, BP_GDB);
+#endif
+}
+
+static void gdb_process_breakpoint_remove_all(const GDBState *s, GDBProcess *p)
+{
+ CPUState *cpu = get_first_cpu_in_process(s, p);
+
+ while (cpu) {
+ gdb_cpu_breakpoint_remove_all(cpu);
+ cpu = gdb_next_cpu_in_process(s, cpu);
+ }
+}
+
static void gdb_breakpoint_remove_all(void)
{
CPUState *cpu;
}
CPU_FOREACH(cpu) {
- cpu_breakpoint_remove_all(cpu, BP_GDB);
-#ifndef CONFIG_USER_ONLY
- cpu_watchpoint_remove_all(cpu, BP_GDB);
-#endif
+ gdb_cpu_breakpoint_remove_all(cpu);
}
}
cpu_set_pc(cpu, pc);
}
-static CPUState *find_cpu(uint32_t thread_id)
+static char *gdb_fmt_thread_id(const GDBState *s, CPUState *cpu,
+ char *buf, size_t buf_size)
{
- CPUState *cpu;
+ if (s->multiprocess) {
+ snprintf(buf, buf_size, "p%02x.%02x",
+ gdb_get_cpu_pid(s, cpu), cpu_gdb_index(cpu));
+ } else {
+ snprintf(buf, buf_size, "%02x", cpu_gdb_index(cpu));
+ }
- CPU_FOREACH(cpu) {
- if (cpu_index(cpu) == thread_id) {
- return cpu;
+ return buf;
+}
+
+typedef enum GDBThreadIdKind {
+ GDB_ONE_THREAD = 0,
+ GDB_ALL_THREADS, /* One process, all threads */
+ GDB_ALL_PROCESSES,
+ GDB_READ_THREAD_ERR
+} GDBThreadIdKind;
+
+static GDBThreadIdKind read_thread_id(const char *buf, const char **end_buf,
+ uint32_t *pid, uint32_t *tid)
+{
+ unsigned long p, t;
+ int ret;
+
+ if (*buf == 'p') {
+ buf++;
+ ret = qemu_strtoul(buf, &buf, 16, &p);
+
+ if (ret) {
+ return GDB_READ_THREAD_ERR;
}
+
+ /* Skip '.' */
+ buf++;
+ } else {
+ p = 1;
}
- return NULL;
+ ret = qemu_strtoul(buf, &buf, 16, &t);
+
+ if (ret) {
+ return GDB_READ_THREAD_ERR;
+ }
+
+ *end_buf = buf;
+
+ if (p == -1) {
+ return GDB_ALL_PROCESSES;
+ }
+
+ if (pid) {
+ *pid = p;
+ }
+
+ if (t == -1) {
+ return GDB_ALL_THREADS;
+ }
+
+ if (tid) {
+ *tid = t;
+ }
+
+ return GDB_ONE_THREAD;
}
static int is_query_packet(const char *p, const char *query, char separator)
(p[query_len] == '\0' || p[query_len] == separator);
}
+/**
+ * gdb_handle_vcont - Parses and handles a vCont packet.
+ * returns -ENOTSUP if a command is unsupported, -EINVAL or -ERANGE if there is
+ * a format error, 0 on success.
+ */
+static int gdb_handle_vcont(GDBState *s, const char *p)
+{
+ int res, signal = 0;
+ char cur_action;
+ char *newstates;
+ unsigned long tmp;
+ uint32_t pid, tid;
+ GDBProcess *process;
+ CPUState *cpu;
+#ifdef CONFIG_USER_ONLY
+ int max_cpus = 1; /* global variable max_cpus exists only in system mode */
+
+ CPU_FOREACH(cpu) {
+ max_cpus = max_cpus <= cpu->cpu_index ? cpu->cpu_index + 1 : max_cpus;
+ }
+#endif
+ /* uninitialised CPUs stay 0 */
+ newstates = g_new0(char, max_cpus);
+
+ /* mark valid CPUs with 1 */
+ CPU_FOREACH(cpu) {
+ newstates[cpu->cpu_index] = 1;
+ }
+
+ /*
+ * res keeps track of what error we are returning, with -ENOTSUP meaning
+ * that the command is unknown or unsupported, thus returning an empty
+ * packet, while -EINVAL and -ERANGE cause an E22 packet, due to invalid,
+ * or incorrect parameters passed.
+ */
+ res = 0;
+ while (*p) {
+ if (*p++ != ';') {
+ res = -ENOTSUP;
+ goto out;
+ }
+
+ cur_action = *p++;
+ if (cur_action == 'C' || cur_action == 'S') {
+ cur_action = qemu_tolower(cur_action);
+ res = qemu_strtoul(p + 1, &p, 16, &tmp);
+ if (res) {
+ goto out;
+ }
+ signal = gdb_signal_to_target(tmp);
+ } else if (cur_action != 'c' && cur_action != 's') {
+ /* unknown/invalid/unsupported command */
+ res = -ENOTSUP;
+ goto out;
+ }
+
+ if (*p++ != ':') {
+ res = -ENOTSUP;
+ goto out;
+ }
+
+ switch (read_thread_id(p, &p, &pid, &tid)) {
+ case GDB_READ_THREAD_ERR:
+ res = -EINVAL;
+ goto out;
+
+ case GDB_ALL_PROCESSES:
+ cpu = gdb_first_attached_cpu(s);
+ while (cpu) {
+ if (newstates[cpu->cpu_index] == 1) {
+ newstates[cpu->cpu_index] = cur_action;
+ }
+
+ cpu = gdb_next_attached_cpu(s, cpu);
+ }
+ break;
+
+ case GDB_ALL_THREADS:
+ process = gdb_get_process(s, pid);
+
+ if (!process->attached) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ cpu = get_first_cpu_in_process(s, process);
+ while (cpu) {
+ if (newstates[cpu->cpu_index] == 1) {
+ newstates[cpu->cpu_index] = cur_action;
+ }
+
+ cpu = gdb_next_cpu_in_process(s, cpu);
+ }
+ break;
+
+ case GDB_ONE_THREAD:
+ cpu = gdb_get_cpu(s, pid, tid);
+
+ /* invalid CPU/thread specified */
+ if (!cpu) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ /* only use if no previous match occourred */
+ if (newstates[cpu->cpu_index] == 1) {
+ newstates[cpu->cpu_index] = cur_action;
+ }
+ break;
+ }
+ }
+ s->signal = signal;
+ gdb_continue_partial(s, newstates);
+
+out:
+ g_free(newstates);
+
+ return res;
+}
+
static int gdb_handle_packet(GDBState *s, const char *line_buf)
{
CPUState *cpu;
+ GDBProcess *process;
CPUClass *cc;
const char *p;
- uint32_t thread;
+ uint32_t pid, tid;
int ch, reg_size, type, res;
- char buf[MAX_PACKET_LENGTH];
uint8_t mem_buf[MAX_PACKET_LENGTH];
+ char buf[sizeof(mem_buf) + 1 /* trailing NUL */];
+ char thread_id[16];
uint8_t *registers;
target_ulong addr, len;
+ GDBThreadIdKind thread_kind;
+
+ trace_gdbstub_io_command(line_buf);
-#ifdef DEBUG_GDB
- printf("command='%s'\n", line_buf);
-#endif
p = line_buf;
ch = *p++;
switch(ch) {
+ case '!':
+ put_packet(s, "OK");
+ break;
case '?':
/* TODO: Make this return the correct value for user-mode. */
- snprintf(buf, sizeof(buf), "T%02xthread:%02x;", GDB_SIGNAL_TRAP,
- cpu_index(s->c_cpu));
+ snprintf(buf, sizeof(buf), "T%02xthread:%s;", GDB_SIGNAL_TRAP,
+ gdb_fmt_thread_id(s, s->c_cpu, thread_id, sizeof(thread_id)));
put_packet(s, buf);
/* Remove all the breakpoints when this query is issued,
* because gdb is doing and initial connect and the state
}
s->signal = 0;
gdb_continue(s);
- return RS_IDLE;
+ return RS_IDLE;
case 'C':
s->signal = gdb_signal_to_target (strtoul(p, (char **)&p, 16));
if (s->signal == -1)
return RS_IDLE;
case 'v':
if (strncmp(p, "Cont", 4) == 0) {
- int res_signal, res_thread;
-
p += 4;
if (*p == '?') {
put_packet(s, "vCont;c;C;s;S");
break;
}
- res = 0;
- res_signal = 0;
- res_thread = 0;
- while (*p) {
- int action, signal;
-
- if (*p++ != ';') {
- res = 0;
- break;
- }
- action = *p++;
- signal = 0;
- if (action == 'C' || action == 'S') {
- signal = gdb_signal_to_target(strtoul(p, (char **)&p, 16));
- if (signal == -1) {
- signal = 0;
- }
- } else if (action != 'c' && action != 's') {
- res = 0;
- break;
- }
- thread = 0;
- if (*p == ':') {
- thread = strtoull(p+1, (char **)&p, 16);
- }
- action = tolower(action);
- if (res == 0 || (res == 'c' && action == 's')) {
- res = action;
- res_signal = signal;
- res_thread = thread;
- }
- }
+
+ res = gdb_handle_vcont(s, p);
+
if (res) {
- if (res_thread != -1 && res_thread != 0) {
- cpu = find_cpu(res_thread);
- if (cpu == NULL) {
- put_packet(s, "E22");
- break;
- }
- s->c_cpu = cpu;
- }
- if (res == 's') {
- cpu_single_step(s->c_cpu, sstep_flags);
+ if ((res == -EINVAL) || (res == -ERANGE)) {
+ put_packet(s, "E22");
+ break;
}
- s->signal = res_signal;
- gdb_continue(s);
- return RS_IDLE;
+ goto unknown_command;
}
break;
} else {
exit(0);
case 'D':
/* Detach packet */
- gdb_breakpoint_remove_all();
- gdb_syscall_mode = GDB_SYS_DISABLED;
- gdb_continue(s);
+ pid = 1;
+
+ if (s->multiprocess) {
+ unsigned long lpid;
+ if (*p != ';') {
+ put_packet(s, "E22");
+ break;
+ }
+
+ if (qemu_strtoul(p + 1, &p, 16, &lpid)) {
+ put_packet(s, "E22");
+ break;
+ }
+
+ pid = lpid;
+ }
+
+ process = gdb_get_process(s, pid);
+ gdb_process_breakpoint_remove_all(s, process);
+ process->attached = false;
+
+ if (pid == gdb_get_cpu_pid(s, s->c_cpu)) {
+ s->c_cpu = gdb_first_attached_cpu(s);
+ }
+
+ if (pid == gdb_get_cpu_pid(s, s->g_cpu)) {
+ s->g_cpu = gdb_first_attached_cpu(s);
+ }
+
+ if (s->c_cpu == NULL) {
+ /* No more process attached */
+ gdb_syscall_mode = GDB_SYS_DISABLED;
+ gdb_continue(s);
+ }
put_packet(s, "OK");
break;
case 's':
}
cpu_single_step(s->c_cpu, sstep_flags);
gdb_continue(s);
- return RS_IDLE;
+ return RS_IDLE;
case 'F':
{
target_ulong ret;
break;
case 'H':
type = *p++;
- thread = strtoull(p, (char **)&p, 16);
- if (thread == -1 || thread == 0) {
+
+ thread_kind = read_thread_id(p, &p, &pid, &tid);
+ if (thread_kind == GDB_READ_THREAD_ERR) {
+ put_packet(s, "E22");
+ break;
+ }
+
+ if (thread_kind != GDB_ONE_THREAD) {
put_packet(s, "OK");
break;
}
- cpu = find_cpu(thread);
+ cpu = gdb_get_cpu(s, pid, tid);
if (cpu == NULL) {
put_packet(s, "E22");
break;
}
break;
case 'T':
- thread = strtoull(p, (char **)&p, 16);
- cpu = find_cpu(thread);
+ thread_kind = read_thread_id(p, &p, &pid, &tid);
+ if (thread_kind == GDB_READ_THREAD_ERR) {
+ put_packet(s, "E22");
+ break;
+ }
+ cpu = gdb_get_cpu(s, pid, tid);
if (cpu != NULL) {
put_packet(s, "OK");
put_packet(s, "OK");
break;
} else if (strcmp(p,"C") == 0) {
- /* "Current thread" remains vague in the spec, so always return
- * the first CPU (gdb returns the first thread). */
- put_packet(s, "QC1");
+ /*
+ * "Current thread" remains vague in the spec, so always return
+ * the first thread of the current process (gdb returns the
+ * first thread).
+ */
+ cpu = get_first_cpu_in_process(s, gdb_get_cpu_process(s, s->g_cpu));
+ snprintf(buf, sizeof(buf), "QC%s",
+ gdb_fmt_thread_id(s, cpu, thread_id, sizeof(thread_id)));
+ put_packet(s, buf);
break;
} else if (strcmp(p,"fThreadInfo") == 0) {
- s->query_cpu = first_cpu;
+ s->query_cpu = gdb_first_attached_cpu(s);
goto report_cpuinfo;
} else if (strcmp(p,"sThreadInfo") == 0) {
report_cpuinfo:
if (s->query_cpu) {
- snprintf(buf, sizeof(buf), "m%x", cpu_index(s->query_cpu));
+ snprintf(buf, sizeof(buf), "m%s",
+ gdb_fmt_thread_id(s, s->query_cpu,
+ thread_id, sizeof(thread_id)));
put_packet(s, buf);
- s->query_cpu = CPU_NEXT(s->query_cpu);
+ s->query_cpu = gdb_next_attached_cpu(s, s->query_cpu);
} else
put_packet(s, "l");
break;
} else if (strncmp(p,"ThreadExtraInfo,", 16) == 0) {
- thread = strtoull(p+16, (char **)&p, 16);
- cpu = find_cpu(thread);
+ if (read_thread_id(p + 16, &p, &pid, &tid) == GDB_READ_THREAD_ERR) {
+ put_packet(s, "E22");
+ break;
+ }
+ cpu = gdb_get_cpu(s, pid, tid);
if (cpu != NULL) {
cpu_synchronize_state(cpu);
- /* memtohex() doubles the required space */
- len = snprintf((char *)mem_buf, sizeof(buf) / 2,
- "CPU#%d [%s]", cpu->cpu_index,
- cpu->halted ? "halted " : "running");
+
+ if (s->multiprocess && (s->process_num > 1)) {
+ /* Print the CPU model and name in multiprocess mode */
+ ObjectClass *oc = object_get_class(OBJECT(cpu));
+ const char *cpu_model = object_class_get_name(oc);
+ char *cpu_name =
+ object_get_canonical_path_component(OBJECT(cpu));
+ len = snprintf((char *)mem_buf, sizeof(buf) / 2,
+ "%s %s [%s]", cpu_model, cpu_name,
+ cpu->halted ? "halted " : "running");
+ g_free(cpu_name);
+ } else {
+ /* memtohex() doubles the required space */
+ len = snprintf((char *)mem_buf, sizeof(buf) / 2,
+ "CPU#%d [%s]", cpu->cpu_index,
+ cpu->halted ? "halted " : "running");
+ }
+ trace_gdbstub_op_extra_info((char *)mem_buf);
memtohex(buf, mem_buf, len);
put_packet(s, buf);
}
const char *xml;
target_ulong total_len;
- cc = CPU_GET_CLASS(first_cpu);
+ process = gdb_get_cpu_process(s, s->g_cpu);
+ cc = CPU_GET_CLASS(s->g_cpu);
if (cc->gdb_core_xml_file == NULL) {
goto unknown_command;
}
gdb_has_xml = true;
p += 19;
- xml = get_feature_xml(p, &p, cc);
+ xml = get_feature_xml(s, p, &p, process);
if (!xml) {
snprintf(buf, sizeof(buf), "E00");
put_packet(s, buf);
buf[0] = 'l';
len = memtox(buf + 1, xml + addr, total_len - addr);
}
- put_packet_binary(s, buf, len + 1);
+ put_packet_binary(s, buf, len + 1, true);
break;
}
if (is_query_packet(p, "Attached", ':')) {
GDBState *s = gdbserver_state;
CPUState *cpu = s->c_cpu;
char buf[256];
+ char thread_id[16];
const char *type;
int ret;
put_packet(s, s->syscall_buf);
return;
}
+
+ if (cpu == NULL) {
+ /* No process attached */
+ return;
+ }
+
+ gdb_fmt_thread_id(s, cpu, thread_id, sizeof(thread_id));
+
switch (state) {
case RUN_STATE_DEBUG:
if (cpu->watchpoint_hit) {
type = "";
break;
}
+ trace_gdbstub_hit_watchpoint(type, cpu_gdb_index(cpu),
+ (target_ulong)cpu->watchpoint_hit->vaddr);
snprintf(buf, sizeof(buf),
- "T%02xthread:%02x;%swatch:" TARGET_FMT_lx ";",
- GDB_SIGNAL_TRAP, cpu_index(cpu), type,
+ "T%02xthread:%s;%swatch:" TARGET_FMT_lx ";",
+ GDB_SIGNAL_TRAP, thread_id, type,
(target_ulong)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:
ret = GDB_SIGNAL_XCPU;
break;
default:
+ trace_gdbstub_hit_unknown(state);
ret = GDB_SIGNAL_UNKNOWN;
break;
}
gdb_set_stop_cpu(cpu);
- snprintf(buf, sizeof(buf), "T%02xthread:%02x;", ret, cpu_index(cpu));
+ snprintf(buf, sizeof(buf), "T%02xthread:%s;", ret, thread_id);
send_packet:
put_packet(s, buf);
*p = 0;
#ifdef CONFIG_USER_ONLY
put_packet(s, s->syscall_buf);
+ /* 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).
+ */
gdb_handlesig(s->c_cpu, 0);
#else
/* In this case wait to send the syscall packet until notification that
static void gdb_read_byte(GDBState *s, int ch)
{
- int i, csum;
uint8_t reply;
#ifndef CONFIG_USER_ONLY
/* Waiting for a response to the last packet. If we see the start
of a new command then abandon the previous response. */
if (ch == '-') {
-#ifdef DEBUG_GDB
- printf("Got NACK, retransmitting\n");
-#endif
+ trace_gdbstub_err_got_nack();
put_buffer(s, (uint8_t *)s->last_packet, s->last_packet_len);
+ } else if (ch == '+') {
+ trace_gdbstub_io_got_ack();
+ } else {
+ trace_gdbstub_io_got_unexpected((uint8_t)ch);
}
-#ifdef DEBUG_GDB
- else if (ch == '+')
- printf("Got ACK\n");
- else
- printf("Got '%c' when expecting ACK/NACK\n", ch);
-#endif
+
if (ch == '+' || ch == '$')
s->last_packet_len = 0;
if (ch != '$')
switch(s->state) {
case RS_IDLE:
if (ch == '$') {
+ /* start of command packet */
s->line_buf_index = 0;
+ s->line_sum = 0;
s->state = RS_GETLINE;
+ } else {
+ trace_gdbstub_err_garbage((uint8_t)ch);
}
break;
case RS_GETLINE:
+ if (ch == '}') {
+ /* start escape sequence */
+ s->state = RS_GETLINE_ESC;
+ s->line_sum += ch;
+ } else if (ch == '*') {
+ /* start run length encoding sequence */
+ s->state = RS_GETLINE_RLE;
+ s->line_sum += ch;
+ } else if (ch == '#') {
+ /* end of command, start of checksum*/
+ s->state = RS_CHKSUM1;
+ } else if (s->line_buf_index >= sizeof(s->line_buf) - 1) {
+ trace_gdbstub_err_overrun();
+ s->state = RS_IDLE;
+ } else {
+ /* unescaped command character */
+ s->line_buf[s->line_buf_index++] = ch;
+ s->line_sum += ch;
+ }
+ break;
+ case RS_GETLINE_ESC:
if (ch == '#') {
- s->state = RS_CHKSUM1;
+ /* unexpected end of command in escape sequence */
+ s->state = RS_CHKSUM1;
} else if (s->line_buf_index >= sizeof(s->line_buf) - 1) {
+ /* command buffer overrun */
+ trace_gdbstub_err_overrun();
s->state = RS_IDLE;
} else {
- s->line_buf[s->line_buf_index++] = ch;
+ /* parse escaped character and leave escape state */
+ s->line_buf[s->line_buf_index++] = ch ^ 0x20;
+ s->line_sum += ch;
+ s->state = RS_GETLINE;
+ }
+ break;
+ case RS_GETLINE_RLE:
+ if (ch < ' ') {
+ /* invalid RLE count encoding */
+ trace_gdbstub_err_invalid_repeat((uint8_t)ch);
+ s->state = RS_GETLINE;
+ } else {
+ /* decode repeat length */
+ int repeat = (unsigned char)ch - ' ' + 3;
+ if (s->line_buf_index + repeat >= sizeof(s->line_buf) - 1) {
+ /* that many repeats would overrun the command buffer */
+ trace_gdbstub_err_overrun();
+ s->state = RS_IDLE;
+ } else if (s->line_buf_index < 1) {
+ /* got a repeat but we have nothing to repeat */
+ trace_gdbstub_err_invalid_rle();
+ s->state = RS_GETLINE;
+ } else {
+ /* repeat the last character */
+ memset(s->line_buf + s->line_buf_index,
+ s->line_buf[s->line_buf_index - 1], repeat);
+ s->line_buf_index += repeat;
+ s->line_sum += ch;
+ s->state = RS_GETLINE;
+ }
}
break;
case RS_CHKSUM1:
+ /* get high hex digit of checksum */
+ if (!isxdigit(ch)) {
+ trace_gdbstub_err_checksum_invalid((uint8_t)ch);
+ s->state = RS_GETLINE;
+ break;
+ }
s->line_buf[s->line_buf_index] = '\0';
s->line_csum = fromhex(ch) << 4;
s->state = RS_CHKSUM2;
break;
case RS_CHKSUM2:
- s->line_csum |= fromhex(ch);
- csum = 0;
- for(i = 0; i < s->line_buf_index; i++) {
- csum += s->line_buf[i];
+ /* get low hex digit of checksum */
+ if (!isxdigit(ch)) {
+ trace_gdbstub_err_checksum_invalid((uint8_t)ch);
+ s->state = RS_GETLINE;
+ break;
}
- if (s->line_csum != (csum & 0xff)) {
+ s->line_csum |= fromhex(ch);
+
+ if (s->line_csum != (s->line_sum & 0xff)) {
+ trace_gdbstub_err_checksum_incorrect(s->line_sum, s->line_csum);
+ /* send NAK reply */
reply = '-';
put_buffer(s, &reply, 1);
s->state = RS_IDLE;
} else {
+ /* send ACK reply */
reply = '+';
put_buffer(s, &reply, 1);
s->state = gdb_handle_packet(s, s->line_buf);
{
GDBState *s;
char buf[4];
-#ifndef CONFIG_USER_ONLY
- CharDriverState *chr;
-#endif
s = gdbserver_state;
if (!s) {
if (gdbserver_fd < 0 || s->fd < 0) {
return;
}
-#else
- chr = qemu_chr_fe_get_driver(&s->chr);
- if (!chr) {
- return;
- }
#endif
+ trace_gdbstub_op_exiting((uint8_t)code);
+
snprintf(buf, sizeof(buf), "W%02x", (uint8_t)code);
put_packet(s, buf);
#ifndef CONFIG_USER_ONLY
- qemu_chr_fe_deinit(&s->chr);
- qemu_chr_delete(chr);
+ qemu_chr_fe_deinit(&s->chr, true);
#endif
}
+/*
+ * Create the process that will contain all the "orphan" CPUs (that are not
+ * part of a CPU cluster). Note that if this process contains no CPUs, it won't
+ * be attachable and thus will be invisible to the user.
+ */
+static void create_default_process(GDBState *s)
+{
+ GDBProcess *process;
+ int max_pid = 0;
+
+ if (s->process_num) {
+ max_pid = s->processes[s->process_num - 1].pid;
+ }
+
+ s->processes = g_renew(GDBProcess, s->processes, ++s->process_num);
+ process = &s->processes[s->process_num - 1];
+
+ /* We need an available PID slot for this process */
+ assert(max_pid < UINT32_MAX);
+
+ process->pid = max_pid + 1;
+ process->attached = false;
+ process->target_xml[0] = '\0';
+}
+
#ifdef CONFIG_USER_ONLY
int
gdb_handlesig(CPUState *cpu, int sig)
put_packet(s, buf);
}
-static void gdb_accept(void)
+static bool gdb_accept(void)
{
GDBState *s;
struct sockaddr_in sockaddr;
fd = accept(gdbserver_fd, (struct sockaddr *)&sockaddr, &len);
if (fd < 0 && errno != EINTR) {
perror("accept");
- return;
+ return false;
} else if (fd >= 0) {
-#ifndef _WIN32
- fcntl(fd, F_SETFD, FD_CLOEXEC);
-#endif
+ qemu_set_cloexec(fd);
break;
}
}
/* set short latency */
- socket_set_nodelay(fd);
+ if (socket_set_nodelay(fd)) {
+ perror("setsockopt");
+ close(fd);
+ return false;
+ }
s = g_malloc0(sizeof(GDBState));
s->c_cpu = first_cpu;
s->g_cpu = first_cpu;
+ create_default_process(s);
s->fd = fd;
gdb_has_xml = false;
gdbserver_state = s;
+ return true;
}
static int gdbserver_open(int port)
perror("socket");
return -1;
}
-#ifndef _WIN32
- fcntl(fd, F_SETFD, FD_CLOEXEC);
-#endif
+ qemu_set_cloexec(fd);
socket_set_fast_reuse(fd);
if (gdbserver_fd < 0)
return -1;
/* accept connections */
- gdb_accept();
+ if (!gdb_accept()) {
+ close(gdbserver_fd);
+ gdbserver_fd = -1;
+ return -1;
+ }
return 0;
}
put_packet(s, buf);
}
-static int gdb_monitor_write(CharDriverState *chr, const uint8_t *buf, int len)
+static int gdb_monitor_write(Chardev *chr, const uint8_t *buf, int len)
{
const char *p = (const char *)buf;
int max_sz;
}
#endif
+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 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 (s->processes) {
+ /* Sort by PID */
+ qsort(s->processes, s->process_num, sizeof(s->processes[0]), pid_order);
+ }
+
+ create_default_process(s);
+}
+
+static void cleanup_processes(GDBState *s)
+{
+ g_free(s->processes);
+ s->process_num = 0;
+ s->processes = NULL;
+}
+
int gdbserver_start(const char *device)
{
+ trace_gdbstub_op_start(device);
+
GDBState *s;
char gdbstub_device_name[128];
- CharDriverState *chr = NULL;
- CharDriverState *mon_chr;
- ChardevCommon common = { 0 };
+ Chardev *chr = NULL;
+ Chardev *mon_chr;
if (!first_cpu) {
error_report("gdbstub: meaningless to attach gdb to a "
sigaction(SIGINT, &act, NULL);
}
#endif
- chr = qemu_chr_new_noreplay("gdb", device);
+ /*
+ * 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", device, true);
if (!chr)
return -1;
}
qemu_add_vm_change_state_handler(gdb_vm_state_change, NULL);
/* Initialize a monitor terminal for gdb */
- mon_chr = qemu_chr_alloc(&common, &error_abort);
- mon_chr->chr_write = gdb_monitor_write;
+ mon_chr = qemu_chardev_new(NULL, TYPE_CHARDEV_GDB,
+ NULL, &error_abort);
monitor_init(mon_chr, 0);
} else {
- if (qemu_chr_fe_get_driver(&s->chr)) {
- qemu_chr_delete(qemu_chr_fe_get_driver(&s->chr));
- }
+ qemu_chr_fe_deinit(&s->chr, true);
mon_chr = s->mon_chr;
+ cleanup_processes(s);
memset(s, 0, sizeof(GDBState));
s->mon_chr = mon_chr;
}
s->c_cpu = first_cpu;
s->g_cpu = first_cpu;
+
+ create_processes(s);
+
if (chr) {
qemu_chr_fe_init(&s->chr, chr, &error_abort);
qemu_chr_fe_set_handlers(&s->chr, gdb_chr_can_receive, gdb_chr_receive,
- gdb_chr_event, NULL, NULL, true);
+ gdb_chr_event, NULL, NULL, NULL, true);
}
s->state = chr ? RS_IDLE : RS_INACTIVE;
s->mon_chr = mon_chr;
return 0;
}
+
+void gdbserver_cleanup(void)
+{
+ if (gdbserver_state) {
+ put_packet(gdbserver_state, "W00");
+ }
+}
+
+static void register_types(void)
+{
+ type_register_static(&char_gdb_type_info);
+}
+
+type_init(register_types);
#endif