TCF Agent: more of GDB Remote Serial Protocol
diff --git a/agent/tcf/main/gdb-rsp.c b/agent/tcf/main/gdb-rsp.c
index 46ee631..9dc75be 100644
--- a/agent/tcf/main/gdb-rsp.c
+++ b/agent/tcf/main/gdb-rsp.c
@@ -42,9 +42,11 @@
 */
 
 #ifndef DEBUG_RSP
-#  define DEBUG_RSP 0
+#  define DEBUG_RSP 1
 #endif
 
+#define ID_ANY ~0u
+
 typedef struct GdbServer {
     LINK link_a2s;
     LINK link_s2c;
@@ -84,9 +86,9 @@
     unsigned cur_g_pid;
     unsigned cur_g_tid;
     int no_ack_mode;
-    int non_stop;
     int extended;
     int stopped;
+    int waiting;
 } GdbClient;
 
 typedef struct GdbProcess {
@@ -186,11 +188,6 @@
     p->ctx = ctx;
     list_init(&p->link_p2t);
     list_add_last(&p->link_c2p, &c->link_c2p);
-    if (!c->non_stop && 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));
-    }
     return p;
 }
 
@@ -357,6 +354,7 @@
 
 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;
@@ -365,6 +363,7 @@
             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;
@@ -387,6 +386,7 @@
             GdbThread * t = link_p2t(m);
             Context * ctx = t->ctx;
             assert(t->locked);
+            assert(!t->ctx->exited);
             run_ctrl_ctx_unlock(ctx);
             t->locked = 0;
         }
@@ -398,13 +398,13 @@
     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);
         }
     }
-    p->attached = 1;
 }
 
 static void detach_process(GdbProcess * p) {
@@ -418,18 +418,16 @@
 static void start_client(void * args) {
     LINK * l;
     unsigned has_state_cnt = 0;
-    unsigned intercepted_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)) {
-            if (is_intercepted(ctx)) intercepted_cnt++;
             has_state_cnt++;
         }
     }
 
-    if (c->start_timer > 10 || (has_state_cnt > 0 && intercepted_cnt == 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) {
@@ -542,18 +540,6 @@
     add_res_hex(c, tid);
 }
 
-static void add_res_checksum(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);
-}
-
 static int add_res_target_info(GdbClient * c) {
     const char * regs = get_regs(c);
     if (regs == NULL) return -1;
@@ -591,6 +577,22 @@
     }
 }
 
+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;
@@ -672,7 +674,10 @@
         s++;
     }
     tid = get_cmd_uint(c, &s);
-    if (neg_pid || pid == 0) {
+    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) {
@@ -683,7 +688,10 @@
             }
         }
     }
-    if (neg_tid || tid == 0) {
+    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)) {
@@ -911,24 +919,10 @@
             t = find_thread(c, pid, tid);
             if (t != NULL) {
                 Context * ctx = t->ctx;
-                const char * name = NULL;
-                const char * state = NULL;
-                if (ctx->name != NULL) {
-                    name = ctx->name;
-                }
-                else {
-                    name = ctx->id;
-                }
-                if (ctx->stopped) {
-                    state = context_suspend_reason(ctx);
-                    if (state == NULL) state = "Suspended";
-                }
-                else {
-                    state = get_context_state_name(ctx);
-                    if (state == NULL) state = "Running";
-                }
-                m = name;
-                if (*state) {
+                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);
                 }
@@ -990,22 +984,6 @@
 static int handle_Q_command(GdbClient * c) {
     char * s = c->cmd_buf + 2;
     char * w = get_cmd_word(c, &s);
-    if (strcmp(w, "NonStop") == 0) {
-        if (s + 2 == c->cmd_buf + c->cmd_end) {
-            if (strncmp(s, ":0", 2) == 0) {
-                add_res_str(c, "OK");
-                c->non_stop = 0;
-                return 0;
-            }
-            if (strncmp(s, ":1", 2) == 0) {
-                add_res_str(c, "OK");
-                c->non_stop = 1;
-                return 0;
-            }
-        }
-        add_res_str(c, "E01");
-        return 0;
-    }
     if (strcmp(w, "StartNoAckMode") == 0) {
         add_res_str(c, "OK");
         c->no_ack_mode = 1;
@@ -1029,25 +1007,18 @@
 }
 
 static int handle_qm_command(GdbClient * c) {
-    if (c->non_stop) {
-#if 0
-        LINK * l;
-        for (l = c->link_c2t.next; l != &c->link_c2t; l = l->next) {
-            GdbThread * t = link_c2t(l);
-            if (is_intercepted(t->ctx)) {
-
-            }
-        }
-#endif
-    }
-    else {
-        GdbThread * t = find_thread(c, c->cur_g_pid, c->cur_g_tid);
-        if (t != NULL) {
+    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");
-            return 0;
         }
-        add_res_str(c, "W00");
+        else {
+            suspend_debug_context(t->ctx);
+            c->waiting = 1;
+        }
+        return 0;
     }
+    add_res_str(c, "W00");
     return 0;
 }
 
@@ -1060,15 +1031,20 @@
             GdbProcess * p = find_process_pid(c, pid);
             if (p != NULL) {
                 if (!p->attached) attach_process(p);
-                if (list_is_empty(&p->link_p2t) || c->non_stop) {
-                    add_res_str(c, "OK");
+                if (list_is_empty(&p->link_p2t)) {
+                    add_res_str(c, "N");
                 }
                 else {
                     GdbThread * t = link_p2t(p->link_p2t.next);
-                    lock_threads(c);
-                    c->cur_g_pid = p->pid;
-                    c->cur_g_tid = t->tid;
-                    add_res_str(c, "S00");
+                    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;
             }
@@ -1076,6 +1052,82 @@
         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;
 }
 
@@ -1160,29 +1212,30 @@
 #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->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;
+                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;
                     }
-                    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;
                 }
-                add_res_checksum(c);
-#if DEBUG_RSP
-                printf("GDB <- %.*s\n", c->res_pos, c->res_buf);
-#endif
-                if (send(c->req.u.sio.sock, c->res_buf, c->res_pos, 0) < 0) return -1;
                 c->cmd_pos = 0;
                 c->cmd_end = 0;
                 c->cmd_esc = 0;
@@ -1266,6 +1319,21 @@
     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;
@@ -1291,6 +1359,17 @@
             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;
                 }
@@ -1324,6 +1403,38 @@
     }
 }
 
+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,
@@ -1338,6 +1449,11 @@
     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];
@@ -1359,6 +1475,7 @@
         context_extension_offset = context_extension(sizeof(LINK));
         add_context_event_listener(&context_listener, NULL);
         add_registers_event_listener(&registers_listener, NULL);
+        add_run_control_event_listener(&run_ctrl_listener, NULL);
         ini_done = 1;
     }
     s = (GdbServer *)loc_alloc_zero(sizeof(GdbServer));
diff --git a/agent/tcf/services/runctrl.c b/agent/tcf/services/runctrl.c
index fadf21b..72402c7 100644
--- a/agent/tcf/services/runctrl.c
+++ b/agent/tcf/services/runctrl.c
@@ -2653,7 +2653,9 @@
 
 const char * get_context_state_name(Context * ctx) {
     ContextExtensionRC * ext = EXT(ctx);
-    return ext->state_name;
+    if (ext->intercepted) return get_suspend_reason(ctx);
+    if (ext->state_name) return ext->state_name;
+    return "Running";
 }
 
 int is_run_ctrl_idle(void) {