| /******************************************************************************* |
| * Copyright (c) 2016-2020 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_ANY ~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 client; |
| 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, client))) |
| |
| 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(GdbThread * t, GdbRegister * r) { |
| RegisterDefinition ** map = t->regs_nm_map; |
| unsigned n = 0; |
| |
| 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 (map == NULL) { |
| unsigned map_len = 0; |
| unsigned map_len_p2 = 1; |
| 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; |
| } |
| n = reg_name_hash(r->name) & t->regs_nm_map_index_mask; |
| while (map[n] != NULL) { |
| if (strcmp(map[n]->name, r->name) == 0) return map[n]; |
| n = (n + 1) & t->regs_nm_map_index_mask; |
| } |
| return NULL; |
| } |
| |
| 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->client); |
| } |
| } |
| |
| static void dispose_client(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); |
| if (neg_pid) { |
| pid = ID_ANY; |
| } |
| else if (pid == 0) { |
| LINK * l; |
| pid = 0; |
| 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_ANY) { |
| tid = ID_ANY; |
| } |
| else if (tid == 0) { |
| GdbProcess * p = find_process_pid(c, pid); |
| tid = 0; |
| 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_ANY) { |
| 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->client.dispose = dispose_client; |
| |
| 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->client); |
| 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(®isters_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 */ |