/*******************************************************************************
 * Copyright (c) 2016-2022 Xilinx, Inc. and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v1.0 which accompany this distribution.
 * The Eclipse Public License is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 * You may elect to redistribute this code under either of these licenses.
 *
 * Contributors:
 *     Xilinx - initial API and implementation
 *******************************************************************************/


#include <tcf/config.h>

#if !defined(ENABLE_GdbRemoteSerialProtocol)
#  define ENABLE_GdbRemoteSerialProtocol 0
#endif

#if ENABLE_GdbRemoteSerialProtocol

#include <assert.h>

#include <tcf/framework/errors.h>
#include <tcf/framework/exceptions.h>
#include <tcf/framework/myalloc.h>
#include <tcf/framework/mdep-inet.h>
#include <tcf/framework/asyncreq.h>
#include <tcf/framework/context.h>
#include <tcf/framework/trace.h>
#include <tcf/framework/link.h>
#include <tcf/framework/json.h>
#include <tcf/services/runctrl.h>
#include <tcf/services/registers.h>
#include <tcf/services/breakpoints.h>
#include <tcf/services/memorymap.h>

#include <machine/i386/tcf/cpu-regs-gdb.h>
#include <machine/x86_64/tcf/cpu-regs-gdb.h>
#include <machine/arm/tcf/cpu-regs-gdb.h>
#include <machine/a64/tcf/cpu-regs-gdb.h>
#include <machine/powerpc/tcf/cpu-regs-gdb.h>
#include <machine/ppc64/tcf/cpu-regs-gdb.h>
#include <machine/microblaze/tcf/cpu-regs-gdb.h>
#include <machine/microblaze64/tcf/cpu-regs-gdb.h>
#include <machine/riscv/tcf/cpu-regs-gdb.h>
#include <machine/riscv64/tcf/cpu-regs-gdb.h>

#include <tcf/main/gdb-rsp.h>

/*
(gdb) set remotetimeout 1000
(gdb) target extended-remote localhost:3000
*/

#ifndef DEBUG_RSP
#  define DEBUG_RSP 0
#endif

#define ID_ALL ~0u

typedef struct GdbServer {
    LINK link_a2s;
    LINK link_s2c;
    int disposed;
    AsyncReqInfo req;
    char port[32];
    char isa[32];
} GdbServer;

typedef struct GdbClient {
    LINK link_s2c;
    LINK link_c2p;
    size_t buf_max;
    uint8_t * buf;
    AsyncReqInfo req;
    GdbServer * server;
    ClientConnection connection;
    int closed;

    /* Command packet */
    char * cmd_buf;
    unsigned cmd_pos;
    unsigned cmd_max;
    unsigned cmd_end;
    int cmd_esc;

    /* Response packet */
    char * res_buf;
    unsigned res_pos;
    unsigned res_max;
    unsigned xfer_range_offs;
    unsigned xfer_range_size;

    unsigned start_timer;
    unsigned process_id_cnt;
    unsigned cur_c_pid;
    unsigned cur_c_tid;
    unsigned cur_g_pid;
    unsigned cur_g_tid;
    int no_ack_mode;
    int multiprocess;
    int swbreak;
    int hwbreak;
    int extended;
    int stopped;
    int waiting;
} GdbClient;

typedef struct GdbProcess {
    LINK link_c2p;
    LINK link_p2t;
    LINK link_p2b;
    GdbClient * client;
    unsigned pid;
    Context * ctx;
    unsigned thread_id_cnt;
    int attached;
} GdbProcess;

typedef struct GdbBreakpoint {
    LINK link_p2b;
    unsigned type;
    unsigned kind;
    uint64_t addr;
    GdbProcess * process;
    BreakpointInfo * bp;
} GdbBreakpoint;

typedef struct GdbThread {
    LINK link_p2t;
    GdbProcess * process;
    unsigned tid;
    Context * ctx;
    RegisterDefinition ** regs_nm_map;
    unsigned regs_nm_map_index_mask;
    int locked;
    GdbBreakpoint * bp_arr;
    unsigned bp_cnt;
    unsigned bp_max;
} GdbThread;

typedef struct GdbRegister {
    unsigned regnum;
    char name[256];
    unsigned bits;
    int id;
} GdbRegister;

typedef struct MonitorCommand {
    const char * name;
    void (*func)(GdbClient *, const char *);
} MonitorCommand;

#define link_a2s(x) ((GdbServer *)((char *)(x) - offsetof(GdbServer, link_a2s)))
#define link_s2c(x) ((GdbClient *)((char *)(x) - offsetof(GdbClient, link_s2c)))
#define link_c2p(x) ((GdbProcess *)((char *)(x) - offsetof(GdbProcess, link_c2p)))
#define link_p2t(x) ((GdbThread *)((char *)(x) - offsetof(GdbThread, link_p2t)))
#define link_p2b(x) ((GdbBreakpoint *)((char *)(x) - offsetof(GdbBreakpoint, link_p2b)))

#define client2gdb(c)  ((GdbClient *)((char *)(c) - offsetof(GdbClient, connection)))

static int ini_done = 0;
static LINK link_a2s;

static GdbProcess * add_process(GdbClient * c, Context * ctx) {
    GdbProcess * p = (GdbProcess *)loc_alloc_zero(sizeof(GdbProcess));
    assert(ctx->mem == ctx);
    p->client = c;
    p->pid = ++c->process_id_cnt;
    p->ctx = ctx;
    list_init(&p->link_p2t);
    list_init(&p->link_p2b);
    list_add_last(&p->link_c2p, &c->link_c2p);
    return p;
}

static GdbProcess * find_process_pid(GdbClient * c, unsigned pid) {
    LINK * l;
    for (l = c->link_c2p.next; l != &c->link_c2p; l = l->next) {
        GdbProcess * p = link_c2p(l);
        if (p->pid == pid) return p;
    }
    return NULL;
}

static GdbProcess * find_process_ctx(GdbClient * c, Context * ctx) {
    LINK * l;
    for (l = c->link_c2p.next; l != &c->link_c2p; l = l->next) {
        GdbProcess * p = link_c2p(l);
        if (p->ctx == ctx) return p;
    }
    return NULL;
}

static void add_thread(GdbClient * c, Context * ctx) {
    GdbThread * t = (GdbThread *)loc_alloc_zero(sizeof(GdbThread));
    t->process = find_process_ctx(c, context_get_group(ctx, CONTEXT_GROUP_PROCESS));
    t->tid = ++t->process->thread_id_cnt;
    t->ctx = ctx;
    list_add_last(&t->link_p2t, &t->process->link_p2t);
    if (c->stopped) {
        t->locked = 1;
        run_ctrl_ctx_lock(ctx);
        if (suspend_debug_context(ctx) < 0) {
            char * name = ctx->name;
            if (name == NULL) name = ctx->id;
            trace(LOG_ALWAYS, "GDB Server: cannot suspend context %s: %s", name, errno_to_str(errno));
        }
    }
}

static GdbThread * find_thread(GdbClient * c, unsigned pid, unsigned tid) {
    GdbProcess * p = find_process_pid(c, pid);
    if (p != NULL) {
        LINK * l;
        for (l = p->link_p2t.next; l != &p->link_p2t; l = l->next) {
            GdbThread * t = link_p2t(l);
            if (t->tid == tid) return t;
        }
    }
    return NULL;
}

static void free_thread(GdbThread * t) {
    if (t->process->client->stopped) {
        assert(t->locked);
        run_ctrl_ctx_unlock(t->ctx);
        t->locked = 0;
    }
    loc_free(t->regs_nm_map);
    list_remove(&t->link_p2t);
    loc_free(t);
}

static void free_breakpoint(GdbBreakpoint * b) {
    if (b->bp != NULL) destroy_eventpoint(b->bp);
    list_remove(&b->link_p2b);
    loc_free(b);
}

static void free_process(GdbProcess * p) {
    while (!list_is_empty(&p->link_p2t)) {
        assert(p->attached);
        free_thread(link_p2t(p->link_p2t.next));
    }
    while (!list_is_empty(&p->link_p2b)) {
        free_breakpoint(link_p2b(p->link_p2b.next));
    }
    list_remove(&p->link_c2p);
    loc_free(p);
}

static const char * get_regs(GdbClient * c) {
    if (strcmp(c->server->isa, "i386") == 0) return cpu_regs_gdb_i386;
    if (strcmp(c->server->isa, "i486") == 0) return cpu_regs_gdb_i386;
    if (strcmp(c->server->isa, "i586") == 0) return cpu_regs_gdb_i386;
    if (strcmp(c->server->isa, "i686") == 0) return cpu_regs_gdb_i386;
    if (strcmp(c->server->isa, "x86") == 0) return cpu_regs_gdb_i386;
    if (strcmp(c->server->isa, "ia32") == 0) return cpu_regs_gdb_i386;
    if (strcmp(c->server->isa, "x86_64") == 0) return cpu_regs_gdb_x86_64;
    if (strcmp(c->server->isa, "amd64") == 0) return cpu_regs_gdb_x86_64;
    if (strcmp(c->server->isa, "x64") == 0) return cpu_regs_gdb_x86_64;
    if (strcmp(c->server->isa, "arm") == 0) return cpu_regs_gdb_arm;
    if (strcmp(c->server->isa, "a32") == 0) return cpu_regs_gdb_arm;
    if (strcmp(c->server->isa, "arm64") == 0) return cpu_regs_gdb_a64;
    if (strcmp(c->server->isa, "aarch64") == 0) return cpu_regs_gdb_a64;
    if (strcmp(c->server->isa, "a64") == 0) return cpu_regs_gdb_a64;
    if (strcmp(c->server->isa, "ppc") == 0) return cpu_regs_gdb_powerpc;
    if (strcmp(c->server->isa, "ppc32") == 0) return cpu_regs_gdb_powerpc;
    if (strcmp(c->server->isa, "power32") == 0) return cpu_regs_gdb_powerpc;
    if (strcmp(c->server->isa, "powerpc") == 0) return cpu_regs_gdb_powerpc;
    if (strcmp(c->server->isa, "ppc64") == 0) return cpu_regs_gdb_ppc64;
    if (strcmp(c->server->isa, "power64") == 0) return cpu_regs_gdb_ppc64;
    if (strcmp(c->server->isa, "microblaze") == 0) return cpu_regs_gdb_microblaze;
    if (strcmp(c->server->isa, "microblaze64") == 0) return cpu_regs_gdb_microblaze64;
    if (strcmp(c->server->isa, "mb") == 0) return cpu_regs_gdb_microblaze;
    if (strcmp(c->server->isa, "mb64") == 0) return cpu_regs_gdb_microblaze64;
    if (strcmp(c->server->isa, "riscv32") == 0) return cpu_regs_gdb_riscv32;
    if (strcmp(c->server->isa, "riscv64") == 0) return cpu_regs_gdb_riscv64;
    if (strcmp(c->server->isa, "rv32") == 0) return cpu_regs_gdb_riscv32;
    if (strcmp(c->server->isa, "rv64") == 0) return cpu_regs_gdb_riscv64;
    set_fmt_errno(ERR_OTHER, "Unsupported ISA %s", c->server->isa);
    return NULL;
}

static int check_process_isa(GdbClient * c, Context * prs) {
    ContextISA isa;
    const char * regs = get_regs(c);
    if (regs != NULL) {
        memset(&isa, 0, sizeof(isa));
        if (context_get_isa(prs, 0, &isa) < 0) {
            trace(LOG_ALWAYS, "Cannot get process ISA: %s", errno_to_str(errno));
            return 0;
        }
        if (isa.def != NULL) {
            if (strcmp(isa.def, "386") == 0) return regs == cpu_regs_gdb_i386;
            if (strcmp(isa.def, "X86_64") == 0) return regs == cpu_regs_gdb_x86_64;
            if (strcmp(isa.def, "ARM") == 0) return regs == cpu_regs_gdb_arm;
            if (strcmp(isa.def, "Thumb") == 0) return regs == cpu_regs_gdb_arm;
            if (strcmp(isa.def, "ThumbEE") == 0) return regs == cpu_regs_gdb_arm;
            if (strcmp(isa.def, "Jazelle") == 0) return regs == cpu_regs_gdb_arm;
            if (strcmp(isa.def, "A64") == 0) return regs == cpu_regs_gdb_a64;
            if (strcmp(isa.def, "PPC") == 0) return regs == cpu_regs_gdb_powerpc;
            if (strcmp(isa.def, "PPC64") == 0) return regs == cpu_regs_gdb_ppc64;
            if (strcmp(isa.def, "MicroBlaze") == 0) return regs == cpu_regs_gdb_microblaze;
            if (strcmp(isa.def, "MicroBlaze64") == 0) return regs == cpu_regs_gdb_microblaze64;
            if (strcmp(isa.def, "Riscv32") == 0) return regs == cpu_regs_gdb_riscv32;
            if (strcmp(isa.def, "Riscv64") == 0) return regs == cpu_regs_gdb_riscv64;
        }
    }
    return 0;
}

static unsigned reg_name_hash(const char * name) {
    unsigned h = 5381;
    while (*name) h = ((h << 5) + h) + *name++;
    return h;
}

static RegisterDefinition * find_register_by_name(GdbThread * t, const char * name) {
    RegisterDefinition ** map = t->regs_nm_map;
    unsigned n = reg_name_hash(name) & t->regs_nm_map_index_mask;
    while (map[n] != NULL) {
        if (strcmp(map[n]->name, name) == 0) return map[n];
        n = (n + 1) & t->regs_nm_map_index_mask;
    }
    return NULL;
}

static RegisterDefinition * find_register(GdbThread * t, GdbRegister * r) {
    RegisterDefinition * res = NULL;

    if (r->id >= 0) {
        RegisterDefinition * def = get_reg_definitions(t->ctx);
        if (def == NULL) return NULL;
        while (def->name != NULL) {
            if (def->dwarf_id == r->id) return def;
            def++;
        }
        return NULL;
    }
    if (t->regs_nm_map == NULL) {
        unsigned map_len = 0;
        unsigned map_len_p2 = 1;
        RegisterDefinition ** map = NULL;
        RegisterDefinition * def = get_reg_definitions(t->ctx);
        if (def == NULL) return NULL;
        while (def->name != NULL) {
            map_len++;
            def++;
        }
        if (map_len == 0) return NULL;
        while (map_len_p2 < map_len * 3) map_len_p2 <<= 2;
        map = (RegisterDefinition **)loc_alloc_zero(sizeof(RegisterDefinition *) * map_len_p2);
        t->regs_nm_map_index_mask = map_len_p2 - 1;
        def = get_reg_definitions(t->ctx);
        while (def->name != NULL) {
            unsigned h = reg_name_hash(def->name) & t->regs_nm_map_index_mask;
            while (map[h] != NULL) h = (h + 1) & t->regs_nm_map_index_mask;
            map[h] = def;
            def++;
        }
        t->regs_nm_map = map;
    }
    res = find_register_by_name(t, r->name);
    if (res == NULL) {
        const char * regs = get_regs(t->process->client);
        /* Try alternative register names */
        if (regs == cpu_regs_gdb_i386 || regs == cpu_regs_gdb_x86_64) {
            static const char * alt_names[] = {
                "fctrl", "control", "fpcr", "cwd", NULL,
                "fstat", "status", "fpsr", "swd", NULL,
                "ftag",  "tag", "fptag", "twd", NULL,
                "fiseg", "fcs", NULL,
                "fioff", "fip", NULL,
                "foseg", "fos", NULL,
                "fooff", "foo", NULL,
                NULL
            };
            unsigned i = 0;
            for (; alt_names[i] != NULL && res == NULL; i++) {
                int b = strcmp(alt_names[i++], r->name) == 0;
                for (; alt_names[i] != NULL && res == NULL; i++) {
                    if (b) res = find_register_by_name(t, alt_names[i]);
                }
            }
        }
    }
    return res;
}

static int open_server(const char * port) {
    int err = 0;
    int sock = -1;
    struct addrinfo hints;
    struct addrinfo * reslist = NULL;
    struct addrinfo * res = NULL;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = PF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    err = loc_getaddrinfo(NULL, port, &hints, &reslist);
    if (err) {
        set_gai_errno(err);
        return -1;
    }

    for (res = reslist; res != NULL; res = res->ai_next) {
        const int i = 1;
        sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        if (sock < 0) continue;

        if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&i, sizeof(i)) < 0) err = errno;
        if (!err && bind(sock, res->ai_addr, res->ai_addrlen)) err = errno;
        if (!err && listen(sock, 4)) err = errno;
        if (!err) break;

        closesocket(sock);
        sock = -1;
    }

    freeaddrinfo(reslist);
    return sock;
}

static void dispose_server(GdbServer * s) {
    list_remove(&s->link_a2s);
    closesocket(s->req.u.acc.sock);
    s->req.u.acc.sock = -1;
    s->disposed = 1;
    if (list_is_empty(&s->link_s2c)) {
        loc_free(s);
    }
}

static void lock_threads(GdbClient * c) {
    LINK * l;
    assert(!c->closed);
    if (c->stopped) return;
    for (l = c->link_c2p.next; l != &c->link_c2p; l = l->next) {
        LINK * m;
        GdbProcess * p = link_c2p(l);
        for (m = p->link_p2t.next; m != &p->link_p2t; m = m->next) {
            GdbThread * t = link_p2t(m);
            Context * ctx = t->ctx;
            assert(!t->locked);
            assert(!t->ctx->exited);
            run_ctrl_ctx_lock(ctx);
            if (suspend_debug_context(ctx) < 0) {
                char * name = ctx->name;
                if (name == NULL) name = ctx->id;
                trace(LOG_ALWAYS, "GDB Server: cannot suspend context %s: %s", name, errno_to_str(errno));
            }
            t->locked = 1;
        }
    }
    c->stopped = 1;
}

static void unlock_threads(GdbClient * c) {
    LINK * l;
    if (!c->stopped) return;
    for (l = c->link_c2p.next; l != &c->link_c2p; l = l->next) {
        LINK * m;
        GdbProcess * p = link_c2p(l);
        for (m = p->link_p2t.next; m != &p->link_p2t; m = m->next) {
            GdbThread * t = link_p2t(m);
            Context * ctx = t->ctx;
            assert(t->locked);
            assert(!t->ctx->exited);
            run_ctrl_ctx_unlock(ctx);
            t->locked = 0;
            t->bp_cnt = 0;
        }
    }
    c->stopped = 0;
}

static void attach_process(GdbProcess * p) {
    GdbClient * c = p->client;
    LINK * l;
    if (p->attached) return;
    p->attached = 1;
    for (l = context_root.next; l != &context_root; l = l->next) {
        Context * ctx = ctxl2ctxp(l);
        if (!ctx->exited && context_has_state(ctx) && context_get_group(ctx, CONTEXT_GROUP_PROCESS) == p->ctx) {
            add_thread(c, ctx);
        }
    }
}

static void detach_process(GdbProcess * p) {
    if (!p->attached) return;
    while (!list_is_empty(&p->link_p2t)) {
        free_thread(link_p2t(p->link_p2t.next));
    }
    p->attached = 0;
}

static int is_all_intercepted(GdbClient * c) {
    LINK * l, * m;
    for (l = c->link_c2p.next; l != &c->link_c2p; l = l->next) {
        GdbProcess * p = link_c2p(l);
        for (m = p->link_p2t.next; m != &p->link_p2t; m = m->next) {
            GdbThread * t = link_p2t(m);
            assert(p->attached);
            assert(!t->ctx->exited);
            assert(context_has_state(t->ctx));
            if (!is_intercepted(t->ctx)) return 0;
        }
    }
    return 1;
}

static void start_client(void * args) {
    GdbClient * c = (GdbClient *)args;

    if (c->start_timer > 10 || (c->stopped && is_all_intercepted(c))) {
        if (c->stopped && !is_all_intercepted(c)) {
            LINK * l;
            c->cur_g_pid = 0;
            c->cur_g_tid = 0;
            for (l = c->link_c2p.next; l != &c->link_c2p; l = l->next) {
                GdbProcess * p = link_c2p(l);
                detach_process(p);
            }
        }
        c->req.u.sio.rval = 0;
        async_req_post(&c->req);
        return;
    }

    if (!c->stopped) {
        LINK * l;
        /* Select initial debug target */
        for (l = context_root.next; l != &context_root; l = l->next) {
            Context * ctx = ctxl2ctxp(l);
            if (!ctx->exited && context_has_state(ctx)) {
                Context * prs = context_get_group(ctx, CONTEXT_GROUP_PROCESS);
                GdbProcess * p = find_process_ctx(c, prs);
                if (p != NULL) {
                    attach_process(p);
                    lock_threads(c);
                    break;
                }
            }
        }
    }

    post_event_with_delay(start_client, args, 500000);
    c->start_timer++;
}

static void close_client(GdbClient * c) {
    if (!c->closed) {
        c->closed = 1;
        unlock_threads(c);
        closesocket(c->req.u.sio.sock);
        notify_client_disconnected(&c->connection);
    }
}

static void dispose_connection(ClientConnection * cc) {
    GdbClient * c = client2gdb(cc);
    GdbServer * s = c->server;

    assert(c->closed);
    while (!list_is_empty(&c->link_c2p)) {
        free_process(link_c2p(c->link_c2p.next));
    }
    list_remove(&c->link_s2c);
    loc_free(c->cmd_buf);
    loc_free(c->buf);
    loc_free(c);

    if (s->disposed && list_is_empty(&s->link_s2c)) {
        loc_free(s);
    }
}

static char hex_digit(unsigned d) {
    assert(d < 0x10);
    if (d < 10) return (char)('0' + d);
    /* Note: don't use capital 'E' - GDB can take it as error reply */
    return (char)('a' + d - 10);
}

static void add_res_ch_no_esc(GdbClient * c, char ch) {
    if (c->res_pos >= c->res_max) {
        c->res_max = c->res_max == 0 ? 0x1000 : c->res_max * 2;
        c->res_buf = (char *)loc_realloc(c->res_buf, c->res_max);
    }
    c->res_buf[c->res_pos++] = ch;
}

static void add_res_ch(GdbClient * c, char ch) {
    switch (ch) {
    case '}':
    case '$':
    case '#':
    case '*':
        add_res_ch_no_esc(c, '}');
        ch ^= 0x20;
        break;
    }
    add_res_ch_no_esc(c, ch);
}

static void add_res_str(GdbClient * c, const char * s) {
    while (*s != 0) add_res_ch(c, *s++);
}

static void add_res_hex(GdbClient * c, uint64_t n) {
    char s[17];
    unsigned i = sizeof(s);
    s[--i] = 0;
    do {
        unsigned d = n & 0xf;
        s[--i] = hex_digit(d);
        n = n >> 4;
    }
    while (n != 0 && i > 0);
    add_res_str(c, s + i);
}

static void add_res_hex8(GdbClient * c, unsigned n) {
    char s[3];
    unsigned i = sizeof(s);
    s[--i] = 0;
    do {
        unsigned d = n & 0xf;
        s[--i] = hex_digit(d);
        n = n >> 4;
    }
    while (i > 0);
    add_res_str(c, s);
}

static void add_res_hex8_str(GdbClient * c, const char * s) {
    while (*s) add_res_hex8(c, *s++);
}

static void add_res_ptid(GdbClient * c, unsigned pid, unsigned tid) {
    if (c->multiprocess) {
        add_res_ch(c, 'p');
        add_res_hex(c, pid);
        add_res_ch(c, '.');
    }
    add_res_hex(c, tid);
}

static int add_res_target_info(GdbClient * c) {
    const char * regs = get_regs(c);
    if (regs == NULL) return -1;
    add_res_str(c, "l<?xml version=\"1.0\"?>\n");
    add_res_str(c, "<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n");
    add_res_str(c, "<target version=\"1.0\">\n");
    add_res_str(c, regs);
    add_res_str(c, "</target>\n");
    return 0;
}

static int add_res_exec_file(GdbClient * c, unsigned pid) {
    GdbProcess * p = find_process_pid(c, pid);
    if (p != NULL) {
        MemoryMap * client_map;
        MemoryMap * target_map;
        unsigned i;
        if (memory_map_get(p->ctx, &client_map, &target_map) < 0) return -1;
        for (i = 0; i < target_map->region_cnt; i++) {
            MemoryRegion * r = target_map->regions + i;
            if (r->file_name != NULL) {
                add_res_ch(c, 'l');
                add_res_str(c, r->file_name);
                return 0;
            }
        }
    }
    add_res_str(c, "E01");
    return 0;
}

static void add_res_reg_value(GdbClient * c, GdbThread * t, GdbRegister * r) {
    RegisterDefinition * def = NULL;
    unsigned size = (r->bits + 7) / 8;
    void * buf = tmp_alloc_zero(size);
    unsigned i = 0;
    if (t != NULL) def = find_register(t, r);
    if (def != NULL) {
        unsigned rd = def->size < size ? def->size : size;
        if (context_read_reg(t->ctx, def, 0, rd, buf) >= 0) {
            /* Register bytes are transmitted in the target byte order */
            if (def->big_endian) swap_bytes(buf, rd);
            if (t->ctx->big_endian) swap_bytes(buf, size);
        }
        else {
            def = NULL;
        }
    }
    while (i < size) {
        if (def == NULL) {
            add_res_str(c, "xx");
        }
        else {
            add_res_hex8(c, ((uint8_t *)buf)[i]);
        }
        i++;
    }
}

static void add_res_stop_reason(GdbClient * c) {
    GdbThread * t = find_thread(c, c->cur_g_pid, c->cur_g_tid);
    if (t != NULL) {
        unsigned i;
        add_res_str(c, "T05");
        add_res_str(c, "thread:");
        add_res_ptid(c, c->cur_g_pid, c->cur_g_tid);
        add_res_ch(c, ';');
        for (i = 0; i < t->bp_cnt; i++) {
            GdbBreakpoint * bp = t->bp_arr + i;
            switch (bp->type) {
            case 0:
                if (c->swbreak) add_res_str(c, "swbreak:;");
                break;
            case 1:
                if (c->hwbreak) add_res_str(c, "hwbreak:;");
                break;
            case 2:
            case 3:
            case 4:
                if (bp->type == 3) add_res_ch(c, 'r');
                if (bp->type == 4) add_res_ch(c, 'a');
                add_res_str(c, "watch:");
                add_res_hex(c, bp->addr);
                add_res_ch(c, ';');
                break;
            }
        }
    }
    else {
        add_res_str(c, "W00");
    }
}

static int send_res(GdbClient * c) {
    unsigned i;
    unsigned char sum = 0;
    assert(c->res_pos > 0);
    assert(c->res_buf[0] == '$');
    for (i = 1; i < c->res_pos; i++) {
        sum += (unsigned char)c->res_buf[i];
    }
    add_res_ch_no_esc(c, '#');
    add_res_hex8(c, sum);
#if DEBUG_RSP
    printf("GDB <- %.*s\n", c->res_pos, c->res_buf);
#endif
    return send(c->req.u.sio.sock, c->res_buf, c->res_pos, 0);
}

static char * get_cmd_word(GdbClient * c, char ** p) {
    char * s = *p;
    char * e = s;
    char * w = NULL;
    while (e < c->cmd_buf + c->cmd_end) {
        if (*e == ':') break;
        if (*e == ';') break;
        if (*e == ',') break;
        e++;
    }
    w = (char *)tmp_alloc_zero(e - s + 1);
    memcpy(w, s, e - s);
    *p = e;
    return w;
}

static uint8_t get_cmd_uint8(GdbClient * c, char ** p) {
    char * s = *p;
    uint8_t n = 0;
    while (s < c->cmd_buf + c->cmd_end && s < *p + 2) {
        char ch = *s;
        if (ch >= '0' && ch <= '9') n = (n << 4) + (ch - '0');
        else if (ch >= 'A' && ch <= 'F') n = (n << 4) + (ch - 'A' + 10);
        else if (ch >= 'a' && ch <= 'f') n = (n << 4) + (ch - 'a' + 10);
        else break;
        s++;
    }
    *p = s;
    return n;
}

static unsigned get_cmd_uint(GdbClient * c, char ** p) {
    char * s = *p;
    unsigned n = 0;
    while (s < c->cmd_buf + c->cmd_end) {
        char ch = *s;
        if (ch >= '0' && ch <= '9') n = (n << 4) + (ch - '0');
        else if (ch >= 'A' && ch <= 'F') n = (n << 4) + (ch - 'A' + 10);
        else if (ch >= 'a' && ch <= 'f') n = (n << 4) + (ch - 'a' + 10);
        else break;
        s++;
    }
    *p = s;
    return n;
}

static uint64_t get_cmd_uint64(GdbClient * c, char ** p) {
    char * s = *p;
    uint64_t n = 0;
    while (s < c->cmd_buf + c->cmd_end && s < *p + 16) {
        char ch = *s;
        if (ch >= '0' && ch <= '9') n = (n << 4) + (ch - '0');
        else if (ch >= 'A' && ch <= 'F') n = (n << 4) + (ch - 'A' + 10);
        else if (ch >= 'a' && ch <= 'f') n = (n << 4) + (ch - 'a' + 10);
        else break;
        s++;
    }
    *p = s;
    return n;
}

static void get_cmd_ptid(GdbClient * c, char ** pp, unsigned * res_pid, unsigned * res_tid) {
    char * s = *pp;
    unsigned pid = 0;
    unsigned tid = 0;
    int neg_pid = 0;
    int neg_tid = 0;
    if (*s == 'p') {
        s++;
        if (*s == '-') {
            neg_pid = 1;
            s++;
        }
        pid = get_cmd_uint(c, &s);
    }
    if (*s == '.') s++;
    if (*s == '-') {
        neg_tid = 1;
        s++;
    }
    tid = get_cmd_uint(c, &s);
    /*
        ID is a positive number with target-specific interpretation formatted as a big-endian hex string,
        or literal '-1' to indicate all processes or threads (respectively),
        or '0' to indicate an arbitrary process (keep current selection if any)
    */
    if (neg_pid) {
        pid = ID_ALL;
    }
    else if (pid == 0) {
        if (find_process_pid(c, *res_pid)) {
            pid = *res_pid;
        }
        else {
            LINK * l;
            for (l = c->link_c2p.next; l != &c->link_c2p; l = l->next) {
                GdbProcess * p = link_c2p(l);
                if (p->attached) {
                    pid = p->pid;
                    break;
                }
            }
        }
    }
    if (neg_tid || pid == ID_ALL) {
        tid = ID_ALL;
    }
    else if (tid == 0) {
        if (find_thread(c, pid, *res_tid)) {
            tid = *res_tid;
        }
        else {
            GdbProcess * p = find_process_pid(c, pid);
            if (p != NULL && !list_is_empty(&p->link_p2t)) {
                tid = link_p2t(p->link_p2t.next)->tid;
            }
        }
    }
    *pp = s;
    *res_pid = pid;
    *res_tid = tid;
}

static void get_xfer_range(GdbClient * c, char ** p) {
    c->xfer_range_offs = get_cmd_uint(c, p);
    if (**p != ',') return;
    (*p)++;
    c->xfer_range_size = get_cmd_uint(c, p);
}

static int read_reg_attributes(const char * p, unsigned n, GdbRegister * r) {
    const char * p0 = p;
    memset(r, 0, sizeof(GdbRegister));
    r->regnum = n;
    r->id = -1;
    while (*p == ' ') p++;
    if (strncmp(p, "<reg ", 5) != 0) return 0;
    p += 5;
    while (*p) {
        if (*p == '\n') break;
        if (p[0] == '=' && (p[1] == '"' || p[1] == '\'')) {
            char q = p[1];
            const char * n0 = p;
            const char * n1 = p;
            const char * v0 = p + 2;
            const char * v1 = p + 2;
            while (*v1 != 0 && *v1 != q) v1++;
            while (n0 > p0 && *(n0 - 1) != ' ') n0--;
            if (n1 - n0 == 4 && strncmp(n0, "name", 4) == 0) {
                size_t l = v1 - v0 + 1;
                if (l > sizeof(r->name)) l = sizeof(r->name);
                strlcpy(r->name, v0, l);
            }
            if (n1 - n0 == 7 && strncmp(n0, "bitsize", 7) == 0) {
                r->bits = (unsigned)atoi(v0);
            }
            if (n1 - n0 == 6 && strncmp(n0, "regnum", 6) == 0) {
                r->regnum = (unsigned)atoi(v0);
            }
            if (n1 - n0 == 2 && strncmp(n0, "id", 2) == 0) {
                r->id = atoi(v0);
            }
            if (*v1 != q) break;
            p = v1;
        }
        p++;
    }
    return r->name[0] && r->bits > 0;
}

static void breakpoint_cb(Context * ctx, void * args) {
    GdbBreakpoint * b = (GdbBreakpoint *)args;
    GdbProcess * p = b->process;
    LINK * l;
    for (l = p->link_p2t.next; l != &p->link_p2t; l = l->next) {
        GdbThread * t = link_p2t(l);
        if (t->ctx == ctx) {
            if (t->bp_cnt >= t->bp_max) {
                t->bp_max += 8;
                t->bp_arr = (GdbBreakpoint *)loc_realloc(t->bp_arr, t->bp_max * sizeof(GdbBreakpoint));
            }
            t->bp_arr[t->bp_cnt++] = *b;
            ctx->pending_intercept = 1;
        }
    }
}

static void monitor_ps(GdbClient * c, const char * args) {
    LINK * l;
    unsigned cnt = 0;
    for (l = c->link_c2p.next; l != &c->link_c2p; l = l->next) {
        char * m = NULL;
        GdbProcess * p = link_c2p(l);
        if (context_has_state(p->ctx)) {
            const char * state = get_context_state_name(p->ctx);
            m = tmp_printf("%u: %s (%s)\n", (unsigned)p->pid, p->ctx->name ? p->ctx->name : p->ctx->id, state);
        }
        else {
            m = tmp_printf("%u: %s\n", (unsigned)p->pid, p->ctx->name ? p->ctx->name : p->ctx->id);
        }
        add_res_hex8_str(c, m);
        cnt++;
    }
    if (cnt == 0) add_res_hex8_str(c, "No debug targets found\n");
}

static void monitor_info(GdbClient * c, const char * args) {
    unsigned pid = 0;
    char * s = (char *)args;
    while (*s == ' ') s++;
    pid = (unsigned)strtol(s, &s, 10);
    while (*s == ' ') s++;
    if (*s == 0 && pid != 0) {
        GdbProcess * prs = find_process_pid(c, pid);
        if (prs != NULL) {
            Context * ctx = prs->ctx;
            add_res_hex8_str(c, tmp_printf("Target %u properties:\n", pid));
            for (;;) {
                add_res_hex8_str(c, tmp_printf(" ID        : \"%s\"\n", ctx->id));
                if (ctx->parent != NULL) add_res_hex8_str(c, tmp_printf(" ParentID  : \"%s\"\n", ctx->parent->id));
                if (ctx->name != NULL) add_res_hex8_str(c, tmp_printf(" Name      : \"%s\"\n", ctx->name));
                add_res_hex8_str(c, tmp_printf(" WordSize  : %u\n", context_word_size(ctx)));
                add_res_hex8_str(c, tmp_printf(" BigEndian : %d\n", ctx->big_endian));
#if ENABLE_ContextExtraProperties
                {
                    /* Back-end context properties */
                    int cnt = 0;
                    const char ** names = NULL;
                    const char ** values = NULL;
                    if (context_get_extra_properties(ctx, &names, &values, &cnt) == 0) {
                        while (cnt > 0) {
                            if (*values != NULL) add_res_hex8_str(c, tmp_printf(" %-10s: %s\n", *names, *values));
                            names++;
                            values++;
                            cnt--;
                        }
                    }
                }
#endif
                if (ctx->parent == NULL) break;
                add_res_hex8_str(c, "Parent properties:\n");
                ctx = ctx->parent;
            }
            return;
        }
    }
    add_res_hex8_str(c, "Invalid target ID.\n");
    add_res_hex8_str(c, "Available targets:\n");
    monitor_ps(c, "");
}

static void monitor_help(GdbClient * c, const char * args) {
    add_res_hex8_str(c, "Usage: monitor <command> [<arguments>]\n");
    add_res_hex8_str(c, "Commands:\n");
    add_res_hex8_str(c, " ps - list of debug targets\n");
    add_res_hex8_str(c, " info <target ID> - properties of a target\n");
    add_res_hex8_str(c, " help - print this text\n");
}

static MonitorCommand mon_cmds[] = {
    { "ps", monitor_ps },
    { "info", monitor_info },
    { "help", monitor_help },
    { NULL }
};

static int handle_g_command(GdbClient * c) {
    /* Read general registers */
    GdbThread * t = find_thread(c, c->cur_g_pid, c->cur_g_tid);
    const char * regs = get_regs(c);
    const char * p = regs;
    const char * q = regs;
    unsigned regnum = 0;
    if (p == NULL) return -1;
    while (*p) {
        if (*p++ == '\n') {
            GdbRegister r;
            if (read_reg_attributes(q, regnum, &r)) {
                add_res_reg_value(c, t, &r);
                regnum = r.regnum + 1;
            }
            q = p;
        }
    }
    return 0;
}

static int handle_m_command(GdbClient * c) {
    /* Read memory */
    char * s = c->cmd_buf + 2;
    ContextAddress addr = (ContextAddress)get_cmd_uint64(c, &s);
    GdbThread * t = find_thread(c, c->cur_g_pid, c->cur_g_tid);
    void * buf = NULL;
    size_t size = 0;
    if (*s == ',') {
        s++;
        size = (size_t)get_cmd_uint(c, &s);
    }
    buf = tmp_alloc_zero(size);
    if (t == NULL || context_read_mem(t->ctx, addr, buf, size) < 0) {
        add_res_str(c, "E01");
    }
    else {
        unsigned i = 0;
        while (i < size) {
            add_res_hex8(c, ((uint8_t *)buf)[i++]);
        }
    }
    return 0;
}

static int handle_M_command(GdbClient * c) {
    /* Write memory */
    char * s = c->cmd_buf + 2;
    ContextAddress addr = (ContextAddress)get_cmd_uint64(c, &s);
    GdbThread * t = find_thread(c, c->cur_g_pid, c->cur_g_tid);
    void * buf = NULL;
    size_t size = 0;
    if (*s == ',') {
        s++;
        size = (size_t)get_cmd_uint(c, &s);
    }
    buf = tmp_alloc_zero(size);
    if (*s == ':') {
        unsigned i = 0;
        s++;
        while (i < size) {
            ((uint8_t *)buf)[i++] = get_cmd_uint8(c, &s);
        }
    }
    if (t == NULL || context_write_mem(t->ctx, addr, buf, size) < 0) {
        add_res_str(c, "E01");
    }
    else {
        add_res_str(c, "OK");
    }
    return 0;
}

static int handle_p_command(GdbClient * c) {
    /* Read register */
    char * s = c->cmd_buf + 2;
    GdbThread * t = find_thread(c, c->cur_g_pid, c->cur_g_tid);
    unsigned reg = get_cmd_uint(c, &s);
    const char * regs = get_regs(c);
    const char * p = regs;
    const char * q = regs;
    unsigned regnum = 0;
    if (p == NULL) return -1;
    while (*p) {
        if (*p++ == '\n') {
            GdbRegister r;
            if (read_reg_attributes(q, regnum, &r)) {
                if (r.regnum == reg) {
                    add_res_reg_value(c, t, &r);
                    return 0;
                }
                regnum = r.regnum + 1;
            }
            q = p;
        }
    }
    add_res_str(c, "E01");
    return 0;
}

static int handle_P_command(GdbClient * c) {
    /* Write register */
    char * s = c->cmd_buf + 2;
    GdbThread * t = find_thread(c, c->cur_g_pid, c->cur_g_tid);
    unsigned reg = get_cmd_uint(c, &s);
    const char * regs = get_regs(c);
    const char * p = regs;
    const char * q = regs;
    unsigned regnum = 0;
    if (p == NULL) return -1;
    while (*p) {
        if (*p++ == '\n') {
            GdbRegister r;
            if (read_reg_attributes(q, regnum, &r)) {
                if (r.regnum == reg) {
                    RegisterDefinition * def = NULL;
                    unsigned size = (r.bits + 7) / 8;
                    void * buf = tmp_alloc_zero(size);
                    if (*s++ == '=') {
                        unsigned i = 0;
                        while (i < size) ((uint8_t *)buf)[i++] = get_cmd_uint8(c, &s);
                    }
                    if (t != NULL) def = find_register(t, &r);
                    if (def != NULL && context_write_reg(t->ctx, def, 0, def->size < size ? def->size : size, buf) == 0) {
                        add_res_str(c, "OK");
                        return 0;
                    }
                    break;
                }
                regnum = r.regnum + 1;
            }
            q = p;
        }
    }
    add_res_str(c, "E01");
    return 0;
}

static int handle_q_command(GdbClient * c) {
    char * s = c->cmd_buf + 2;
    char * w = get_cmd_word(c, &s);
    if (strcmp(w, "Supported") == 0) {
        if (*s++ == ':') {
            while (s < c->cmd_buf + c->cmd_end) {
                char name[256];
                char value[256];
                unsigned i = 0;
                while (s < c->cmd_buf + c->cmd_end) {
                    if (*s == ';' || *s == '+' || *s == '-' || *s == '=') break;
                    if (i < sizeof(name) - 1) name[i++] = *s;
                    s++;
                }
                name[i] = 0;
                if (*s == '+') {
                    if (strcmp(name, "multiprocess") == 0) c->multiprocess = 1;
                    if (strcmp(name, "swbreak") == 0) c->swbreak = 1;
                    if (strcmp(name, "hwbreak") == 0) c->hwbreak = 1;
                    s++;
                }
                else if (*s == '-') {
                    s++;
                }
                else if (*s == '=') {
                    s++;
                    i = 0;
                    while (s < c->cmd_buf + c->cmd_end) {
                        if (*s == ';') break;
                        if (i < sizeof(value) - 1) value[i++] = *s;
                        s++;
                    }
                    value[i] = 0;
                }
                if (*s == ';') s++;
            }
        }
        add_res_str(c, "PacketSize=4000");
        add_res_str(c, ";QStartNoAckMode+");
        add_res_str(c, ";qXfer:features:read+");
        add_res_str(c, ";qXfer:exec-file:read+");
        if (c->multiprocess) add_res_str(c, ";multiprocess+");
        if (c->swbreak) add_res_str(c, ";swbreak+");
        if (c->hwbreak) add_res_str(c, ";hwbreak+");
#if 0
        add_res_str(c, ";QNonStop+;QAgent+");
        add_res_str(c, ";QPassSignals+;QProgramSignals+");
        add_res_str(c, ";ConditionalBreakpoints+;BreakpointCommands+");
        add_res_str(c, ";qXfer:osdata:read+;qXfer:threads:read+");
        add_res_str(c, ";qXfer:libraries-svr4:read+");
        add_res_str(c, ";qXfer:auxv:read+");
        add_res_str(c, ";qXfer:spu:read+;qXfer:spu:write+");
        add_res_str(c, ";qXfer:siginfo:read+;qXfer:siginfo:write+");
#endif
        return 0;
    }
    if (strcmp(w, "Attached") == 0) {
        add_res_str(c, "1");
        return 0;
    }
    if (strcmp(w, "TStatus") == 0) {
        add_res_str(c, "T0");
        return 0;
    }
    if (strcmp(w, "C") == 0) {
        add_res_str(c, "QC");
        add_res_ptid(c, c->cur_g_pid, c->cur_g_tid);
        return 0;
    }
    if (strcmp(w, "Xfer") == 0 && *s++ == ':') {
        w = get_cmd_word(c, &s);
        if (strcmp(w, "features") == 0 && *s++ == ':') {
            w = get_cmd_word(c, &s);
            if (strcmp(w, "read") == 0 && *s++ == ':') {
                w = get_cmd_word(c, &s);
                if (strcmp(w, "target.xml") == 0) {
                    if (add_res_target_info(c) < 0) return -1;
                    if (*s++ == ':') get_xfer_range(c, &s);
                    return 0;
                }
            }
        }
        if (strcmp(w, "exec-file") == 0 && *s++ == ':') {
            w = get_cmd_word(c, &s);
            if (strcmp(w, "read") == 0 && *s++ == ':') {
                int pid = get_cmd_uint(c, &s);
                if (pid == 0) pid = c->cur_g_pid;
                if (add_res_exec_file(c, pid) < 0) return -1;
                if (*s++ == ':') get_xfer_range(c, &s);
                return 0;
            }
        }
        return 0;
    }
    if (strcmp(w, "fThreadInfo") == 0) {
        LINK * l;
        unsigned cnt = 0;
        for (l = c->link_c2p.next; l != &c->link_c2p; l = l->next) {
            GdbProcess * p = link_c2p(l);
            LINK * m;
            for (m = p->link_p2t.next; m != &p->link_p2t; m = m->next) {
                GdbThread * t = link_p2t(m);
                if (cnt == 0) add_res_ch(c, 'm');
                else add_res_ch(c, ',');
                add_res_ptid(c, p->pid, t->tid);
                cnt++;
            }
        }
        if (cnt == 0) add_res_ch(c, 'l');
        return 0;
    }
    if (strcmp(w, "sThreadInfo") == 0) {
        add_res_ch(c, 'l');
        return 0;
    }
    if (strcmp(w, "ThreadExtraInfo") == 0) {
        const char * m = NULL;
        if (*s++ == ',') {
            unsigned pid = 0;
            unsigned tid = 0;
            GdbThread * t = NULL;
            get_cmd_ptid(c, &s, &pid, &tid);
            t = find_thread(c, pid, tid);
            if (t != NULL) {
                Context * ctx = t->ctx;
                const char * state = get_context_state_name(ctx);
                m = ctx->name;
                if (m == NULL) m = ctx->id;
                if (state != NULL && *state) {
                    m = tmp_strdup2(m, ": ");
                    m = tmp_strdup2(m, state);
                }
            }
        }
        if (m == NULL) m = "Invalid ID";
        add_res_hex8_str(c, m);
        return 0;
    }
    if (strcmp(w, "Rcmd") == 0) {
        if (*s++ == ',') {
            unsigned i = 0;
            unsigned max = (c->cmd_buf + c->cmd_end - s) / 2 + 2;
            char * cmd = (char *)tmp_alloc_zero(max);
            MonitorCommand * mon_cmd = NULL;
            unsigned mon_cnt = 0;
            unsigned cmd_pos = 0;
            while (i < max - 1) {
                char ch = get_cmd_uint8(c, &s);
                if (ch == 0) break;
                cmd[i++] = ch;
            }
            for (i = 0;; i++) {
                unsigned j;
                MonitorCommand * m = mon_cmds + i;
                if (m->name == NULL) break;
                for (j = 0;; j++) {
                    if (cmd[j] != m->name[j] || m->name[j] == 0) {
                        if (j > 0 && (cmd[j] == ' ' || cmd[j] == 0)) {
                            mon_cmd = m;
                            cmd_pos = j;
                            mon_cnt++;
                        }
                        break;
                    }
                }
            }
            if (mon_cnt > 1) {
                add_res_hex8_str(c, "Ambiguous command.\n");
            }
            else if (mon_cmd == NULL) {
                add_res_hex8_str(c, "Invalid command.\n");
                monitor_help(c, "");
            }
            else {
                while (cmd[cmd_pos] == ' ') cmd_pos++;
                mon_cmd->func(c, cmd + cmd_pos);
            }
            return 0;
        }
        add_res_str(c, "E02");
    }
    return 0;
}

static int handle_Q_command(GdbClient * c) {
    char * s = c->cmd_buf + 2;
    char * w = get_cmd_word(c, &s);
    if (strcmp(w, "StartNoAckMode") == 0) {
        add_res_str(c, "OK");
        c->no_ack_mode = 1;
        return 0;
    }
    return 0;
}

static int handle_H_command(GdbClient * c) {
    if (c->cmd_end > 2) {
        char * s = c->cmd_buf + 3;
        if (c->cmd_buf[2] == 'c') {
            get_cmd_ptid(c, &s, &c->cur_c_pid, &c->cur_c_tid);
        }
        else {
            get_cmd_ptid(c, &s, &c->cur_g_pid, &c->cur_g_tid);
        }
    }
    add_res_str(c, "OK");
    return 0;
}

static int handle_qm_command(GdbClient * c) {
    GdbThread * t = find_thread(c, c->cur_g_pid, c->cur_g_tid);
    if (t != NULL) {
        if (is_intercepted(t->ctx)) {
            add_res_stop_reason(c);
        }
        else {
            suspend_debug_context(t->ctx);
            c->waiting = 1;
        }
        return 0;
    }
    add_res_str(c, "W00");
    return 0;
}

static int handle_v_command(GdbClient * c) {
    char * s = c->cmd_buf + 2;
    char * w = get_cmd_word(c, &s);
    if (strcmp(w, "Attach") == 0) {
        if (*s++ == ';') {
            unsigned pid = get_cmd_uint(c, &s);
            GdbProcess * p = find_process_pid(c, pid);
            if (p != NULL) {
                if (!p->attached) attach_process(p);
                if (list_is_empty(&p->link_p2t)) {
                    add_res_str(c, "N");
                }
                else {
                    GdbThread * t = link_p2t(p->link_p2t.next);
                    c->cur_g_pid = p->pid;
                    c->cur_g_tid = t->tid;
                    lock_threads(c);
                    if (is_all_intercepted(c)) {
                        add_res_stop_reason(c);
                    }
                    else {
                        c->waiting = 1;
                    }
                }
                return 0;
            }
        }
        add_res_str(c, "E01");
        return 0;
    }
    if (strcmp(w, "Cont?") == 0) {
        add_res_str(c, "vCont;c;C;s;S;t;r");
        return 0;
    }
    if (strcmp(w, "Cont") == 0) {
        while (*s++ == ';') {
            char mode = *s++;
            unsigned sig = 0;
            ContextAddress range_fr = 0;
            ContextAddress range_to = 0;
            switch (mode) {
            case 'C':
            case 'S':
                sig = get_cmd_uint8(c, &s);
                break;
            case 'r':
                range_fr = (ContextAddress)get_cmd_uint64(c, &s);
                if (*s == ',') {
                    s++;
                    range_to = (ContextAddress)get_cmd_uint64(c, &s);
                }
                break;
            }
            if (*s == ':') {
                s++;
                get_cmd_ptid(c, &s, &c->cur_g_pid, &c->cur_g_tid);
            }
            if (c->cur_g_tid == ID_ALL) {
                GdbProcess * p = find_process_pid(c, c->cur_g_pid);
                switch (mode) {
                case 'c':
                    continue_debug_context(p->ctx, NULL, RM_RESUME, 1, 0, 0);
                    break;
                case 't':
                    suspend_debug_context(p->ctx);
                    break;
                }
            }
            else {
                GdbThread * t = find_thread(c, c->cur_g_pid, c->cur_g_tid);
                if (t != NULL) {
                    sigset_clear(&t->ctx->pending_signals);
                    switch (mode) {
                    case 'c':
                        continue_debug_context(t->ctx, NULL, RM_RESUME, 1, 0, 0);
                        break;
                    case 'C':
                        sigset_set(&t->ctx->pending_signals, sig, 1);
                        continue_debug_context(t->ctx, NULL, RM_RESUME, 1, 0, 0);
                        break;
                    case 's':
                        continue_debug_context(t->ctx, NULL, RM_STEP_INTO, 1, 0, 0);
                        break;
                    case 'S':
                        sigset_set(&t->ctx->pending_signals, sig, 1);
                        continue_debug_context(t->ctx, NULL, RM_STEP_INTO, 1, 0, 0);
                        break;
                    case 'r':
                        continue_debug_context(t->ctx, NULL, RM_STEP_INTO_RANGE, 1, range_fr, range_to);
                        break;
                    case 't':
                        suspend_debug_context(t->ctx);
                        break;
                    }
                }
            }
        }
        if (list_is_empty(&c->link_c2p)) {
            add_res_str(c, "N");
        }
        else {
            unlock_threads(c);
            c->waiting = 1;
        }
        return 0;
    }
    return 0;
}

static int handle_T_command(GdbClient * c) {
    char * s = c->cmd_buf + 2;
    unsigned pid = 0;
    unsigned tid = 0;
    GdbThread * t = NULL;
    get_cmd_ptid(c, &s, &pid, &tid);
    t = find_thread(c, pid, tid);
    if (t != NULL) {
        add_res_str(c, "OK");
    }
    else {
        add_res_str(c, "E01");
    }
    return 0;
}

static int handle_D_command(GdbClient * c) {
    char * s = c->cmd_buf + 2;
    if (*s++ == ';') {
        unsigned pid = get_cmd_uint(c, &s);
        GdbProcess * p = find_process_pid(c, pid);
        if (p != NULL) {
            if (c->cur_g_pid == p->pid) {
                c->cur_g_pid = 0;
                c->cur_g_tid = 0;
            }
            /* According to the GDB manual: Detaching the process continues its execution. */
            if (!p->ctx->exited) continue_debug_context(p->ctx, NULL, RM_RESUME, 1, 0, 0);
            detach_process(p);
            add_res_str(c, "OK");
            return 0;
        }
    }
    add_res_str(c, "E01");
    return 0;
}

static int handle_Z_command(GdbClient * c) {
    char * s = c->cmd_buf + 2;
    unsigned type = get_cmd_uint(c, &s);
    if (*s++ == ',') {
        uint64_t addr = get_cmd_uint64(c, &s);
        if (*s++ == ',') {
            unsigned kind = get_cmd_uint(c, &s);
            GdbProcess * p = find_process_pid(c, c->cur_g_pid);
            unsigned size = kind;
            unsigned mode = 0;

            if (type < 2) {
                size = 1;
                mode = CTX_BP_ACCESS_INSTRUCTION;
            }
            else if (type == 2) {
                mode = CTX_BP_ACCESS_DATA_WRITE;
            }
            else if (type == 3) {
                mode = CTX_BP_ACCESS_DATA_READ;
            }
            else if (type == 4) {
                mode = CTX_BP_ACCESS_DATA_READ | CTX_BP_ACCESS_DATA_WRITE;
            }
            else {
                /* not supported */
                return 0;
            }

            if (p != NULL) {
                GdbBreakpoint * b = (GdbBreakpoint *)loc_alloc_zero(sizeof(GdbBreakpoint));
                static const char * attr_list[] = {
                    BREAKPOINT_ENABLED,
                    BREAKPOINT_ACCESSMODE,
                    BREAKPOINT_CONTEXTIDS,
                    BREAKPOINT_LOCATION,
                    BREAKPOINT_SIZE,
                    BREAKPOINT_SERVICE
                };
                BreakpointAttribute * attrs = NULL;
                BreakpointAttribute ** ref = &attrs;
                char str[32];
                unsigned i;

                for (i = 0; i < sizeof(attr_list) / sizeof(char *); i++) {
                    ByteArrayOutputStream buf;
                    BreakpointAttribute * attr = (BreakpointAttribute *)loc_alloc_zero(sizeof(BreakpointAttribute));
                    OutputStream * out = create_byte_array_output_stream(&buf);
                    attr->name = loc_strdup(attr_list[i]);
                    switch (i) {
                    case 0:
                        json_write_boolean(out, 1);
                        break;
                    case 1:
                        json_write_long(out, mode);
                        break;
                    case 2:
                        write_stream(out, '[');
                        json_write_string(out, p->ctx->id);
                        write_stream(out, ']');
                        break;
                    case 3:
                        snprintf(str, sizeof(str), "%#" PRIx64, (uint64_t)addr);
                        json_write_string(out, str);
                        break;
                    case 4:
                        json_write_long(out, size);
                        break;
                    case 5:
                        json_write_string(out, "GDB-RSP");
                        break;
                    }
                    write_stream(out, 0);
                    get_byte_array_output_stream_data(&buf, &attr->value, NULL);
                    *ref = attr;
                    ref = &attr->next;
                }
                b->type = type;
                b->addr = addr;
                b->kind = kind;
                b->process = p;
                list_add_last(&b->link_p2b, &p->link_p2b);
                b->bp = create_eventpoint_ext(attrs, NULL, breakpoint_cb, b);
                add_res_str(c, "OK");
                return 0;
            }
        }
    }
    add_res_str(c, "E01");
    return 0;
}

static int handle_z_command(GdbClient * c) {
    char * s = c->cmd_buf + 2;
    unsigned type = get_cmd_uint(c, &s);
    if (*s++ == ',') {
        uint64_t addr = get_cmd_uint64(c, &s);
        if (*s++ == ',') {
            unsigned kind = get_cmd_uint(c, &s);
            GdbProcess * p = find_process_pid(c, c->cur_g_pid);
            if (p != NULL) {
                LINK * l;
                for (l = p->link_p2b.next; l != &p->link_p2b; l = l->next) {
                    GdbBreakpoint * b = link_p2b(l);
                    if (b->type == type && b->addr == addr && b->kind == kind) {
                        free_breakpoint(b);
                        add_res_str(c, "OK");
                        return 0;
                    }
                }
            }
        }
    }
    add_res_str(c, "E01");
    return 0;
}

static int handle_command(GdbClient * c) {
    if (c->cmd_end < 2) return 0;
    switch (c->cmd_buf[1]) {
    case 'A': add_res_str(c, "E01"); return 0;
    case 'b': return 0;
    case 'B': return 0;
    case 'd': return 0;
    case 'g': return handle_g_command(c);
    case 'm': return handle_m_command(c);
    case 'M': return handle_M_command(c);
    case 'p': return handle_p_command(c);
    case 'P': return handle_P_command(c);
    case 'q': return handle_q_command(c);
    case 'Q': return handle_Q_command(c);
    case 'H': return handle_H_command(c);
    case '!': c->extended = 1; add_res_str(c, "OK"); return 0;
    case '?': return handle_qm_command(c);
    case 'v': return handle_v_command(c);
    case 'T': return handle_T_command(c);
    case 'D': return handle_D_command(c);
    case 'Z': return handle_Z_command(c);
    case 'z': return handle_z_command(c);
    }
    return 0;
}

static int read_packet(GdbClient * c, unsigned len) {
    unsigned char * b = c->buf;
    unsigned char * e = b + len;

    while (b < e) {
        char ch = *b++;
        if (c->cmd_pos > 0 || ch == '$') {
            if (ch == 0x7d && !c->cmd_esc) {
                c->cmd_esc = 1;
                continue;
            }
            if (ch == '#') {
                c->cmd_end = c->cmd_pos;
            }
            if (c->cmd_esc) {
                c->cmd_esc = 0;
                ch = (char)(ch ^ 0x20);
            }
            if (c->cmd_pos >= c->cmd_max) {
                c->cmd_max = c->cmd_max == 0 ? 0x100 : c->cmd_max * 2;
                c->cmd_buf = (char *)loc_realloc(c->cmd_buf, c->cmd_max);
            }
            c->cmd_buf[c->cmd_pos++] = ch;
            if (c->cmd_end > 0 && c->cmd_pos == c->cmd_end + 3) {
                if (!c->no_ack_mode && send(c->req.u.sio.sock, "+", 1, 0) < 0) return -1;
#if DEBUG_RSP
                printf("GDB -> %.*s\n", c->cmd_pos, c->cmd_buf);
#endif
                c->waiting = 0;
                lock_threads(c);
                c->res_pos = 0;
                c->xfer_range_offs = 0;
                c->xfer_range_size = 0;
                add_res_ch_no_esc(c, '$');
                if (handle_command(c) < 0) return -1;
                if (!c->waiting || c->res_pos > 1) {
                    c->waiting = 0;
                    if (c->xfer_range_offs > 0 || (c->xfer_range_size > 0 && c->xfer_range_size + 2 < c->res_pos)) {
                        unsigned offs = c->xfer_range_offs + 2; /* First bytes are "$l" */
                        unsigned size = c->xfer_range_size;
                        assert(c->res_buf[0] == '$');
                        assert(c->res_buf[1] == 'l');
                        if (offs >= c->res_pos) {
                            offs = 2;
                            size = 0;
                        }
                        else if (offs + size > c->res_pos) {
                            size = c->res_pos - offs;
                        }
                        else {
                            c->res_buf[1] = 'm';
                        }
                        memmove(c->res_buf + 2, c->res_buf + offs, size);
                        c->res_pos = size + 2;
                    }
                    if (send_res(c) < 0) return -1;
                }
                c->cmd_pos = 0;
                c->cmd_end = 0;
                c->cmd_esc = 0;
            }
        }
        else if (!c->no_ack_mode && ch == '-' && c->res_pos > 0) {
            if (send(c->req.u.sio.sock, c->res_buf, c->res_pos, 0) < 0) return -1;
        }
        else if (ch == 3) {
            LINK * l;
            for (l = c->link_c2p.next; l != &c->link_c2p; l = l->next) {
                LINK * m;
                GdbProcess * p = link_c2p(l);
                for (m = p->link_p2t.next; m != &p->link_p2t; m = m->next) {
                    GdbThread * t = link_p2t(m);
                    Context * ctx = t->ctx;
                    if (suspend_debug_context(ctx) < 0) {
                        char * name = ctx->name;
                        if (name == NULL) name = ctx->id;
                        trace(LOG_ALWAYS, "GDB Server: cannot suspend context %s: %s", name, errno_to_str(errno));
                    }
                }
            }
        }
    }

    return 0;
}

static void recv_done(void * args) {
    GdbClient * c = (GdbClient *)((AsyncReqInfo *)args)->client_data;
    if (c->req.error) {
        trace(LOG_ALWAYS, "GDB Server connection closed: %s", errno_to_str(c->req.error));
        close_client(c);
    }
    else if (c->req.u.sio.rval == 0) {
        close_client(c);
    }
    else {
        if (read_packet(c, c->req.u.sio.rval) < 0) {
            trace(LOG_ALWAYS, "GDB Server connection terminated: %s", errno_to_str(errno));
            close_client(c);
            return;
        }
        c->req.u.sio.rval = 0;
        async_req_post(&c->req);
    }
}

static void accept_done(void * args) {
    GdbServer * s = (GdbServer *)((AsyncReqInfo *)args)->client_data;
    GdbClient * c = NULL;
    const int opt = 1;
    int sock = 0;
    LINK * l;

    if (s->req.error) {
        trace(LOG_ALWAYS, "GDB Server terminated: %s", errno_to_str(s->req.error));
        dispose_server(s);
        return;
    }

    sock = s->req.u.acc.rval;
    c = (GdbClient *)loc_alloc_zero(sizeof(GdbClient));
    c->server = s;
    c->buf_max = 0x1000;
    c->buf = (uint8_t *)loc_alloc(c->buf_max);
    c->req.type = AsyncReqRecv;
    c->req.client_data = c;
    c->req.done = recv_done;
    c->req.u.sio.sock = sock;
    c->req.u.sio.bufp = c->buf;
    c->req.u.sio.bufsz = c->buf_max;
    c->req.u.sio.flags = 0;
    list_init(&c->link_c2p);
    list_add_last(&c->link_s2c, &s->link_s2c);
    c->connection.dispose = dispose_connection;

    for (l = context_root.next; l != &context_root; l = l->next) {
        Context * ctx = ctxl2ctxp(l);
        if (!ctx->exited && context_has_state(ctx)) {
            Context * prs = context_get_group(ctx, CONTEXT_GROUP_PROCESS);
            if (check_process_isa(c, prs)) {
                GdbProcess * p = find_process_ctx(c, prs);
                if (p == NULL) p = add_process(c, prs);
            }
        }
    }

    notify_client_connected(&c->connection);
    async_req_post(&s->req);

    if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&opt, sizeof(opt)) < 0) {
        trace(LOG_ALWAYS, "GDB Server setsockopt failed: %s", errno_to_str(errno));
        close_client(c);
        return;
    }

    if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&opt, sizeof(opt)) < 0) {
        trace(LOG_ALWAYS, "GDB Server setsockopt failed: %s", errno_to_str(errno));
        close_client(c);
        return;
    }

    post_event(start_client, c);
}

static void event_context_created(Context * ctx, void * args) {
    if (context_has_state(ctx)) {
        LINK * l, * n;
        Context * prs = context_get_group(ctx, CONTEXT_GROUP_PROCESS);
        for (l = link_a2s.next; l != &link_a2s; l = l->next) {
            GdbServer * s = link_a2s(l);
            for (n = s->link_s2c.next; n != &s->link_s2c; n = n->next) {
                GdbClient * c = link_s2c(n);
                if (check_process_isa(c, prs)) {
                    GdbProcess * p = find_process_ctx(c, prs);
                    if (p == NULL) p = add_process(c, prs);
                    else if (p->attached) add_thread(c, ctx);
                }
            }
        }
    }
}

static void event_context_exited(Context * ctx, void * args) {
    LINK * l, *n, *m, *o;
    for (l = link_a2s.next; l != &link_a2s; l = l->next) {
        GdbServer * s = link_a2s(l);
        for (n = s->link_s2c.next; n != &s->link_s2c; n = n->next) {
            GdbClient * c = link_s2c(n);
            for (m = c->link_c2p.next; m != &c->link_c2p; m = m->next) {
                GdbProcess * p = link_c2p(m);
                if (p->ctx == ctx) {
                    if (c->waiting) {
                        lock_threads(c);
                        if (is_all_intercepted(c)) {
                            c->res_pos = 0;
                            c->waiting = 0;
                            add_res_ch_no_esc(c, '$');
                            add_res_str(c, "W00;process:");
                            add_res_hex(c, p->pid);
                            if (send_res(c) < 0) trace(LOG_ALWAYS, "GDB Server send error: %s", errno_to_str(errno));
                        }
                    }
                    free_process(p);
                    break;
                }
                for (o = p->link_p2t.next; o != &p->link_p2t; o = o->next) {
                    GdbThread * t = link_p2t(o);
                    if (t->ctx == ctx) {
                        free_thread(t);
                        break;
                    }
                }
            }
        }
    }
}

static void event_register_definitions_changed(void * args) {
    LINK * l, * n, * m, * o;
    for (l = link_a2s.next; l != &link_a2s; l = l->next) {
        GdbServer * s = link_a2s(l);
        for (n = s->link_s2c.next; n != &s->link_s2c; n = n->next) {
            GdbClient * c = link_s2c(n);
            for (m = c->link_c2p.next; m != &c->link_c2p; m = m->next) {
                GdbProcess * p = link_c2p(m);
                for (o = p->link_p2t.next; o != &p->link_p2t; o = o->next) {
                    GdbThread * t = link_p2t(o);
                    loc_free(t->regs_nm_map);
                    t->regs_nm_map = NULL;
                }
            }
        }
    }
}

static void event_context_intercepted(Context * ctx, void * args) {
    LINK * l, *n, *m, *o;
    for (l = link_a2s.next; l != &link_a2s; l = l->next) {
        GdbServer * s = link_a2s(l);
        for (n = s->link_s2c.next; n != &s->link_s2c; n = n->next) {
            GdbClient * c = link_s2c(n);
            if (c->waiting) {
                for (m = c->link_c2p.next; m != &c->link_c2p; m = m->next) {
                    GdbProcess * p = link_c2p(m);
                    for (o = p->link_p2t.next; o != &p->link_p2t; o = o->next) {
                        GdbThread * t = link_p2t(o);
                        if (t->ctx == ctx) {
                            if (!c->stopped) {
                                c->cur_g_pid = p->pid;
                                c->cur_g_tid = t->tid;
                                lock_threads(c);
                            }
                        }
                    }
                }
                if (c->stopped && is_all_intercepted(c)) {
                    c->res_pos = 0;
                    c->waiting = 0;
                    add_res_ch_no_esc(c, '$');
                    add_res_stop_reason(c);
                    if (send_res(c) < 0) trace(LOG_ALWAYS, "GDB Server send error: %s", errno_to_str(errno));
                }
            }
        }
    }
}

static ContextEventListener context_listener = {
    event_context_created,
    event_context_exited,
    NULL,
    NULL,
    NULL,
    NULL
};

static RegistersEventListener registers_listener = {
    NULL,
    event_register_definitions_changed
};

static RunControlEventListener run_ctrl_listener = {
    event_context_intercepted,
    NULL,
};

int ini_gdb_rsp(const char * conf) {
    GdbServer * s = NULL;
    char port[64];
    const char * isa = NULL;
    const char * sep = strchr(conf, ':');
    int sock = -1;
    strlcpy(port, conf, sizeof(port));
    if (sep != NULL) {
        isa = sep + 1;
        if ((size_t)(sep - conf) < sizeof(port)) port[sep - conf] = 0;
    }
    else {
#if defined(_AMD64_) || defined(__x86_64__)
        isa = "amd64";
#elif defined(__aarch64__)
        isa = "aarch64";
#elif defined(__arm__)
        isa = "arm";
#elif defined(__powerpc64__)
        isa = "powerpc64";
#elif defined(__powerpc__)
        isa = "powerpc";
#elif defined(__MICROBLAZE__)
        isa = "microblaze";
#elif defined(__MICROBLAZE64__)
        isa = "microblaze64";
#elif defined(__riscv) && __riscv_xlen == 32
        isa = "riscv32";
#elif defined(__riscv) && __riscv_xlen == 64
        isa = "riscv64";
#elif defined(__riscv) && __riscv_xlen == 128
        isa = "riscv128";
#else
        isa = "i386";
#endif
    }
    if (ini_done) {
        LINK * l;
        for (l = link_a2s.next; l != &link_a2s; l = l->next) {
            GdbServer * g = link_a2s(l);
            if (strcmp(g->port, port) == 0) {
                if (strcmp(g->isa, isa) == 0) return 0;
                set_fmt_errno(ERR_OTHER, "Port is used by '%s:%s' server", g->port, g->isa);
                return -1;
            }
        }
    }
    sock = open_server(port);
    if (sock < 0) return -1;
    if (!ini_done) {
        list_init(&link_a2s);
        add_context_event_listener(&context_listener, NULL);
        add_registers_event_listener(&registers_listener, NULL);
        add_run_control_event_listener(&run_ctrl_listener, NULL);
        ini_done = 1;
    }
    s = (GdbServer *)loc_alloc_zero(sizeof(GdbServer));
    list_init(&s->link_s2c);
    list_add_last(&s->link_a2s, &link_a2s);
    s->req.type = AsyncReqAccept;
    s->req.client_data = s;
    s->req.done = accept_done;
    s->req.u.acc.sock = sock;
    s->req.u.acc.rval = 0;
    strlcpy(s->port, port, sizeof(s->port));
    strlcpy(s->isa, isa, sizeof(s->isa));
    async_req_post(&s->req);
    return 0;
}

#endif /* ENABLE_GdbRemoteSerialProtocol */
