+static bool gdb_handle_vcont_packet(struct connection *connection, const char *packet, int packet_size)
+{
+ struct gdb_connection *gdb_connection = connection->priv;
+ struct target *target = get_target_from_connection(connection);
+ const char *parse = packet;
+ int retval;
+
+ /* query for vCont supported */
+ if (parse[0] == '?') {
+ if (target->type->step != NULL) {
+ /* gdb doesn't accept c without C and s without S */
+ gdb_put_packet(connection, "vCont;c;C;s;S", 13);
+ return true;
+ }
+ return false;
+ }
+
+ if (parse[0] == ';') {
+ ++parse;
+ --packet_size;
+ }
+
+ /* simple case, a continue packet */
+ if (parse[0] == 'c') {
+ gdb_running_type = 'c';
+ LOG_DEBUG("target %s continue", target_name(target));
+ log_add_callback(gdb_log_callback, connection);
+ retval = target_resume(target, 1, 0, 0, 0);
+ if (retval == ERROR_TARGET_NOT_HALTED)
+ LOG_INFO("target %s was not halted when resume was requested", target_name(target));
+
+ /* poll target in an attempt to make its internal state consistent */
+ if (retval != ERROR_OK) {
+ retval = target_poll(target);
+ if (retval != ERROR_OK)
+ LOG_DEBUG("error polling target %s after failed resume", target_name(target));
+ }
+
+ /*
+ * We don't report errors to gdb here, move frontend_state to
+ * TARGET_RUNNING to stay in sync with gdb's expectation of the
+ * target state
+ */
+ gdb_connection->frontend_state = TARGET_RUNNING;
+ target_call_event_callbacks(target, TARGET_EVENT_GDB_START);
+
+ return true;
+ }
+
+ /* single-step or step-over-breakpoint */
+ if (parse[0] == 's') {
+ gdb_running_type = 's';
+ bool fake_step = false;
+
+ if (strncmp(parse, "s:", 2) == 0) {
+ struct target *ct = target;
+ int current_pc = 1;
+ int64_t thread_id;
+ char *endp;
+
+ parse += 2;
+ packet_size -= 2;
+
+ thread_id = strtoll(parse, &endp, 16);
+ if (endp != NULL) {
+ packet_size -= endp - parse;
+ parse = endp;
+ }
+
+ if (target->rtos != NULL) {
+ /* FIXME: why is this necessary? rtos state should be up-to-date here already! */
+ rtos_update_threads(target);
+
+ target->rtos->gdb_target_for_threadid(connection, thread_id, &ct);
+
+ /*
+ * check if the thread to be stepped is the current rtos thread
+ * if not, we must fake the step
+ */
+ if (target->rtos->current_thread != thread_id)
+ fake_step = true;
+ }
+
+ if (parse[0] == ';') {
+ ++parse;
+ --packet_size;
+
+ if (parse[0] == 'c') {
+ parse += 1;
+
+ /* check if thread-id follows */
+ if (parse[0] == ':') {
+ int64_t tid;
+ parse += 1;
+
+ tid = strtoll(parse, &endp, 16);
+ if (tid == thread_id) {
+ /*
+ * Special case: only step a single thread (core),
+ * keep the other threads halted. Currently, only
+ * aarch64 target understands it. Other target types don't
+ * care (nobody checks the actual value of 'current')
+ * and it doesn't really matter. This deserves
+ * a symbolic constant and a formal interface documentation
+ * at a later time.
+ */
+ LOG_DEBUG("request to step current core only");
+ /* uncomment after checking that indeed other targets are safe */
+ /*current_pc = 2;*/
+ }
+ }
+ }
+ }
+
+ LOG_DEBUG("target %s single-step thread %"PRIx64, target_name(ct), thread_id);
+ log_add_callback(gdb_log_callback, connection);
+ target_call_event_callbacks(ct, TARGET_EVENT_GDB_START);
+
+ /*
+ * work around an annoying gdb behaviour: when the current thread
+ * is changed in gdb, it assumes that the target can follow and also
+ * make the thread current. This is an assumption that cannot hold
+ * for a real target running a multi-threading OS. We just fake
+ * the step to not trigger an internal error in gdb. See
+ * https://sourceware.org/bugzilla/show_bug.cgi?id=22925 for details
+ */
+ if (fake_step) {
+ int sig_reply_len;
+ char sig_reply[128];
+
+ LOG_DEBUG("fake step thread %"PRIx64, thread_id);
+
+ sig_reply_len = snprintf(sig_reply, sizeof(sig_reply),
+ "T05thread:%016"PRIx64";", thread_id);
+
+ gdb_put_packet(connection, sig_reply, sig_reply_len);
+ log_remove_callback(gdb_log_callback, connection);
+
+ return true;
+ }
+
+ /* support for gdb_sync command */
+ if (gdb_connection->sync) {
+ gdb_connection->sync = false;
+ if (ct->state == TARGET_HALTED) {
+ LOG_DEBUG("stepi ignored. GDB will now fetch the register state " \
+ "from the target.");
+ gdb_sig_halted(connection);
+ log_remove_callback(gdb_log_callback, connection);
+ } else
+ gdb_connection->frontend_state = TARGET_RUNNING;
+ return true;
+ }
+
+ retval = target_step(ct, current_pc, 0, 0);
+ if (retval == ERROR_TARGET_NOT_HALTED)
+ LOG_INFO("target %s was not halted when step was requested", target_name(ct));
+
+ /* if step was successful send a reply back to gdb */
+ if (retval == ERROR_OK) {
+ retval = target_poll(ct);
+ if (retval != ERROR_OK)
+ LOG_DEBUG("error polling target %s after successful step", target_name(ct));
+ /* send back signal information */
+ gdb_signal_reply(ct, connection);
+ /* stop forwarding log packets! */
+ log_remove_callback(gdb_log_callback, connection);
+ } else
+ gdb_connection->frontend_state = TARGET_RUNNING;
+ } else {
+ LOG_ERROR("Unknown vCont packet");
+ return false;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+static char *next_hex_encoded_field(const char **str, char sep)
+{
+ size_t hexlen;
+ const char *hex = *str;
+ if (hex[0] == '\0')
+ return NULL;
+
+ const char *end = strchr(hex, sep);
+ if (end == NULL)
+ hexlen = strlen(hex);
+ else
+ hexlen = end - hex;
+ *str = hex + hexlen + 1;
+
+ if (hexlen % 2 != 0) {
+ /* Malformed hex data */
+ return NULL;
+ }
+
+ size_t count = hexlen / 2;
+ char *decoded = malloc(count + 1);
+ if (decoded == NULL)
+ return NULL;
+
+ size_t converted = unhexify((void *)decoded, hex, count);
+ if (converted != count) {
+ free(decoded);
+ return NULL;
+ }
+
+ decoded[count] = '\0';
+ return decoded;
+}
+
+/* handle extended restart packet */
+static void gdb_restart_inferior(struct connection *connection, const char *packet, int packet_size)
+{
+ struct gdb_connection *gdb_con = connection->priv;
+ struct target *target = get_target_from_connection(connection);
+
+ breakpoint_clear_target(target);
+ watchpoint_clear_target(target);
+ command_run_linef(connection->cmd_ctx, "ocd_gdb_restart %s",
+ target_name(target));
+ /* set connection as attached after reset */
+ gdb_con->attached = true;
+ /* info rtos parts */
+ gdb_thread_packet(connection, packet, packet_size);
+}
+
+static bool gdb_handle_vrun_packet(struct connection *connection, const char *packet, int packet_size)
+{
+ struct target *target = get_target_from_connection(connection);
+ const char *parse = packet;
+
+ /* Skip "vRun" */
+ parse += 4;
+
+ if (parse[0] != ';')
+ return false;
+ parse++;
+
+ /* Skip first field "filename"; don't know what to do with it. */
+ free(next_hex_encoded_field(&parse, ';'));
+
+ char *cmdline = next_hex_encoded_field(&parse, ';');
+ char *arg;
+ while (cmdline != NULL && (arg = next_hex_encoded_field(&parse, ';')) != NULL) {
+ char *new_cmdline = alloc_printf("%s %s", cmdline, arg);
+ free(cmdline);
+ free(arg);
+ cmdline = new_cmdline;
+ }
+
+ if (cmdline != NULL) {
+ if (target->semihosting != NULL) {
+ LOG_INFO("GDB set inferior command line to '%s'", cmdline);
+ free(target->semihosting->cmdline);
+ target->semihosting->cmdline = cmdline;
+ } else {
+ LOG_INFO("GDB set inferior command line to '%s' but semihosting is unavailable", cmdline);
+ free(cmdline);
+ }
+ }
+
+ gdb_restart_inferior(connection, packet, packet_size);
+ gdb_put_packet(connection, "S00", 3);
+ return true;
+}
+