| /******************************************************************************* |
| * Copyright (c) 2016-2017 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/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/services/runctrl.h> |
| #include <tcf/services/registers.h> |
| |
| #include <tcf/main/gdb-rsp.h> |
| |
| /* |
| (gdb) set remotetimeout 1000 |
| (gdb) target extended-remote localhost:3000 |
| */ |
| |
| #ifndef DEBUG_RSP |
| # define DEBUG_RSP 1 |
| #endif |
| |
| #define ID_ANY ~0u |
| |
| typedef struct GdbServer { |
| LINK link_a2s; |
| LINK link_s2c; |
| int disposed; |
| AsyncReqInfo req; |
| 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 extended; |
| int stopped; |
| int waiting; |
| } GdbClient; |
| |
| typedef struct GdbProcess { |
| LINK link_c2p; |
| LINK link_p2t; |
| GdbClient * client; |
| unsigned pid; |
| Context * ctx; |
| unsigned thread_id_cnt; |
| int attached; |
| } GdbProcess; |
| |
| typedef struct GdbThread { |
| LINK link_p2t; |
| GdbProcess * process; |
| unsigned tid; |
| Context * ctx; |
| RegisterDefinition ** regs_nm_map; |
| unsigned regs_nm_map_index_mask; |
| int locked; |
| } GdbThread; |
| |
| 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 client2gdb(c) ((GdbClient *)((char *)(c) - offsetof(GdbClient, client))) |
| |
| static size_t context_extension_offset = 0; |
| static int ini_done = 0; |
| static LINK link_a2s; |
| |
| static const char * regs_i386 = |
| " <reg name='eax' bitsize='32' type='int32'/>\n" |
| " <reg name='ecx' bitsize='32' type='int32'/>\n" |
| " <reg name='edx' bitsize='32' type='int32'/>\n" |
| " <reg name='ebx' bitsize='32' type='int32'/>\n" |
| " <reg name='esp' bitsize='32' type='data_ptr'/>\n" |
| " <reg name='ebp' bitsize='32' type='data_ptr'/>\n" |
| " <reg name='esi' bitsize='32' type='int32'/>\n" |
| " <reg name='edi' bitsize='32' type='int32'/>\n" |
| " <reg name='eip' bitsize='32' type='code_ptr'/>\n" |
| " <reg name='eflags' bitsize='32' type='int32'/>\n" |
| " <reg name='cs' bitsize='16' type='int32'/>\n" |
| " <reg name='ss' bitsize='16' type='int32'/>\n" |
| " <reg name='ds' bitsize='16' type='int32'/>\n" |
| " <reg name='es' bitsize='16' type='int32'/>\n" |
| " <reg name='fs' bitsize='16' type='int32'/>\n" |
| " <reg name='gs' bitsize='16' type='int32'/>\n" |
| " <reg name='st0' bitsize='80' type='i387_ext'/>\n" |
| " <reg name='st1' bitsize='80' type='i387_ext'/>\n" |
| " <reg name='st2' bitsize='80' type='i387_ext'/>\n" |
| " <reg name='st3' bitsize='80' type='i387_ext'/>\n" |
| " <reg name='st4' bitsize='80' type='i387_ext'/>\n" |
| " <reg name='st5' bitsize='80' type='i387_ext'/>\n" |
| " <reg name='st6' bitsize='80' type='i387_ext'/>\n" |
| " <reg name='st7' bitsize='80' type='i387_ext'/>\n" |
| " <reg name='fctrl' bitsize='32' type='int' group='float'/>\n" |
| " <reg name='fstat' bitsize='32' type='int' group='float'/>\n" |
| " <reg name='ftag' bitsize='32' type='int' group='float'/>\n" |
| " <reg name='fiseg' bitsize='32' type='int' group='float'/>\n" |
| " <reg name='fioff' bitsize='32' type='int' group='float'/>\n" |
| " <reg name='foseg' bitsize='32' type='int' group='float'/>\n" |
| " <reg name='fooff' bitsize='32' type='int' group='float'/>\n" |
| " <reg name='fop' bitsize='32' type='int' group='float'/>\n"; |
| |
| static const char * regs_arm = |
| " <reg name='r0' bitsize='32' type='uint32'/>\n" |
| " <reg name='r1' bitsize='32' type='uint32'/>\n" |
| " <reg name='r2' bitsize='32' type='uint32'/>\n" |
| " <reg name='r3' bitsize='32' type='uint32'/>\n" |
| " <reg name='r4' bitsize='32' type='uint32'/>\n" |
| " <reg name='r5' bitsize='32' type='uint32'/>\n" |
| " <reg name='r6' bitsize='32' type='uint32'/>\n" |
| " <reg name='r7' bitsize='32' type='uint32'/>\n" |
| " <reg name='r8' bitsize='32' type='uint32'/>\n" |
| " <reg name='r9' bitsize='32' type='uint32'/>\n" |
| " <reg name='r10' bitsize='32' type='uint32'/>\n" |
| " <reg name='r11' bitsize='32' type='uint32'/>\n" |
| " <reg name='r12' bitsize='32' type='uint32'/>\n" |
| " <reg name='sp' bitsize='32' type='data_ptr'/>\n" |
| " <reg name='lr' bitsize='32'/>\n" |
| " <reg name='pc' bitsize='32' type='code_ptr'/>\n" |
| " <reg name='cpsr' bitsize='32' regnum='25'/>\n"; |
| |
| 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_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", errno_to_str(errno)); |
| } |
| } |
| } |
| |
| static GdbThread * find_thread(GdbClient * c, unsigned pid, unsigned tid) { |
| LINK * l; |
| GdbProcess * p = find_process_pid(c, pid); |
| if (p != NULL) { |
| 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_process(GdbProcess * p) { |
| while (!list_is_empty(&p->link_p2t)) { |
| assert(p->attached); |
| free_thread(link_p2t(p->link_p2t.next)); |
| } |
| list_remove(&p->link_c2p); |
| loc_free(p); |
| } |
| |
| static const char * get_regs(GdbClient * c) { |
| if (strcmp(c->server->isa, "i386") == 0) return regs_i386; |
| if (strcmp(c->server->isa, "arm") == 0) return regs_arm; |
| set_fmt_errno(ERR_OTHER, "Unsupported ISA %s", c->server->isa); |
| return NULL; |
| } |
| |
| 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, const char * name) { |
| RegisterDefinition ** map = t->regs_nm_map; |
| unsigned n = 0; |
| |
| 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(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 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", 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; |
| } |
| } |
| 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 void start_client(void * args) { |
| LINK * l; |
| unsigned has_state_cnt = 0; |
| GdbClient * c = (GdbClient *)args; |
| |
| for (l = context_root.next; l != &context_root; l = l->next) { |
| Context * ctx = ctxl2ctxp(l); |
| if (!ctx->exited && context_has_state(ctx)) { |
| has_state_cnt++; |
| } |
| } |
| |
| if (c->start_timer > 10 || has_state_cnt > 0) { |
| |
| /* Select initial debug target */ |
| for (l = context_root.next; l != &context_root; l = l->next) { |
| Context * ctx = ctxl2ctxp(l); |
| Context * prs = context_get_group(ctx, CONTEXT_GROUP_PROCESS); |
| if (!ctx->exited && context_has_state(ctx)) { |
| attach_process(find_process_ctx(c, prs)); |
| lock_threads(c); |
| break; |
| } |
| } |
| |
| c->req.u.sio.rval = 0; |
| async_req_post(&c->req); |
| } |
| else { |
| 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); |
| 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, uint32_t n) { |
| char s[9]; |
| 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_ptid(GdbClient * c, unsigned pid, unsigned tid) { |
| 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, "<architecture>"); |
| add_res_str(c, c->server->isa); |
| add_res_str(c, "</architecture>\n"); |
| add_res_str(c, "<feature name=\"org.gnu.gdb."); |
| add_res_str(c, c->server->isa); |
| add_res_str(c, ".core\">\n"); |
| add_res_str(c, regs); |
| add_res_str(c, "</feature>\n"); |
| add_res_str(c, "</target>\n"); |
| return 0; |
| } |
| |
| static void add_res_reg_value(GdbClient * c, GdbThread * t, const char * name, unsigned bits) { |
| RegisterDefinition * def = NULL; |
| unsigned size = (bits + 7) / 8; |
| void * buf = tmp_alloc_zero(size); |
| unsigned i = 0; |
| if (t != NULL) def = find_register(t, name); |
| if (def != NULL && context_read_reg(t->ctx, def, 0, size, buf) < 0) def = NULL; |
| while (i < size) { |
| if (def == NULL) { |
| add_res_str(c, "xx"); |
| } |
| else { |
| unsigned byte = ((uint8_t *)buf)[i]; |
| add_res_hex8(c, byte); |
| } |
| i++; |
| } |
| } |
| |
| 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 void read_reg_attributes(const char * p, char ** name, unsigned * bits, unsigned * regnum) { |
| const char * p0 = p; |
| *name = NULL; |
| *bits = 0; |
| for (;;) { |
| if (*p == 0) break; |
| 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; |
| *name = (char *)tmp_alloc_zero(l + 1); |
| memcpy(*name, v0, l); |
| } |
| if (n1 - n0 == 7 && strncmp(n0, "bitsize", 7) == 0) { |
| *bits = (unsigned)atoi(v0); |
| } |
| if (n1 - n0 == 6 && strncmp(n0, "regnum", 6) == 0) { |
| *regnum = (unsigned)atoi(v0); |
| } |
| if (*v1 != q) break; |
| p = v1; |
| } |
| p++; |
| } |
| } |
| |
| 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 s[256]; |
| char * m = s; |
| GdbProcess * p = link_c2p(l); |
| snprintf(s, sizeof(s), "%u: %s\n", (unsigned)p->pid, p->ctx->name ? p->ctx->name : p->ctx->id); |
| while (*m) add_res_hex8(c, *m++); |
| cnt++; |
| } |
| if (cnt == 0) { |
| const char * m = "No processes\n"; |
| while (*m) add_res_hex8(c, *m++); |
| } |
| } |
| |
| static MonitorCommand mon_cmds[] = { |
| { "ps", monitor_ps }, |
| { 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 * s = regs; |
| unsigned regnum = 0; |
| if (p == NULL) return -1; |
| while (*p) { |
| if (*p++ == '\n') { |
| char * name = NULL; |
| unsigned bits = 0; |
| read_reg_attributes(s, &name, &bits, ®num); |
| if (name != NULL && bits != 0) { |
| add_res_reg_value(c, t, name, bits); |
| regnum++; |
| } |
| s = 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) { |
| unsigned byte = ((uint8_t *)buf)[i]; |
| add_res_hex8(c, byte); |
| i++; |
| } |
| } |
| 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 * r = regs; |
| unsigned regnum = 0; |
| if (p == NULL) return -1; |
| while (*p) { |
| if (*p++ == '\n') { |
| char * name = NULL; |
| unsigned bits = 0; |
| read_reg_attributes(r, &name, &bits, ®num); |
| if (name != NULL && bits != 0) { |
| if (regnum == reg) { |
| add_res_reg_value(c, t, name, bits); |
| break; |
| } |
| regnum++; |
| } |
| r = p; |
| } |
| } |
| 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) { |
| add_res_str(c, "PacketSize=4000"); |
| add_res_str(c, ";QStartNoAckMode+"); |
| add_res_str(c, ";qXfer:features:read+"); |
| add_res_str(c, ";multiprocess+"); |
| #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 |
| } |
| 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, "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"; |
| while (*m) add_res_hex8(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; |
| const char * res = NULL; |
| 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) { |
| res = "Ambiguous command\n"; |
| } |
| else if (mon_cmd == NULL) { |
| res = "Invalid command\n"; |
| } |
| else { |
| while (cmd[cmd_pos] == ' ') cmd_pos++; |
| mon_cmd->func(c, cmd + cmd_pos); |
| } |
| if (res) { |
| while (*res) add_res_hex8(c, *res++); |
| } |
| 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_str(c, "S00"); |
| } |
| 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); |
| if (is_intercepted(t->ctx)) { |
| c->cur_g_pid = p->pid; |
| c->cur_g_tid = t->tid; |
| add_res_str(c, "S00"); |
| } |
| else { |
| suspend_debug_context(t->ctx); |
| 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) { |
| detach_process(p); |
| 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 '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); |
| } |
| return 0; |
| } |
| |
| static int read_packet(GdbClient * c, unsigned len) { |
| unsigned char * p = c->buf; |
| unsigned char * e = p + len; |
| |
| while (p < e) { |
| char ch = *p++; |
| 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 + 1 > c->res_pos) { |
| unsigned offs = c->xfer_range_offs + 1; /* First byte is '$' */ |
| unsigned size = c->xfer_range_size; |
| if (offs >= c->res_pos) { |
| offs = 1; |
| size = 0; |
| } |
| else if (offs + size > c->res_pos) { |
| size = c->res_pos - offs; |
| } |
| memmove(c->res_buf + 1, c->res_buf + offs, size); |
| c->res_pos = size + 1; |
| } |
| 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; |
| } |
| } |
| |
| 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); |
| 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; |
| } |
| |
| post_event(start_client, c); |
| } |
| |
| 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 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); |
| 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 = t->process->pid; |
| c->cur_g_tid = t->tid; |
| } |
| 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, "S00"); |
| 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[32]; |
| char isa[32]; |
| const char * sep = strchr(conf, ':'); |
| int sock = -1; |
| strlcpy(port, conf, sizeof(port)); |
| if (sep != NULL) { |
| if ((size_t)(sep - conf) < sizeof(port)) port[sep - conf] = 0; |
| strlcpy(isa, sep + 1, sizeof(isa)); |
| } |
| else { |
| strlcpy(isa, "i386", sizeof(isa)); |
| } |
| sock = open_server(port); |
| if (sock < 0) return -1; |
| if (!ini_done) { |
| list_init(&link_a2s); |
| context_extension_offset = context_extension(sizeof(LINK)); |
| 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->isa, isa, sizeof(s->isa)); |
| async_req_post(&s->req); |
| return 0; |
| } |
| |
| #endif /* ENABLE_GdbRemoteSerialProtocol */ |