#ifndef GDB_SERVER_IMPL #ifndef GDB_SERVER_H #define GDB_SERVER_H #include #include // // cross-platform Berkeley sockets shim // #if PLATFORM_WINDOWS #include #include typedef int socklen_t; typedef u_long ioctlarg_t; #define sockerrno WSAGetLastError() #define SHUT_RD SD_RECEIVE #define SHUT_WR SD_SEND #define SHUT_RDWR SD_BOTH #else #include #include #include #include #include #include #include #include #include #include #include #include typedef int SOCKET; typedef int ioctlarg_t; #define INVALID_SOCKET -1 #define SOCKET_ERROR -1 #define closesocket close #define ioctlsocket ioctl #define sockerrno errno #endif // // target machine interface // typedef enum { GDB_LITTLE_ENDIAN, GDB_BIG_ENDIAN, } endianness_t; typedef struct { void *ctx; endianness_t endian; int num_regs; void (*detach)(void *); void (*stop)(void *); void (*resume)(void *); void (*step)(void *); void (*add_bp)(void *, int type, intmax_t addr); void (*rem_bp)(void *, int type, intmax_t addr); void (*read_mem)(void *, intmax_t addr, uint8_t *value, int size); void (*read_reg)(void *, int n, intmax_t *value, int *size); } gdb_target_t; // // gdb server interface // #define GDB_MAX_PACKET_SIZE (1024 * 128) #define GDB_MAX_DATA_SIZE (GDB_MAX_PACKET_SIZE - 5) enum { GDB_SIGNAL_0, GDB_SIGNAL_HUP, GDB_SIGNAL_INT, GDB_SIGNAL_QUIT, GDB_SIGNAL_ILL, GDB_SIGNAL_TRAP, GDB_SIGNAL_ABRT, GDB_SIGNAL_EMT, GDB_SIGNAL_FPE, GDB_SIGNAL_KILL, GDB_SIGNAL_BUS, GDB_SIGNAL_SEGV, GDB_SIGNAL_SYS, GDB_SIGNAL_PIPE, GDB_SIGNAL_ALRM, GDB_SIGNAL_TERM, GDB_SIGNAL_URG, GDB_SIGNAL_STOP, GDB_SIGNAL_TSTP, GDB_SIGNAL_CONT, GDB_SIGNAL_CHLD, GDB_SIGNAL_TTIN, GDB_SIGNAL_TTOU, GDB_SIGNAL_IO, GDB_SIGNAL_XCPU, GDB_SIGNAL_XFSZ, GDB_SIGNAL_VTALRM, GDB_SIGNAL_PROF, GDB_SIGNAL_WINCH, GDB_SIGNAL_LOST, GDB_SIGNAL_USR1, GDB_SIGNAL_USR2, GDB_SIGNAL_PWR, GDB_SIGNAL_POLL, GDB_SIGNAL_WIND, GDB_SIGNAL_PHONE, GDB_SIGNAL_WAITING, GDB_SIGNAL_LWP, GDB_SIGNAL_DANGER, GDB_SIGNAL_GRANT, GDB_SIGNAL_RETRACT, GDB_SIGNAL_MSG, GDB_SIGNAL_SOUND, GDB_SIGNAL_SAK, GDB_SIGNAL_PRIO, }; enum { GDB_BP_SW, // software breakpoint GDB_BP_HW, // hardware breakpoint GDB_BP_W, // write watchpoint GDB_BP_R, // read watchpoint GDB_BP_A // access watchpoint }; enum { PARSE_WAIT, PARSE_DATA, PARSE_CHECKSUM_HIGH, PARSE_CHECKSUM_LOW, PARSE_DONE, }; typedef struct { int recv_state; char recv_data[GDB_MAX_DATA_SIZE]; int recv_length; uint8_t recv_checksum; char last_sent[GDB_MAX_PACKET_SIZE]; int ack_disabled; } gdb_connection_t; typedef struct { gdb_target_t target; SOCKET listen; SOCKET client; gdb_connection_t conn; } gdb_server_t; gdb_server_t *gdb_server_create(gdb_target_t *target, int port); void gdb_server_interrupt(gdb_server_t *sv, int signal); void gdb_server_pump(gdb_server_t *sv); void gdb_server_destroy(gdb_server_t *sv); #endif // #ifndef GDB_SERVER_H #else // #ifndef GDB_SERVER_IMPL #undef GDB_SERVER_IMPL #ifndef GDB_SERVER_ASSERT #include #define GDB_SERVER_ASSERT assert #endif #ifndef GDB_SERVER_LOG #define GDB_SERVER_LOG(x, ...) printf(x "\n", ##__VA_ARGS__) #endif #ifndef GDB_SERVER_MALLOC #define GDB_SERVER_MALLOC malloc #endif #ifndef GDB_SERVER_ALLOCA #if PLATFORM_WINDOWS #include #define GDB_SERVER_ALLOCA _alloca #else #define GDB_SERVER_ALLOCA alloca #endif #endif #ifndef GDB_SERVER_FREE #define GDB_SERVER_FREE free #endif #define GDB_SERVER_UNUSED(x) ((void)x) // // gdb server implementation // #include #include #include #include "gdb_server.h" // used by 'q' and 'Q' packets typedef struct { const char *name; void (*callback)(gdb_server_t *sv, const char *data); } query_handler_t; static const char GDB_PACKET_BEGIN = '$'; static const char GDB_PACKET_END = '#'; static const char *GDB_PACKET_ACK = "+"; static const char *GDB_PACKET_NACK = "-"; static char scratch_buffer[GDB_MAX_DATA_SIZE] = {0}; static int gdb_server_create_listen(gdb_server_t *sv, int port); static void gdb_server_destroy_listen(gdb_server_t *sv); static void gdb_server_accept_client(gdb_server_t *sv); static void gdb_server_destroy_client(gdb_server_t *sv); static int gdb_server_data_available(gdb_server_t *sv); static const char *gdb_server_recv_packet(gdb_server_t *sv); static int gdb_server_send_raw(gdb_server_t *sv, const char *data); static int gdb_server_send_packet(gdb_server_t *sv, const char *data); static void gdb_server_handle_packet(gdb_server_t *sv, const char *data); // // packet parsing and formatting helpers // static int xtoi(char c) { int i = -1; c = tolower(c); if (c >= 'a' && c <= 'f') { i = 0xa + (c - 'a'); } else if (c >= '0' && c <= '9') { i = c - '0'; } return i; } static const char *parse_hex(const char *buffer, int *value) { while (isxdigit(*buffer)) { *value <<= 4; *value |= xtoi(*buffer); buffer++; } return buffer; } static const char *parse_tid(const char *buffer, int *value) { if (!strncmp(buffer, "-1", 2)) { *value = -1; return buffer + 2; } return parse_hex(buffer, value); } static int format_register(intmax_t value, int width, endianness_t endian, char *buffer, int size) { uint8_t *ptr = (uint8_t *)&value; int remaining = size; for (int i = 0; i < width; i++) { uint8_t c = endian == GDB_LITTLE_ENDIAN ? ptr[i] : ptr[width - 1 - i]; int n = snprintf(buffer, remaining, "%02x", c); GDB_SERVER_ASSERT(n); buffer += n; remaining -= n; } return size - remaining; } // // generate checksum for data of gdb server packet. the checksum is calculated // as the modulo 256 of the sum of all characters in the data portion of the // packet // static uint8_t packet_data_checksum(const char *data) { int checksum = 0; int len = (int)strlen(data); for (int i = 0; i < len; i++) { checksum += data[i]; } return (uint8_t)(checksum % 256); } // // create an instance of the gdb server for the supplied target // gdb_server_t *gdb_server_create(gdb_target_t *target, int port) { if (!target) { return NULL; } #if PLATFORM_WINDOWS WSADATA wsadata; int r = WSAStartup(MAKEWORD(2, 2), &wsadata); if (r) { GDB_SERVER_LOG("Failed to initialize WinSock"); return NULL; } #endif gdb_server_t *sv = (gdb_server_t *)GDB_SERVER_MALLOC(sizeof(gdb_server_t)); memset(sv, 0, sizeof(*sv)); sv->target = *target; sv->listen = INVALID_SOCKET; sv->client = INVALID_SOCKET; if (gdb_server_create_listen(sv, port) == -1) { gdb_server_destroy(sv); return NULL; } return sv; } // // tell the client that we've halted due to an interrupt // void gdb_server_interrupt(gdb_server_t *sv, int signal) { snprintf(scratch_buffer, sizeof(scratch_buffer), "T%02x", signal); gdb_server_send_packet(sv, scratch_buffer); } // // check for a new connection, or handle pending messages from an existing // connection // void gdb_server_pump(gdb_server_t *sv) { const char *data = NULL; gdb_server_accept_client(sv); while ((data = gdb_server_recv_packet(sv))) { gdb_server_handle_packet(sv, data); } } // // shutdown and free the server instance // void gdb_server_destroy(gdb_server_t *sv) { gdb_server_destroy_listen(sv); GDB_SERVER_FREE(sv); #if PLATFORM_WINDOWS int r = WSACleanup(); GDB_SERVER_ASSERT(r == 0); #endif } // // create a TCP-based listen server // static int gdb_server_create_listen(gdb_server_t *sv, int port) { int ret = 0; for (;;) { // create the socket to monitor for connections on sv->listen = socket(PF_INET, SOCK_STREAM, 0); if (sv->listen == INVALID_SOCKET) { GDB_SERVER_LOG("Failed to create gdb socket"); ret = -1; break; } // enable reusing of the address / port int on = 1; if (setsockopt(sv->listen, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) == SOCKET_ERROR) { GDB_SERVER_LOG("Failed to set socket options for gdb socket"); ret = -1; break; } // bind the socket to an address / port struct sockaddr_in listen_addr_in; memset(&listen_addr_in, 0, sizeof(listen_addr_in)); listen_addr_in.sin_family = AF_INET; listen_addr_in.sin_port = htons(port); listen_addr_in.sin_addr.s_addr = INADDR_ANY; const struct sockaddr *listen_addr = (const struct sockaddr *)&listen_addr_in; socklen_t listen_addr_in_len = sizeof(listen_addr_in); if (bind(sv->listen, listen_addr, listen_addr_in_len) == SOCKET_ERROR) { GDB_SERVER_LOG("Failed to bind gdb socket"); ret = -1; break; } // start listening if (listen(sv->listen, 1) == SOCKET_ERROR) { GDB_SERVER_LOG("Failed to listen to gdb socket"); ret = -1; break; } GDB_SERVER_LOG("GDB server started on localhost:%d", port); break; } // cleanup if necessary if (ret == -1) { shutdown(sv->listen, SHUT_RDWR); sv->listen = INVALID_SOCKET; } return ret; } // // destroy the listen server // static void gdb_server_destroy_listen(gdb_server_t *sv) { if (sv->listen == INVALID_SOCKET) { return; } gdb_server_destroy_client(sv); shutdown(sv->listen, SHUT_RDWR); sv->listen = INVALID_SOCKET; } // // attempt to accept a new client connection. if a client is already connected, // destroy it in favor of the new one // static void gdb_server_accept_client(gdb_server_t *sv) { if (sv->listen == INVALID_SOCKET) { return; } // check to see if there is a pending connection fd_set fd_read; FD_ZERO(&fd_read); FD_SET(sv->listen, &fd_read); // return immediately struct timeval t; t.tv_sec = 0; t.tv_usec = 0; if (select((int)(sv->listen + 1), &fd_read, NULL, NULL, &t) == SOCKET_ERROR) { return; } // no new connections if (!FD_ISSET(sv->listen, &fd_read)) { return; } // new connection, shut down the existing one and accept it if (sv->client != INVALID_SOCKET) { gdb_server_destroy_client(sv); } sv->client = accept(sv->listen, NULL, NULL); } // // destroy the current client connection // static void gdb_server_destroy_client(gdb_server_t *sv) { if (sv->client == INVALID_SOCKET) { return; } shutdown(sv->client, SHUT_RDWR); sv->client = INVALID_SOCKET; memset(&sv->conn, 0, sizeof(sv->conn)); } // // check if any data is available to be read from the client // static int gdb_server_data_available(gdb_server_t *sv) { if (sv->client == INVALID_SOCKET) { return 0; } fd_set fd_read; FD_ZERO(&fd_read); FD_SET(sv->client, &fd_read); // return immediately struct timeval t; t.tv_sec = 0; t.tv_usec = 0; if (select((int)(sv->client + 1), &fd_read, NULL, NULL, &t) == SOCKET_ERROR) { return -1; } if (!FD_ISSET(sv->client, &fd_read)) { return 0; } return 1; } // // read and parse new data from the socket, dispatching it once a complete // packet has been parsed // static const char *gdb_server_recv_packet(gdb_server_t *sv) { if (sv->client == INVALID_SOCKET) { return NULL; } int parsed_ack = 0; while (gdb_server_data_available(sv) && sv->conn.recv_state != PARSE_DONE) { // read the next byte from the stream char c = 0; int n = (int)recv(sv->client, &c, 1, 0); if (n == 0) { // client disconnected gdb_server_destroy_client(sv); break; } else if (n == SOCKET_ERROR) { GDB_SERVER_LOG("gdb server recv failed, %d", n); break; } GDB_SERVER_ASSERT(n == 1); // process the byte into the current packet switch (sv->conn.recv_state) { case PARSE_WAIT: { if (c == GDB_PACKET_ACK[0] || c == GDB_PACKET_NACK[0] || c == 0x3) { GDB_SERVER_ASSERT(sv->conn.recv_length < GDB_MAX_DATA_SIZE); sv->conn.recv_data[sv->conn.recv_length++] = c; sv->conn.recv_data[sv->conn.recv_length] = 0; sv->conn.recv_state = PARSE_DONE; parsed_ack = 1; } else if (c == GDB_PACKET_BEGIN) { sv->conn.recv_state = PARSE_DATA; } else { GDB_SERVER_ASSERT(0); } } break; case PARSE_DATA: { if (c == GDB_PACKET_END) { sv->conn.recv_state = PARSE_CHECKSUM_HIGH; } else { GDB_SERVER_ASSERT(sv->conn.recv_length < GDB_MAX_DATA_SIZE); sv->conn.recv_data[sv->conn.recv_length++] = c; sv->conn.recv_data[sv->conn.recv_length] = 0; } } break; case PARSE_CHECKSUM_HIGH: { sv->conn.recv_checksum = xtoi(c) << 4; sv->conn.recv_state = PARSE_CHECKSUM_LOW; } break; case PARSE_CHECKSUM_LOW: { sv->conn.recv_checksum |= xtoi(c); sv->conn.recv_state = PARSE_DONE; } break; case PARSE_DONE: GDB_SERVER_ASSERT(0); break; } } if (sv->conn.recv_state == PARSE_DONE) { // validate and ack non-notification packets if (!sv->conn.ack_disabled && !parsed_ack) { uint8_t expected_checksum = packet_data_checksum(sv->conn.recv_data); if (sv->conn.recv_checksum == expected_checksum) { gdb_server_send_raw(sv, GDB_PACKET_ACK); } else { gdb_server_send_raw(sv, GDB_PACKET_NACK); } } // reset parse state sv->conn.recv_state = PARSE_WAIT; sv->conn.recv_length = 0; return sv->conn.recv_data; } return NULL; } // // send a raw packet to the client // static int gdb_server_send_raw(gdb_server_t *sv, const char *data) { int len = (int)strlen(data); int n = send(sv->client, data, len, 0); if (n != len) { GDB_SERVER_LOG("gdb server failed to send raw packet %s, %d", data, n); return -1; } return 0; } // // send a formatted packet to the client. all gdb commands and responses (other // than acks and nacks) are sent as a packet. a packet starts with the character // '$', the actual packet data, and the terminating character '#' followed by a // two-digit checksum. for example: // $packet-data#checksum // static int gdb_server_send_packet(gdb_server_t *sv, const char *data) { uint8_t cs = packet_data_checksum(data); // make sure the data won't overflow our buffer int length = (int)strlen(data); if (length > GDB_MAX_DATA_SIZE) { return -1; } snprintf(sv->conn.last_sent, sizeof(sv->conn.last_sent), "%c%s%c%02x", GDB_PACKET_BEGIN, data, GDB_PACKET_END, cs); return gdb_server_send_raw(sv, sv->conn.last_sent); } static int gdb_server_handle_ack(gdb_server_t *sv, const char *data) { GDB_SERVER_ASSERT(!sv->conn.ack_disabled); // nothing to do return 1; } static int gdb_server_handle_nack(gdb_server_t *sv, const char *data) { GDB_SERVER_ASSERT(!sv->conn.ack_disabled); // resend last packet gdb_server_send_raw(sv, sv->conn.last_sent); return 1; } static int gdb_server_handle_int3(gdb_server_t *sv, const char *data) { // halt the target sv->target.stop(sv->target.ctx); // let the client know we've stopped gdb_server_interrupt(sv, GDB_SIGNAL_TRAP); return 1; } static int gdb_server_handle_detach(gdb_server_t *sv, const char *data) { sv->target.detach(sv->target.ctx); gdb_server_destroy_client(sv); return 1; } // 'c [addr]' // Continue at addr, which is the address to resume. If addr is omitted, resume // at current address. static int gdb_server_handle_c(gdb_server_t *sv, const char *data) { int addr = 0; data = parse_hex(&data[1], &addr); if (addr != 0) { return 0; } sv->target.resume(sv->target.ctx); return 1; } // 'g' // Read general registers. // Reply: // 'XX...' // Each byte of register data is described by two hex digits. The bytes with the // register are transmitted in target byte order. The size of each register and // their position within the 'g' packet are determined by the gdb internal // gdbarch functions DEPRECATED_REGISTER_RAW_SIZE and gdbarch_register_name. The // specification of several standard 'g' packets is specified below. // When reading registers from a trace frame (see Using the Collected Data), the // stub may also return a string of literal 'x''s in place of the register data // digits, to indicate that the corresponding register has not been collected, // thus its value is unavailable. For example, for an architecture with 4 // registers of 4 bytes each, the following reply indicates to gdb that // registers 0 and 2 have not been collected, while registers 1 and 3 have been // collected, and both have zero value: // -> g // <- xxxxxxxx00000000xxxxxxxx00000000 // 'E NN' // for an error. static int gdb_server_handle_g(gdb_server_t *sv, const char *data) { char *buffer = scratch_buffer; int remaining = (int)sizeof(scratch_buffer); // reset buffer buffer[0] = 0; // read all registers for (int i = 0; i < sv->target.num_regs; i++) { // read the register intmax_t value = 0; int size = 0; sv->target.read_reg(sv->target.ctx, i, &value, &size); // format the register int n = format_register(value, size, sv->target.endian, buffer, remaining); buffer += n; remaining -= n; } // send the reply gdb_server_send_packet(sv, scratch_buffer); return 1; } // 'H op thread-id' // Set thread for subsequent operations ('m', 'M', 'g', 'G', et.al.). Depending // on the operation to be performed, op should be 'c' for step and continue // operations (note that this is deprecated, supporting the 'vCont' command is a // better option), and 'g' for other operations. The thread designator thread-id // has the format and interpretation described in thread-id syntax. // Reply: // 'OK' // for success // 'E NN' // for an error static int gdb_server_handle_H(gdb_server_t *sv, const char *data) { // TODO proper thread support int op = data[1]; GDB_SERVER_UNUSED(op); int thread = 0; data = parse_tid(&data[2], &thread); if (thread != -1 && thread != 0) { gdb_server_send_packet(sv, "E01"); } else { gdb_server_send_packet(sv, "OK"); } return 1; } // 'm addr,length' // Read length addressable memory units starting at address addr (see // addressable memory unit). Note that addr may not be aligned to any particular // boundary. // The stub need not use any particular size or alignment when gathering data // from memory for the response; even if addr is word-aligned and length is a // multiple of the word size, the stub is free to use byte accesses, or not. For // this reason, this packet may not be suitable for accessing memory-mapped I/O // devices. Reply: // 'XX...' // Memory contents; each byte is transmitted as a two-digit hexadecimal number. // The reply may contain fewer addressable memory units than requested if the // server was able to read only part of the region of memory. // 'E NN' // NN is errno static int gdb_server_handle_m(gdb_server_t *sv, const char *data) { int addr = 0; data = parse_hex(&data[1], &addr); int length = 0; data = parse_hex(&data[1], &length); // read bytes from the target uint8_t *memory = (uint8_t *)GDB_SERVER_ALLOCA(length); memset(memory, 0, length); sv->target.read_mem(sv->target.ctx, addr, memory, length); // format bytes into response char *buffer = scratch_buffer; int remaining = (int)sizeof(scratch_buffer); GDB_SERVER_ASSERT((length * 2 + 1) < remaining); // reset buffer buffer[0] = 0; for (int i = 0; i < length; i++) { int n = snprintf(buffer, remaining, "%02x", memory[i]); GDB_SERVER_ASSERT(n); buffer += n; remaining -= n; } gdb_server_send_packet(sv, scratch_buffer); return 1; } // 'p n' // Read the value of register n; n is in hex. See read registers packet, for a // description of how the returned register value is encoded. // Reply: // 'XX...' // the register's value // 'E NN' // for an error // '' // Indicating an unrecognized query. static int gdb_server_handle_p(gdb_server_t *sv, const char *data) { int reg = 0; data = parse_hex(&data[1], ®); // read the register intmax_t value = 0; int size = 0; sv->target.read_reg(sv->target.ctx, reg, &value, &size); // reset buffer scratch_buffer[0] = 0; // format the register format_register(value, size, sv->target.endian, scratch_buffer, sizeof(scratch_buffer)); // send the reply gdb_server_send_packet(sv, scratch_buffer); return 1; } // 'qAttached:pid' // Return an indication of whether the remote server attached to an existing // process or created a new process. When the multiprocess protocol extensions // are supported (see multiprocess extensions), pid is an integer in hexadecimal // format identifying the target process. Otherwise, gdb will omit the pid field // and the query packet will be simplified as 'qAttached'. // This query is used, for example, to know whether the remote process should be // detached or killed when a gdb session is ended with the quit command. static void gdb_server_handle_qAttached(gdb_server_t *sv, const char *data) { gdb_server_send_packet(sv, "1"); } // 'qC' // Return the current thread ID. static void gdb_server_handle_qC(gdb_server_t *sv, const char *data) { // TODO proper thread support gdb_server_send_packet(sv, "QC0"); } // 'qfThreadInfo' // Obtain a list of all active thread IDs from the target (OS). Since there // may be too many active threads to fit into one reply packet, this query works // iteratively: it may require more than one query/reply sequence to obtain the // entire list of threads. The first query of the sequence will be the // ‘qfThreadInfo’ query; subsequent queries in the sequence will be the // ‘qsThreadInfo’ query. static void gdb_server_handle_qfThreadInfo(gdb_server_t *sv, const char *data) { // TODO proper thread support gdb_server_send_packet(sv, "m0"); } // 'qsThreadInfo' static void gdb_server_handle_qsThreadInfo(gdb_server_t *sv, const char *data) { // TODO proper thread support gdb_server_send_packet(sv, "l"); } // 'q name params...' // General query packets. static int gdb_server_handle_q(gdb_server_t *sv, const char *data) { static query_handler_t query_handlers[] = { {"qAttached", gdb_server_handle_qAttached}, {"qC", gdb_server_handle_qC}, {"qfThreadInfo", gdb_server_handle_qfThreadInfo}, {"qsThreadInfo", gdb_server_handle_qsThreadInfo}, }; static int num_query_handlers = (int)(sizeof(query_handlers) / sizeof(query_handler_t)); // check to see if there is a handler registered for the incoming query int handled = 0; for (int i = 0; i < num_query_handlers; i++) { query_handler_t *handler = &query_handlers[i]; if (!strcmp(data, handler->name)) { handler->callback(sv, data); handled = 1; break; } } return handled; } // 'Q name params...' // General set packets. static int gdb_server_handle_Q(gdb_server_t *sv, const char *data) { return 0; } // 's [addr]' // Single step, resuming at addr. If addr is omitted, resume at same address. static int gdb_server_handle_s(gdb_server_t *sv, const char *data) { int addr = 0; data = parse_hex(&data[1], &addr); if (addr != 0) { return 0; } sv->target.step(sv->target.ctx); return 1; } // 'z type,addr,kind' // Remove a type breakpoint or watchpoint starting at address address of kind // kind. static int gdb_server_handle_z(gdb_server_t *sv, const char *data) { int type = 0; data = parse_hex(&data[1], &type); int addr = 0; data = parse_hex(&data[1], &addr); int kind = 0; data = parse_hex(&data[1], &kind); GDB_SERVER_UNUSED(kind); sv->target.rem_bp(sv->target.ctx, type, addr); gdb_server_send_packet(sv, "OK"); return 1; } // 'Z type,addr,kind' // Insert a type breakpoint or watchpoint starting at address address of kind // kind. static int gdb_server_handle_Z(gdb_server_t *sv, const char *data) { int type = 0; data = parse_hex(&data[1], &type); int addr = 0; data = parse_hex(&data[1], &addr); int kind = 0; data = parse_hex(&data[1], &kind); GDB_SERVER_UNUSED(kind); sv->target.add_bp(sv->target.ctx, type, addr); gdb_server_send_packet(sv, "OK"); return 1; } // '?' // Indicate the reason the target halted. The reply is only returned once the // target halts. static int gdb_server_handle_question(gdb_server_t *sv, const char *data) { // halt the target sv->target.stop(sv->target.ctx); // send the reply, letting gdb know we're halted gdb_server_interrupt(sv, GDB_SIGNAL_0); return 1; } // // handle a received packet, dispatching it to the appropriate handler // static void gdb_server_handle_packet(gdb_server_t *sv, const char *data) { int handled = 0; if (!strcmp(data, GDB_PACKET_ACK)) { handled = gdb_server_handle_ack(sv, data); } else if (!strcmp(data, GDB_PACKET_NACK)) { handled = gdb_server_handle_nack(sv, data); } else if (data[0] == 0x3) { handled = gdb_server_handle_int3(sv, data); } else if (data[0] == 'D') { handled = gdb_server_handle_detach(sv, data); } else if (data[0] == 'c') { handled = gdb_server_handle_c(sv, data); } else if (data[0] == 'g') { handled = gdb_server_handle_g(sv, data); } else if (data[0] == 'H') { handled = gdb_server_handle_H(sv, data); } else if (data[0] == 'm') { handled = gdb_server_handle_m(sv, data); } else if (data[0] == 'p') { handled = gdb_server_handle_p(sv, data); } else if (data[0] == 'q') { handled = gdb_server_handle_q(sv, data); } else if (data[0] == 'Q') { handled = gdb_server_handle_Q(sv, data); } else if (data[0] == 's') { handled = gdb_server_handle_s(sv, data); } else if (data[0] == 'z') { handled = gdb_server_handle_z(sv, data); } else if (data[0] == 'Z') { handled = gdb_server_handle_Z(sv, data); } else if (!strcmp(data, "?")) { handled = gdb_server_handle_question(sv, data); } if (!handled) { GDB_SERVER_LOG("Unsupported packet %s", data); gdb_server_send_packet(sv, ""); } } #endif