X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=gdbstub.c;h=991115361e0310dd6615fffcc2d80610ce5b0748;hb=a2f80fdfc683019901cdf4c0863a5920c0ca7245;hp=d2c95b5d1ef1bc76870456f4b5936ca4ae975770;hpb=27c7275a56948f48f536e2d1599b22355f5714ac;p=mirror_qemu.git diff --git a/gdbstub.c b/gdbstub.c index d2c95b5d1e..991115361e 100644 --- a/gdbstub.c +++ b/gdbstub.c @@ -16,17 +16,12 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see . */ -#include "config.h" -#include "qemu-common.h" +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "qemu/cutils.h" +#include "cpu.h" #ifdef CONFIG_USER_ONLY -#include -#include -#include -#include -#include -#include -#include - #include "qemu.h" #else #include "monitor/monitor.h" @@ -37,10 +32,11 @@ #define MAX_PACKET_LENGTH 4096 -#include "cpu.h" #include "qemu/sockets.h" +#include "sysemu/hw_accel.h" #include "sysemu/kvm.h" #include "exec/semihost.h" +#include "exec/exec-all.h" #ifdef CONFIG_USER_ONLY #define GDB_ATTACHED "0" @@ -308,8 +304,8 @@ typedef struct GDBState { int fd; int running_state; #else - CharDriverState *chr; - CharDriverState *mon_chr; + CharBackend chr; + Chardev *mon_chr; #endif char syscall_buf[256]; gdb_syscall_complete_cb current_syscall_cb; @@ -338,7 +334,7 @@ static int get_char(GDBState *s) if (ret < 0) { if (errno == ECONNRESET) s->fd = -1; - if (errno != EINTR && errno != EAGAIN) + if (errno != EINTR) return -1; } else if (ret == 0) { close(s->fd); @@ -391,6 +387,60 @@ static inline void gdb_continue(GDBState *s) #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') { + 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': + cpu_single_step(cpu, sstep_flags); + cpu_resume(cpu); + flag = 1; + break; + case 'c': + 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 @@ -399,7 +449,7 @@ static void put_buffer(GDBState *s, const uint8_t *buf, int len) while (len > 0) { ret = send(s->fd, buf, len, 0); if (ret < 0) { - if (errno != EINTR && errno != EAGAIN) + if (errno != EINTR) return; } else { buf += ret; @@ -407,7 +457,9 @@ static void put_buffer(GDBState *s, const uint8_t *buf, int len) } } #else - qemu_chr_fe_write(s->chr, buf, len); + /* XXX this blocks entire thread. Rewrite to use + * qemu_chr_fe_write and background I/O callbacks */ + qemu_chr_fe_write_all(&s->chr, buf, len); #endif } @@ -540,13 +592,20 @@ static const char *get_feature_xml(const char *p, const char **newp, GDBRegisterState *r; CPUState *cpu = first_cpu; - snprintf(target_xml, sizeof(target_xml), - "" - "" - "" - "", - cc->gdb_core_xml_file); - + pstrcat(target_xml, sizeof(target_xml), + "" + "" + ""); + if (cc->gdb_arch_name) { + gchar *arch = cc->gdb_arch_name(cpu); + pstrcat(target_xml, sizeof(target_xml), ""); + pstrcat(target_xml, sizeof(target_xml), arch); + pstrcat(target_xml, sizeof(target_xml), ""); + g_free(arch); + } + pstrcat(target_xml, sizeof(target_xml), "gdb_core_xml_file); + pstrcat(target_xml, sizeof(target_xml), "\"/>"); for (r = cpu->gdb_regs; r; r = r->next) { pstrcat(target_xml, sizeof(target_xml), "xml); @@ -633,8 +692,8 @@ void gdb_register_coprocessor(CPUState *cpu, *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); + error_report("Error: Bad gdb register numbering for '%s', " + "expected %d got %d", xml, g_pos, s->base_reg); } else { cpu->gdb_num_g_regs = cpu->gdb_num_regs; } @@ -780,6 +839,107 @@ 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, idx, signal = 0; + char cur_action; + char *newstates; + unsigned long tmp; + 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 = 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; + } + /* thread specification. special values: (none), -1 = all; 0 = any */ + if ((p[0] == ':' && p[1] == '-' && p[2] == '1') || (p[0] != ':')) { + if (*p == ':') { + p += 3; + } + for (idx = 0; idx < max_cpus; idx++) { + if (newstates[idx] == 1) { + newstates[idx] = cur_action; + } + } + } else if (*p == ':') { + p++; + res = qemu_strtoul(p, &p, 16, &tmp); + if (res) { + goto out; + } + idx = tmp; + /* 0 means any thread, so we pick the first valid CPU */ + if (!idx) { + idx = cpu_index(first_cpu); + } + + /* + * If we are in user mode, the thread specified is actually a + * thread id, and not an index. We need to find the actual + * CPU first, and only then we can use its index. + */ + cpu = find_cpu(idx); + /* invalid CPU/thread specified */ + if (!idx || !cpu) { + res = -EINVAL; + goto out; + } + /* only use if no previous match occourred */ + if (newstates[cpu->cpu_index] == 1) { + newstates[cpu->cpu_index] = cur_action; + } + } + } + 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; @@ -825,60 +985,20 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) 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 { @@ -886,7 +1006,7 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) } case 'k': /* Kill the target */ - fprintf(stderr, "\nQEMU: Terminated via GDBstub\n"); + error_report("QEMU: Terminated via GDBstub"); exit(0); case 'D': /* Detach packet */ @@ -956,6 +1076,13 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) if (*p == ',') p++; len = strtoull(p, NULL, 16); + + /* memtohex() doubles the required space */ + if (len > MAX_PACKET_LENGTH / 2) { + put_packet (s, "E22"); + break; + } + if (target_memory_rw_debug(s->g_cpu, addr, mem_buf, len, false) != 0) { put_packet (s, "E14"); } else { @@ -970,6 +1097,12 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) len = strtoull(p, (char **)&p, 16); if (*p == ':') p++; + + /* hextomem() reads 2*len bytes */ + if (len > strlen(p) / 2) { + put_packet (s, "E22"); + break; + } hextomem(mem_buf, p, len); if (target_memory_rw_debug(s->g_cpu, addr, mem_buf, len, true) != 0) { @@ -1107,7 +1240,8 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) cpu = find_cpu(thread); if (cpu != NULL) { cpu_synchronize_state(cpu); - len = snprintf((char *)mem_buf, sizeof(mem_buf), + /* memtohex() doubles the required space */ + len = snprintf((char *)mem_buf, sizeof(buf) / 2, "CPU#%d [%s]", cpu->cpu_index, cpu->halted ? "halted " : "running"); memtohex(buf, mem_buf, len); @@ -1136,8 +1270,8 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) put_packet(s, "E01"); break; } - hextomem(mem_buf, p + 5, len); len = len / 2; + hextomem(mem_buf, p + 5, len); mem_buf[len++] = 0; qemu_chr_be_write(s->mon_chr, mem_buf, len); put_packet(s, "OK"); @@ -1340,8 +1474,8 @@ void gdb_do_syscallv(gdb_syscall_complete_cb cb, const char *fmt, va_list va) break; default: bad_format: - fprintf(stderr, "gdbstub: Bad syscall format string '%s'\n", - fmt - 1); + error_report("gdbstub: Bad syscall format string '%s'", + fmt - 1); break; } } else { @@ -1453,6 +1587,9 @@ void gdb_exit(CPUArchState *env, int code) { GDBState *s; char buf[4]; +#ifndef CONFIG_USER_ONLY + Chardev *chr; +#endif s = gdbserver_state; if (!s) { @@ -1463,7 +1600,8 @@ void gdb_exit(CPUArchState *env, int code) return; } #else - if (!s->chr) { + chr = qemu_chr_fe_get_driver(&s->chr); + if (!chr) { return; } #endif @@ -1472,24 +1610,12 @@ void gdb_exit(CPUArchState *env, int code) put_packet(s, buf); #ifndef CONFIG_USER_ONLY - qemu_chr_delete(s->chr); + qemu_chr_fe_deinit(&s->chr); + qemu_chr_delete(chr); #endif } #ifdef CONFIG_USER_ONLY -int -gdb_queuesig (void) -{ - GDBState *s; - - s = gdbserver_state; - - if (gdbserver_fd < 0 || s->fd < 0) - return 0; - else - return 1; -} - int gdb_handlesig(CPUState *cpu, int sig) { @@ -1527,9 +1653,13 @@ gdb_handlesig(CPUState *cpu, int sig) for (i = 0; i < n; i++) { gdb_read_byte(s, buf[i]); } - } else if (n == 0 || errno != EAGAIN) { + } else { /* XXX: Connection closed. Should probably wait for another connection before continuing. */ + if (n == 0) { + close(s->fd); + } + s->fd = -1; return sig; } } @@ -1584,8 +1714,6 @@ static void gdb_accept(void) gdb_has_xml = false; gdbserver_state = s; - - fcntl(fd, F_SETFL, O_NONBLOCK); } static int gdbserver_open(int port) @@ -1613,7 +1741,7 @@ static int gdbserver_open(int port) close(fd); return -1; } - ret = listen(fd, 0); + ret = listen(fd, 1); if (ret < 0) { perror("listen"); close(fd); @@ -1685,7 +1813,7 @@ static void gdb_monitor_output(GDBState *s, const char *msg, int len) 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; @@ -1712,12 +1840,41 @@ static void gdb_sigterm_handler(int signal) } #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, +}; + int gdbserver_start(const char *device) { GDBState *s; char gdbstub_device_name[128]; - CharDriverState *chr = NULL; - CharDriverState *mon_chr; + Chardev *chr = NULL; + Chardev *mon_chr; + + if (!first_cpu) { + error_report("gdbstub: meaningless to attach gdb to a " + "machine without any CPU."); + return -1; + } if (!device) return -1; @@ -1737,13 +1894,9 @@ int gdbserver_start(const char *device) sigaction(SIGINT, &act, NULL); } #endif - chr = qemu_chr_new("gdb", device, NULL); + chr = qemu_chr_new_noreplay("gdb", device); if (!chr) return -1; - - qemu_chr_fe_claim_no_fail(chr); - qemu_chr_add_handlers(chr, gdb_chr_can_receive, gdb_chr_receive, - gdb_chr_event, NULL); } s = gdbserver_state; @@ -1754,22 +1907,35 @@ int gdbserver_start(const char *device) qemu_add_vm_change_state_handler(gdb_vm_state_change, NULL); /* Initialize a monitor terminal for gdb */ - mon_chr = qemu_chr_alloc(); - 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 (s->chr) - qemu_chr_delete(s->chr); + if (qemu_chr_fe_get_driver(&s->chr)) { + qemu_chr_delete(qemu_chr_fe_get_driver(&s->chr)); + } mon_chr = s->mon_chr; memset(s, 0, sizeof(GDBState)); + s->mon_chr = mon_chr; } s->c_cpu = first_cpu; s->g_cpu = first_cpu; - s->chr = chr; + 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); + } s->state = chr ? RS_IDLE : RS_INACTIVE; s->mon_chr = mon_chr; s->current_syscall_cb = NULL; return 0; } + +static void register_types(void) +{ + type_register_static(&char_gdb_type_info); +} + +type_init(register_types); #endif