diff --git a/CMakeLists.txt b/CMakeLists.txt index 80a77d9f7..ac268dea5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,7 +121,7 @@ option(DINPUT "DirectInput" option(CPPTHREADS "C++11 threads" ON) option(NEW_DYNAREC "Use the PCem v15 (\"new\") dynamic recompiler" OFF) option(MINITRACE "Enable Chrome tracing using the modified minitrace library" OFF) -option(GDBSTUB "Enable GDB stub server for debugging" ON) +option(GDBSTUB "Enable GDB stub server for debugging" OFF) option(DEV_BRANCH "Development branch" OFF) if(NOT WIN32) option(QT "QT GUI" ON) diff --git a/src/86box.c b/src/86box.c index 262dc436d..2d6095560 100644 --- a/src/86box.c +++ b/src/86box.c @@ -1204,6 +1204,9 @@ pc_run(void) /* Run a block of code. */ startblit(); cpu_exec(cpu_s->rspeed / 100); +#ifdef USE_GDBSTUB /* avoid a KBC FIFO overflow when CPU emulation is stalled */ + if (gdbstub_step == GDBSTUB_EXEC) +#endif mouse_process(); joystick_process(); endblit(); diff --git a/src/cpu/386.c b/src/cpu/386.c index 48fb30798..2484cbb66 100644 --- a/src/cpu/386.c +++ b/src/cpu/386.c @@ -259,7 +259,7 @@ exec386(int cycs) timer_process_inline(); #ifdef USE_GDBSTUB - if (gdbstub_singlestep) + if (gdbstub_instruction()) return; #endif } diff --git a/src/cpu/386_dynarec.c b/src/cpu/386_dynarec.c index 6e70c03ea..e9a187216 100644 --- a/src/cpu/386_dynarec.c +++ b/src/cpu/386_dynarec.c @@ -26,6 +26,7 @@ #include <86box/fdd.h> #include <86box/fdc.h> #include <86box/machine.h> +#include <86box/gdbstub.h> #ifdef USE_DYNAREC #include "codegen.h" #ifdef USE_NEW_DYNAREC @@ -858,6 +859,11 @@ exec386_dynarec(int cycs) if (TIMER_VAL_LESS_THAN_VAL(timer_target, (uint32_t) tsc)) timer_process_inline(); } + +#ifdef USE_GDBSTUB + if (gdbstub_instruction()) + return; +#endif } cycles_main -= (cycles_start - cycles); diff --git a/src/cpu/386_dynarec_ops.c b/src/cpu/386_dynarec_ops.c index 2eb3a5f00..28fd3ec79 100644 --- a/src/cpu/386_dynarec_ops.c +++ b/src/cpu/386_dynarec_ops.c @@ -19,6 +19,7 @@ #include <86box/mem.h> #include <86box/nmi.h> #include <86box/pic.h> +#include <86box/gdbstub.h> #include "codegen.h" #define CPU_BLOCK_END() cpu_block_end = 1 diff --git a/src/cpu/808x.c b/src/cpu/808x.c index a00a4b057..96b184bc4 100644 --- a/src/cpu/808x.c +++ b/src/cpu/808x.c @@ -2835,8 +2835,8 @@ execx86(int cycs) } #ifdef USE_GDBSTUB - if (gdbstub_singlestep) - return; + if (gdbstub_instruction()) + return; #endif } } diff --git a/src/cpu/cpu.c b/src/cpu/cpu.c index 9909e243c..b416206f6 100644 --- a/src/cpu/cpu.c +++ b/src/cpu/cpu.c @@ -36,6 +36,7 @@ #include <86box/nmi.h> #include <86box/pic.h> #include <86box/pci.h> +#include <86box/gdbstub.h> #ifdef USE_DYNAREC # include "codegen.h" #endif @@ -1383,6 +1384,7 @@ cpu_set(void) cpu_exec = exec386; else cpu_exec = execx86; + gdbstub_cpu_init(); } diff --git a/src/cpu/x86_ops_int.h b/src/cpu/x86_ops_int.h index f35e526a6..0074aec29 100644 --- a/src/cpu/x86_ops_int.h +++ b/src/cpu/x86_ops_int.h @@ -1,6 +1,10 @@ static int opINT3(uint32_t fetchdat) { int cycles_old = cycles; UN_USED(cycles_old); +#ifdef USE_GDBSTUB + if (gdbstub_int3()) + return 1; +#endif if ((cr0 & 1) && (cpu_state.eflags & VM_FLAG) && (IOPL != 3)) { x86gpf(NULL,0); diff --git a/src/cpu/x87_ops.h b/src/cpu/x87_ops.h index 2f8927060..c41074a2c 100644 --- a/src/cpu/x87_ops.h +++ b/src/cpu/x87_ops.h @@ -239,89 +239,25 @@ static __inline int64_t x87_fround(double b) return 0LL; } -#define BIAS80 16383 -#define BIAS64 1023 + +#include "x87_ops_conv.h" static __inline double x87_ld80() { - int64_t exp64; - int64_t blah; - int64_t exp64final; - int64_t mant64; - int64_t sign; - struct { - int16_t begin; - union - { - double d; - uint64_t ll; - } eind; - } test; - test.eind.ll = readmeml(easeg,cpu_state.eaaddr); - test.eind.ll |= (uint64_t)readmeml(easeg,cpu_state.eaaddr+4)<<32; - test.begin = readmemw(easeg,cpu_state.eaaddr+8); - - exp64 = (((test.begin&0x7fff) - BIAS80)); - blah = ((exp64 >0)?exp64:-exp64)&0x3ff; - exp64final = ((exp64 >0)?blah:-blah) +BIAS64; - - mant64 = (test.eind.ll >> 11) & (0xfffffffffffffll); - sign = (test.begin&0x8000)?1:0; - - if ((test.begin & 0x7fff) == 0x7fff) - exp64final = 0x7ff; - if ((test.begin & 0x7fff) == 0) - exp64final = 0; - if (test.eind.ll & 0x400) - mant64++; - - test.eind.ll = (sign <<63)|(exp64final << 52)| mant64; - - return test.eind.d; + x87_conv_t test; + test.eind.ll = readmeml(easeg,cpu_state.eaaddr); + test.eind.ll |= (uint64_t)readmeml(easeg,cpu_state.eaaddr+4)<<32; + test.begin = readmemw(easeg,cpu_state.eaaddr+8); + return x87_from80(&test); } static __inline void x87_st80(double d) { - int64_t sign80; - int64_t exp80; - int64_t exp80final; - int64_t mant80; - int64_t mant80final; - - struct { - int16_t begin; - union - { - double d; - uint64_t ll; - } eind; - } test; - - test.eind.d=d; - - sign80 = (test.eind.ll&(0x8000000000000000ll))?1:0; - exp80 = test.eind.ll&(0x7ff0000000000000ll); - exp80final = (exp80>>52); - mant80 = test.eind.ll&(0x000fffffffffffffll); - mant80final = (mant80 << 11); - - if (exp80final == 0x7ff) /*Infinity / Nan*/ - { - exp80final = 0x7fff; - mant80final |= (0x8000000000000000ll); - } - else if (d != 0){ /* Zero is a special case */ - /* Elvira wants the 8 and tcalc doesn't */ - mant80final |= (0x8000000000000000ll); - /* Ca-cyber doesn't like this when result is zero. */ - exp80final += (BIAS80 - BIAS64); - } - test.begin = (((int16_t)sign80)<<15)| (int16_t)exp80final; - test.eind.ll = mant80final; - - writememl(easeg,cpu_state.eaaddr,test.eind.ll & 0xffffffff); - writememl(easeg,cpu_state.eaaddr+4,test.eind.ll>>32); - writememw(easeg,cpu_state.eaaddr+8,test.begin); + x87_conv_t test; + x87_to80(d, &test); + writememl(easeg,cpu_state.eaaddr,test.eind.ll & 0xffffffff); + writememl(easeg,cpu_state.eaaddr+4,test.eind.ll>>32); + writememw(easeg,cpu_state.eaaddr+8,test.begin); } static __inline void x87_st_fsave(int reg) diff --git a/src/cpu/x87_ops_conv.h b/src/cpu/x87_ops_conv.h new file mode 100644 index 000000000..44304c1bc --- /dev/null +++ b/src/cpu/x87_ops_conv.h @@ -0,0 +1,68 @@ +#define BIAS80 16383 +#define BIAS64 1023 + +typedef struct { + int16_t begin; + union { + double d; + uint64_t ll; + } eind; +} x87_conv_t; + +static __inline double x87_from80(x87_conv_t *test) +{ + int64_t exp64; + int64_t blah; + int64_t exp64final; + int64_t mant64; + int64_t sign; + + exp64 = (((test->begin&0x7fff) - BIAS80)); + blah = ((exp64 >0)?exp64:-exp64)&0x3ff; + exp64final = ((exp64 >0)?blah:-blah) +BIAS64; + + mant64 = (test->eind.ll >> 11) & (0xfffffffffffffll); + sign = (test->begin&0x8000)?1:0; + + if ((test->begin & 0x7fff) == 0x7fff) + exp64final = 0x7ff; + if ((test->begin & 0x7fff) == 0) + exp64final = 0; + if (test->eind.ll & 0x400) + mant64++; + + test->eind.ll = (sign <<63)|(exp64final << 52)| mant64; + + return test->eind.d; +} + +static __inline void x87_to80(double d, x87_conv_t *test) +{ + int64_t sign80; + int64_t exp80; + int64_t exp80final; + int64_t mant80; + int64_t mant80final; + + test->eind.d=d; + + sign80 = (test->eind.ll&(0x8000000000000000ll))?1:0; + exp80 = test->eind.ll&(0x7ff0000000000000ll); + exp80final = (exp80>>52); + mant80 = test->eind.ll&(0x000fffffffffffffll); + mant80final = (mant80 << 11); + + if (exp80final == 0x7ff) /*Infinity / Nan*/ + { + exp80final = 0x7fff; + mant80final |= (0x8000000000000000ll); + } + else if (d != 0){ /* Zero is a special case */ + /* Elvira wants the 8 and tcalc doesn't */ + mant80final |= (0x8000000000000000ll); + /* Ca-cyber doesn't like this when result is zero. */ + exp80final += (BIAS80 - BIAS64); + } + test->begin = (((int16_t)sign80)<<15)| (int16_t)exp80final; + test->eind.ll = mant80final; +} diff --git a/src/gdbstub.c b/src/gdbstub.c index 9d5c32519..70c67d662 100644 --- a/src/gdbstub.c +++ b/src/gdbstub.c @@ -32,30 +32,65 @@ #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/gdbstub.h> -#define GDBSTUB_MAX_REG 38 +#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 packet_pos, response_pos; + int has_packet, waiting_stop, packet_pos, response_pos; - event_t *response_event; + 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; @@ -77,7 +112,7 @@ gdbstub_log(const char *fmt, ...) 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 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 */ "" "" @@ -202,43 +237,55 @@ static const char target_xml[] = /* based on qemu's i386-32bit.xml */ "" "" "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" "" ""; #ifdef _WIN32 -static WSADATA wsa; +static WSADATA wsa; #endif -static int gdbstub_socket = -1, gdbstub_paused = 0; -static char stop_reason[2048]; -static gdbstub_client_t *first_client, *last_client; -static mutex_t *client_list_mutex; -static void (*cpu_exec_shadow)(int cycs); +static int gdbstub_socket = -1, stop_reason_len = 0, in_gdbstub = 0; +static uint32_t watch_addr; +static char stop_reason[2048]; -int gdbstub_singlestep = 0; +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() { - /* Initiate pause. */ - plat_pause(1); - - /* Force CPU execution to return as soon as possible. */ - gdbstub_singlestep = 1; -} - - -static void -gdbstub_singlestep_exec(int cycs) -{ - /* Call the original cpu_exec function. */ - cpu_exec_shadow(cycs); - - /* Swap the original function back in. */ - cpu_exec = cpu_exec_shadow; - - /* Break immediately. */ - gdbstub_break(); + /* Pause CPU execution as soon as possible. */ + if (gdbstub_step <= GDBSTUB_EXEC) + gdbstub_step = GDBSTUB_BREAK; } @@ -252,6 +299,7 @@ gdbstub_jump(uint32_t new_pc) loadseg((new_pc >> 4) & 0xf000, &cpu_state.seg_cs); cpu_state.pc = new_pc & 0xffff; } + flushmmucache(); } @@ -279,6 +327,95 @@ gdbstub_hex_encode(int c) } +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) { @@ -324,49 +461,81 @@ gdbstub_client_write_reg(int index, uint8_t *buf) { int width = 4; switch (index) { - case 0 ... 7: /* [EAX:EDI] */ - cpu_state.regs[index].l = *((uint32_t *) buf); + case GDB_REG_EAX ... GDB_REG_EDI: + cpu_state.regs[index - GDB_REG_EAX].l = *((uint32_t *) buf); break; - case 8: /* EIP */ + case GDB_REG_EIP: gdbstub_jump(*((uint32_t *) buf)); break; - case 9: /* EFLAGS */ + case GDB_REG_EFLAGS: cpu_state.flags = *((uint16_t *) &buf[0]); cpu_state.eflags = *((uint16_t *) &buf[2]); break; - case 10 ... 15: /* [CS:GS] */ + case GDB_REG_CS ... GDB_REG_GS: width = 2; - loadseg(*((uint16_t *) buf), segment_regs[index - 10]); + loadseg(*((uint16_t *) buf), segment_regs[index - GDB_REG_CS]); + flushmmucache(); break; - case 16 ... 17: /* FSbase, GSbase */ + case GDB_REG_FS_BASE ... GDB_REG_GS_BASE: /* Do what qemu does and just load the base. */ - segment_regs[index - 12]->base = *((uint32_t *) buf); + segment_regs[(index - 16) + (GDB_REG_FS - GDB_REG_CS)]->base = *((uint32_t *) buf); break; - case 18 ... 21: /* [CR0:CR4] */ - *cr_regs[index - 18] = *((uint32_t *) buf); + case GDB_REG_CR0 ... GDB_REG_CR4: + *cr_regs[index - GDB_REG_CR0] = *((uint32_t *) buf); + flushmmucache(); break; - case 22: /* EFER */ + case GDB_REG_EFER: msr.amd_efer = *((uint64_t *) buf); break; - case 23 ... 30: /* ST(0:7) */ + 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; - /* FPU CONTROL REGS */ + 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; } - flushmmucache(); /* incredibly cursed to be calling that from here */ - return width; } @@ -375,8 +544,7 @@ static void gdbstub_client_respond(gdbstub_client_t *client) { /* Calculate checksum. */ - uint8_t checksum = 0; - int i; + int checksum = 0, i; for (i = 0; i < client->response_pos; i++) checksum += client->response[i]; @@ -390,11 +558,23 @@ gdbstub_client_respond(gdbstub_client_t *client) #endif send(client->socket, "$", 1, 0); send(client->socket, client->response, client->response_pos, 0); - char response_cksum[3] = {'#', gdbstub_hex_encode(checksum >> 4), gdbstub_hex_encode(checksum & 0x0f)}; + 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) { @@ -410,63 +590,70 @@ gdbstub_client_read_reg(int index, uint8_t *buf) { int width = 4; switch (index) { - case 0 ... 7: /* [EAX:EDI] */ + case GDB_REG_EAX ... GDB_REG_EDI: *((uint32_t *) buf) = cpu_state.regs[index].l; break; - case 8: /* EIP */ + case GDB_REG_EIP: *((uint32_t *) buf) = cs + cpu_state.pc; break; - case 9: /* EFLAGS */ + case GDB_REG_EFLAGS: *((uint16_t *) &buf[0]) = cpu_state.flags; *((uint16_t *) &buf[2]) = cpu_state.eflags; break; - case 10 ... 15: /* [CS:GS] */ - *((uint16_t *) buf) = segment_regs[index - 10]->seg; + case GDB_REG_CS ... GDB_REG_GS: + *((uint16_t *) buf) = segment_regs[index - GDB_REG_CS]->seg; break; - case 16 ... 17: /* FSbase, GSbase */ - *((uint32_t *) buf) = segment_regs[index - 12]->base; + 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 18 ... 21: /* [CR0:CR4] */ - *((uint32_t *) buf) = *cr_regs[index - 18]; + case GDB_REG_CR0 ... GDB_REG_CR4: + *((uint32_t *) buf) = *cr_regs[index - GDB_REG_CR0]; break; - case 22: /* EFER */ + case GDB_REG_EFER: *((uint64_t *) buf) = msr.amd_efer; break; - case 23 ... 30: /* ST(0:7) */ + case GDB_REG_ST0 ... GDB_REG_ST7: width = 10; - *((uint64_t *) &buf[0]) = 0; - *((uint16_t *) &buf[8]) = 0; + 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 31 ... 32: /* [FCTRL:FSTAT] */ - case 34: /* [FISEG] */ - case 36: /* [FOSEG] */ + 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 - 31]); + *((uint16_t *) buf) = *((uint16_t *) fpu_regs[index - GDB_REG_FCTRL]); break; - case 33: /* FTAG */ + case GDB_REG_FTAG: width = 2; *((uint16_t *) buf) = x87_gettag(); break; - case 35: /* [FIOFF] */ - case 37: /* [FOOFF] */ - *((uint32_t *) buf) = *((uint32_t *) fpu_regs[index - 31]); + case GDB_REG_FIOFF: + case GDB_REG_FOOFF: + *((uint32_t *) buf) = *((uint32_t *) fpu_regs[index - GDB_REG_FCTRL]); break; - case 38: /* [FOP] */ + 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; } @@ -478,21 +665,23 @@ gdbstub_client_read_reg(int index, uint8_t *buf) 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); - client->packet[client->packet_pos] = '\0'; - client->packet[client->packet_pos + 1] = '\0'; - client->packet[--client->packet_pos] = '\0'; +#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 0 /* msys2 gdb 11.1 transmits qSupported and H with invalid checksum... */ if (checksum != rcv_checksum) { /* Send negative acknowledgement. */ #ifdef ENABLE_GDBSTUB_LOG @@ -515,61 +704,54 @@ gdbstub_client_packet(gdbstub_client_t *client) #endif send(client->socket, "+", 1, 0); - /* Block other responses from being written while this one isn't acknowledged. */ - thread_wait_event(client->response_event, 0); - thread_reset_event(client->response_event); + /* 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. */ - strcpy(client->response, stop_reason); - client->response_pos = strlen(client->response); + /* 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 */ - /* No immediate response. */ - thread_set_event(client->response_event); + /* Flag that the client is waiting for a stop reason. */ + client->waiting_stop = 1; /* Jump to address if specified. */ - if (gdbstub_client_read_word(client, &j)) + if (client->packet[1] && gdbstub_client_read_word(client, &j)) gdbstub_jump(j); - /* Resume emulation. */ - if (client->packet[0] == 'c') { - gdbstub_singlestep = 0; - } else { - /* Replace cpu_exec with our own function, which breaks after a single step. */ - gdbstub_singlestep = 1; - if (cpu_exec != gdbstub_singlestep_exec) - cpu_exec_shadow = cpu_exec; - cpu_exec = gdbstub_singlestep_exec; - } - gdbstub_paused = 0; - plat_pause(0); + /* Resume CPU. */ + gdbstub_step = gdbstub_next_asap = (client->packet[0] == 's') ? GDBSTUB_SSTEP : GDBSTUB_EXEC; return; case 'D': /* detach */ /* Resume emulation. */ - gdbstub_paused = 0; - plat_pause(0); + gdbstub_step = GDBSTUB_EXEC; /* Respond positively. */ - client->response_pos = sprintf(client->response, "OK"); +ok: FAST_RESPONSE("OK"); break; case 'g': /* read all registers */ /* Output the values of all registers. */ - for (i = 0; i <= GDBSTUB_MAX_REG; i++) + 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 <= GDBSTUB_MAX_REG; i++) { + 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); @@ -579,16 +761,15 @@ gdbstub_client_packet(gdbstub_client_t *client) case 'H': /* set thread */ /* Read operation type and thread ID. */ if ((client->packet[1] == '\0') || (client->packet[2] == '\0')) { -e22: client->response_pos = sprintf(client->response, "E22"); +e22: FAST_RESPONSE("E22"); break; } /* Respond positively only on thread 1. */ if ((client->packet[2] == '1') && !client->packet[3]) - client->response_pos = sprintf(client->response, "OK"); + goto ok; else goto e22; - break; case 'm': /* read memory */ /* Read address and length. */ @@ -683,13 +864,12 @@ e22: client->response_pos = sprintf(client->response, "E22"); } /* Respond positively. */ - client->response_pos = sprintf(client->response, "OK"); - break; + goto ok; case 'p': /* read register */ /* Read register index. */ if (!gdbstub_client_read_word(client, &j)) { -e14: client->response_pos = sprintf(client->response, "E14"); +e14: FAST_RESPONSE("E14"); break; } @@ -714,15 +894,15 @@ e14: client->response_pos = sprintf(client->response, "E14"); goto e14; /* Respond positively. */ - client->response_pos = sprintf(client->response, "OK"); - break; + 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, ':') + 1; + 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")) { @@ -765,7 +945,7 @@ e14: client->response_pos = sprintf(client->response, "E14"); /* Stop if the file wasn't found. */ if (!p) { -e00: client->response_pos = sprintf(client->response, "E00"); +e00: FAST_RESPONSE("E00"); break; } @@ -796,7 +976,7 @@ e00: client->response_pos = sprintf(c /* Encode the data. */ while (k--) { i = *p++; - if ((i == '#') || (i == '$') || (i == '*') || (i == '}')) { + if ((i == '\0') || (i == '#') || (i == '$') || (i == '*') || (i == '}')) { client->response[client->response_pos++] = '}'; client->response[client->response_pos++] = i ^ 0x20; } else { @@ -806,33 +986,371 @@ e00: client->response_pos = sprintf(c 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; } - /* No response by default. */ - client->response_pos = 0; - 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, *other_client; + 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; @@ -847,21 +1365,37 @@ gdbstub_client_thread(void *priv) 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_paused = 0; 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 this is the end of a packet. */ + /* Check if we're at the end of a packet. */ if ((client->packet_pos >= 3) && (client->packet[client->packet_pos - 3] == '#')) { /* packet checksum start */ - gdbstub_client_packet(client); - client->packet_pos = 0; + /* 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; @@ -882,11 +1416,15 @@ gdbstub_client_thread(void *priv) /* 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) + if (first_client == NULL) { last_client = NULL; - gdbstub_paused = 0; /* allow user to unpause when all clients are disconnected */ + gdbstub_step = GDBSTUB_EXEC; /* unpause CPU when all clients are disconnected */ + } +#ifdef GDBSTUB_ALLOW_MULTI_CLIENTS } else { other_client = first_client; while (other_client) { @@ -899,6 +1437,8 @@ gdbstub_client_thread(void *priv) other_client = other_client->next; } } +#endif + free(client); thread_release_mutex(client_list_mutex); } @@ -917,6 +1457,7 @@ gdbstub_server_thread(void *priv) /* 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. */ @@ -927,15 +1468,19 @@ gdbstub_server_thread(void *priv) /* 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 emulation. */ - gdbstub_paused = 1; + /* Pause CPU execution. */ gdbstub_break(); /* Start client thread. */ @@ -943,40 +1488,128 @@ gdbstub_server_thread(void *priv) } /* Deallocate the redundant client structure. */ + thread_destroy_event(client->processed_event); thread_destroy_event(client->response_event); free(client); } void -gdbstub_pause(int *p) +gdbstub_cpu_init() { - if (!(*p) && gdbstub_paused) { - /* Don't allow the user to unpause if we're pausing. */ - gdbstub_log("GDB Stub: Blocked user unpause\n"); - *p = 1; - } else if (*p) { - sprintf(stop_reason, "S%02X", gdbstub_singlestep ? GDB_SIGTRAP : GDB_SIGINT); - if (!gdbstub_paused) { - /* Send interrupt packet to all clients. */ - gdbstub_log("GDB Stub: Pausing\n"); - gdbstub_paused = 1; - thread_wait_mutex(client_list_mutex); - gdbstub_client_t *client = first_client; - while (client) { - 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); + /* 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; + } +} - /* Write stop reply packet. */ - client->response_pos = sprintf(client->response, "%s", stop_reason); - gdbstub_client_respond(client); - } else { - gdbstub_log("GDB Stub: Timed out waiting for client %08X\n", client); - } - client = client->next; + +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; } - thread_release_mutex(client_list_mutex); } } } @@ -1011,9 +1644,15 @@ gdbstub_init() /* 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; } diff --git a/src/include/86box/gdbstub.h b/src/include/86box/gdbstub.h index d46c8fe6d..d562f6a6e 100644 --- a/src/include/86box/gdbstub.h +++ b/src/include/86box/gdbstub.h @@ -16,20 +16,60 @@ */ #ifndef EMU_GDBSTUB_H # define EMU_GDBSTUB_H +#include <86box/mem.h> + +#define GDBSTUB_MEM_READ 0 +#define GDBSTUB_MEM_WRITE 16 +#define GDBSTUB_MEM_AWATCH 32 + +enum { + GDBSTUB_EXEC = 0, + GDBSTUB_SSTEP, + GDBSTUB_BREAK, + GDBSTUB_BREAK_SW, + GDBSTUB_BREAK_HW, + GDBSTUB_BREAK_RWATCH, + GDBSTUB_BREAK_WWATCH, + GDBSTUB_BREAK_AWATCH +}; #ifdef USE_GDBSTUB -extern int gdbstub_singlestep; +#define GDBSTUB_MEM_ACCESS(addr, access, width) \ + uint32_t gdbstub_page = addr >> MEM_GRANULARITY_BITS; \ + if (gdbstub_watch_pages[gdbstub_page >> 6] & (1 << (gdbstub_page & 63)) || (addr == 0xb8dd4)) { \ + uint32_t gdbstub_addrs[width]; \ + for (int gdbstub_i = 0; gdbstub_i < width; gdbstub_i++) \ + gdbstub_addrs[gdbstub_i] = addr + gdbstub_i; \ + gdbstub_mem_access(gdbstub_addrs, access | width); \ + } -extern void gdbstub_pause(int *p); +#define GDBSTUB_MEM_ACCESS_FAST(addrs, access, width) \ + uint32_t gdbstub_page = addr >> MEM_GRANULARITY_BITS; \ + if (gdbstub_watch_pages[gdbstub_page >> 6] & (1 << (gdbstub_page & 63)) || (addr == 0xb8dd4)) \ + gdbstub_mem_access(addrs, access | width); + +extern int gdbstub_step, gdbstub_next_asap; +extern uint64_t gdbstub_watch_pages[(((uint32_t) -1) >> (MEM_GRANULARITY_BITS + 6)) + 1]; + +extern void gdbstub_cpu_init(); +extern int gdbstub_instruction(); +extern int gdbstub_int3(); +extern void gdbstub_mem_access(uint32_t *addrs, int access); extern void gdbstub_init(); extern void gdbstub_close(); #else -#define gdbstub_singlestep 0 +#define GDBSTUB_MEM_ACCESS(addr, access, width) +#define GDBSTUB_MEM_ACCESS_FAST(addrs, access, width) -#define gdbstub_pause(p) +#define gdbstub_step 0 +#define gdbstub_next_asap 0 + +#define gdbstub_cpu_init() +#define gdbstub_instruction() 0 +#define gdbstub_int3() 0 #define gdbstub_init() #define gdbstub_close() diff --git a/src/mem/mem.c b/src/mem/mem.c index fbc7d8f08..bdb0c4cdb 100644 --- a/src/mem/mem.c +++ b/src/mem/mem.c @@ -36,6 +36,7 @@ #include <86box/mem.h> #include <86box/plat.h> #include <86box/rom.h> +#include <86box/gdbstub.h> #ifdef USE_DYNAREC # include "codegen_public.h" #else @@ -783,6 +784,8 @@ readmembl(uint32_t addr) mem_mapping_t *map; uint64_t a; + GDBSTUB_MEM_ACCESS(addr, GDBSTUB_MEM_READ, 1); + addr64 = (uint64_t) addr; mem_logical_addr = addr; @@ -811,6 +814,8 @@ writemembl(uint32_t addr, uint8_t val) mem_mapping_t *map; uint64_t a; + GDBSTUB_MEM_ACCESS(addr, GDBSTUB_MEM_WRITE, 1); + addr64 = (uint64_t) addr; mem_logical_addr = addr; @@ -842,6 +847,8 @@ readmembl_no_mmut(uint32_t addr, uint32_t a64) { mem_mapping_t *map; + GDBSTUB_MEM_ACCESS(addr, GDBSTUB_MEM_READ, 1); + mem_logical_addr = addr; if (cr0 >> 31) { @@ -866,6 +873,8 @@ writemembl_no_mmut(uint32_t addr, uint32_t a64, uint8_t val) { mem_mapping_t *map; + GDBSTUB_MEM_ACCESS(addr, GDBSTUB_MEM_WRITE, 1); + mem_logical_addr = addr; if (page_lookup[addr >> 12] && page_lookup[addr >> 12]->write_b) { @@ -896,6 +905,7 @@ readmemwl(uint32_t addr) addr64a[0] = addr; addr64a[1] = addr + 1; + GDBSTUB_MEM_ACCESS_FAST(addr64a, GDBSTUB_MEM_READ, 2); mem_logical_addr = addr; @@ -957,6 +967,7 @@ writememwl(uint32_t addr, uint16_t val) addr64a[0] = addr; addr64a[1] = addr + 1; + GDBSTUB_MEM_ACCESS_FAST(addr64a, GDBSTUB_MEM_WRITE, 2); mem_logical_addr = addr; @@ -1029,6 +1040,8 @@ readmemwl_no_mmut(uint32_t addr, uint32_t *a64) { mem_mapping_t *map; + GDBSTUB_MEM_ACCESS(addr, GDBSTUB_MEM_READ, 2); + mem_logical_addr = addr; if (addr & 1) { @@ -1076,6 +1089,8 @@ writememwl_no_mmut(uint32_t addr, uint32_t *a64, uint16_t val) { mem_mapping_t *map; + GDBSTUB_MEM_ACCESS(addr, GDBSTUB_MEM_WRITE, 2); + mem_logical_addr = addr; if (addr & 1) { @@ -1135,6 +1150,7 @@ readmemll(uint32_t addr) for (i = 0; i < 4; i++) addr64a[i] = (uint64_t) (addr + i); + GDBSTUB_MEM_ACCESS_FAST(addr64a, GDBSTUB_MEM_READ, 4); mem_logical_addr = addr; @@ -1214,6 +1230,7 @@ writememll(uint32_t addr, uint32_t val) for (i = 0; i < 4; i++) addr64a[i] = (uint64_t) (addr + i); + GDBSTUB_MEM_ACCESS_FAST(addr64a, GDBSTUB_MEM_WRITE, 4); mem_logical_addr = addr; @@ -1305,6 +1322,8 @@ readmemll_no_mmut(uint32_t addr, uint32_t *a64) #ifndef NO_MMUT mem_mapping_t *map; + GDBSTUB_MEM_ACCESS(addr, GDBSTUB_MEM_READ, 4); + mem_logical_addr = addr; if (addr & 3) { @@ -1361,6 +1380,8 @@ writememll_no_mmut(uint32_t addr, uint32_t *a64, uint32_t val) #ifndef NO_MMUT mem_mapping_t *map; + GDBSTUB_MEM_ACCESS(addr, GDBSTUB_MEM_WRITE, 4); + mem_logical_addr = addr; if (addr & 3) { @@ -1429,6 +1450,7 @@ readmemql(uint32_t addr) for (i = 0; i < 8; i++) addr64a[i] = (uint64_t) (addr + i); + GDBSTUB_MEM_ACCESS_FAST(addr64a, GDBSTUB_MEM_READ, 8); mem_logical_addr = addr; @@ -1496,6 +1518,7 @@ writememql(uint32_t addr, uint64_t val) for (i = 0; i < 8; i++) addr64a[i] = (uint64_t) (addr + i); + GDBSTUB_MEM_ACCESS_FAST(addr64a, GDBSTUB_MEM_WRITE, 8); mem_logical_addr = addr; diff --git a/src/qt/qt_main.cpp b/src/qt/qt_main.cpp index 7ec4930d2..47dc52356 100644 --- a/src/qt/qt_main.cpp +++ b/src/qt/qt_main.cpp @@ -54,6 +54,7 @@ extern "C" #include <86box/ui.h> #include <86box/video.h> #include <86box/discord.h> +#include <86box/gdbstub.h> } #include @@ -95,6 +96,11 @@ main_thread_fn() while (!is_quit && cpu_thread_run) { /* See if it is time to run a frame of code. */ new_time = elapsed_timer.elapsed(); +#ifdef USE_GDBSTUB + if (gdbstub_next_asap && (drawits <= 0)) + drawits = 10; + else +#endif drawits += (new_time - old_time); old_time = new_time; if (drawits > 0 && !dopause) { diff --git a/src/qt/qt_platform.cpp b/src/qt/qt_platform.cpp index b4bd38e83..4ec31b842 100644 --- a/src/qt/qt_platform.cpp +++ b/src/qt/qt_platform.cpp @@ -92,7 +92,6 @@ extern "C" { #include "../cpu/cpu.h" #include <86box/plat.h> -#include <86box/gdbstub.h> volatile int cpu_thread_run = 1; int mouse_capture = 0; @@ -315,8 +314,6 @@ plat_pause(int p) static wchar_t oldtitle[512]; wchar_t title[512], paused_msg[64]; - gdbstub_pause(&p); - if (p == dopause) { #ifdef Q_OS_WINDOWS if (source_hwnd) diff --git a/src/unix/unix.c b/src/unix/unix.c index ead580372..ab3f5b349 100644 --- a/src/unix/unix.c +++ b/src/unix/unix.c @@ -523,6 +523,11 @@ main_thread(void *param) while (!is_quit && cpu_thread_run) { /* See if it is time to run a frame of code. */ new_time = SDL_GetTicks(); +#ifdef USE_GDBSTUB + if (gdbstub_next_asap && (drawits <= 0)) + drawits = 10; + else +#endif drawits += (new_time - old_time); old_time = new_time; if (drawits > 0 && !dopause) { @@ -720,8 +725,6 @@ plat_pause(int p) static wchar_t oldtitle[512]; wchar_t title[512]; - gdbstub_pause(&p); - if ((p == 0) && (time_sync & TIME_SYNC_ENABLED)) nvr_time_sync(); diff --git a/src/win/win.c b/src/win/win.c index 1490a1ce1..f4b906d7d 100644 --- a/src/win/win.c +++ b/src/win/win.c @@ -55,6 +55,7 @@ #include <86box/win_opengl.h> #include <86box/win.h> #include <86box/version.h> +#include <86box/gdbstub.h> #ifdef MTR_ENABLED #include #endif @@ -525,6 +526,11 @@ main_thread(void *param) while (!is_quit && cpu_thread_run) { /* See if it is time to run a frame of code. */ new_time = GetTickCount(); +#ifdef USE_GDBSTUB + if (gdbstub_next_asap && (drawits <= 0)) + drawits = 10; + else +#endif drawits += (new_time - old_time); old_time = new_time; if (drawits > 0 && !dopause) { diff --git a/src/win/win_ui.c b/src/win/win_ui.c index b99ca6414..1a9ad961a 100644 --- a/src/win/win_ui.c +++ b/src/win/win_ui.c @@ -44,7 +44,6 @@ #include <86box/win.h> #include <86box/version.h> #include <86box/discord.h> -#include <86box/gdbstub.h> #ifdef MTR_ENABLED #include @@ -1497,8 +1496,6 @@ plat_pause(int p) static wchar_t oldtitle[512]; wchar_t title[512]; - gdbstub_pause(&p); - /* If un-pausing, as the renderer if that's OK. */ if (p == 0) p = get_vidpause();