Commit 2704efad authored by Jon Doron's avatar Jon Doron Committed by Alex Bennée
Browse files

gdbstub: Implement generic set/query (Q/q pkt) with new infra



The generic set/query packets contains implementation for varioius
sub-commands which are required for GDB and also additional commands
which are QEMU specific.

To see which QEMU specific commands are available use the command
gdb> maintenance packet qqemu.Supported

Currently the only implemented QEMU specific command is the command
that sets the single step behavior.

gdb> maintenance packet qqemu.sstepbits
Will display the MASK bits used to control the single stepping.

gdb> maintenance packet qqemu.sstep
Will display the current value of the mask used when single stepping.

gdb> maintenance packet Qqemu.sstep:HEX_VALUE
Will change the single step mask.

Signed-off-by: default avatarJon Doron <arilou@gmail.com>
Message-Id: <20190529064148.19856-18-arilou@gmail.com>
Signed-off-by: default avatarAlex Bennée <alex.bennee@linaro.org>
parent 8536ec02
Loading
Loading
Loading
Loading
+373 −186
Original line number Diff line number Diff line
@@ -1134,14 +1134,6 @@ static GDBThreadIdKind read_thread_id(const char *buf, const char **end_buf,
    return GDB_ONE_THREAD;
}

static int is_query_packet(const char *p, const char *query, char separator)
{
    unsigned int query_len = strlen(query);

    return strncmp(p, query, query_len) == 0 &&
        (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
@@ -1922,18 +1914,368 @@ static void handle_v_commands(GdbCmdContext *gdb_ctx, void *user_ctx)
    }
}

static int gdb_handle_packet(GDBState *s, const char *line_buf)
static void handle_query_qemu_sstepbits(GdbCmdContext *gdb_ctx, void *user_ctx)
{
    snprintf(gdb_ctx->str_buf, sizeof(gdb_ctx->str_buf),
             "ENABLE=%x,NOIRQ=%x,NOTIMER=%x", SSTEP_ENABLE,
             SSTEP_NOIRQ, SSTEP_NOTIMER);
    put_packet(gdb_ctx->s, gdb_ctx->str_buf);
}

static void handle_set_qemu_sstep(GdbCmdContext *gdb_ctx, void *user_ctx)
{
    if (!gdb_ctx->num_params) {
        return;
    }

    sstep_flags = gdb_ctx->params[0].val_ul;
    put_packet(gdb_ctx->s, "OK");
}

static void handle_query_qemu_sstep(GdbCmdContext *gdb_ctx, void *user_ctx)
{
    snprintf(gdb_ctx->str_buf, sizeof(gdb_ctx->str_buf), "0x%x", sstep_flags);
    put_packet(gdb_ctx->s, gdb_ctx->str_buf);
}

static void handle_query_curr_tid(GdbCmdContext *gdb_ctx, void *user_ctx)
{
    CPUState *cpu;
    GDBProcess *process;
    char thread_id[16];

    /*
     * "Current thread" remains vague in the spec, so always return
     * the first thread of the current process (gdb returns the
     * first thread).
     */
    process = gdb_get_cpu_process(gdb_ctx->s, gdb_ctx->s->g_cpu);
    cpu = get_first_cpu_in_process(gdb_ctx->s, process);
    gdb_fmt_thread_id(gdb_ctx->s, cpu, thread_id, sizeof(thread_id));
    snprintf(gdb_ctx->str_buf, sizeof(gdb_ctx->str_buf), "QC%s", thread_id);
    put_packet(gdb_ctx->s, gdb_ctx->str_buf);
}

static void handle_query_threads(GdbCmdContext *gdb_ctx, void *user_ctx)
{
    char thread_id[16];

    if (!gdb_ctx->s->query_cpu) {
        put_packet(gdb_ctx->s, "l");
        return;
    }

    gdb_fmt_thread_id(gdb_ctx->s, gdb_ctx->s->query_cpu, thread_id,
                      sizeof(thread_id));
    snprintf(gdb_ctx->str_buf, sizeof(gdb_ctx->str_buf), "m%s", thread_id);
    put_packet(gdb_ctx->s, gdb_ctx->str_buf);
    gdb_ctx->s->query_cpu =
        gdb_next_attached_cpu(gdb_ctx->s, gdb_ctx->s->query_cpu);
}

static void handle_query_first_threads(GdbCmdContext *gdb_ctx, void *user_ctx)
{
    gdb_ctx->s->query_cpu = gdb_first_attached_cpu(gdb_ctx->s);
    handle_query_threads(gdb_ctx, user_ctx);
}

static void handle_query_thread_extra(GdbCmdContext *gdb_ctx, void *user_ctx)
{
    CPUState *cpu;
    int len;

    if (!gdb_ctx->num_params ||
        gdb_ctx->params[0].thread_id.kind == GDB_READ_THREAD_ERR) {
        put_packet(gdb_ctx->s, "E22");
        return;
    }

    cpu = gdb_get_cpu(gdb_ctx->s, gdb_ctx->params[0].thread_id.pid,
                      gdb_ctx->params[0].thread_id.tid);
    if (!cpu) {
        return;
    }

    cpu_synchronize_state(cpu);

    if (gdb_ctx->s->multiprocess && (gdb_ctx->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 *)gdb_ctx->mem_buf, sizeof(gdb_ctx->str_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 *)gdb_ctx->mem_buf, sizeof(gdb_ctx->str_buf) / 2,
                        "CPU#%d [%s]", cpu->cpu_index,
                        cpu->halted ? "halted " : "running");
    }
    trace_gdbstub_op_extra_info((char *)gdb_ctx->mem_buf);
    memtohex(gdb_ctx->str_buf, gdb_ctx->mem_buf, len);
    put_packet(gdb_ctx->s, gdb_ctx->str_buf);
}

#ifdef CONFIG_USER_ONLY
static void handle_query_offsets(GdbCmdContext *gdb_ctx, void *user_ctx)
{
    TaskState *ts;

    ts = gdb_ctx->s->c_cpu->opaque;
    snprintf(gdb_ctx->str_buf, sizeof(gdb_ctx->str_buf),
             "Text=" TARGET_ABI_FMT_lx ";Data=" TARGET_ABI_FMT_lx
             ";Bss=" TARGET_ABI_FMT_lx,
             ts->info->code_offset,
             ts->info->data_offset,
             ts->info->data_offset);
    put_packet(gdb_ctx->s, gdb_ctx->str_buf);
}
#else
static void handle_query_rcmd(GdbCmdContext *gdb_ctx, void *user_ctx)
{
    int len;

    if (!gdb_ctx->num_params) {
        put_packet(gdb_ctx->s, "E22");
        return;
    }

    len = strlen(gdb_ctx->params[0].data);
    if (len % 2) {
        put_packet(gdb_ctx->s, "E01");
        return;
    }

    len = len / 2;
    hextomem(gdb_ctx->mem_buf, gdb_ctx->params[0].data, len);
    gdb_ctx->mem_buf[len++] = 0;
    qemu_chr_be_write(gdb_ctx->s->mon_chr, gdb_ctx->mem_buf, len);
    put_packet(gdb_ctx->s, "OK");

}
#endif

static void handle_query_supported(GdbCmdContext *gdb_ctx, void *user_ctx)
{
    CPUClass *cc;

    snprintf(gdb_ctx->str_buf, sizeof(gdb_ctx->str_buf), "PacketSize=%x",
             MAX_PACKET_LENGTH);
    cc = CPU_GET_CLASS(first_cpu);
    if (cc->gdb_core_xml_file) {
        pstrcat(gdb_ctx->str_buf, sizeof(gdb_ctx->str_buf),
                ";qXfer:features:read+");
    }

    if (gdb_ctx->num_params &&
        strstr(gdb_ctx->params[0].data, "multiprocess+")) {
        gdb_ctx->s->multiprocess = true;
    }

    pstrcat(gdb_ctx->str_buf, sizeof(gdb_ctx->str_buf), ";multiprocess+");
    put_packet(gdb_ctx->s, gdb_ctx->str_buf);
}

static void handle_query_xfer_features(GdbCmdContext *gdb_ctx, void *user_ctx)
{
    GDBProcess *process;
    CPUClass *cc;
    unsigned long len, total_len, addr;
    const char *xml;
    const char *p;
    uint32_t pid, tid;
    int ch, type;

    if (gdb_ctx->num_params < 3) {
        put_packet(gdb_ctx->s, "E22");
        return;
    }

    process = gdb_get_cpu_process(gdb_ctx->s, gdb_ctx->s->g_cpu);
    cc = CPU_GET_CLASS(gdb_ctx->s->g_cpu);
    if (!cc->gdb_core_xml_file) {
        put_packet(gdb_ctx->s, "");
        return;
    }

    gdb_has_xml = true;
    p = gdb_ctx->params[0].data;
    xml = get_feature_xml(gdb_ctx->s, p, &p, process);
    if (!xml) {
        put_packet(gdb_ctx->s, "E00");
        return;
    }

    addr = gdb_ctx->params[1].val_ul;
    len = gdb_ctx->params[2].val_ul;
    total_len = strlen(xml);
    if (addr > total_len) {
        put_packet(gdb_ctx->s, "E00");
        return;
    }

    if (len > (MAX_PACKET_LENGTH - 5) / 2) {
        len = (MAX_PACKET_LENGTH - 5) / 2;
    }

    if (len < total_len - addr) {
        gdb_ctx->str_buf[0] = 'm';
        len = memtox(gdb_ctx->str_buf + 1, xml + addr, len);
    } else {
        gdb_ctx->str_buf[0] = 'l';
        len = memtox(gdb_ctx->str_buf + 1, xml + addr, total_len - addr);
    }

    put_packet_binary(gdb_ctx->s, gdb_ctx->str_buf, len + 1, true);
}

static void handle_query_attached(GdbCmdContext *gdb_ctx, void *user_ctx)
{
    put_packet(gdb_ctx->s, GDB_ATTACHED);
}

static void handle_query_qemu_supported(GdbCmdContext *gdb_ctx, void *user_ctx)
{
    put_packet(gdb_ctx->s, "sstepbits;sstep");
}

static GdbCmdParseEntry gdb_gen_query_set_common_table[] = {
    /* Order is important if has same prefix */
    {
        .handler = handle_query_qemu_sstepbits,
        .cmd = "qemu.sstepbits",
    },
    {
        .handler = handle_query_qemu_sstep,
        .cmd = "qemu.sstep",
    },
    {
        .handler = handle_set_qemu_sstep,
        .cmd = "qemu.sstep=",
        .cmd_startswith = 1,
        .schema = "l0"
    },
};

static GdbCmdParseEntry gdb_gen_query_table[] = {
    {
        .handler = handle_query_curr_tid,
        .cmd = "C",
    },
    {
        .handler = handle_query_threads,
        .cmd = "sThreadInfo",
    },
    {
        .handler = handle_query_first_threads,
        .cmd = "fThreadInfo",
    },
    {
        .handler = handle_query_thread_extra,
        .cmd = "ThreadExtraInfo,",
        .cmd_startswith = 1,
        .schema = "t0"
    },
#ifdef CONFIG_USER_ONLY
    {
        .handler = handle_query_offsets,
        .cmd = "Offsets",
    },
#else
    {
        .handler = handle_query_rcmd,
        .cmd = "Rcmd,",
        .cmd_startswith = 1,
        .schema = "s0"
    },
#endif
    {
        .handler = handle_query_supported,
        .cmd = "Supported:",
        .cmd_startswith = 1,
        .schema = "s0"
    },
    {
        .handler = handle_query_supported,
        .cmd = "Supported",
        .schema = "s0"
    },
    {
        .handler = handle_query_xfer_features,
        .cmd = "Xfer:features:read:",
        .cmd_startswith = 1,
        .schema = "s:l,l0"
    },
    {
        .handler = handle_query_attached,
        .cmd = "Attached:",
        .cmd_startswith = 1
    },
    {
        .handler = handle_query_attached,
        .cmd = "Attached",
    },
    {
        .handler = handle_query_qemu_supported,
        .cmd = "qemu.Supported",
    },
};

static GdbCmdParseEntry gdb_gen_set_table[] = {
    /* Order is important if has same prefix */
    {
        .handler = handle_set_qemu_sstep,
        .cmd = "qemu.sstep:",
        .cmd_startswith = 1,
        .schema = "l0"
    },
};

static void handle_gen_query(GdbCmdContext *gdb_ctx, void *user_ctx)
{
    if (!gdb_ctx->num_params) {
        return;
    }

    if (!process_string_cmd(gdb_ctx->s, NULL, gdb_ctx->params[0].data,
                            gdb_gen_query_set_common_table,
                            ARRAY_SIZE(gdb_gen_query_set_common_table))) {
        return;
    }

    if (process_string_cmd(gdb_ctx->s, NULL, gdb_ctx->params[0].data,
                           gdb_gen_query_table,
                           ARRAY_SIZE(gdb_gen_query_table))) {
        put_packet(gdb_ctx->s, "");
    }
}

static void handle_gen_set(GdbCmdContext *gdb_ctx, void *user_ctx)
{
    if (!gdb_ctx->num_params) {
        return;
    }

    if (!process_string_cmd(gdb_ctx->s, NULL, gdb_ctx->params[0].data,
                            gdb_gen_query_set_common_table,
                            ARRAY_SIZE(gdb_gen_query_set_common_table))) {
        return;
    }

    if (process_string_cmd(gdb_ctx->s, NULL, gdb_ctx->params[0].data,
                           gdb_gen_set_table,
                           ARRAY_SIZE(gdb_gen_set_table))) {
        put_packet(gdb_ctx->s, "");
    }
}

static int gdb_handle_packet(GDBState *s, const char *line_buf)
{
    const char *p;
    int ch;
    uint8_t mem_buf[MAX_PACKET_LENGTH];
    char buf[sizeof(mem_buf) + 1 /* trailing NUL */];
    char thread_id[16];
    target_ulong addr, len;
    const GdbCmdParseEntry *cmd_parser = NULL;

    trace_gdbstub_io_command(line_buf);
@@ -2135,183 +2477,28 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf)
        }
        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 (is_query_packet(p, "qemu.sstep", '=')) {
            /* 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 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 = 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%s",
                         gdb_fmt_thread_id(s, s->query_cpu,
                                       thread_id, sizeof(thread_id)));
                put_packet(s, buf);
                s->query_cpu = gdb_next_attached_cpu(s, s->query_cpu);
            } else
                put_packet(s, "l");
            break;
        } else if (strncmp(p,"ThreadExtraInfo,", 16) == 0) {
            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);

                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);
            }
            break;
        }
#ifdef CONFIG_USER_ONLY
        else if (strcmp(p, "Offsets") == 0) {
            TaskState *ts = s->c_cpu->opaque;

            snprintf(buf, sizeof(buf),
                     "Text=" TARGET_ABI_FMT_lx ";Data=" TARGET_ABI_FMT_lx
                     ";Bss=" TARGET_ABI_FMT_lx,
                     ts->info->code_offset,
                     ts->info->data_offset,
                     ts->info->data_offset);
            put_packet(s, buf);
            break;
        }
#else /* !CONFIG_USER_ONLY */
        else if (strncmp(p, "Rcmd,", 5) == 0) {
            int len = strlen(p + 5);

            if ((len % 2) != 0) {
                put_packet(s, "E01");
                break;
            }
            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");
            break;
        }
#endif /* !CONFIG_USER_ONLY */
        if (is_query_packet(p, "Supported", ':')) {
            snprintf(buf, sizeof(buf), "PacketSize=%x", MAX_PACKET_LENGTH);
            cc = CPU_GET_CLASS(first_cpu);
            if (cc->gdb_core_xml_file != NULL) {
                pstrcat(buf, sizeof(buf), ";qXfer:features:read+");
            }

            if (strstr(p, "multiprocess+")) {
                s->multiprocess = true;
            }
            pstrcat(buf, sizeof(buf), ";multiprocess+");

            put_packet(s, buf);
            break;
        }
        if (strncmp(p, "Xfer:features:read:", 19) == 0) {
            const char *xml;
            target_ulong total_len;

            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(s, p, &p, process);
            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);
        {
            static const GdbCmdParseEntry gen_query_cmd_desc = {
                .handler = handle_gen_query,
                .cmd = "q",
                .cmd_startswith = 1,
                .schema = "s0"
            };
            cmd_parser = &gen_query_cmd_desc;
        }
            put_packet_binary(s, buf, len + 1, true);
        break;
    case 'Q':
        {
            static const GdbCmdParseEntry gen_set_cmd_desc = {
                .handler = handle_gen_set,
                .cmd = "Q",
                .cmd_startswith = 1,
                .schema = "s0"
            };
            cmd_parser = &gen_set_cmd_desc;
        }
        if (is_query_packet(p, "Attached", ':')) {
            put_packet(s, GDB_ATTACHED);
        break;
        }
        /* Unrecognised 'q' command.  */
        goto unknown_command;

    default:
    unknown_command:
        /* put empty packet */
        buf[0] = '\0';
        put_packet(s, buf);