diff --git a/gdbstub.c b/gdbstub.c index a0fc62d3d7..b522a8923a 100644 --- a/gdbstub.c +++ b/gdbstub.c @@ -69,7 +69,9 @@ enum RSState { 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[MAX_PACKET_LENGTH]; int line_buf_index; @@ -90,6 +92,8 @@ typedef struct GDBState { */ 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. */ @@ -99,9 +103,6 @@ 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; @@ -126,8 +127,6 @@ static int get_char(GDBState *s) } #endif -/* GDB stub state for use by semihosting syscalls. */ -static GDBState *gdb_syscall_state; static gdb_syscall_complete_cb gdb_current_syscall_cb; enum { @@ -141,8 +140,8 @@ 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; } @@ -1031,7 +1030,7 @@ static int memtox(char *buf, const char *mem, int len) return p - buf; } -const char *get_feature_xml(CPUState *env, const char *p, const char **newp) +const char *get_feature_xml(const char *p, const char **newp) { extern const char *const xml_builtin[][2]; size_t len; @@ -1057,7 +1056,7 @@ const char *get_feature_xml(CPUState *env, const char *p, const char **newp) "", GDB_CORE_XML); - for (r = env->gdb_regs; r; r = r->next) { + for (r = first_cpu->gdb_regs; r; r = r->next) { strcat(target_xml, "xml); strcat(target_xml, "\"/>"); @@ -1160,55 +1159,84 @@ static const int xlat_gdb_type[] = { }; #endif -static int gdb_breakpoint_insert(CPUState *env, target_ulong addr, - target_ulong len, int type) +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: - return cpu_breakpoint_insert(env, addr, BP_GDB, NULL); + 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: - return cpu_watchpoint_insert(env, addr, len, xlat_gdb_type[type], - NULL); + 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(CPUState *env, target_ulong addr, - target_ulong len, int type) +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: - return cpu_breakpoint_remove(env, addr, BP_GDB); + 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: - return cpu_watchpoint_remove(env, addr, len, xlat_gdb_type[type]); + 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(CPUState *env) +static void gdb_breakpoint_remove_all(void) { - cpu_breakpoint_remove_all(env, BP_GDB); + 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); + cpu_watchpoint_remove_all(env, BP_GDB); #endif + } } -static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) +static int gdb_handle_packet(GDBState *s, const char *line_buf) { + CPUState *env; const char *p; - int ch, reg_size, type, res; + int ch, reg_size, type, res, thread; char buf[MAX_PACKET_LENGTH]; uint8_t mem_buf[MAX_PACKET_LENGTH]; uint8_t *registers; @@ -1222,32 +1250,33 @@ 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;", SIGTRAP, + s->c_cpu->cpu_index+1); 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(env); + gdb_breakpoint_remove_all(); break; case 'c': if (*p != '\0') { addr = strtoull(p, (char **)&p, 16); #if defined(TARGET_I386) - env->eip = addr; + s->c_cpu->eip = addr; #elif defined (TARGET_PPC) - env->nip = addr; + s->c_cpu->nip = addr; #elif defined (TARGET_SPARC) - env->pc = addr; - env->npc = addr + 4; + s->c_cpu->pc = addr; + s->c_cpu->npc = addr + 4; #elif defined (TARGET_ARM) - env->regs[15] = addr; + s->c_cpu->regs[15] = addr; #elif defined (TARGET_SH4) - env->pc = addr; + s->c_cpu->pc = addr; #elif defined (TARGET_MIPS) - env->active_tc.PC = addr; + s->c_cpu->active_tc.PC = addr; #elif defined (TARGET_CRIS) - env->pc = addr; + s->c_cpu->pc = addr; #endif } gdb_continue(s); @@ -1262,7 +1291,7 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) exit(0); case 'D': /* Detach packet */ - gdb_breakpoint_remove_all(env); + gdb_breakpoint_remove_all(); gdb_continue(s); put_packet(s, "OK"); break; @@ -1270,23 +1299,23 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) if (*p != '\0') { addr = strtoull(p, (char **)&p, 16); #if defined(TARGET_I386) - env->eip = addr; + s->c_cpu->eip = addr; #elif defined (TARGET_PPC) - env->nip = addr; + s->c_cpu->nip = addr; #elif defined (TARGET_SPARC) - env->pc = addr; - env->npc = addr + 4; + s->c_cpu->pc = addr; + s->c_cpu->npc = addr + 4; #elif defined (TARGET_ARM) - env->regs[15] = addr; + s->c_cpu->regs[15] = addr; #elif defined (TARGET_SH4) - env->pc = addr; + s->c_cpu->pc = addr; #elif defined (TARGET_MIPS) - env->active_tc.PC = addr; + s->c_cpu->active_tc.PC = addr; #elif defined (TARGET_CRIS) - env->pc = addr; + s->c_cpu->pc = addr; #endif } - cpu_single_step(env, sstep_flags); + cpu_single_step(s->c_cpu, sstep_flags); gdb_continue(s); return RS_IDLE; case 'F': @@ -1305,7 +1334,7 @@ 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 { @@ -1316,7 +1345,7 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) case 'g': len = 0; for (addr = 0; addr < num_g_regs; addr++) { - reg_size = gdb_read_register(env, mem_buf + len, addr); + reg_size = gdb_read_register(s->g_cpu, mem_buf + len, addr); len += reg_size; } memtohex(buf, mem_buf, len); @@ -1327,7 +1356,7 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) len = strlen(p) / 2; hextomem((uint8_t *)registers, p, len); for (addr = 0; addr < num_g_regs && len > 0; addr++) { - reg_size = gdb_write_register(env, registers, addr); + reg_size = gdb_write_register(s->g_cpu, registers, addr); len -= reg_size; registers += reg_size; } @@ -1338,7 +1367,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); @@ -1353,7 +1382,7 @@ 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"); @@ -1365,7 +1394,7 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) if (!gdb_has_xml) goto unknown_command; addr = strtoull(p, (char **)&p, 16); - reg_size = gdb_read_register(env, mem_buf, addr); + reg_size = gdb_read_register(s->g_cpu, mem_buf, addr); if (reg_size) { memtohex(buf, mem_buf, reg_size); put_packet(s, buf); @@ -1381,7 +1410,7 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) p++; reg_size = strlen(p) / 2; hextomem(mem_buf, p, reg_size); - gdb_write_register(env, mem_buf, addr); + gdb_write_register(s->g_cpu, mem_buf, addr); put_packet(s, "OK"); break; case 'Z': @@ -1394,9 +1423,9 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) p++; len = strtoull(p, (char **)&p, 16); if (ch == 'Z') - res = gdb_breakpoint_insert(env, addr, len, type); + res = gdb_breakpoint_insert(addr, len, type); else - res = gdb_breakpoint_remove(env, addr, len, type); + res = gdb_breakpoint_remove(addr, len, type); if (res >= 0) put_packet(s, "OK"); else if (res == -ENOSYS) @@ -1404,6 +1433,45 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) 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"); + break; + } + for (env = first_cpu; env != NULL; env = env->next_cpu) + if (env->cpu_index + 1 == thread) + break; + 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); +#ifndef CONFIG_USER_ONLY + if (thread > 0 && thread < smp_cpus + 1) +#else + if (thread == 1) +#endif + put_packet(s, "OK"); + else + put_packet(s, "E22"); + break; case 'q': case 'Q': /* parse any 'q' packets here */ @@ -1429,10 +1497,39 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) 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", s->query_cpu->cpu_index+1); + 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); + for (env = first_cpu; env != NULL; env = env->next_cpu) + if (env->cpu_index + 1 == thread) { + 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; + } + break; } #ifdef CONFIG_LINUX_USER else if (strncmp(p, "Offsets", 7) == 0) { - TaskState *ts = env->opaque; + TaskState *ts = s->c_cpu->opaque; snprintf(buf, sizeof(buf), "Text=" TARGET_ABI_FMT_lx ";Data=" TARGET_ABI_FMT_lx @@ -1459,7 +1556,7 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) gdb_has_xml = 1; p += 19; - xml = get_feature_xml(env, p, &p); + xml = get_feature_xml(p, &p); if (!xml) { snprintf(buf, sizeof(buf), "E00"); put_packet(s, buf); @@ -1507,10 +1604,17 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf) extern void tb_flush(CPUState *env); +void gdb_set_stop_cpu(CPUState *env) +{ + gdbserver_state->c_cpu = env; + gdbserver_state->g_cpu = env; +} + #ifndef CONFIG_USER_ONLY static void gdb_vm_stopped(void *opaque, int reason) { - GDBState *s = opaque; + GDBState *s = gdbserver_state; + CPUState *env = s->c_cpu; char buf[256]; const char *type; int ret; @@ -1519,11 +1623,11 @@ static void gdb_vm_stopped(void *opaque, int reason) return; /* disable single step if it was enable */ - cpu_single_step(s->env, 0); + cpu_single_step(env, 0); if (reason == EXCP_DEBUG) { - if (s->env->watchpoint_hit) { - switch (s->env->watchpoint_hit->flags & BP_MEM_ACCESS) { + if (env->watchpoint_hit) { + switch (env->watchpoint_hit->flags & BP_MEM_ACCESS) { case BP_MEM_READ: type = "r"; break; @@ -1534,20 +1638,22 @@ static void gdb_vm_stopped(void *opaque, int reason) type = ""; break; } - snprintf(buf, sizeof(buf), "T%02x%swatch:" TARGET_FMT_lx ";", - SIGTRAP, type, s->env->watchpoint_hit->vaddr); + snprintf(buf, sizeof(buf), + "T%02xthread:%02x;%swatch:" TARGET_FMT_lx ";", + SIGTRAP, env->cpu_index+1, type, + env->watchpoint_hit->vaddr); put_packet(s, buf); - s->env->watchpoint_hit = NULL; + env->watchpoint_hit = NULL; return; } - tb_flush(s->env); + tb_flush(env); ret = SIGTRAP; } else if (reason == EXCP_INTERRUPT) { ret = SIGINT; } else { ret = 0; } - snprintf(buf, sizeof(buf), "S%02x", ret); + snprintf(buf, sizeof(buf), "T%02xthread:%02x;", ret, env->cpu_index+1); put_packet(s, buf); } #endif @@ -1566,7 +1672,7 @@ void gdb_do_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...) uint64_t i64; GDBState *s; - s = gdb_syscall_state; + s = gdbserver_state; if (!s) return; gdb_current_syscall_cb = cb; @@ -1611,15 +1717,14 @@ void gdb_do_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...) va_end(va); put_packet(s, buf); #ifdef CONFIG_USER_ONLY - gdb_handlesig(s->env, 0); + gdb_handlesig(s->c_cpu, 0); #else - cpu_interrupt(s->env, CPU_INTERRUPT_EXIT); + cpu_interrupt(s->c_cpu, CPU_INTERRUPT_EXIT); #endif } static void gdb_read_byte(GDBState *s, int ch) { - CPUState *env = s->env; int i, csum; uint8_t reply; @@ -1685,7 +1790,7 @@ static void gdb_read_byte(GDBState *s, int ch) } else { reply = '+'; put_buffer(s, &reply, 1); - s->state = gdb_handle_packet(s, env, s->line_buf); + s->state = gdb_handle_packet(s, s->line_buf); } break; default: @@ -1702,7 +1807,7 @@ gdb_handlesig (CPUState *env, int sig) char buf[256]; int n; - s = &gdbserver_state; + s = gdbserver_state; if (gdbserver_fd < 0 || s->fd < 0) return sig; @@ -1750,7 +1855,7 @@ void gdb_exit(CPUState *env, int code) GDBState *s; char buf[4]; - s = &gdbserver_state; + s = gdbserver_state; if (gdbserver_fd < 0 || s->fd < 0) return; @@ -1759,7 +1864,7 @@ void gdb_exit(CPUState *env, int code) } -static void gdb_accept(void *opaque) +static void gdb_accept(void) { GDBState *s; struct sockaddr_in sockaddr; @@ -1781,13 +1886,20 @@ static void gdb_accept(void *opaque) val = 1; setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); - s = &gdbserver_state; + s = qemu_mallocz(sizeof(GDBState)); + if (!s) { + errno = ENOMEM; + perror("accept"); + return; + } + memset (s, 0, sizeof (GDBState)); - s->env = first_cpu; /* XXX: allow to change CPU */ + s->c_cpu = first_cpu; + s->g_cpu = first_cpu; s->fd = fd; gdb_has_xml = 0; - gdb_syscall_state = s; + gdbserver_state = s; fcntl(fd, F_SETFL, O_NONBLOCK); } @@ -1829,7 +1941,7 @@ int gdbserver_start(int port) if (gdbserver_fd < 0) return -1; /* accept connections */ - gdb_accept (NULL); + gdb_accept(); return 0; } #else @@ -1842,11 +1954,10 @@ static int gdb_chr_can_receive(void *opaque) static void gdb_chr_receive(void *opaque, const uint8_t *buf, int size) { - GDBState *s = opaque; int i; for (i = 0; i < size; i++) { - gdb_read_byte(s, buf[i]); + gdb_read_byte(gdbserver_state, buf[i]); } } @@ -1855,7 +1966,6 @@ static void gdb_chr_event(void *opaque, int event) switch (event) { case CHR_EVENT_RESET: vm_stop(EXCP_INTERRUPT); - gdb_syscall_state = opaque; gdb_has_xml = 0; break; default: @@ -1890,11 +2000,13 @@ int gdbserver_start(const char *port) if (!s) { return -1; } - s->env = first_cpu; /* XXX: allow to change CPU */ + s->c_cpu = first_cpu; + s->g_cpu = first_cpu; s->chr = chr; + gdbserver_state = s; qemu_chr_add_handlers(chr, gdb_chr_can_receive, gdb_chr_receive, - gdb_chr_event, s); - qemu_add_vm_stop_handler(gdb_vm_stopped, s); + gdb_chr_event, NULL); + qemu_add_vm_stop_handler(gdb_vm_stopped, NULL); return 0; } #endif diff --git a/gdbstub.h b/gdbstub.h index 76e9482d36..1ba7931f77 100644 --- a/gdbstub.h +++ b/gdbstub.h @@ -8,6 +8,7 @@ typedef void (*gdb_syscall_complete_cb)(CPUState *env, void gdb_do_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...); int use_gdb_syscalls(void); +void gdb_set_stop_cpu(CPUState *env); #ifdef CONFIG_USER_ONLY int gdb_handlesig (CPUState *, int); void gdb_exit(CPUState *, int); diff --git a/vl.c b/vl.c index 2cfe23d9e1..ef531df0cb 100644 --- a/vl.c +++ b/vl.c @@ -3720,6 +3720,7 @@ static int main_loop(void) ret = EXCP_INTERRUPT; } if (unlikely(ret == EXCP_DEBUG)) { + gdb_set_stop_cpu(cur_cpu); vm_stop(EXCP_DEBUG); } /* If all cpus are halted then wait until the next IRQ */