X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=gdbstub.c;h=991115361e0310dd6615fffcc2d80610ce5b0748;hb=fb59dabd4fa7e6586824ac3012073b943fc8dc79;hp=de9b62b8f8628cbb731135cd878b2429b924f2d1;hpb=598cf1c805271564686f2d732b36f50c3c40dcdd;p=mirror_qemu.git diff --git a/gdbstub.c b/gdbstub.c index de9b62b8f8..991115361e 100644 --- a/gdbstub.c +++ b/gdbstub.c @@ -18,6 +18,7 @@ */ #include "qemu/osdep.h" #include "qapi/error.h" +#include "qemu/error-report.h" #include "qemu/cutils.h" #include "cpu.h" #ifdef CONFIG_USER_ONLY @@ -304,7 +305,7 @@ typedef struct GDBState { int running_state; #else CharBackend chr; - CharDriverState *mon_chr; + Chardev *mon_chr; #endif char syscall_buf[256]; gdb_syscall_complete_cb current_syscall_cb; @@ -386,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 @@ -637,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; } @@ -784,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; @@ -829,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 { @@ -890,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 */ @@ -1358,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 { @@ -1472,7 +1588,7 @@ void gdb_exit(CPUArchState *env, int code) GDBState *s; char buf[4]; #ifndef CONFIG_USER_ONLY - CharDriverState *chr; + Chardev *chr; #endif s = gdbserver_state; @@ -1697,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; @@ -1724,13 +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; - ChardevCommon common = { 0 }; + 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; @@ -1763,8 +1907,8 @@ 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(&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)) { @@ -1787,4 +1931,11 @@ int gdbserver_start(const char *device) return 0; } + +static void register_types(void) +{ + type_register_static(&char_gdb_type_info); +} + +type_init(register_types); #endif