/* * 86Box A hypervisor and IBM PC system emulator that specializes in * running old operating systems and software designed for IBM * PC systems and compatibles from 1981 through fairly recent * system designs based on the PCI bus. * * This file is part of the 86Box distribution. * * GDB stub server for remote debugging. * * * * Authors: RichardG, * * Copyright 2022 RichardG. */ #include #include #include #include #include #include #include #ifdef _WIN32 # include # include #else # include # include # include #endif #define HAVE_STDARG_H #include <86box/86box.h> #include "cpu.h" #include "x86seg.h" #include "x87.h" #include "x87_ops_conv.h" #include <86box/io.h> #include <86box/mem.h> #include <86box/plat.h> #include <86box/thread.h> #include <86box/gdbstub.h> #define FAST_RESPONSE(s) \ strcpy(client->response, s); \ client->response_pos = sizeof(s); #define FAST_RESPONSE_HEX(s) gdbstub_client_respond_hex(client, (uint8_t *) s, sizeof(s)); enum { GDB_SIGINT = 2, GDB_SIGTRAP = 5 }; enum { GDB_REG_EAX = 0, GDB_REG_ECX, GDB_REG_EDX, GDB_REG_EBX, GDB_REG_ESP, GDB_REG_EBP, GDB_REG_ESI, GDB_REG_EDI, GDB_REG_EIP, GDB_REG_EFLAGS, GDB_REG_CS, GDB_REG_SS, GDB_REG_DS, GDB_REG_ES, GDB_REG_FS, GDB_REG_GS, GDB_REG_FS_BASE, GDB_REG_GS_BASE, GDB_REG_CR0, GDB_REG_CR2, GDB_REG_CR3, GDB_REG_CR4, GDB_REG_EFER, GDB_REG_ST0, GDB_REG_ST1, GDB_REG_ST2, GDB_REG_ST3, GDB_REG_ST4, GDB_REG_ST5, GDB_REG_ST6, GDB_REG_ST7, GDB_REG_FCTRL, GDB_REG_FSTAT, GDB_REG_FTAG, GDB_REG_FISEG, GDB_REG_FIOFF, GDB_REG_FOSEG, GDB_REG_FOOFF, GDB_REG_FOP, GDB_REG_MM0, GDB_REG_MM1, GDB_REG_MM2, GDB_REG_MM3, GDB_REG_MM4, GDB_REG_MM5, GDB_REG_MM6, GDB_REG_MM7, GDB_REG_MAX }; enum { GDB_MODE_BASE10 = 0, GDB_MODE_HEX, GDB_MODE_OCT, GDB_MODE_BIN }; typedef struct _gdbstub_client_ { int socket; struct sockaddr_in addr; char packet[16384], response[16384]; int has_packet, waiting_stop, packet_pos, response_pos; event_t *processed_event, *response_event; uint16_t last_io_base, last_io_len; struct _gdbstub_client_ *next; } gdbstub_client_t; typedef struct _gdbstub_breakpoint_ { uint32_t addr; union { uint8_t orig_val; uint32_t end; }; struct _gdbstub_breakpoint_ *next; } gdbstub_breakpoint_t; #define ENABLE_GDBSTUB_LOG 1 #ifdef ENABLE_GDBSTUB_LOG int gdbstub_do_log = ENABLE_GDBSTUB_LOG; static void gdbstub_log(const char *fmt, ...) { va_list ap; if (gdbstub_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else # define gdbstub_log(fmt, ...) #endif static x86seg *segment_regs[] = { &cpu_state.seg_cs, &cpu_state.seg_ss, &cpu_state.seg_ds, &cpu_state.seg_es, &cpu_state.seg_fs, &cpu_state.seg_gs }; static uint32_t *cr_regs[] = { &cpu_state.CR0.l, &cr2, &cr3, &cr4 }; static void *fpu_regs[] = { &cpu_state.npxc, &cpu_state.npxs, NULL, &x87_pc_seg, &x87_pc_off, &x87_op_seg, &x87_op_off }; static const char target_xml[] = /* based on qemu's i386-32bit.xml */ // clang-format off "" "" "" "i8086" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; // clang-format on #ifdef _WIN32 static WSADATA wsa; #endif static int gdbstub_socket = -1, stop_reason_len = 0, in_gdbstub = 0; static uint32_t watch_addr; static char stop_reason[2048]; static gdbstub_client_t *first_client = NULL, *last_client = NULL; static mutex_t *client_list_mutex; static void (*cpu_exec_shadow)(int cycs); static gdbstub_breakpoint_t *first_swbreak = NULL, *first_hwbreak = NULL, *first_rwatch = NULL, *first_wwatch = NULL, *first_awatch = NULL; int gdbstub_step = 0, gdbstub_next_asap = 0; uint64_t gdbstub_watch_pages[(((uint32_t) -1) >> (MEM_GRANULARITY_BITS + 6)) + 1]; static void gdbstub_break() { /* Pause CPU execution as soon as possible. */ if (gdbstub_step <= GDBSTUB_EXEC) gdbstub_step = GDBSTUB_BREAK; } static void gdbstub_jump(uint32_t new_pc) { /* Nasty hack; qemu always uses the full 32-bit EIP internally... */ if (cpu_state.op32 || ((new_pc >= cs) && (new_pc < (cs + 65536)))) { cpu_state.pc = new_pc - cs; } else { loadseg((new_pc >> 4) & 0xf000, &cpu_state.seg_cs); cpu_state.pc = new_pc & 0xffff; } flushmmucache(); } static inline int gdbstub_hex_decode(int c) { if ((c >= '0') && (c <= '9')) return c - '0'; else if ((c >= 'A') && (c <= 'F')) return c - 'A' + 10; else if ((c >= 'a') && (c <= 'f')) return c - 'a' + 10; else return 0; } static inline int gdbstub_hex_encode(int c) { if (c < 10) return c + '0'; else return c - 10 + 'A'; } static int gdbstub_num_decode(char *p, int *dest, int mode) { /* Stop if the pointer is invalid. */ if (!p) return 0; /* Read sign. */ int sign = 1; if ((p[0] == '+') || (p[0] == '-')) { if (p[0] == '-') sign = -1; p++; } /* Read type identifer if present (0x/0o/0b/0n) */ if (p[0] == '0') { switch (p[1]) { case 'x': mode = GDB_MODE_HEX; break; case '0' ... '7': p -= 1; /* fall-through */ case 'o': mode = GDB_MODE_OCT; break; case 'b': mode = GDB_MODE_BIN; break; case 'n': mode = GDB_MODE_BASE10; break; default: p -= 2; break; } p += 2; } /* Parse each character. */ *dest = 0; while (*p) { switch (mode) { case GDB_MODE_BASE10: if ((*p >= '0') && (*p <= '9')) *dest = ((*dest) * 10) + ((*p) - '0'); else return 0; break; case GDB_MODE_HEX: if (((*p >= '0') && (*p <= '9')) || ((*p >= 'A') && (*p <= 'F')) || ((*p >= 'a') && (*p <= 'f'))) *dest = ((*dest) << 4) | gdbstub_hex_decode(*p); else return 0; break; case GDB_MODE_OCT: if ((*p >= '0') && (*p <= '7')) *dest = ((*dest) << 3) | ((*p) - '0'); else return 0; break; case GDB_MODE_BIN: if ((*p == '0') || (*p == '1')) *dest = ((*dest) << 1) | ((*p) - '0'); else return 0; break; } p++; } /* Apply sign. */ if (sign < 0) *dest = -(*dest); /* Return success. */ return 1; } static int gdbstub_client_read_word(gdbstub_client_t *client, int *dest) { char *p = &client->packet[client->packet_pos], *q = p; while (((*p >= '0') && (*p <= '9')) || ((*p >= 'A') && (*p <= 'F')) || ((*p >= 'a') && (*p <= 'f'))) *dest = ((*dest) << 4) | gdbstub_hex_decode(*p++); return p - q; } static int gdbstub_client_read_hex(gdbstub_client_t *client, uint8_t *buf, int size) { int pp = client->packet_pos; while (size-- && (pp < (sizeof(client->packet) - 2))) { *buf = gdbstub_hex_decode(client->packet[pp++]) << 4; *buf++ |= gdbstub_hex_decode(client->packet[pp++]); } return pp - client->packet_pos; } static int gdbstub_client_read_string(gdbstub_client_t *client, char *buf, int size, char terminator) { int pp = client->packet_pos; char c; while (size-- && (pp < (sizeof(client->packet) - 1))) { c = client->packet[pp]; if ((c == terminator) || (c == '\0')) { *buf = '\0'; break; } pp++; *buf++ = c; } return pp - client->packet_pos; } static int gdbstub_client_write_reg(int index, uint8_t *buf) { int width = 4; switch (index) { case GDB_REG_EAX ... GDB_REG_EDI: cpu_state.regs[index - GDB_REG_EAX].l = *((uint32_t *) buf); break; case GDB_REG_EIP: gdbstub_jump(*((uint32_t *) buf)); break; case GDB_REG_EFLAGS: cpu_state.flags = *((uint16_t *) &buf[0]); cpu_state.eflags = *((uint16_t *) &buf[2]); break; case GDB_REG_CS ... GDB_REG_GS: width = 2; loadseg(*((uint16_t *) buf), segment_regs[index - GDB_REG_CS]); flushmmucache(); break; case GDB_REG_FS_BASE ... GDB_REG_GS_BASE: /* Do what qemu does and just load the base. */ segment_regs[(index - 16) + (GDB_REG_FS - GDB_REG_CS)]->base = *((uint32_t *) buf); break; case GDB_REG_CR0 ... GDB_REG_CR4: *cr_regs[index - GDB_REG_CR0] = *((uint32_t *) buf); flushmmucache(); break; case GDB_REG_EFER: msr.amd_efer = *((uint64_t *) buf); break; case GDB_REG_ST0 ... GDB_REG_ST7: width = 10; x87_conv_t conv = { .eind = { .ll = *((uint64_t *) &buf[0]) }, .begin = *((uint16_t *) &buf[8]) }; cpu_state.ST[(cpu_state.TOP + (index - GDB_REG_ST0)) & 7] = x87_from80(&conv); break; case GDB_REG_FCTRL: case GDB_REG_FISEG: case GDB_REG_FOSEG: width = 2; *((uint16_t *) fpu_regs[index - GDB_REG_FCTRL]) = *((uint16_t *) buf); if (index >= GDB_REG_FISEG) flushmmucache(); break; case GDB_REG_FSTAT: case GDB_REG_FOP: width = 2; break; case GDB_REG_FTAG: width = 2; x87_settag(*((uint16_t *) buf)); break; case GDB_REG_FIOFF: case GDB_REG_FOOFF: *((uint32_t *) fpu_regs[index - GDB_REG_FCTRL]) = *((uint32_t *) buf); break; case GDB_REG_MM0 ... GDB_REG_MM7: width = 8; cpu_state.MM[index - GDB_REG_MM0].q = *((uint64_t *) &buf); break; default: width = 0; } return width; } static void gdbstub_client_respond(gdbstub_client_t *client) { /* Calculate checksum. */ int checksum = 0, i; for (i = 0; i < client->response_pos; i++) checksum += client->response[i]; /* Send response packet. */ client->response[client->response_pos] = '\0'; #ifdef ENABLE_GDBSTUB_LOG i = client->response[995]; /* pclog_ex buffer too small */ client->response[995] = '\0'; gdbstub_log("GDB Stub: Sending response: %s\n", client->response); client->response[995] = i; #endif send(client->socket, "$", 1, 0); send(client->socket, client->response, client->response_pos, 0); char response_cksum[3] = { '#', gdbstub_hex_encode((checksum >> 4) & 0x0f), gdbstub_hex_encode(checksum & 0x0f) }; send(client->socket, response_cksum, sizeof(response_cksum), 0); } static void gdbstub_client_respond_partial(gdbstub_client_t *client) { /* Send response. */ gdbstub_client_respond(client); /* Wait for the response to be acknowledged. */ thread_wait_event(client->response_event, -1); thread_reset_event(client->response_event); } static void gdbstub_client_respond_hex(gdbstub_client_t *client, uint8_t *buf, int size) { while (size-- && (client->response_pos < (sizeof(client->response) - 2))) { client->response[client->response_pos++] = gdbstub_hex_encode((*buf) >> 4); client->response[client->response_pos++] = gdbstub_hex_encode((*buf++) & 0x0f); } } static int gdbstub_client_read_reg(int index, uint8_t *buf) { int width = 4; switch (index) { case GDB_REG_EAX ... GDB_REG_EDI: *((uint32_t *) buf) = cpu_state.regs[index].l; break; case GDB_REG_EIP: *((uint32_t *) buf) = cs + cpu_state.pc; break; case GDB_REG_EFLAGS: *((uint16_t *) &buf[0]) = cpu_state.flags; *((uint16_t *) &buf[2]) = cpu_state.eflags; break; case GDB_REG_CS ... GDB_REG_GS: *((uint16_t *) buf) = segment_regs[index - GDB_REG_CS]->seg; break; case GDB_REG_FS_BASE ... GDB_REG_GS_BASE: *((uint32_t *) buf) = segment_regs[(index - 16) + (GDB_REG_FS - GDB_REG_CS)]->base; break; case GDB_REG_CR0 ... GDB_REG_CR4: *((uint32_t *) buf) = *cr_regs[index - GDB_REG_CR0]; break; case GDB_REG_EFER: *((uint64_t *) buf) = msr.amd_efer; break; case GDB_REG_ST0 ... GDB_REG_ST7: width = 10; x87_conv_t conv; x87_to80(cpu_state.ST[(cpu_state.TOP + (index - GDB_REG_ST0)) & 7], &conv); *((uint64_t *) &buf[0]) = conv.eind.ll; *((uint16_t *) &buf[8]) = conv.begin; break; case GDB_REG_FCTRL ... GDB_REG_FSTAT: case GDB_REG_FISEG: case GDB_REG_FOSEG: width = 2; *((uint16_t *) buf) = *((uint16_t *) fpu_regs[index - GDB_REG_FCTRL]); break; case GDB_REG_FTAG: width = 2; *((uint16_t *) buf) = x87_gettag(); break; case GDB_REG_FIOFF: case GDB_REG_FOOFF: *((uint32_t *) buf) = *((uint32_t *) fpu_regs[index - GDB_REG_FCTRL]); break; case GDB_REG_FOP: width = 2; *((uint16_t *) buf) = 0; /* we don't store the FPU opcode */ break; case GDB_REG_MM0 ... GDB_REG_MM7: width = 8; cpu_state.MM[index - GDB_REG_MM0].q = *((uint64_t *) &buf); break; default: width = 0; } return width; } static void gdbstub_client_packet(gdbstub_client_t *client) { #ifdef GDBSTUB_CHECK_CHECKSUM /* msys2 gdb 11.1 transmits qSupported and H with invalid checksum... */ uint8_t rcv_checksum = 0, checksum = 0; #endif int i, j = 0, k = 0, l; uint8_t buf[10] = { 0 }; char *p; /* Validate checksum. */ client->packet_pos -= 2; #ifdef GDBSTUB_CHECK_CHECKSUM gdbstub_client_read_hex(client, &rcv_checksum, 1); #endif *((uint16_t *) &client->packet[--client->packet_pos]) = 0; #ifdef GDBSTUB_CHECK_CHECKSUM for (i = 0; i < client->packet_pos; i++) checksum += client->packet[i]; if (checksum != rcv_checksum) { /* Send negative acknowledgement. */ # ifdef ENABLE_GDBSTUB_LOG i = client->packet[953]; /* pclog_ex buffer too small */ client->packet[953] = '\0'; gdbstub_log("GDB Stub: Received packet with invalid checksum (expected %02X got %02X): %s\n", checksum, rcv_checksum, client->packet); client->packet[953] = i; # endif send(client->socket, "-", 1, 0); return; } #endif /* Send positive acknowledgement. */ #ifdef ENABLE_GDBSTUB_LOG i = client->packet[996]; /* pclog_ex buffer too small */ client->packet[996] = '\0'; gdbstub_log("GDB Stub: Received packet: %s\n", client->packet); client->packet[996] = i; #endif send(client->socket, "+", 1, 0); /* Block other responses from being written while this one (if any is produced) isn't acknowledged. */ if ((client->packet[0] != 'c') && (client->packet[0] != 's')) { thread_wait_event(client->response_event, -1); thread_reset_event(client->response_event); } client->response_pos = 0; client->packet_pos = 1; /* Parse command. */ switch (client->packet[0]) { case '?': /* stop reason */ /* Respond with a stop reply packet if one is present. */ if (stop_reason_len) { strcpy(client->response, stop_reason); client->response_pos = strlen(client->response); } break; case 'c': /* continue */ case 's': /* step */ /* Flag that the client is waiting for a stop reason. */ client->waiting_stop = 1; /* Jump to address if specified. */ if (client->packet[1] && gdbstub_client_read_word(client, &j)) gdbstub_jump(j); /* Resume CPU. */ gdbstub_step = gdbstub_next_asap = (client->packet[0] == 's') ? GDBSTUB_SSTEP : GDBSTUB_EXEC; return; case 'D': /* detach */ /* Resume emulation. */ gdbstub_step = GDBSTUB_EXEC; /* Respond positively. */ ok: FAST_RESPONSE("OK"); break; case 'g': /* read all registers */ /* Output the values of all registers. */ for (i = 0; i < GDB_REG_MAX; i++) gdbstub_client_respond_hex(client, buf, gdbstub_client_read_reg(i, buf)); break; case 'G': /* write all registers */ /* Write the values of all registers. */ for (i = 0; i < GDB_REG_MAX; i++) { if (!gdbstub_client_read_hex(client, buf, sizeof(buf))) break; client->packet_pos += gdbstub_client_write_reg(i, buf); } break; case 'H': /* set thread */ /* Read operation type and thread ID. */ if ((client->packet[1] == '\0') || (client->packet[2] == '\0')) { e22: FAST_RESPONSE("E22"); break; } /* Respond positively only on thread 1. */ if ((client->packet[2] == '1') && !client->packet[3]) goto ok; else goto e22; case 'm': /* read memory */ /* Read address and length. */ if (!(i = gdbstub_client_read_word(client, &j))) goto e22; client->packet_pos += i + 1; gdbstub_client_read_word(client, &k); if (!k) goto e22; /* Clamp length. */ if (k >= (sizeof(client->response) >> 1)) k = (sizeof(client->response) >> 1) - 1; /* Read by qwords, then by dwords, then by words, then by bytes. */ i = 0; if (is386) { for (; i < (k & ~7); i += 8) { *((uint64_t *) buf) = readmemql(j); j += 8; gdbstub_client_respond_hex(client, buf, 8); } for (; i < (k & ~3); i += 4) { *((uint32_t *) buf) = readmemll(j); j += 4; gdbstub_client_respond_hex(client, buf, 4); } } for (; i < (k & ~1); i += 2) { *((uint16_t *) buf) = readmemwl(j); j += 2; gdbstub_client_respond_hex(client, buf, 2); } for (; i < k; i++) { buf[0] = readmembl(j++); gdbstub_client_respond_hex(client, buf, 1); } break; case 'M': /* write memory */ case 'X': /* write memory binary */ /* Read address and length. */ if (!(i = gdbstub_client_read_word(client, &j))) goto e22; client->packet_pos += i + 1; client->packet_pos += gdbstub_client_read_word(client, &k) + 1; if (!k) goto e22; /* Clamp length. */ if (k >= ((sizeof(client->response) >> 1) - client->packet_pos)) k = (sizeof(client->response) >> 1) - client->packet_pos - 1; /* Decode the data. */ if (client->packet[0] == 'M') { /* hex encoded */ gdbstub_client_read_hex(client, (uint8_t *) client->packet, k); } else { /* binary encoded */ i = 0; while (i < k) { if (client->packet[client->packet_pos] == '}') { client->packet_pos++; client->packet[i++] = client->packet[client->packet_pos++] ^ 0x20; } else { client->packet[i++] = client->packet[client->packet_pos++]; } } } /* Write by qwords, then by dwords, then by words, then by bytes. */ p = client->packet; i = 0; if (is386) { for (; i < (k & ~7); i += 8) { writememql(j, *((uint64_t *) p)); j += 8; p += 8; } for (; i < (k & ~3); i += 4) { writememll(j, *((uint32_t *) p)); j += 4; p += 4; } } for (; i < (k & ~1); i += 2) { writememwl(j, *((uint16_t *) p)); j += 2; p += 2; } for (; i < k; i++) { writemembl(j++, p[0]); p++; } /* Respond positively. */ goto ok; case 'p': /* read register */ /* Read register index. */ if (!gdbstub_client_read_word(client, &j)) { e14: FAST_RESPONSE("E14"); break; } /* Read the register's value. */ if (!(i = gdbstub_client_read_reg(j, buf))) goto e14; /* Return value. */ gdbstub_client_respond_hex(client, buf, i); break; case 'P': /* write register */ /* Read register index and value. */ if (!(i = gdbstub_client_read_word(client, &j))) goto e14; client->packet_pos += i + 1; if (!gdbstub_client_read_hex(client, buf, sizeof(buf))) goto e14; /* Write the value to the register. */ if (!gdbstub_client_write_reg(j, buf)) goto e14; /* Respond positively. */ goto ok; case 'q': /* query */ /* Erase response, as we'll use it as a scratch buffer. */ memset(client->response, 0, sizeof(client->response)); /* Read the query type. */ client->packet_pos += gdbstub_client_read_string(client, client->response, sizeof(client->response) - 1, (client->packet[1] == 'R') ? ',' : ':') + 1; /* Perform the query. */ if (!strcmp(client->response, "Supported")) { /* Go through the feature list and negate ones we don't support. */ while ((client->response_pos < (sizeof(client->response) - 1)) && (i = gdbstub_client_read_string(client, &client->response[client->response_pos], sizeof(client->response) - client->response_pos - 1, ';'))) { client->packet_pos += i + 1; if (strncmp(&client->response[client->response_pos], "PacketSize", 10) && strcmp(&client->response[client->response_pos], "swbreak") && strcmp(&client->response[client->response_pos], "hwbreak") && strncmp(&client->response[client->response_pos], "xmlRegisters", 12) && strcmp(&client->response[client->response_pos], "qXfer:features:read")) { gdbstub_log("GDB Stub: Feature \"%s\" is not supported\n", &client->response[client->response_pos]); client->response_pos += i; client->response[client->response_pos++] = '-'; client->response[client->response_pos++] = ';'; } else { gdbstub_log("GDB Stub: Feature \"%s\" is supported\n", &client->response[client->response_pos]); } } /* Add our supported features to the end. */ if (client->response_pos < (sizeof(client->response) - 1)) client->response_pos += snprintf(&client->response[client->response_pos], sizeof(client->response) - client->response_pos, "PacketSize=%lX;swbreak+;hwbreak+;qXfer:features:read+", sizeof(client->packet) - 1); break; } else if (!strcmp(client->response, "Xfer")) { /* Read the transfer object. */ client->packet_pos += gdbstub_client_read_string(client, client->response, sizeof(client->response) - 1, ':') + 1; if (!strcmp(client->response, "features")) { /* Read the transfer operation. */ client->packet_pos += gdbstub_client_read_string(client, client->response, sizeof(client->response) - 1, ':') + 1; if (!strcmp(client->response, "read")) { /* Read the transfer annex. */ client->packet_pos += gdbstub_client_read_string(client, client->response, sizeof(client->response) - 1, ':') + 1; if (!strcmp(client->response, "target.xml")) p = (char *) target_xml; else p = NULL; /* Stop if the file wasn't found. */ if (!p) { e00: FAST_RESPONSE("E00"); break; } /* Read offset and length. */ if (!(i = gdbstub_client_read_word(client, &j))) goto e22; client->packet_pos += i + 1; client->packet_pos += gdbstub_client_read_word(client, &k) + 1; if (!k) goto e22; /* Check if the offset is valid. */ l = strlen(p); if (j > l) goto e00; p += j; /* Return the more/less flag while also clamping the length. */ if (k >= ((sizeof(client->response) >> 1) - 2)) k = (sizeof(client->response) >> 1) - 3; if (k < (l - j)) { client->response[client->response_pos++] = 'm'; } else { client->response[client->response_pos++] = 'l'; k = l - j; } /* Encode the data. */ while (k--) { i = *p++; if ((i == '\0') || (i == '#') || (i == '$') || (i == '*') || (i == '}')) { client->response[client->response_pos++] = '}'; client->response[client->response_pos++] = i ^ 0x20; } else { client->response[client->response_pos++] = i; } } break; } } } else if (!strcmp(client->response, "Rcmd")) { /* Read and decode command in-place. */ i = gdbstub_client_read_hex(client, (uint8_t *) client->packet, strlen(client->packet) - client->packet_pos); client->packet[i] = 0; gdbstub_log("GDB Stub: Monitor command: %s\n", client->packet); /* Parse the command name. */ char *strtok_save; p = strtok_r(client->packet, " ", &strtok_save); if (!p) goto ok; /* Interpret the command. */ if (p[0] == 'i') { /* Read I/O operation width. */ l = (p[1] == 'n') ? p[2] : p[1]; /* Read optional I/O port. */ if (!(p = strtok_r(NULL, " ", &strtok_save)) || !gdbstub_num_decode(p, &j, GDB_MODE_HEX) || (j < 0) || (j >= 65536)) j = client->last_io_base; /* Read optional length. */ if (!(p = strtok_r(NULL, " ", &strtok_save)) || !gdbstub_num_decode(p, &k, GDB_MODE_BASE10)) k = client->last_io_len; /* Clamp length. */ if (k < 1) k = 1; if (k > (65536 - j)) k = 65536 - j; /* Read ports. */ i = 0; while (i < k) { if ((i % 16) == 0) { if (i) { client->packet[client->packet_pos++] = '\n'; /* Provide partial response with the last line. */ client->response_pos = 0; client->response[client->response_pos++] = 'O'; gdbstub_client_respond_hex(client, (uint8_t *) client->packet, client->packet_pos); gdbstub_client_respond_partial(client); } client->packet_pos = sprintf(client->packet, "%04X:", j + i); } /* Act according to I/O operation width. */ switch (l) { case 'd': case 'l': client->packet_pos += sprintf(&client->packet[client->packet_pos], " %08X", inl(j + i)); i += 4; break; case 'w': client->packet_pos += sprintf(&client->packet[client->packet_pos], " %04X", inw(j + i)); i += 2; break; case 'b': case '\0': client->packet_pos += sprintf(&client->packet[client->packet_pos], " %02X", inb(j + i)); i++; break; default: goto unknown; } } client->packet[client->packet_pos++] = '\n'; /* Respond with the final line. */ client->response_pos = 0; gdbstub_client_respond_hex(client, (uint8_t *) &client->packet, client->packet_pos); break; } else { unknown: FAST_RESPONSE_HEX("Unknown command\n"); break; } goto ok; } break; case 'z': /* remove break/watchpoint */ case 'Z': /* insert break/watchpoint */ { gdbstub_breakpoint_t *breakpoint, *prev_breakpoint = NULL, **first_breakpoint; /* Parse breakpoint type. */ switch (client->packet[1]) { case '0': /* software breakpoint */ first_breakpoint = &first_swbreak; break; case '1': /* hardware breakpoint */ first_breakpoint = &first_hwbreak; break; case '2': /* write watchpoint */ first_breakpoint = &first_wwatch; break; case '3': /* read watchpoint */ first_breakpoint = &first_rwatch; break; case '4': /* access watchpoint */ first_breakpoint = &first_awatch; break; default: /* unknown type */ client->packet[2] = '\0'; /* force address check to fail */ break; } /* Read address. */ if (client->packet[2] != ',') break; client->packet_pos = 3; if (!(i = gdbstub_client_read_word(client, &j))) break; client->packet_pos += i; if (client->packet[client->packet_pos++] == ',') gdbstub_client_read_word(client, &k); else k = 1; /* Test writability of software breakpoint. */ if (client->packet[1] == '0') { buf[0] = readmembl(j); writemembl(j, 0xcc); buf[1] = readmembl(j); writemembl(j, buf[0]); if (buf[1] != 0xcc) goto end; } /* Find an existing breakpoint with this address. */ breakpoint = *first_breakpoint; while (breakpoint) { if (breakpoint->addr == j) break; prev_breakpoint = breakpoint; breakpoint = breakpoint->next; } /* Check if the breakpoint is already present (when inserting) or not found (when removing). */ if ((!!breakpoint) ^ (client->packet[0] == 'z')) goto e22; /* Insert or remove the breakpoint. */ if (client->packet[0] != 'z') { /* Allocate a new breakpoint. */ breakpoint = malloc(sizeof(gdbstub_breakpoint_t)); breakpoint->addr = j; breakpoint->end = j + k; breakpoint->next = NULL; /* Add the new breakpoint to the list. */ if (!(*first_breakpoint)) *first_breakpoint = breakpoint; else if (prev_breakpoint) prev_breakpoint->next = breakpoint; } else { /* Remove breakpoint from the list. */ if (breakpoint == *first_breakpoint) *first_breakpoint = breakpoint->next; else if (prev_breakpoint) prev_breakpoint->next = breakpoint->next; /* De-allocate breakpoint. */ free(breakpoint); } /* Update the page watchpoint map if we're dealing with a watchpoint. */ if (client->packet[1] >= '2') { /* Clear this watchpoint's corresponding page map groups, as everything is going to be recomputed soon anyway. */ memset(&gdbstub_watch_pages[j >> (MEM_GRANULARITY_BITS + 6)], 0, (((k - 1) >> (MEM_GRANULARITY_BITS + 6)) + 1) * sizeof(gdbstub_watch_pages[0])); /* Go through all watchpoint lists. */ l = 0; breakpoint = first_rwatch; while (1) { if (breakpoint) { /* Flag this watchpoint's corresponding pages as having a watchpoint. */ k = (breakpoint->end - 1) >> MEM_GRANULARITY_BITS; for (i = breakpoint->addr >> MEM_GRANULARITY_BITS; i <= k; i++) gdbstub_watch_pages[i >> 6] |= (1 << (i & 63)); breakpoint = breakpoint->next; } else { /* Jump from list to list as a shortcut. */ if (l == 0) breakpoint = first_wwatch; else if (l == 1) breakpoint = first_awatch; else break; l++; } } } /* Respond positively. */ goto ok; } } end: /* Send response. */ gdbstub_client_respond(client); } static void gdbstub_cpu_exec(int cycs) { /* Flag that we're now in the debugger context to avoid triggering watchpoints. */ in_gdbstub = 1; /* Handle CPU execution if it isn't paused. */ if (gdbstub_step <= GDBSTUB_SSTEP) { /* Swap in any software breakpoints. */ gdbstub_breakpoint_t *swbreak = first_swbreak; while (swbreak) { /* Swap the INT 3 opcode into the address. */ swbreak->orig_val = readmembl(swbreak->addr); writemembl(swbreak->addr, 0xcc); swbreak = swbreak->next; } /* Call the original cpu_exec function outside the debugger context. */ if ((gdbstub_step == GDBSTUB_SSTEP) && ((cycles + cycs) <= 0)) cycs += -(cycles + cycs) + 1; in_gdbstub = 0; cpu_exec_shadow(cycs); in_gdbstub = 1; /* Swap out any software breakpoints. */ swbreak = first_swbreak; while (swbreak) { if (readmembl(swbreak->addr) == 0xcc) writemembl(swbreak->addr, swbreak->orig_val); swbreak = swbreak->next; } } /* Populate stop reason if we have stopped. */ stop_reason_len = 0; if (gdbstub_step > GDBSTUB_EXEC) { /* Assemble stop reason manually (avoiding sprintf and friends) for performance. */ stop_reason[stop_reason_len++] = 'T'; stop_reason[stop_reason_len++] = '0'; stop_reason[stop_reason_len++] = '0' + ((gdbstub_step == GDBSTUB_BREAK) ? GDB_SIGINT : GDB_SIGTRAP); /* Add extended break reason. */ if (gdbstub_step >= GDBSTUB_BREAK_RWATCH) { if (gdbstub_step != GDBSTUB_BREAK_WWATCH) stop_reason[stop_reason_len++] = (gdbstub_step == GDBSTUB_BREAK_RWATCH) ? 'r' : 'a'; stop_reason[stop_reason_len++] = 'w'; stop_reason[stop_reason_len++] = 'a'; stop_reason[stop_reason_len++] = 't'; stop_reason[stop_reason_len++] = 'c'; stop_reason[stop_reason_len++] = 'h'; stop_reason[stop_reason_len++] = ':'; stop_reason_len += sprintf(&stop_reason[stop_reason_len], "%X;", watch_addr); } else if (gdbstub_step >= GDBSTUB_BREAK_SW) { stop_reason[stop_reason_len++] = (gdbstub_step == GDBSTUB_BREAK_SW) ? 's' : 'h'; stop_reason[stop_reason_len++] = 'w'; stop_reason[stop_reason_len++] = 'b'; stop_reason[stop_reason_len++] = 'r'; stop_reason[stop_reason_len++] = 'e'; stop_reason[stop_reason_len++] = 'a'; stop_reason[stop_reason_len++] = 'k'; stop_reason[stop_reason_len++] = ':'; stop_reason[stop_reason_len++] = ';'; } /* Add register dump. */ uint8_t buf[10] = { 0 }; int i, j, k; for (i = 0; i < GDB_REG_MAX; i++) { if (i >= 0x10) stop_reason[stop_reason_len++] = gdbstub_hex_encode(i >> 4); stop_reason[stop_reason_len++] = gdbstub_hex_encode(i & 0x0f); stop_reason[stop_reason_len++] = ':'; j = gdbstub_client_read_reg(i, buf); for (k = 0; k < j; k++) { stop_reason[stop_reason_len++] = gdbstub_hex_encode(buf[k] >> 4); stop_reason[stop_reason_len++] = gdbstub_hex_encode(buf[k] & 0x0f); } stop_reason[stop_reason_len++] = ';'; } /* Don't execute the CPU any further if single-stepping. */ gdbstub_step = GDBSTUB_BREAK; } /* Return the framerate to normal. */ gdbstub_next_asap = 0; /* Process client packets. */ thread_wait_mutex(client_list_mutex); gdbstub_client_t *client = first_client; while (client) { /* Report stop reason if the client is waiting for one. */ if (client->waiting_stop && stop_reason_len) { client->waiting_stop = 0; /* Wait for any pending responses to be acknowledged. */ if (!thread_wait_event(client->response_event, -1)) { /* Block other responses from being written while this one isn't acknowledged. */ thread_reset_event(client->response_event); /* Write stop reason response. */ strcpy(client->response, stop_reason); client->response_pos = stop_reason_len; gdbstub_client_respond(client); } else { gdbstub_log("GDB Stub: Timed out waiting for client %s:%d\n", inet_ntoa(client->addr.sin_addr), client->addr.sin_port); } } if (client->has_packet) { gdbstub_client_packet(client); client->has_packet = client->packet_pos = 0; thread_set_event(client->processed_event); } #ifdef GDBSTUB_ALLOW_MULTI_CLIENTS client = client->next; #else break; #endif } thread_release_mutex(client_list_mutex); /* Flag that we're now out of the debugger context. */ in_gdbstub = 0; } static void gdbstub_client_thread(void *priv) { gdbstub_client_t *client = (gdbstub_client_t *) priv; uint8_t buf[256]; ssize_t bytes_read; int i; gdbstub_log("GDB Stub: New connection from %s:%d\n", inet_ntoa(client->addr.sin_addr), client->addr.sin_port); /* Allow packets to be processed. */ thread_set_event(client->processed_event); /* Read data from client. */ while ((bytes_read = recv(client->socket, (char *) buf, sizeof(buf), 0)) > 0) { for (i = 0; i < bytes_read; i++) { switch (buf[i]) { case '$': /* packet start */ /* Wait for any existing packets to be processed. */ thread_wait_event(client->processed_event, -1); client->packet_pos = 0; break; case '-': /* negative acknowledgement */ /* Retransmit the current response. */ gdbstub_client_respond(client); break; case '+': /* positive acknowledgement */ /* Allow another response to be written. */ thread_set_event(client->response_event); break; case 0x03: /* break */ /* Wait for any existing packets to be processed. */ thread_wait_event(client->processed_event, -1); /* Break immediately. */ gdbstub_log("GDB Stub: Break requested\n"); gdbstub_break(); break; default: /* Wait for any existing packets to be processed, just in case. */ thread_wait_event(client->processed_event, -1); if (client->packet_pos < (sizeof(client->packet) - 1)) { /* Append byte to the packet. */ client->packet[client->packet_pos++] = buf[i]; /* Check if we're at the end of a packet. */ if ((client->packet_pos >= 3) && (client->packet[client->packet_pos - 3] == '#')) { /* packet checksum start */ /* Small hack to speed up IDA instruction trace mode. */ if (*((uint32_t *) client->packet) == ('H' | ('c' << 8) | ('1' << 16) | ('#' << 24))) { /* Send pre-computed response. */ send(client->socket, "+$OK#9A", 7, 0); /* Skip processing. */ continue; } /* Flag that a packet should be processed. */ client->packet[client->packet_pos] = '\0'; thread_reset_event(client->processed_event); gdbstub_next_asap = client->has_packet = 1; } } break; } } } gdbstub_log("GDB Stub: Connection with %s:%d broken\n", inet_ntoa(client->addr.sin_addr), client->addr.sin_port); /* Close socket. */ if (client->socket != -1) { close(client->socket); client->socket = -1; } /* Unblock anyone waiting on the response event. */ thread_set_event(client->response_event); /* Remove this client from the list. */ thread_wait_mutex(client_list_mutex); #ifdef GDBSTUB_ALLOW_MULTI_CLIENTS if (client == first_client) { #endif first_client = client->next; if (first_client == NULL) { last_client = NULL; gdbstub_step = GDBSTUB_EXEC; /* unpause CPU when all clients are disconnected */ } #ifdef GDBSTUB_ALLOW_MULTI_CLIENTS } else { other_client = first_client; while (other_client) { if (other_client->next == client) { if (last_client == client) last_client = other_client; other_client->next = client->next; break; } other_client = other_client->next; } } #endif free(client); thread_release_mutex(client_list_mutex); } static void gdbstub_server_thread(void *priv) { /* Listen on GDB socket. */ listen(gdbstub_socket, 1); /* Accept connections. */ gdbstub_client_t *client; socklen_t sl = sizeof(struct sockaddr_in); while (1) { /* Allocate client structure. */ client = malloc(sizeof(gdbstub_client_t)); memset(client, 0, sizeof(gdbstub_client_t)); client->processed_event = thread_create_event(); client->response_event = thread_create_event(); /* Accept connection. */ client->socket = accept(gdbstub_socket, (struct sockaddr *) &client->addr, &sl); if (client->socket < 0) break; /* Add to client list. */ thread_wait_mutex(client_list_mutex); if (first_client) { #ifdef GDBSTUB_ALLOW_MULTI_CLIENTS last_client->next = client; last_client = client; #else first_client->next = last_client = client; close(first_client->socket); #endif } else { first_client = last_client = client; } thread_release_mutex(client_list_mutex); /* Pause CPU execution. */ gdbstub_break(); /* Start client thread. */ thread_create(gdbstub_client_thread, client); } /* Deallocate the redundant client structure. */ thread_destroy_event(client->processed_event); thread_destroy_event(client->response_event); free(client); } void gdbstub_cpu_init() { /* Replace cpu_exec with our own function if the GDB stub is active. */ if ((gdbstub_socket != -1) && (cpu_exec != gdbstub_cpu_exec)) { cpu_exec_shadow = cpu_exec; cpu_exec = gdbstub_cpu_exec; } } int gdbstub_instruction() { /* Check hardware breakpoints if any are present. */ gdbstub_breakpoint_t *breakpoint = first_hwbreak; if (breakpoint) { /* Calculate the current instruction's address. */ uint32_t wanted_addr = cs + cpu_state.pc; /* Go through the list of software breakpoints. */ do { /* Check if the breakpoint coincides with this address. */ if (breakpoint->addr == wanted_addr) { gdbstub_log("GDB Stub: Hardware breakpoint at %08X\n", wanted_addr); /* Flag that we're in a hardware breakpoint. */ gdbstub_step = GDBSTUB_BREAK_HW; /* Pause execution. */ return 1; } breakpoint = breakpoint->next; } while (breakpoint); } /* No breakpoint found, continue execution or stop if execution is paused. */ return gdbstub_step - GDBSTUB_EXEC; } int gdbstub_int3() { /* Check software breakpoints if any are present. */ gdbstub_breakpoint_t *breakpoint = first_swbreak; if (breakpoint) { /* Calculate the breakpoint instruction's address. */ uint32_t new_pc = cpu_state.pc - 1; if (cpu_state.op32) new_pc &= 0xffff; uint32_t wanted_addr = cs + new_pc; /* Go through the list of software breakpoints. */ do { /* Check if the breakpoint coincides with this address. */ if (breakpoint->addr == wanted_addr) { gdbstub_log("GDB Stub: Software breakpoint at %08X\n", wanted_addr); /* Move EIP back to where the break instruction was. */ cpu_state.pc = new_pc; /* Flag that we're in a software breakpoint. */ gdbstub_step = GDBSTUB_BREAK_SW; /* Abort INT 3 execution. */ return 1; } breakpoint = breakpoint->next; } while (breakpoint); } /* No breakpoint found, continue INT 3 execution as normal. */ return 0; } void gdbstub_mem_access(uint32_t *addrs, int access) { /* Stop if we're in the debugger context. */ if (in_gdbstub) return; int width = access & (GDBSTUB_MEM_WRITE - 1), i; /* Go through the lists of watchpoints for this type of access. */ gdbstub_breakpoint_t *watchpoint = (access & GDBSTUB_MEM_WRITE) ? first_wwatch : first_rwatch; while (1) { if (watchpoint) { /* Check if any component of this address is within the breakpoint's range. */ for (i = 0; i < width; i++) { if ((addrs[i] >= watchpoint->addr) && (addrs[i] < watchpoint->end)) break; } if (i < width) { gdbstub_log("GDB Stub: %s watchpoint at %08X\n", (access & GDBSTUB_MEM_AWATCH) ? "Access" : ((access & GDBSTUB_MEM_WRITE) ? "Write" : "Read"), watch_addr); /* Flag that we're in a read/write watchpoint. */ gdbstub_step = (access & GDBSTUB_MEM_AWATCH) ? GDBSTUB_BREAK_AWATCH : ((access & GDBSTUB_MEM_WRITE) ? GDBSTUB_BREAK_WWATCH : GDBSTUB_BREAK_RWATCH); /* Stop looking. */ return; } watchpoint = watchpoint->next; } else { /* Jump from list to list as a shortcut. */ if (access & GDBSTUB_MEM_AWATCH) { break; } else { watchpoint = first_awatch; access |= GDBSTUB_MEM_AWATCH; } } } } void gdbstub_init() { #ifdef _WIN32 WSAStartup(MAKEWORD(2, 2), &wsa); #endif /* Create GDB server socket. */ if ((gdbstub_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) { pclog("GDB Stub: Failed to create socket\n"); return; } /* Bind GDB server socket. */ int port = 12345; struct sockaddr_in bind_addr = { .sin_family = AF_INET, .sin_addr = { .s_addr = INADDR_ANY }, .sin_port = htons(port) }; if (bind(gdbstub_socket, (struct sockaddr *) &bind_addr, sizeof(bind_addr)) == -1) { #ifdef _WIN32 pclog("GDB Stub: Failed to bind on port %d (%d)\n", port, WSAGetLastError()); #else pclog("GDB Stup: Failed to bind on port %d (%d)\n", port, errno); #endif gdbstub_socket = -1; return; } /* Create client list mutex. */ client_list_mutex = thread_create_mutex(); /* Clear watchpoint page map. */ memset(gdbstub_watch_pages, 0, sizeof(gdbstub_watch_pages)); /* Start server thread. */ pclog("GDB Stub: Listening on port %d\n", port); thread_create(gdbstub_server_thread, NULL); /* Start the CPU paused. */ gdbstub_step = GDBSTUB_BREAK; } void gdbstub_close() { /* Stop if the GDB server hasn't initialized. */ if (gdbstub_socket < 0) return; /* Close GDB server socket. */ close(gdbstub_socket); /* Clear client list. */ thread_wait_mutex(client_list_mutex); gdbstub_client_t *client = first_client; int socket; while (client) { socket = client->socket; client->socket = -1; close(socket); client = client->next; } thread_release_mutex(client_list_mutex); thread_close_mutex(client_list_mutex); }