Skip to content
Snippets Groups Projects
gdbstub.c 89.4 KiB
Newer Older
Fabrice Bellard's avatar
Fabrice Bellard committed
/*
 * gdb server stub
 * This implements a subset of the remote protocol as described in:
 *
 *   https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html
 *
 * Copyright (c) 2003-2005 Fabrice Bellard
Fabrice Bellard's avatar
Fabrice Bellard committed
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * 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, see <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: LGPL-2.0+
Fabrice Bellard's avatar
Fabrice Bellard committed
 */
Peter Maydell's avatar
Peter Maydell committed
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/cutils.h"
#include "trace/trace-root.h"
#ifdef CONFIG_USER_ONLY
#include "qemu.h"
#else
#include "monitor/monitor.h"
#include "chardev/char.h"
#include "chardev/char-fe.h"
#include "sysemu/sysemu.h"
#include "exec/gdbstub.h"
#include "hw/cpu/cluster.h"
#define MAX_PACKET_LENGTH 4096

#include "qemu/sockets.h"
#include "sysemu/hw_accel.h"
#include "sysemu/kvm.h"
#include "sysemu/runstate.h"
#include "hw/semihosting/semihost.h"
#include "exec/exec-all.h"
#include "sysemu/replay.h"
Jan Kiszka's avatar
Jan Kiszka committed
#ifdef CONFIG_USER_ONLY
#define GDB_ATTACHED "0"
#else
#define GDB_ATTACHED "1"
#endif

#ifndef CONFIG_USER_ONLY
static int phy_memory_mode;
#endif

static inline int target_memory_rw_debug(CPUState *cpu, target_ulong addr,
                                         uint8_t *buf, int len, bool is_write)
#ifndef CONFIG_USER_ONLY
    if (phy_memory_mode) {
        if (is_write) {
            cpu_physical_memory_write(addr, buf, len);
        } else {
            cpu_physical_memory_read(addr, buf, len);
        }
        return 0;
    }
#endif

    cc = CPU_GET_CLASS(cpu);
    if (cc->memory_rw_debug) {
        return cc->memory_rw_debug(cpu, addr, buf, len, is_write);
    }
    return cpu_memory_rw_debug(cpu, addr, buf, len, is_write);
/* Return the GDB index for a given vCPU state.
 *
 * For user mode this is simply the thread id. In system mode GDB
 * numbers CPUs from 1 as 0 is reserved as an "any cpu" index.
 */
static inline int cpu_gdb_index(CPUState *cpu)
{
#if defined(CONFIG_USER_ONLY)
    TaskState *ts = (TaskState *) cpu->opaque;
    return ts->ts_tid;
#else
    return cpu->cpu_index + 1;
#endif
}

enum {
    GDB_SIGNAL_0 = 0,
    GDB_SIGNAL_INT = 2,
    GDB_SIGNAL_QUIT = 3,
    GDB_SIGNAL_TRAP = 5,
    GDB_SIGNAL_ABRT = 6,
    GDB_SIGNAL_ALRM = 14,
    GDB_SIGNAL_IO = 23,
    GDB_SIGNAL_XCPU = 24,
    GDB_SIGNAL_UNKNOWN = 143
};

#ifdef CONFIG_USER_ONLY

/* Map target signal numbers to GDB protocol signal numbers and vice
 * versa.  For user emulation's currently supported systems, we can
 * assume most signals are defined.
 */

static int gdb_signal_table[] = {
    0,
    TARGET_SIGHUP,
    TARGET_SIGINT,
    TARGET_SIGQUIT,
    TARGET_SIGILL,
    TARGET_SIGTRAP,
    TARGET_SIGABRT,
    -1, /* SIGEMT */
    TARGET_SIGFPE,
    TARGET_SIGKILL,
    TARGET_SIGBUS,
    TARGET_SIGSEGV,
    TARGET_SIGSYS,
    TARGET_SIGPIPE,
    TARGET_SIGALRM,
    TARGET_SIGTERM,
    TARGET_SIGURG,
    TARGET_SIGSTOP,
    TARGET_SIGTSTP,
    TARGET_SIGCONT,
    TARGET_SIGCHLD,
    TARGET_SIGTTIN,
    TARGET_SIGTTOU,
    TARGET_SIGIO,
    TARGET_SIGXCPU,
    TARGET_SIGXFSZ,
    TARGET_SIGVTALRM,
    TARGET_SIGPROF,
    TARGET_SIGWINCH,
    -1, /* SIGLOST */
    TARGET_SIGUSR1,
    TARGET_SIGUSR2,
Blue Swirl's avatar
Blue Swirl committed
#ifdef TARGET_SIGPWR
Blue Swirl's avatar
Blue Swirl committed
#else
    -1,
#endif
Blue Swirl's avatar
Blue Swirl committed
#ifdef __SIGRTMIN
    __SIGRTMIN + 1,
    __SIGRTMIN + 2,
    __SIGRTMIN + 3,
    __SIGRTMIN + 4,
    __SIGRTMIN + 5,
    __SIGRTMIN + 6,
    __SIGRTMIN + 7,
    __SIGRTMIN + 8,
    __SIGRTMIN + 9,
    __SIGRTMIN + 10,
    __SIGRTMIN + 11,
    __SIGRTMIN + 12,
    __SIGRTMIN + 13,
    __SIGRTMIN + 14,
    __SIGRTMIN + 15,
    __SIGRTMIN + 16,
    __SIGRTMIN + 17,
    __SIGRTMIN + 18,
    __SIGRTMIN + 19,
    __SIGRTMIN + 20,
    __SIGRTMIN + 21,
    __SIGRTMIN + 22,
    __SIGRTMIN + 23,
    __SIGRTMIN + 24,
    __SIGRTMIN + 25,
    __SIGRTMIN + 26,
    __SIGRTMIN + 27,
    __SIGRTMIN + 28,
    __SIGRTMIN + 29,
    __SIGRTMIN + 30,
    __SIGRTMIN + 31,
    -1, /* SIGCANCEL */
    __SIGRTMIN,
    __SIGRTMIN + 32,
    __SIGRTMIN + 33,
    __SIGRTMIN + 34,
    __SIGRTMIN + 35,
    __SIGRTMIN + 36,
    __SIGRTMIN + 37,
    __SIGRTMIN + 38,
    __SIGRTMIN + 39,
    __SIGRTMIN + 40,
    __SIGRTMIN + 41,
    __SIGRTMIN + 42,
    __SIGRTMIN + 43,
    __SIGRTMIN + 44,
    __SIGRTMIN + 45,
    __SIGRTMIN + 46,
    __SIGRTMIN + 47,
    __SIGRTMIN + 48,
    __SIGRTMIN + 49,
    __SIGRTMIN + 50,
    __SIGRTMIN + 51,
    __SIGRTMIN + 52,
    __SIGRTMIN + 53,
    __SIGRTMIN + 54,
    __SIGRTMIN + 55,
    __SIGRTMIN + 56,
    __SIGRTMIN + 57,
    __SIGRTMIN + 58,
    __SIGRTMIN + 59,
    __SIGRTMIN + 60,
    __SIGRTMIN + 61,
    __SIGRTMIN + 62,
    __SIGRTMIN + 63,
    __SIGRTMIN + 64,
    __SIGRTMIN + 65,
    __SIGRTMIN + 66,
    __SIGRTMIN + 67,
    __SIGRTMIN + 68,
    __SIGRTMIN + 69,
    __SIGRTMIN + 70,
    __SIGRTMIN + 71,
    __SIGRTMIN + 72,
    __SIGRTMIN + 73,
    __SIGRTMIN + 74,
    __SIGRTMIN + 75,
    __SIGRTMIN + 76,
    __SIGRTMIN + 77,
    __SIGRTMIN + 78,
    __SIGRTMIN + 79,
    __SIGRTMIN + 80,
    __SIGRTMIN + 81,
    __SIGRTMIN + 82,
    __SIGRTMIN + 83,
    __SIGRTMIN + 84,
    __SIGRTMIN + 85,
    __SIGRTMIN + 86,
    __SIGRTMIN + 87,
    __SIGRTMIN + 88,
    __SIGRTMIN + 89,
    __SIGRTMIN + 90,
    __SIGRTMIN + 91,
    __SIGRTMIN + 92,
    __SIGRTMIN + 93,
    __SIGRTMIN + 94,
    __SIGRTMIN + 95,
    -1, /* SIGINFO */
    -1, /* UNKNOWN */
    -1, /* DEFAULT */
    -1,
    -1,
    -1,
    -1,
    -1,
    -1
Blue Swirl's avatar
Blue Swirl committed
#endif
Fabrice Bellard's avatar
Fabrice Bellard committed
#else
/* 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
};
#endif

#ifdef CONFIG_USER_ONLY
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;
}
Fabrice Bellard's avatar
Fabrice Bellard committed
#endif
static int gdb_signal_to_target (int sig)
{
    if (sig < ARRAY_SIZE (gdb_signal_table))
        return gdb_signal_table[sig];
    else
        return -1;
}

typedef struct GDBRegisterState {
    int base_reg;
    int num_regs;
    gdb_get_reg_cb get_reg;
    gdb_set_reg_cb set_reg;
    const char *xml;
    struct GDBRegisterState *next;
} GDBRegisterState;

typedef struct GDBProcess {
    uint32_t pid;
    bool attached;
} GDBProcess;

    RS_CHKSUM1,
    RS_CHKSUM2,
};
typedef struct GDBState {
    bool init;       /* have we been initialised? */
    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_sum; /* running checksum */
    int line_csum; /* checksum at the end of the packet */
    GByteArray *last_packet;
#ifdef CONFIG_USER_ONLY
    int running_state;
    Chardev *mon_chr;
#endif
    bool multiprocess;
    GDBProcess *processes;
    int process_num;
    char syscall_buf[256];
    gdb_syscall_complete_cb current_syscall_cb;
/* 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;

/* Retrieves flags for single step mode. */
static int get_sstep_flags(void)
{
    /*
     * In replay mode all events written into the log should be replayed.
     * That is why NOIRQ flag is removed in this mode.
     */
    if (replay_mode != REPLAY_MODE_NONE) {
        return SSTEP_ENABLE;
    } else {
        return sstep_flags;
    }
}

static GDBState gdbserver_state;

static void init_gdbserver_state(void)
{
    g_assert(!gdbserver_state.init);
    memset(&gdbserver_state, 0, sizeof(GDBState));
    gdbserver_state.init = true;
    gdbserver_state.str_buf = g_string_new(NULL);
    gdbserver_state.mem_buf = g_byte_array_sized_new(MAX_PACKET_LENGTH);
    gdbserver_state.last_packet = g_byte_array_sized_new(MAX_PACKET_LENGTH + 4);
}

#ifndef CONFIG_USER_ONLY
static void reset_gdbserver_state(void)
{
    g_free(gdbserver_state.processes);
    gdbserver_state.processes = NULL;
    gdbserver_state.process_num = 0;
}
#endif
#ifdef CONFIG_USER_ONLY
static int get_char(void)
Fabrice Bellard's avatar
Fabrice Bellard committed
{
    uint8_t ch;
    int ret;

    for(;;) {
        ret = qemu_recv(gdbserver_state.fd, &ch, 1, 0);
Fabrice Bellard's avatar
Fabrice Bellard committed
        if (ret < 0) {
                gdbserver_state.fd = -1;
            if (errno != EINTR)
Fabrice Bellard's avatar
Fabrice Bellard committed
                return -1;
        } else if (ret == 0) {
            close(gdbserver_state.fd);
            gdbserver_state.fd = -1;
Fabrice Bellard's avatar
Fabrice Bellard committed
            return -1;
        } else {
            break;
        }
    }
    return ch;
}
static enum {
Paul Brook's avatar
Paul Brook committed
    GDB_SYS_UNKNOWN,
    GDB_SYS_ENABLED,
    GDB_SYS_DISABLED,
} gdb_syscall_mode;

/* Decide if either remote gdb syscalls or native file IO should be used. */
Paul Brook's avatar
Paul Brook committed
int use_gdb_syscalls(void)
{
    SemihostingTarget target = semihosting_get_target();
    if (target == SEMIHOSTING_TARGET_NATIVE) {
        /* -semihosting-config target=native */
        return false;
    } else if (target == SEMIHOSTING_TARGET_GDB) {
        /* -semihosting-config target=gdb */
        return true;
    }

    /* -semihosting-config target=auto */
    /* On the first call check if gdb is connected and remember. */
Paul Brook's avatar
Paul Brook committed
    if (gdb_syscall_mode == GDB_SYS_UNKNOWN) {
        gdb_syscall_mode = gdbserver_state.init ?
            GDB_SYS_ENABLED : GDB_SYS_DISABLED;
Paul Brook's avatar
Paul Brook committed
    }
    return gdb_syscall_mode == GDB_SYS_ENABLED;
}

static inline void gdb_continue(void)
Doug Gale's avatar
Doug Gale committed

    gdbserver_state.running_state = 1;
Doug Gale's avatar
Doug Gale committed
    trace_gdbstub_op_continue();
    if (!runstate_needs_reset()) {
Doug Gale's avatar
Doug Gale committed
        trace_gdbstub_op_continue();
/*
 * Resume execution, per CPU actions. For user-mode emulation it's
 * equivalent to gdb_continue.
 */
static int gdb_continue_partial(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') {
Doug Gale's avatar
Doug Gale committed
            trace_gdbstub_op_stepping(cpu->cpu_index);
            cpu_single_step(cpu, sstep_flags);
        }
    }
    gdbserver_state.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':
Doug Gale's avatar
Doug Gale committed
                trace_gdbstub_op_stepping(cpu->cpu_index);
                cpu_single_step(cpu, get_sstep_flags());
                cpu_resume(cpu);
                flag = 1;
                break;
            case 'c':
Doug Gale's avatar
Doug Gale committed
                trace_gdbstub_op_continue_cpu(cpu->cpu_index);
                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(const uint8_t *buf, int len)
#ifdef CONFIG_USER_ONLY
Fabrice Bellard's avatar
Fabrice Bellard committed
    int ret;

    while (len > 0) {
        ret = send(gdbserver_state.fd, buf, len, 0);
Fabrice Bellard's avatar
Fabrice Bellard committed
        if (ret < 0) {
            if (errno != EINTR)
Fabrice Bellard's avatar
Fabrice Bellard committed
                return;
        } else {
            buf += ret;
            len -= ret;
        }
    }
    /* XXX this blocks entire thread. Rewrite to use
     * qemu_chr_fe_write and background I/O callbacks */
    qemu_chr_fe_write_all(&gdbserver_state.chr, buf, len);
Fabrice Bellard's avatar
Fabrice Bellard committed
}

static inline int fromhex(int v)
{
    if (v >= '0' && v <= '9')
        return v - '0';
    else if (v >= 'A' && v <= 'F')
        return v - 'A' + 10;
    else if (v >= 'a' && v <= 'f')
        return v - 'a' + 10;
    else
        return 0;
}

static inline int tohex(int v)
{
    if (v < 10)
        return v + '0';
    else
        return v - 10 + 'a';
}

/* writes 2*len+1 bytes in buf */
static void memtohex(GString *buf, const uint8_t *mem, int len)
Fabrice Bellard's avatar
Fabrice Bellard committed
{
    int i, c;
    for(i = 0; i < len; i++) {
        c = mem[i];
        g_string_append_c(buf, tohex(c >> 4));
        g_string_append_c(buf, tohex(c & 0xf));
    g_string_append_c(buf, '\0');
static void hextomem(GByteArray *mem, const char *buf, int len)
Fabrice Bellard's avatar
Fabrice Bellard committed
{
    int i;

    for(i = 0; i < len; i++) {
        guint8 byte = fromhex(buf[0]) << 4 | fromhex(buf[1]);
        g_byte_array_append(mem, &byte, 1);
Doug Gale's avatar
Doug Gale committed
static void hexdump(const char *buf, int len,
                    void (*trace_fn)(size_t ofs, char const *text))
{
    char line_buffer[3 * 16 + 4 + 16 + 1];

    size_t i;
    for (i = 0; i < len || (i & 0xF); ++i) {
        size_t byte_ofs = i & 15;

        if (byte_ofs == 0) {
            memset(line_buffer, ' ', 3 * 16 + 4 + 16);
            line_buffer[3 * 16 + 4 + 16] = 0;
        }

        size_t col_group = (i >> 2) & 3;
        size_t hex_col = byte_ofs * 3 + col_group;
        size_t txt_col = 3 * 16 + 4 + byte_ofs;

        if (i < len) {
            char value = buf[i];

            line_buffer[hex_col + 0] = tohex((value >> 4) & 0xF);
            line_buffer[hex_col + 1] = tohex((value >> 0) & 0xF);
            line_buffer[txt_col + 0] = (value >= ' ' && value < 127)
                    ? value
                    : '.';
        }

        if (byte_ofs == 0xF)
            trace_fn(i & -16, line_buffer);
    }
}

Fabrice Bellard's avatar
Fabrice Bellard committed
/* return -1 if error, 0 if OK */
static int put_packet_binary(const char *buf, int len, bool dump)
    int csum, i;
Doug Gale's avatar
Doug Gale committed
    if (dump && trace_event_get_state_backends(TRACE_GDBSTUB_IO_BINARYREPLY)) {
        hexdump(buf, len, trace_gdbstub_io_binaryreply);
    }

Fabrice Bellard's avatar
Fabrice Bellard committed
    for(;;) {
        g_byte_array_set_size(gdbserver_state.last_packet, 0);
        g_byte_array_append(gdbserver_state.last_packet,
                            (const uint8_t *) "$", 1);
        g_byte_array_append(gdbserver_state.last_packet,
                            (const uint8_t *) buf, len);
Fabrice Bellard's avatar
Fabrice Bellard committed
        csum = 0;
        for(i = 0; i < len; i++) {
            csum += buf[i];
        }
        footer[0] = '#';
        footer[1] = tohex((csum >> 4) & 0xf);
        footer[2] = tohex((csum) & 0xf);
        g_byte_array_append(gdbserver_state.last_packet, footer, 3);
        put_buffer(gdbserver_state.last_packet->data,
                   gdbserver_state.last_packet->len);
#ifdef CONFIG_USER_ONLY
Fabrice Bellard's avatar
Fabrice Bellard committed
            return -1;
Fabrice Bellard's avatar
Fabrice Bellard committed
            break;
/* return -1 if error, 0 if OK */
static int put_packet(const char *buf)
Doug Gale's avatar
Doug Gale committed
    trace_gdbstub_io_reply(buf);
    return put_packet_binary(buf, strlen(buf), false);
static void put_strbuf(void)
{
    put_packet(gdbserver_state.str_buf->str);
}

/* Encode data using the encoding for 'x' packets.  */
static void memtox(GString *buf, const char *mem, int len)
{
    char c;

    while (len--) {
        c = *(mem++);
        switch (c) {
        case '#': case '$': case '*': case '}':
            g_string_append_c(buf, '}');
            g_string_append_c(buf, c ^ 0x20);
            g_string_append_c(buf, c);
static uint32_t gdb_get_cpu_pid(CPUState *cpu)
    /* TODO: In user mode, we should use the task state PID */
    if (cpu->cluster_index == UNASSIGNED_CLUSTER_INDEX) {
        /* Return the default process' PID */
        int index = gdbserver_state.process_num - 1;
        return gdbserver_state.processes[index].pid;
    return cpu->cluster_index + 1;
static GDBProcess *gdb_get_process(uint32_t pid)
{
    int i;

    if (!pid) {
        /* 0 means any process, we take the first one */
        return &gdbserver_state.processes[0];
    for (i = 0; i < gdbserver_state.process_num; i++) {
        if (gdbserver_state.processes[i].pid == pid) {
            return &gdbserver_state.processes[i];
static GDBProcess *gdb_get_cpu_process(CPUState *cpu)
    return gdb_get_process(gdb_get_cpu_pid(cpu));
}

static CPUState *find_cpu(uint32_t thread_id)
{
    CPUState *cpu;

    CPU_FOREACH(cpu) {
        if (cpu_gdb_index(cpu) == thread_id) {
            return cpu;
        }
    }

    return NULL;
}

static CPUState *get_first_cpu_in_process(GDBProcess *process)
{
    CPUState *cpu;

    CPU_FOREACH(cpu) {
        if (gdb_get_cpu_pid(cpu) == process->pid) {
static CPUState *gdb_next_cpu_in_process(CPUState *cpu)
    uint32_t pid = gdb_get_cpu_pid(cpu);
    cpu = CPU_NEXT(cpu);

    while (cpu) {
        if (gdb_get_cpu_pid(cpu) == pid) {
            break;
        }

        cpu = CPU_NEXT(cpu);
    }

    return cpu;
}

/* Return the cpu following @cpu, while ignoring unattached processes. */
static CPUState *gdb_next_attached_cpu(CPUState *cpu)
{
    cpu = CPU_NEXT(cpu);

    while (cpu) {
        if (gdb_get_cpu_process(cpu)->attached) {
            break;
        }

        cpu = CPU_NEXT(cpu);
    }

    return cpu;
}

/* Return the first attached cpu */
static CPUState *gdb_first_attached_cpu(void)
{
    CPUState *cpu = first_cpu;
    GDBProcess *process = gdb_get_cpu_process(cpu);

    if (!process->attached) {
        return gdb_next_attached_cpu(cpu);
static CPUState *gdb_get_cpu(uint32_t pid, uint32_t tid)
{
    GDBProcess *process;
    CPUState *cpu;

    if (!pid && !tid) {
        /* 0 means any process/thread, we take the first attached one */
        return gdb_first_attached_cpu();
    } else if (pid && !tid) {
        /* any thread in a specific process */
        process = gdb_get_process(pid);

        if (process == NULL) {
            return NULL;
        }

        if (!process->attached) {
            return NULL;
        }

        return get_first_cpu_in_process(process);
    } else {
        /* a specific thread */
        cpu = find_cpu(tid);

        if (cpu == NULL) {
            return NULL;
        }

        process = gdb_get_cpu_process(cpu);

        if (pid && process->pid != pid) {
            return NULL;
        }

        if (!process->attached) {
            return NULL;
        }

        return cpu;
    }
}

static const char *get_feature_xml(const char *p, const char **newp,
                                   GDBProcess *process)
{
    size_t len;
    int i;
    const char *name;
    CPUState *cpu = get_first_cpu_in_process(process);
    CPUClass *cc = CPU_GET_CLASS(cpu);

    len = 0;
    while (p[len] && p[len] != ':')
        len++;
    *newp = p + len;

    name = NULL;
    if (strncmp(p, "target.xml", len) == 0) {
        char *buf = process->target_xml;
        const size_t buf_sz = sizeof(process->target_xml);

        /* Generate the XML description for this CPU.  */
            GDBRegisterState *r;

                    "<?xml version=\"1.0\"?>"
                    "<!DOCTYPE target SYSTEM \"gdb-target.dtd\">"
                    "<target>");
            if (cc->gdb_arch_name) {
                gchar *arch = cc->gdb_arch_name(cpu);
                pstrcat(buf, buf_sz, "<architecture>");
                pstrcat(buf, buf_sz, arch);
                pstrcat(buf, buf_sz, "</architecture>");
            pstrcat(buf, buf_sz, "<xi:include href=\"");
            pstrcat(buf, buf_sz, cc->gdb_core_xml_file);
            pstrcat(buf, buf_sz, "\"/>");
            for (r = cpu->gdb_regs; r; r = r->next) {
                pstrcat(buf, buf_sz, "<xi:include href=\"");
                pstrcat(buf, buf_sz, r->xml);
                pstrcat(buf, buf_sz, "\"/>");
            pstrcat(buf, buf_sz, "</target>");
    if (cc->gdb_get_dynamic_xml) {
        char *xmlname = g_strndup(p, len);
        const char *xml = cc->gdb_get_dynamic_xml(cpu, xmlname);

        g_free(xmlname);
        if (xml) {
            return 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;
}
static int gdb_read_register(CPUState *cpu, GByteArray *buf, int reg)
    CPUClass *cc = CPU_GET_CLASS(cpu);
    CPUArchState *env = cpu->env_ptr;
    GDBRegisterState *r;
    if (reg < cc->gdb_num_core_regs) {
        return cc->gdb_read_register(cpu, buf, reg);
    for (r = cpu->gdb_regs; r; r = r->next) {
        if (r->base_reg <= reg && reg < r->base_reg + r->num_regs) {
            return r->get_reg(env, buf, reg - r->base_reg);
static int gdb_write_register(CPUState *cpu, uint8_t *mem_buf, int reg)
    CPUClass *cc = CPU_GET_CLASS(cpu);
    CPUArchState *env = cpu->env_ptr;
    GDBRegisterState *r;
    if (reg < cc->gdb_num_core_regs) {
        return cc->gdb_write_register(cpu, mem_buf, reg);
    for (r = cpu->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);
        }
    }
Fabrice Bellard's avatar
Fabrice Bellard committed
    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 *cpu,
                              gdb_get_reg_cb get_reg, gdb_set_reg_cb set_reg,
                              int num_regs, const char *xml, int g_pos)
Fabrice Bellard's avatar
Fabrice Bellard committed
{
    GDBRegisterState *s;
    GDBRegisterState **p;

    while (*p) {
        /* Check for duplicates.  */
        if (strcmp((*p)->xml, xml) == 0)
            return;
        p = &(*p)->next;
    }
Stefan Weil's avatar
Stefan Weil committed

    s = g_new0(GDBRegisterState, 1);
Stefan Weil's avatar
Stefan Weil committed
    s->num_regs = num_regs;
    s->get_reg = get_reg;
    s->set_reg = set_reg;
    s->xml = xml;

    /* Add to end of list.  */
    *p = s;
    if (g_pos) {
        if (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;