-/* SPDX-License-Identifier: GPL-2.0-or-later */
+// SPDX-License-Identifier: GPL-2.0-or-later
/***************************************************************************
* Copyright (C) 2005 by Dominic Rath *
enum gdb_output_flag {
/* GDB doesn't accept 'O' packets */
GDB_OUTPUT_NO,
+ /* GDB doesn't accept 'O' packets but accepts notifications */
+ GDB_OUTPUT_NOTIF,
/* GDB accepts 'O' packets */
GDB_OUTPUT_ALL,
};
enum target_state frontend_state;
struct image *vflash_image;
bool closed;
+ /* set to prevent re-entrance from log messages during gdb_get_packet()
+ * and gdb_put_packet(). */
bool busy;
int noack_mode;
/* set flag to true if you want the next stepi to return immediately.
char *thread_list;
/* flag to mask the output from gdb_log_callback() */
enum gdb_output_flag output_flag;
+ /* Unique index for this GDB connection. */
+ unsigned int unique_index;
};
#if 0
/* number of gdb connections, mainly to suppress gdb related debugging spam
* in helper/log.c when no gdb connections are actually active */
-int gdb_actual_connections;
+static int gdb_actual_connections;
/* set if we are sending a memory map to gdb
* via qXfer:memory-map:read packet */
static int gdb_last_signal(struct target *target)
{
+ LOG_TARGET_DEBUG(target, "Debug reason is: %s",
+ target_debug_reason_str(target->debug_reason));
+
switch (target->debug_reason) {
case DBG_REASON_DBGRQ:
return 0x2; /* SIGINT */
case DBG_REASON_NOTHALTED:
return 0x0; /* no signal... shouldn't happen */
default:
- LOG_USER("undefined debug reason %d - target needs reset",
- target->debug_reason);
+ LOG_USER("undefined debug reason %d (%s) - target needs reset",
+ target->debug_reason,
+ target_debug_reason_str(target->debug_reason));
return 0x0;
}
}
}
#ifdef _WIN32
- errno = WSAGetLastError();
-
- switch (errno) {
- case WSAEWOULDBLOCK:
- usleep(1000);
- break;
- case WSAECONNABORTED:
- gdb_con->closed = true;
- return ERROR_SERVER_REMOTE_CLOSED;
- case WSAECONNRESET:
- gdb_con->closed = true;
- return ERROR_SERVER_REMOTE_CLOSED;
- default:
- LOG_ERROR("read: %d", errno);
- exit(-1);
- }
+ bool retry = (WSAGetLastError() == WSAEWOULDBLOCK);
#else
- switch (errno) {
- case EAGAIN:
- usleep(1000);
- break;
- case ECONNABORTED:
- gdb_con->closed = true;
- return ERROR_SERVER_REMOTE_CLOSED;
- case ECONNRESET:
- gdb_con->closed = true;
- return ERROR_SERVER_REMOTE_CLOSED;
- default:
- LOG_ERROR("read: %s", strerror(errno));
- gdb_con->closed = true;
- return ERROR_SERVER_REMOTE_CLOSED;
- }
+ bool retry = (errno == EAGAIN);
#endif
+
+ if (retry) {
+ // Try again after a delay
+ usleep(1000);
+ } else {
+ // Print error and close the socket
+ log_socket_error("GDB");
+ gdb_con->closed = true;
+ return ERROR_SERVER_REMOTE_CLOSED;
+ }
}
#ifdef _DEBUG_GDB_IO_
return;
struct target *target = get_target_from_connection(connection);
+ struct gdb_connection *gdb_connection = connection->priv;
/* Avoid dumping non-printable characters to the terminal */
const unsigned packet_len = strlen(packet);
if (packet_prefix_printable) {
const unsigned int prefix_len = colon - packet + 1; /* + 1 to include the ':' */
const unsigned int payload_len = packet_len - prefix_len;
- LOG_TARGET_DEBUG(target, "received packet: %.*s<binary-data-%u-bytes>", prefix_len,
- packet, payload_len);
+ LOG_TARGET_DEBUG(target, "{%d} received packet: %.*s<binary-data-%u-bytes>",
+ gdb_connection->unique_index, prefix_len, packet, payload_len);
} else {
- LOG_TARGET_DEBUG(target, "received packet: <binary-data-%u-bytes>", packet_len);
+ LOG_TARGET_DEBUG(target, "{%d} received packet: <binary-data-%u-bytes>",
+ gdb_connection->unique_index, packet_len);
}
} else {
/* All chars printable, dump the packet as is */
- LOG_TARGET_DEBUG(target, "received packet: %s", packet);
+ LOG_TARGET_DEBUG(target, "{%d} received packet: %s", gdb_connection->unique_index, packet);
}
}
return;
struct target *target = get_target_from_connection(connection);
+ struct gdb_connection *gdb_connection = connection->priv;
if (find_nonprint_char(packet_buf, packet_len))
- LOG_TARGET_DEBUG(target, "sending packet: $<binary-data-%u-bytes>#%2.2x",
- packet_len, checksum);
+ LOG_TARGET_DEBUG(target, "{%d} sending packet: $<binary-data-%u-bytes>#%2.2x",
+ gdb_connection->unique_index, packet_len, checksum);
else
- LOG_TARGET_DEBUG(target, "sending packet: $%.*s#%2.2x", packet_len, packet_buf,
- checksum);
+ LOG_TARGET_DEBUG(target, "{%d} sending packet: $%.*s#%2.2x",
+ gdb_connection->unique_index, packet_len, packet_buf, checksum);
}
static int gdb_put_packet_inner(struct connection *connection,
}
if (gdb_connection->ctrl_c) {
+ LOG_TARGET_DEBUG(target, "Responding with signal 2 (SIGINT) to debugger due to Ctrl-C");
signal_var = 0x2;
} else
signal_var = gdb_last_signal(ct);
struct target *target;
int retval;
int initial_ack;
+ static unsigned int next_unique_id = 1;
target = get_target_from_connection(connection);
connection->priv = gdb_connection;
gdb_connection->target_desc.tdesc_length = 0;
gdb_connection->thread_list = NULL;
gdb_connection->output_flag = GDB_OUTPUT_NO;
-
- /* send ACK to GDB for debug request */
- gdb_write(connection, "+", 1);
+ gdb_connection->unique_index = next_unique_id++;
/* output goes through gdb connection */
command_set_output_handler(connection->cmd_ctx, gdb_output, connection);
}
}
- gdb_actual_connections++;
log_printf_lf(all_targets->next ? LOG_LVL_INFO : LOG_LVL_DEBUG,
__FILE__, __LINE__, __func__,
"New GDB Connection: %d, Target %s, state: %s",
- gdb_actual_connections,
+ gdb_connection->unique_index,
target_name(target),
target_state_name(target));
if (!target_was_examined(target)) {
LOG_ERROR("Target %s not examined yet, refuse gdb connection %d!",
- target_name(target), gdb_actual_connections);
- gdb_actual_connections--;
+ target_name(target), gdb_connection->unique_index);
return ERROR_TARGET_NOT_EXAMINED;
}
+ gdb_actual_connections++;
if (target->state != TARGET_HALTED)
LOG_WARNING("GDB connection %d on target %s not halted",
log_remove_callback(gdb_log_callback, connection);
gdb_actual_connections--;
- LOG_DEBUG("GDB Close, Target: %s, state: %s, gdb_actual_connections=%d",
+ LOG_DEBUG("{%d} GDB Close, Target: %s, state: %s, gdb_actual_connections=%d",
+ gdb_connection->unique_index,
target_name(target),
target_state_name(target),
gdb_actual_connections);
}
}
+/* get register value if needed and fill the buffer accordingly */
+static int gdb_get_reg_value_as_str(struct target *target, char *tstr, struct reg *reg)
+{
+ int retval = ERROR_OK;
+
+ if (!reg->valid)
+ retval = reg->type->get(reg);
+
+ const unsigned int len = DIV_ROUND_UP(reg->size, 8) * 2;
+ switch (retval) {
+ case ERROR_OK:
+ gdb_str_to_target(target, tstr, reg);
+ return ERROR_OK;
+ case ERROR_TARGET_RESOURCE_NOT_AVAILABLE:
+ memset(tstr, 'x', len);
+ tstr[len] = '\0';
+ return ERROR_OK;
+ }
+ return ERROR_FAIL;
+}
+
static int gdb_get_registers_packet(struct connection *connection,
char const *packet, int packet_size)
{
for (i = 0; i < reg_list_size; i++) {
if (!reg_list[i] || reg_list[i]->exist == false || reg_list[i]->hidden)
continue;
- if (!reg_list[i]->valid) {
- retval = reg_list[i]->type->get(reg_list[i]);
- if (retval != ERROR_OK && gdb_report_register_access_error) {
- LOG_DEBUG("Couldn't get register %s.", reg_list[i]->name);
- free(reg_packet);
- free(reg_list);
- return gdb_error(connection, retval);
- }
+ if (gdb_get_reg_value_as_str(target, reg_packet_p, reg_list[i]) != ERROR_OK) {
+ free(reg_packet);
+ free(reg_list);
+ return gdb_error(connection, retval);
}
- gdb_str_to_target(target, reg_packet_p, reg_list[i]);
reg_packet_p += DIV_ROUND_UP(reg_list[i]->size, 8) * 2;
}
packet_p = packet;
for (i = 0; i < reg_list_size; i++) {
uint8_t *bin_buf;
+ if (!reg_list[i] || !reg_list[i]->exist || reg_list[i]->hidden)
+ continue;
int chars = (DIV_ROUND_UP(reg_list[i]->size, 8) * 2);
if (packet_p + chars > packet + packet_size)
if (retval != ERROR_OK)
return gdb_error(connection, retval);
- if (reg_list_size <= reg_num) {
+ if ((reg_list_size <= reg_num) || !reg_list[reg_num] ||
+ !reg_list[reg_num]->exist || reg_list[reg_num]->hidden) {
LOG_ERROR("gdb requested a non-existing register (reg_num=%d)", reg_num);
return ERROR_SERVER_REMOTE_CLOSED;
}
- if (!reg_list[reg_num]->valid) {
- retval = reg_list[reg_num]->type->get(reg_list[reg_num]);
- if (retval != ERROR_OK && gdb_report_register_access_error) {
- LOG_DEBUG("Couldn't get register %s.", reg_list[reg_num]->name);
- free(reg_list);
- return gdb_error(connection, retval);
- }
- }
-
reg_packet = calloc(DIV_ROUND_UP(reg_list[reg_num]->size, 8) * 2 + 1, 1); /* plus one for string termination null */
- gdb_str_to_target(target, reg_packet, reg_list[reg_num]);
+ if (gdb_get_reg_value_as_str(target, reg_packet, reg_list[reg_num]) != ERROR_OK) {
+ free(reg_packet);
+ free(reg_list);
+ return gdb_error(connection, retval);
+ }
gdb_put_packet(connection, reg_packet, DIV_ROUND_UP(reg_list[reg_num]->size, 8) * 2);
return gdb_error(connection, retval);
}
- if (reg_list_size <= reg_num) {
+ if ((reg_list_size <= reg_num) || !reg_list[reg_num] ||
+ !reg_list[reg_num]->exist || reg_list[reg_num]->hidden) {
LOG_ERROR("gdb requested a non-existing register (reg_num=%d)", reg_num);
free(bin_buf);
free(reg_list);
return ERROR_OK;
}
-/* We don't have to worry about the default 2 second timeout for GDB packets,
- * because GDB breaks up large memory reads into smaller reads.
- */
static int gdb_read_memory_packet(struct connection *connection,
char const *packet, int packet_size)
{
case 1:
if (packet[0] == 'Z') {
retval = breakpoint_add(target, address, size, bp_type);
- if (retval != ERROR_OK) {
+ if (retval == ERROR_NOT_IMPLEMENTED) {
+ /* Send empty reply to report that breakpoints of this type are not supported */
+ gdb_put_packet(connection, "", 0);
+ } else if (retval != ERROR_OK) {
retval = gdb_error(connection, retval);
if (retval != ERROR_OK)
return retval;
case 4:
{
if (packet[0] == 'Z') {
- retval = watchpoint_add(target, address, size, wp_type, 0, 0xffffffffu);
- if (retval != ERROR_OK) {
+ retval = watchpoint_add(target, address, size, wp_type, 0, WATCHPOINT_IGNORE_DATA_VALUE_MASK);
+ if (retval == ERROR_NOT_IMPLEMENTED) {
+ /* Send empty reply to report that watchpoints of this type are not supported */
+ gdb_put_packet(connection, "", 0);
+ } else if (retval != ERROR_OK) {
retval = gdb_error(connection, retval);
if (retval != ERROR_OK)
return retval;
local_list = realloc(local_list, combined_allocated * sizeof(struct reg *));
if (!local_list) {
LOG_ERROR("realloc(%zu) failed", combined_allocated * sizeof(struct reg *));
+ free(reg_list);
return ERROR_FAIL;
}
}
len = strtoul(separator + 1, NULL, 16);
+ gdb_connection->output_flag = GDB_OUTPUT_NOTIF;
retval = target_checksum_memory(target, addr, len, &checksum);
+ gdb_connection->output_flag = GDB_OUTPUT_NO;
if (retval == ERROR_OK) {
snprintf(gdb_reply, 10, "C%8.8" PRIx32 "", checksum);
gdb_connection->noack_mode = 1;
gdb_put_packet(connection, "OK", 2);
return ERROR_OK;
+ } else if (target->type->gdb_query_custom) {
+ char *buffer = NULL;
+ int ret = target->type->gdb_query_custom(target, packet, &buffer);
+ gdb_put_packet(connection, buffer, strlen(buffer));
+ return ret;
}
gdb_put_packet(connection, "", 0);
return ERROR_OK;
}
-static bool gdb_handle_vcont_packet(struct connection *connection, const char *packet, int packet_size)
+static bool gdb_handle_vcont_packet(struct connection *connection, const char *packet,
+ __attribute__((unused)) int packet_size)
{
struct gdb_connection *gdb_connection = connection->priv;
struct target *target = get_target_from_connection(connection);
if (parse[0] == ';') {
++parse;
- --packet_size;
}
/* simple case, a continue packet */
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;
+ struct target *ct = target;
+ int current_pc = 1;
+ int64_t thread_id;
+ parse++;
+ if (parse[0] == ':') {
char *endp;
-
- parse += 2;
- packet_size -= 2;
-
+ parse++;
thread_id = strtoll(parse, &endp, 16);
if (endp) {
- packet_size -= endp - parse;
parse = endp;
}
+ } else {
+ thread_id = 0;
+ }
- if (target->rtos) {
- /* FIXME: why is this necessary? rtos state should be up-to-date here already! */
- rtos_update_threads(target);
+ if (target->rtos) {
+ /* 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);
+ 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;
- }
+ /*
+ * 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;
- if (parse[0] == ';') {
- ++parse;
- --packet_size;
+ if (parse[0] == 'c') {
+ parse += 1;
- if (parse[0] == 'c') {
+ /* check if thread-id follows */
+ if (parse[0] == ':') {
+ int64_t tid;
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;*/
- }
+ tid = strtoll(parse, NULL, 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);
- gdb_connection->output_flag = GDB_OUTPUT_ALL;
- 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("target %s single-step thread %"PRIx64, target_name(ct), thread_id);
+ gdb_connection->output_flag = GDB_OUTPUT_ALL;
+ target_call_event_callbacks(ct, TARGET_EVENT_GDB_START);
- LOG_DEBUG("fake step thread %"PRIx64, thread_id);
+ /*
+ * 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];
- sig_reply_len = snprintf(sig_reply, sizeof(sig_reply),
- "T05thread:%016"PRIx64";", thread_id);
+ LOG_DEBUG("fake step thread %"PRIx64, thread_id);
- gdb_put_packet(connection, sig_reply, sig_reply_len);
- gdb_connection->output_flag = GDB_OUTPUT_NO;
+ sig_reply_len = snprintf(sig_reply, sizeof(sig_reply),
+ "T05thread:%016"PRIx64";", thread_id);
- 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);
- gdb_connection->output_flag = GDB_OUTPUT_NO;
- } else
- gdb_connection->frontend_state = TARGET_RUNNING;
- return true;
- }
+ gdb_put_packet(connection, sig_reply, sig_reply_len);
+ gdb_connection->output_flag = GDB_OUTPUT_NO;
- 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));
+ return true;
+ }
- /* 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! */
+ /* 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);
gdb_connection->output_flag = GDB_OUTPUT_NO;
} else
gdb_connection->frontend_state = TARGET_RUNNING;
- } else {
- LOG_ERROR("Unknown vCont packet");
- return false;
+ 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! */
+ gdb_connection->output_flag = GDB_OUTPUT_NO;
+ } else
+ gdb_connection->frontend_state = TARGET_RUNNING;
return true;
}
-
+ LOG_ERROR("Unknown vCont packet");
return false;
}
}
if (strncmp(packet, "vFlashErase:", 12) == 0) {
- unsigned long addr;
+ target_addr_t addr;
unsigned long length;
char const *parse = packet + 12;
return ERROR_SERVER_REMOTE_CLOSED;
}
- addr = strtoul(parse, (char **)&parse, 16);
+ addr = strtoull(parse, (char **)&parse, 16);
if (*(parse++) != ',' || *parse == '\0') {
LOG_ERROR("incomplete vFlashErase packet received, dropping connection");
if (strncmp(packet, "vFlashWrite:", 12) == 0) {
int retval;
- unsigned long addr;
+ target_addr_t addr;
unsigned long length;
char const *parse = packet + 12;
LOG_ERROR("incomplete vFlashErase packet received, dropping connection");
return ERROR_SERVER_REMOTE_CLOSED;
}
- addr = strtoul(parse, (char **)&parse, 16);
+
+ addr = strtoull(parse, (char **)&parse, 16);
if (*(parse++) != ':') {
LOG_ERROR("incomplete vFlashErase packet received, dropping connection");
return ERROR_SERVER_REMOTE_CLOSED;
if (strncmp(packet, "vFlashDone", 10) == 0) {
uint32_t written;
+ /* GDB command 'flash-erase' does not send a vFlashWrite,
+ * so nothing to write here. */
+ if (!gdb_connection->vflash_image) {
+ gdb_put_packet(connection, "OK", 2);
+ return ERROR_OK;
+ }
+
/* process the flashing buffer. No need to erase as GDB
* always issues a vFlashErase first. */
target_call_event_callbacks(target,
struct connection *connection = priv;
struct gdb_connection *gdb_con = connection->priv;
- if (gdb_con->output_flag == GDB_OUTPUT_NO)
+ if (gdb_con->output_flag != GDB_OUTPUT_ALL)
/* No out allowed */
return;
retval = gdb_set_register_packet(connection, packet, packet_size);
break;
case 'm':
+ gdb_con->output_flag = GDB_OUTPUT_NOTIF;
retval = gdb_read_memory_packet(connection, packet, packet_size);
+ gdb_con->output_flag = GDB_OUTPUT_NO;
break;
case 'M':
+ gdb_con->output_flag = GDB_OUTPUT_NOTIF;
retval = gdb_write_memory_packet(connection, packet, packet_size);
+ gdb_con->output_flag = GDB_OUTPUT_NO;
break;
case 'z':
case 'Z':
retval = gdb_detach(connection);
break;
case 'X':
+ gdb_con->output_flag = GDB_OUTPUT_NOTIF;
retval = gdb_write_memory_binary_packet(connection, packet, packet_size);
- if (retval != ERROR_OK)
- return retval;
+ gdb_con->output_flag = GDB_OUTPUT_NO;
break;
case 'k':
if (gdb_con->extended_protocol) {
return ERROR_OK;
}
+/*
+ * Send custom notification packet as keep-alive during memory read/write.
+ *
+ * From gdb 7.0 (released 2009-10-06) an unknown notification received during
+ * memory read/write would be silently dropped.
+ * Before gdb 7.0 any character, with exclusion of "+-$", would be considered
+ * as junk and ignored.
+ * In both cases the reception will reset the timeout counter in gdb, thus
+ * working as a keep-alive.
+ * Check putpkt_binary() and getpkt_sane() in gdb commit
+ * 74531fed1f2d662debc2c209b8b3faddceb55960
+ *
+ * Enable remote debug in gdb with 'set debug remote 1' to either dump the junk
+ * characters in gdb pre-7.0 and the notification from gdb 7.0.
+ */
+static void gdb_async_notif(struct connection *connection)
+{
+ static unsigned char count;
+ unsigned char checksum = 0;
+ char buf[22];
+
+ int len = sprintf(buf, "%%oocd_keepalive:%2.2x", count++);
+ for (int i = 1; i < len; i++)
+ checksum += buf[i];
+ len += sprintf(buf + len, "#%2.2x", checksum);
+
+#ifdef _DEBUG_GDB_IO_
+ LOG_DEBUG("sending packet '%s'", buf);
+#endif
+
+ gdb_write(connection, buf, len);
+}
+
static void gdb_keep_client_alive(struct connection *connection)
{
struct gdb_connection *gdb_con = connection->priv;
- if (gdb_con->busy) {
- /* do not send packets, retry asap */
- return;
- }
-
switch (gdb_con->output_flag) {
case GDB_OUTPUT_NO:
/* no need for keep-alive */
break;
+ case GDB_OUTPUT_NOTIF:
+ /* send asynchronous notification */
+ gdb_async_notif(connection);
+ break;
case GDB_OUTPUT_ALL:
/* send an empty O packet */
gdb_output_con(connection, "");
free(gdb_port);
free(gdb_port_next);
}
+
+int gdb_get_actual_connections(void)
+{
+ return gdb_actual_connections;
+}