From c3ed313cf7f305052b17c62ec35e6134d0625484 Mon Sep 17 00:00:00 2001 From: claudiol Date: Tue, 28 Aug 2018 12:45:40 -0400 Subject: [PATCH] [3298] Updated GDB server --- gdbstub.c | 985 +++++++++++++++++++++++++++++++++++++++++++++--------- gdbstub.h | 19 +- main.c | 7 - 3 files changed, 852 insertions(+), 159 deletions(-) diff --git a/gdbstub.c b/gdbstub.c index 157d732..1bb2a1f 100644 --- a/gdbstub.c +++ b/gdbstub.c @@ -1,6 +1,6 @@ /* * gdb server stub - * + * * Copyright (c) 2003-2005 Fabrice Bellard * * This library is free software; you can redistribute it and/or @@ -14,10 +14,9 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * License along with this library; if not, see . */ -#include +#include "qemu-common.h" #include #include #include @@ -33,46 +32,99 @@ #include #include "gdbstub.h" -#ifdef _WIN32 -/* XXX: these constants may be independent of the host ones even for Unix */ -#ifndef SIGTRAP -#define SIGTRAP 5 -#endif -#ifndef SIGINT -#define SIGINT 2 -#endif -#else -#include -#endif +#define MAX_PACKET_LENGTH 4096 + + +enum { + GDB_SIGNAL_0 = 0, + GDB_SIGNAL_INT = 2, + GDB_SIGNAL_TRAP = 5, + GDB_SIGNAL_UNKNOWN = 143 +}; + +/* In system mode we only need SIGINT and SIGTRAP; other signals + are not yet supported. */ + +enum { + TARGET_SIGINT = 2, + TARGET_SIGTRAP = 5 +}; + +static int gdb_signal_table[] = { + -1, + -1, + TARGET_SIGINT, + -1, + -1, + TARGET_SIGTRAP +}; + +static int target_signal_to_gdb (int sig) +{ + int i; + for (i = 0; i < ARRAY_SIZE (gdb_signal_table); i++) + if (gdb_signal_table[i] == sig) + return i; + return GDB_SIGNAL_UNKNOWN; +} + +static int gdb_signal_to_target (int sig) +{ + if (sig < ARRAY_SIZE (gdb_signal_table)) + return gdb_signal_table[sig]; + else + return -1; +} //#define DEBUG_GDB +typedef struct GDBRegisterState { + int base_reg; + int num_regs; + gdb_reg_cb get_reg; + gdb_reg_cb set_reg; + const char *xml; + struct GDBRegisterState *next; +} GDBRegisterState; + enum RSState { + RS_INACTIVE, RS_IDLE, RS_GETLINE, RS_CHKSUM1, RS_CHKSUM2, RS_SYSCALL, }; - typedef struct GDBState { - CPUState *env; /* current CPU */ + CPUState *c_cpu; /* current CPU for step/continue ops */ + CPUState *g_cpu; /* current CPU for other ops */ + CPUState *query_cpu; /* for q{f|s}ThreadInfo */ enum RSState state; /* parsing state */ - char line_buf[4096]; + char line_buf[MAX_PACKET_LENGTH]; int line_buf_index; int line_csum; - char last_packet[4100]; + uint8_t last_packet[MAX_PACKET_LENGTH + 4]; int last_packet_len; + int signal; int fd; int running_state; } GDBState; +/* By default use no IRQs and no timers while single stepping so as to + * make single stepping like an ICE HW step. + */ +static int sstep_flags = SSTEP_ENABLE|SSTEP_NOIRQ|SSTEP_NOTIMER; + +static GDBState *gdbserver_state; + +/* This is an ugly hack to cope with both new and old gdb. + If gdb sends qXfer:features:read then assume we're talking to a newish + gdb that understands target descriptions. */ +static int gdb_has_xml; + /* XXX: This is not thread safe. Do we care? */ static int gdbserver_fd = -1; -/* XXX: remove this hack. */ -static GDBState gdbserver_state; - static int get_char(GDBState *s) { uint8_t ch; @@ -81,9 +133,13 @@ static int get_char(GDBState *s) for(;;) { ret = recv(s->fd, &ch, 1, 0); if (ret < 0) { + if (errno == ECONNRESET) + s->fd = -1; if (errno != EINTR && errno != EAGAIN) return -1; } else if (ret == 0) { + close(s->fd); + s->fd = -1; return -1; } else { break; @@ -92,11 +148,9 @@ static int get_char(GDBState *s) return ch; } -/* GDB stub state for use by semihosting syscalls. */ -static GDBState *gdb_syscall_state; static gdb_syscall_complete_cb gdb_current_syscall_cb; -enum { +static enum { GDB_SYS_UNKNOWN, GDB_SYS_ENABLED, GDB_SYS_DISABLED, @@ -107,12 +161,18 @@ enum { int use_gdb_syscalls(void) { if (gdb_syscall_mode == GDB_SYS_UNKNOWN) { - gdb_syscall_mode = (gdb_syscall_state ? GDB_SYS_ENABLED - : GDB_SYS_DISABLED); + gdb_syscall_mode = (gdbserver_state ? GDB_SYS_ENABLED + : GDB_SYS_DISABLED); } return gdb_syscall_mode == GDB_SYS_ENABLED; } +/* Resume execution. */ +static inline void gdb_continue(GDBState *s) +{ + s->running_state = 1; +} + static void put_buffer(GDBState *s, const uint8_t *buf, int len) { int ret; @@ -173,19 +233,14 @@ static void hextomem(uint8_t *mem, const char *buf, int len) } /* return -1 if error, 0 if OK */ -static int put_packet(GDBState *s, char *buf) +static int put_packet_binary(GDBState *s, const char *buf, int len) { - int len, csum, i; - char *p; - -#ifdef DEBUG_GDB - printf("reply='%s'\n", buf); -#endif + int csum, i; + uint8_t *p; for(;;) { p = s->last_packet; *(p++) = '$'; - len = strlen(buf); memcpy(p, buf, len); p += len; csum = 0; @@ -197,7 +252,7 @@ static int put_packet(GDBState *s, char *buf) *(p++) = tohex((csum) & 0xf); s->last_packet_len = p - s->last_packet; - put_buffer(s, (uint8_t *) s->last_packet, s->last_packet_len); + put_buffer(s, (uint8_t *)s->last_packet, s->last_packet_len); i = get_char(s); if (i < 0) @@ -208,55 +263,374 @@ static int put_packet(GDBState *s, char *buf) return 0; } -static int cpu_gdb_read_registers(CPUState *env, uint8_t *mem_buf) +/* return -1 if error, 0 if OK */ +static int put_packet(GDBState *s, const char *buf) { - int i; - uint8_t *ptr; +#ifdef DEBUG_GDB + printf("reply='%s'\n", buf); +#endif - ptr = mem_buf; - /* 16 core integer registers (4 bytes each). */ - for (i = 0; i < 16; i++) - { - *(uint32_t *)ptr = tswapl(env->regs[i]); - ptr += 4; - } - /* 8 FPA registers (12 bytes each), FPS (4 bytes). - Not yet implemented. */ - memset (ptr, 0, 8 * 12 + 4); - ptr += 8 * 12 + 4; - /* CPSR (4 bytes). */ - *(uint32_t *)ptr = tswapl (cpsr_read(env)); - ptr += 4; - - return ptr - mem_buf; + return put_packet_binary(s, buf, strlen(buf)); } -static void cpu_gdb_write_registers(CPUState *env, uint8_t *mem_buf, int size) -{ - int i; - uint8_t *ptr; +/* The GDB remote protocol transfers values in target byte order. This means + we can use the raw memory access routines to access the value buffer. + Conveniently, these also handle the case where the buffer is mis-aligned. + */ +#define GET_REG8(val) do { \ + stb_p(mem_buf, val); \ + return 1; \ + } while(0) +#define GET_REG16(val) do { \ + stw_p(mem_buf, val); \ + return 2; \ + } while(0) +#define GET_REG32(val) do { \ + stl_p(mem_buf, val); \ + return 4; \ + } while(0) +#define GET_REG64(val) do { \ + stq_p(mem_buf, val); \ + return 8; \ + } while(0) - ptr = mem_buf; - /* Core integer registers. */ - for (i = 0; i < 16; i++) - { - env->regs[i] = tswapl(*(uint32_t *)ptr); - ptr += 4; - } - /* Ignore FPA regs and scr. */ - ptr += 8 * 12 + 4; - cpsr_write (env, tswapl(*(uint32_t *)ptr), 0xffffffff); +#if TARGET_LONG_BITS == 64 +#define GET_REGL(val) GET_REG64(val) +#define ldtul_p(addr) ldq_p(addr) +#else +#define GET_REGL(val) GET_REG32(val) +#define ldtul_p(addr) ldl_p(addr) +#endif + +/* Old gdb always expect FPA registers. Newer (xml-aware) gdb only expect + whatever the target description contains. Due to a historical mishap + the FPA registers appear in between core integer regs and the CPSR. + We hack round this by giving the FPA regs zero size when talking to a + newer gdb. */ +#define NUM_CORE_REGS 26 +//#define GDB_CORE_XML "arm-core.xml" + +static int cpu_gdb_read_register(CPUState *env, uint8_t *mem_buf, int n) +{ + if (n < 16) { + /* Core integer register. */ + GET_REG32(env->regs[n]); + } + if (n < 24) { + /* FPA registers. */ + if (gdb_has_xml) + return 0; + memset(mem_buf, 0, 12); + return 12; + } + switch (n) { + case 24: + /* FPA status register. */ + if (gdb_has_xml) + return 0; + GET_REG32(0); + case 25: + /* CPSR */ + GET_REG32(cpsr_read(env)); + } + /* Unknown register. */ + return 0; } -static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) +static int cpu_gdb_write_register(CPUState *env, uint8_t *mem_buf, int n) { + uint32_t tmp; + + tmp = ldl_p(mem_buf); + + /* Mask out low bit of PC to workaround gdb bugs. This will probably + cause problems if we ever implement the Jazelle DBX extensions. */ + if (n == 15) + tmp &= ~1; + + if (n < 16) { + /* Core integer register. */ + env->regs[n] = tmp; + return 4; + } + if (n < 24) { /* 16-23 */ + /* FPA registers (ignored). */ + if (gdb_has_xml) + return 0; + return 12; + } + switch (n) { + case 24: + /* FPA status register (ignored). */ + if (gdb_has_xml) + return 0; + return 4; + case 25: + /* CPSR */ + cpsr_write (env, tmp, 0xffffffff); + return 4; + } + /* Unknown register. */ + return 0; +} + +static int num_g_regs = NUM_CORE_REGS; + +#ifdef GDB_CORE_XML +/* Encode data using the encoding for 'x' packets. */ +static int memtox(char *buf, const char *mem, int len) +{ + char *p = buf; + char c; + + while (len--) { + c = *(mem++); + switch (c) { + case '#': case '$': case '*': case '}': + *(p++) = '}'; + *(p++) = c ^ 0x20; + break; + default: + *(p++) = c; + break; + } + } + return p - buf; +} + +static const char *get_feature_xml(const char *p, const char **newp) +{ + extern const char *const xml_builtin[][2]; + size_t len; + int i; + const char *name; + static char target_xml[1024]; + + len = 0; + while (p[len] && p[len] != ':') + len++; + *newp = p + len; + + name = NULL; + if (strncmp(p, "target.xml", len) == 0) { + /* Generate the XML description for this CPU. */ + if (!target_xml[0]) { + GDBRegisterState *r; + + snprintf(target_xml, sizeof(target_xml), + "" + "" + "" + "", + GDB_CORE_XML); + + for (r = first_cpu->gdb_regs; r; r = r->next) { + pstrcat(target_xml, sizeof(target_xml), "xml); + pstrcat(target_xml, sizeof(target_xml), "\"/>"); + } + pstrcat(target_xml, sizeof(target_xml), ""); + } + return target_xml; + } + for (i = 0; ; i++) { + name = xml_builtin[i][0]; + if (!name || (strncmp(name, p, len) == 0 && strlen(name) == len)) + break; + } + return name ? xml_builtin[i][1] : NULL; +} +#endif + +static int gdb_read_register(CPUState *env, uint8_t *mem_buf, int reg) +{ + GDBRegisterState *r; + + if (reg < NUM_CORE_REGS) + return cpu_gdb_read_register(env, mem_buf, reg); + + for (r = env->gdb_regs; r; r = r->next) { + if (r->base_reg <= reg && reg < r->base_reg + r->num_regs) { + return r->get_reg(env, mem_buf, reg - r->base_reg); + } + } + return 0; +} + +static int gdb_write_register(CPUState *env, uint8_t *mem_buf, int reg) +{ + GDBRegisterState *r; + + if (reg < NUM_CORE_REGS) + return cpu_gdb_write_register(env, mem_buf, reg); + + for (r = env->gdb_regs; r; r = r->next) { + if (r->base_reg <= reg && reg < r->base_reg + r->num_regs) { + return r->set_reg(env, mem_buf, reg - r->base_reg); + } + } + return 0; +} + +/* Register a supplemental set of CPU registers. If g_pos is nonzero it + specifies the first register number and these registers are included in + a standard "g" packet. Direction is relative to gdb, i.e. get_reg is + gdb reading a CPU register, and set_reg is gdb modifying a CPU register. + */ + +void gdb_register_coprocessor(CPUState * env, + gdb_reg_cb get_reg, gdb_reg_cb set_reg, + int num_regs, const char *xml, int g_pos) +{ + GDBRegisterState *s; + GDBRegisterState **p; + static int last_reg = NUM_CORE_REGS; + + s = (GDBRegisterState *)qemu_mallocz(sizeof(GDBRegisterState)); + s->base_reg = last_reg; + s->num_regs = num_regs; + s->get_reg = get_reg; + s->set_reg = set_reg; + s->xml = xml; + p = &env->gdb_regs; + while (*p) { + /* Check for duplicates. */ + if (strcmp((*p)->xml, xml) == 0) + return; + p = &(*p)->next; + } + /* Add to end of list. */ + last_reg += num_regs; + *p = s; + if (g_pos) { + if (g_pos != s->base_reg) { + fprintf(stderr, "Error: Bad gdb register numbering for '%s'\n" + "Expected %d got %d\n", xml, g_pos, s->base_reg); + } else { + num_g_regs = last_reg; + } + } +} + +#ifndef CONFIG_USER_ONLY +static const int xlat_gdb_type[] = { + [GDB_WATCHPOINT_WRITE] = BP_GDB | BP_MEM_WRITE, + [GDB_WATCHPOINT_READ] = BP_GDB | BP_MEM_READ, + [GDB_WATCHPOINT_ACCESS] = BP_GDB | BP_MEM_ACCESS, +}; +#endif + +static int gdb_breakpoint_insert(target_ulong addr, target_ulong len, int type) +{ + CPUState *env; + int err = 0; + + switch (type) { + case GDB_BREAKPOINT_SW: + case GDB_BREAKPOINT_HW: + for (env = first_cpu; env != NULL; env = env->next_cpu) { + err = cpu_breakpoint_insert(env, addr, BP_GDB, NULL); + if (err) + break; + } + return err; +#ifndef CONFIG_USER_ONLY + case GDB_WATCHPOINT_WRITE: + case GDB_WATCHPOINT_READ: + case GDB_WATCHPOINT_ACCESS: + for (env = first_cpu; env != NULL; env = env->next_cpu) { + err = cpu_watchpoint_insert(env, addr, len, xlat_gdb_type[type], + NULL); + if (err) + break; + } + return err; +#endif + default: + return -ENOSYS; + } +} + +static int gdb_breakpoint_remove(target_ulong addr, target_ulong len, int type) +{ + CPUState *env; + int err = 0; + + switch (type) { + case GDB_BREAKPOINT_SW: + case GDB_BREAKPOINT_HW: + for (env = first_cpu; env != NULL; env = env->next_cpu) { + err = cpu_breakpoint_remove(env, addr, BP_GDB); + if (err) + break; + } + return err; +#ifndef CONFIG_USER_ONLY + case GDB_WATCHPOINT_WRITE: + case GDB_WATCHPOINT_READ: + case GDB_WATCHPOINT_ACCESS: + for (env = first_cpu; env != NULL; env = env->next_cpu) { + err = cpu_watchpoint_remove(env, addr, len, xlat_gdb_type[type]); + if (err) + break; + } + return err; +#endif + default: + return -ENOSYS; + } +} + +static void gdb_breakpoint_remove_all(void) +{ + CPUState *env; + + for (env = first_cpu; env != NULL; env = env->next_cpu) { + cpu_breakpoint_remove_all(env, BP_GDB); +#ifndef CONFIG_USER_ONLY + cpu_watchpoint_remove_all(env, BP_GDB); +#endif + } +} + +static void gdb_set_cpu_pc(GDBState *s, target_ulong pc) +{ + s->c_cpu->regs[15] = pc; +} + +static inline int gdb_id(CPUState *env) +{ +#if defined(CONFIG_USER_ONLY) && defined(CONFIG_USE_NPTL) + return env->host_tid; +#else + return env->cpu_index + 1; +#endif +} + +static CPUState *find_cpu(uint32_t thread_id) +{ + CPUState *env; + + for (env = first_cpu; env != NULL; env = env->next_cpu) { + if (gdb_id(env) == thread_id) { + return env; + } + } + + return NULL; +} + +static int gdb_handle_packet(GDBState *s, const char *line_buf) +{ + CPUState *env; const char *p; - int ch, reg_size, type; - char buf[4096]; - uint8_t mem_buf[2000]; - uint32_t *registers; + uint32_t thread; + int ch, reg_size, type, res; + char buf[MAX_PACKET_LENGTH]; + uint8_t mem_buf[MAX_PACKET_LENGTH]; + uint8_t *registers; target_ulong addr, len; - + #ifdef DEBUG_GDB printf("command='%s'\n", line_buf); #endif @@ -265,23 +639,104 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) switch(ch) { case '?': /* TODO: Make this return the correct value for user-mode. */ - snprintf(buf, sizeof(buf), "S%02x", SIGTRAP); + snprintf(buf, sizeof(buf), "T%02xthread:%02x;", GDB_SIGNAL_TRAP, + gdb_id(s->c_cpu)); put_packet(s, buf); + /* Remove all the breakpoints when this query is issued, + * because gdb is doing and initial connect and the state + * should be cleaned up. + */ + gdb_breakpoint_remove_all(); break; case 'c': if (*p != '\0') { addr = strtoull(p, (char **)&p, 16); - env->regs[15] = addr; + gdb_set_cpu_pc(s, addr); } - s->running_state = 1; + s->signal = 0; + gdb_continue(s); return RS_IDLE; + case 'C': + s->signal = gdb_signal_to_target (strtoul(p, (char **)&p, 16)); + if (s->signal == -1) + s->signal = 0; + gdb_continue(s); + 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 = strtoul(p, (char **)&p, 16); + } 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; + } + } + if (res) { + if (res_thread != -1 && res_thread != 0) { + env = find_cpu(res_thread); + if (env == NULL) { + put_packet(s, "E22"); + break; + } + s->c_cpu = env; + } + if (res == 's') { + cpu_single_step(s->c_cpu, sstep_flags); + } + s->signal = res_signal; + gdb_continue(s); + return RS_IDLE; + } + break; + } else { + goto unknown_command; + } + case 'k': + /* Kill the target */ + fprintf(stderr, "\nQEMU: Terminated via GDBstub\n"); + exit(0); + case 'D': + /* Detach packet */ + gdb_breakpoint_remove_all(); + gdb_continue(s); + put_packet(s, "OK"); + break; case 's': if (*p != '\0') { - addr = strtoul(p, (char **)&p, 16); - env->regs[15] = addr; + addr = strtoull(p, (char **)&p, 16); + gdb_set_cpu_pc(s, addr); } - cpu_single_step(env, 1); - s->running_state = 1; + cpu_single_step(s->c_cpu, sstep_flags); + gdb_continue(s); return RS_IDLE; case 'F': { @@ -299,24 +754,32 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) p++; type = *p; if (gdb_current_syscall_cb) - gdb_current_syscall_cb(s->env, ret, err); + gdb_current_syscall_cb(s->c_cpu, ret, err); if (type == 'C') { put_packet(s, "T02"); } else { - s->running_state = 1; + gdb_continue(s); } } break; case 'g': - reg_size = cpu_gdb_read_registers(env, mem_buf); - memtohex(buf, mem_buf, reg_size); + len = 0; + for (addr = 0; addr < num_g_regs; addr++) { + reg_size = gdb_read_register(s->g_cpu, mem_buf + len, addr); + len += reg_size; + } + memtohex(buf, mem_buf, len); put_packet(s, buf); break; case 'G': - registers = (void *)mem_buf; + registers = mem_buf; len = strlen(p) / 2; hextomem((uint8_t *)registers, p, len); - cpu_gdb_write_registers(env, mem_buf, len); + for (addr = 0; addr < num_g_regs && len > 0; addr++) { + reg_size = gdb_write_register(s->g_cpu, registers, addr); + len -= reg_size; + registers += reg_size; + } put_packet(s, "OK"); break; case 'm': @@ -324,7 +787,7 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) if (*p == ',') p++; len = strtoull(p, NULL, 16); - if (cpu_memory_rw_debug(env, addr, mem_buf, len, 0) != 0) { + if (cpu_memory_rw_debug(s->g_cpu, addr, mem_buf, len, 0) != 0) { put_packet (s, "E14"); } else { memtohex(buf, mem_buf, len); @@ -339,28 +802,38 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) if (*p == ':') p++; hextomem(mem_buf, p, len); - if (cpu_memory_rw_debug(env, addr, mem_buf, len, 1) != 0) + if (cpu_memory_rw_debug(s->g_cpu, addr, mem_buf, len, 1) != 0) put_packet(s, "E14"); else put_packet(s, "OK"); break; - case 'Z': - type = strtoul(p, (char **)&p, 16); - if (*p == ',') - p++; + case 'p': + /* Older gdb are really dumb, and don't use 'g' if 'p' is avaialable. + This works, but can be very slow. Anything new enough to + understand XML also knows how to use this properly. */ + if (!gdb_has_xml) + goto unknown_command; addr = strtoull(p, (char **)&p, 16); - if (*p == ',') - p++; - len = strtoull(p, (char **)&p, 16); - if (type == 0 || type == 1) { - if (cpu_breakpoint_insert(env, addr, BP_GDB, NULL) < 0) - goto breakpoint_error; - put_packet(s, "OK"); + reg_size = gdb_read_register(s->g_cpu, mem_buf, addr); + if (reg_size) { + memtohex(buf, mem_buf, reg_size); + put_packet(s, buf); } else { - breakpoint_error: - put_packet(s, "E22"); + put_packet(s, "E14"); } break; + case 'P': + if (!gdb_has_xml) + goto unknown_command; + addr = strtoull(p, (char **)&p, 16); + if (*p == '=') + p++; + reg_size = strlen(p) / 2; + hextomem(mem_buf, p, reg_size); + gdb_write_register(s->g_cpu, mem_buf, addr); + put_packet(s, "OK"); + break; + case 'Z': case 'z': type = strtoul(p, (char **)&p, 16); if (*p == ',') @@ -369,15 +842,160 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) if (*p == ',') p++; len = strtoull(p, (char **)&p, 16); - if (type == 0 || type == 1) { - cpu_breakpoint_remove(env, addr, BP_GDB); + if (ch == 'Z') + res = gdb_breakpoint_insert(addr, len, type); + else + res = gdb_breakpoint_remove(addr, len, type); + if (res >= 0) + put_packet(s, "OK"); + else if (res == -ENOSYS) + put_packet(s, ""); + else + put_packet(s, "E22"); + break; + case 'H': + type = *p++; + thread = strtoull(p, (char **)&p, 16); + if (thread == -1 || thread == 0) { put_packet(s, "OK"); - } else { - goto breakpoint_error; + break; + } + env = find_cpu(thread); + if (env == NULL) { + put_packet(s, "E22"); + break; + } + switch (type) { + case 'c': + s->c_cpu = env; + put_packet(s, "OK"); + break; + case 'g': + s->g_cpu = env; + put_packet(s, "OK"); + break; + default: + put_packet(s, "E22"); + break; } break; + case 'T': + thread = strtoull(p, (char **)&p, 16); + env = find_cpu(thread); + + if (env != NULL) { + put_packet(s, "OK"); + } else { + put_packet(s, "E22"); + } + break; + case 'q': + case 'Q': + /* parse any 'q' packets here */ + if (!strcmp(p,"qemu.sstepbits")) { + /* Query Breakpoint bit definitions */ + snprintf(buf, sizeof(buf), "ENABLE=%x,NOIRQ=%x,NOTIMER=%x", + SSTEP_ENABLE, + SSTEP_NOIRQ, + SSTEP_NOTIMER); + put_packet(s, buf); + break; + } else if (strncmp(p,"qemu.sstep",10) == 0) { + /* Display or change the sstep_flags */ + p += 10; + if (*p != '=') { + /* Display current setting */ + snprintf(buf, sizeof(buf), "0x%x", sstep_flags); + put_packet(s, buf); + break; + } + p++; + type = strtoul(p, (char **)&p, 16); + sstep_flags = type; + 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"); + break; + } else if (strcmp(p,"fThreadInfo") == 0) { + s->query_cpu = first_cpu; + goto report_cpuinfo; + } else if (strcmp(p,"sThreadInfo") == 0) { + report_cpuinfo: + if (s->query_cpu) { + snprintf(buf, sizeof(buf), "m%x", gdb_id(s->query_cpu)); + put_packet(s, buf); + s->query_cpu = s->query_cpu->next_cpu; + } else + put_packet(s, "l"); + break; + } else if (strncmp(p,"ThreadExtraInfo,", 16) == 0) { + thread = strtoull(p+16, (char **)&p, 16); + env = find_cpu(thread); + if (env != NULL) { + len = snprintf((char *)mem_buf, sizeof(mem_buf), + "CPU#%d [%s]", env->cpu_index, + env->halted ? "halted " : "running"); + memtohex(buf, mem_buf, len); + put_packet(s, buf); + } + break; + } + if (strncmp(p, "Supported", 9) == 0) { + snprintf(buf, sizeof(buf), "PacketSize=%x", MAX_PACKET_LENGTH); +#ifdef GDB_CORE_XML + pstrcat(buf, sizeof(buf), ";qXfer:features:read+"); +#endif + put_packet(s, buf); + break; + } +#ifdef GDB_CORE_XML + if (strncmp(p, "Xfer:features:read:", 19) == 0) { + const char *xml; + target_ulong total_len; + + gdb_has_xml = 1; + p += 19; + xml = get_feature_xml(p, &p); + if (!xml) { + snprintf(buf, sizeof(buf), "E00"); + put_packet(s, buf); + break; + } + + if (*p == ':') + p++; + addr = strtoul(p, (char **)&p, 16); + if (*p == ',') + p++; + len = strtoul(p, (char **)&p, 16); + + total_len = strlen(xml); + if (addr > total_len) { + snprintf(buf, sizeof(buf), "E00"); + put_packet(s, buf); + break; + } + if (len > (MAX_PACKET_LENGTH - 5) / 2) + len = (MAX_PACKET_LENGTH - 5) / 2; + if (len < total_len - addr) { + buf[0] = 'm'; + len = memtox(buf + 1, xml + addr, len); + } else { + buf[0] = 'l'; + len = memtox(buf + 1, xml + addr, total_len - addr); + } + put_packet_binary(s, buf, len + 1); + break; + } +#endif + /* Unrecognised 'q' command. */ + goto unknown_command; + default: - // unknown_command: + unknown_command: /* put empty packet */ buf[0] = '\0'; put_packet(s, buf); @@ -386,21 +1004,27 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) return RS_IDLE; } -extern void tb_flush(CPUState *env); +void gdb_set_stop_cpu(CPUState *env) +{ + gdbserver_state->c_cpu = env; + gdbserver_state->g_cpu = env; +} /* Send a gdb syscall request. This accepts limited printf-style format specifiers, specifically: - %x - target_ulong argument printed in hex. - %s - string pointer (target_ulong) and length (int) pair. */ -void gdb_do_syscall(gdb_syscall_complete_cb cb, char *fmt, ...) + %x - target_ulong argument printed in hex. + %lx - 64-bit argument printed in hex. + %s - string pointer (target_ulong) and length (int) pair. */ +void gdb_do_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...) { va_list va; char buf[256]; char *p; target_ulong addr; + uint64_t i64; GDBState *s; - s = gdb_syscall_state; + s = gdbserver_state; if (!s) return; gdb_current_syscall_cb = cb; @@ -414,13 +1038,21 @@ void gdb_do_syscall(gdb_syscall_complete_cb cb, char *fmt, ...) switch (*fmt++) { case 'x': addr = va_arg(va, target_ulong); - p += sprintf(p, TARGET_FMT_lx, addr); + p += snprintf(p, &buf[sizeof(buf)] - p, TARGET_FMT_lx, addr); + break; + case 'l': + if (*(fmt++) != 'x') + goto bad_format; + i64 = va_arg(va, uint64_t); + p += snprintf(p, &buf[sizeof(buf)] - p, "%" PRIx64, i64); break; case 's': addr = va_arg(va, target_ulong); - p += sprintf(p, TARGET_FMT_lx "/%x", addr, va_arg(va, int)); + p += snprintf(p, &buf[sizeof(buf)] - p, TARGET_FMT_lx "/%x", + addr, va_arg(va, int)); break; default: + bad_format: fprintf(stderr, "gdbstub: Bad syscall format string '%s'\n", fmt - 1); break; @@ -429,32 +1061,33 @@ void gdb_do_syscall(gdb_syscall_complete_cb cb, char *fmt, ...) *(p++) = *(fmt++); } } + *p = 0; va_end(va); put_packet(s, buf); - cpu_exit(s->env); + cpu_exit(s->c_cpu); } static void gdb_read_byte(GDBState *s, int ch) { - CPUState *env = s->env; - char buf[256]; + char buf[256]; int i, csum; - char reply[1]; + uint8_t reply; #ifdef DEBUG_GDB -printf("%s: state %u, byte %02x (%c)\n", __FUNCTION__, s->state, ch, ch); -fflush(stdout); + printf("%s: state %u, byte %02x (%c)\n", __FUNCTION__, s->state, ch, ch); + fflush(stdout); #endif + { switch(s->state) { case RS_IDLE: if (ch == '$') { s->line_buf_index = 0; s->state = RS_GETLINE; } else if (ch == 0x03) { - snprintf(buf, sizeof(buf), "S%02x", SIGINT); - put_packet(s, buf); - } + snprintf(buf, sizeof(buf), "S%02x", SIGINT); + put_packet(s, buf); + } break; case RS_GETLINE: if (ch == '#') { @@ -477,18 +1110,32 @@ fflush(stdout); csum += s->line_buf[i]; } if (s->line_csum != (csum & 0xff)) { - reply[0] = '-'; - put_buffer(s, (uint8_t *) reply, 1); + reply = '-'; + put_buffer(s, &reply, 1); s->state = RS_IDLE; } else { - reply[0] = '+'; - put_buffer(s, (uint8_t *) reply, 1); - s->state = gdb_handle_packet(s, env, s->line_buf); + reply = '+'; + put_buffer(s, &reply, 1); + s->state = gdb_handle_packet(s, s->line_buf); } break; default: abort(); } + } +} + +int +gdb_queuesig (void) +{ + GDBState *s; + + s = gdbserver_state; + + if (gdbserver_fd < 0 || s->fd < 0) + return 0; + else + return 1; } int @@ -498,14 +1145,13 @@ gdb_handlesig (CPUState *env, int sig) char buf[256]; int n; - if (gdbserver_fd < 0) + s = gdbserver_state; + if (gdbserver_fd < 0 || s->fd < 0) return sig; - s = &gdbserver_state; - #ifdef DEBUG_GDB -printf("%s: sig: %u\n", __FUNCTION__, sig); -fflush(stdout); + printf("%s: sig: %u\n", __FUNCTION__, sig); + fflush(stdout); #endif /* disable single step if it was enabled */ @@ -514,9 +1160,13 @@ fflush(stdout); if (sig != 0) { - snprintf(buf, sizeof(buf), "S%02x", sig); + snprintf(buf, sizeof(buf), "S%02x", target_signal_to_gdb (sig)); put_packet(s, buf); } + /* put_packet() might have detected that the peer terminated the + connection. */ + if (s->fd < 0) + return sig; sig = 0; s->state = RS_IDLE; @@ -528,8 +1178,8 @@ fflush(stdout); int i; #ifdef DEBUG_GDB -printf("%s: read: %d\n", __FUNCTION__, n); -fflush(stdout); + printf("%s: read: %d\n", __FUNCTION__, n); + fflush(stdout); #endif for (i = 0; i < n; i++) @@ -543,6 +1193,8 @@ fflush(stdout); return sig; } } + sig = s->signal; + s->signal = 0; return sig; } @@ -555,7 +1207,7 @@ gdb_poll (CPUState *env) if (gdbserver_fd < 0) return 0; - s = &gdbserver_state; + s = gdbserver_state; pfd.fd = s->fd; pfd.events = POLLIN | POLLHUP; @@ -567,8 +1219,8 @@ gdb_poll (CPUState *env) } #ifdef DEBUG_GDB -printf("%s: revents: %08x\n", __FUNCTION__, pfd.revents); -fflush(stdout); + printf("%s: revents: %08x\n", __FUNCTION__, pfd.revents); + fflush(stdout); #endif if (pfd.revents & (POLLIN | POLLHUP)) @@ -583,17 +1235,29 @@ void gdb_exit(CPUState *env, int code) GDBState *s; char buf[4]; - if (gdbserver_fd < 0) + s = gdbserver_state; + if (gdbserver_fd < 0 || s->fd < 0) return; - s = &gdbserver_state; - snprintf(buf, sizeof(buf), "W%02x", code); put_packet(s, buf); } +/* Tell the remote gdb that the process has exited due to SIG. */ +void gdb_signalled(CPUState *env, int sig) +{ + GDBState *s; + char buf[4]; -static void gdb_accept(void *opaque) + s = gdbserver_state; + if (gdbserver_fd < 0 || s->fd < 0) + return; + + snprintf(buf, sizeof(buf), "X%02x", target_signal_to_gdb (sig)); + put_packet(s, buf); +} + +static void gdb_accept(void) { GDBState *s; struct sockaddr_in sockaddr; @@ -607,6 +1271,9 @@ static void gdb_accept(void *opaque) perror("accept"); return; } else if (fd >= 0) { +#ifndef _WIN32 + fcntl(fd, F_SETFD, FD_CLOEXEC); +#endif break; } } @@ -614,13 +1281,14 @@ static void gdb_accept(void *opaque) /* set short latency */ val = 1; setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); - - s = &gdbserver_state; - memset (s, 0, sizeof (GDBState)); - s->env = first_cpu; /* XXX: allow to change CPU */ - s->fd = fd; - gdb_syscall_state = s; + s = qemu_mallocz(sizeof(GDBState)); + s->c_cpu = first_cpu; + s->g_cpu = first_cpu; + s->fd = fd; + gdb_has_xml = 0; + + gdbserver_state = s; fcntl(fd, F_SETFL, O_NONBLOCK); @@ -639,6 +1307,9 @@ static int gdbserver_open(int port) perror("socket"); return -1; } +#ifndef _WIN32 + fcntl(fd, F_SETFD, FD_CLOEXEC); +#endif /* allow fast reuse */ val = 1; @@ -669,10 +1340,22 @@ int gdbserver_start(int port) if (gdbserver_fd < 0) return -1; /* accept connections */ - gdb_accept (NULL); + gdb_accept(); return 0; } +/* Disable gdb stub for child processes. */ +void gdbserver_fork(CPUState *env) +{ + GDBState *s = gdbserver_state; + if (gdbserver_fd < 0 || s->fd < 0) + return; + close(s->fd); + s->fd = -1; + cpu_breakpoint_remove_all(env, BP_GDB); + cpu_watchpoint_remove_all(env, BP_GDB); +} + int gdbserver_isactive() { return (gdbserver_fd >= 0); diff --git a/gdbstub.h b/gdbstub.h index 9ca7af1..52130dc 100644 --- a/gdbstub.h +++ b/gdbstub.h @@ -3,17 +3,34 @@ #define DEFAULT_GDBSTUB_PORT 1234 +/* GDB breakpoint/watchpoint types */ +#define GDB_BREAKPOINT_SW 0 +#define GDB_BREAKPOINT_HW 1 +#define GDB_WATCHPOINT_WRITE 2 +#define GDB_WATCHPOINT_READ 3 +#define GDB_WATCHPOINT_ACCESS 4 + typedef void (*gdb_syscall_complete_cb)(CPUState *env, target_ulong ret, target_ulong err); -void gdb_do_syscall(gdb_syscall_complete_cb cb, char *fmt, ...); +void gdb_do_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...); int use_gdb_syscalls(void); +void gdb_set_stop_cpu(CPUState *env); int gdb_poll(CPUState *); +int gdb_queuesig (void); int gdb_handlesig (CPUState *, int); void gdb_exit(CPUState *, int); +void gdb_signalled(CPUState *, int); int gdbserver_start(int); +void gdbserver_fork(CPUState *); + +/* Get or set a register. Returns the size of the register. */ +typedef int (*gdb_reg_cb)(CPUState *env, uint8_t *buf, int reg); +void gdb_register_coprocessor(CPUState *env, + gdb_reg_cb get_reg, gdb_reg_cb set_reg, + int num_regs, const char *xml, int g_pos); int gdbserver_isactive(); diff --git a/main.c b/main.c index 66bd9cc..29ec79b 100644 --- a/main.c +++ b/main.c @@ -96,13 +96,6 @@ void armv7m_nvic_complete_irq(void *opaque, int irq) abort(); } -void gdb_register_coprocessor(CPUState * env, - void * get_reg, void * set_reg, - int num_regs, const char *xml, int g_pos) -{ - fprintf(stderr, "TODO: %s\n", __FUNCTION__); -} - void * qemu_malloc(size_t size) {