diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cc93ac..2ee1ba5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -285,6 +285,11 @@ set(DEVICE_SOURCES ${PROJECT_SOURCE_DIR}/device/sha1.c ) +set(GDB_SOURCES + ${PROJECT_SOURCE_DIR}/gdb/gdb.c + ${PROJECT_SOURCE_DIR}/gdb/protocol.c +) + set(OS_SOURCES ${PROJECT_SOURCE_DIR}/os/common/gl_hints.c ${PROJECT_SOURCE_DIR}/os/common/input.c @@ -438,6 +443,7 @@ add_executable(cen64 ${COMMON_SOURCES} ${DD_SOURCES} ${DEVICE_SOURCES} + ${GDB_SOURCES} ${OS_SOURCES} ${PI_SOURCES} ${RDP_SOURCES} diff --git a/cen64.c b/cen64.c index c8e1385..0aa7ec3 100644 --- a/cen64.c +++ b/cen64.c @@ -16,6 +16,7 @@ #include "device/options.h" #include "device/sha1.h" #include "device/sha1_sums.h" +#include "gdb/gdb.h" #include "os/common/alloc.h" #include "os/common/rom_file.h" #include "os/common/save_file.h" @@ -179,6 +180,8 @@ int cen64_main(int argc, const char **argv) { } } + + // Allocate memory for and create the device. if (cen64_alloc(&cen64_device_mem, sizeof(*device), false) == NULL) { printf("Failed to allocate enough memory for a device.\n"); @@ -197,9 +200,20 @@ int cen64_main(int argc, const char **argv) { } else { + struct gdb* debugger = NULL; + + if (options.debugger_addr) { + debugger = gdb_alloc(); + gdb_init(debugger, device, options.debugger_addr); + } + device->multithread = options.multithread; status = run_device(device, options.no_video); device_destroy(device, options.cart_path); + + if (debugger) { + gdb_destroy(debugger); + } } cen64_free(&cen64_device_mem); diff --git a/gdb/gdb.c b/gdb/gdb.c new file mode 100644 index 0000000..021784d --- /dev/null +++ b/gdb/gdb.c @@ -0,0 +1,260 @@ +// +// gdb.c: gdb remote debugger implementation +// +// CEN64: Cycle-Accurate Nintendo 64 Emulator. +// Copyright (C) 2015, Tyler J. Stachecki. +// +// This file is subject to the terms and conditions defined in +// 'LICENSE', which is part of this source code package. +// + +#include "gdb/gdb.h" +#include "gdb/protocol.h" + +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#endif + +cen64_cold void gdb_handle_breakpoint(void* data, enum vr4300_debug_break_reason reason); + +int gdb_read(struct gdb* gdb) { + if (gdb->pending_data >= MAX_GDB_PACKET_SIZE) { + // if the gdb client is well behaved this should never happen + gdb->pending_data = 0; + } + + int bytes_read = recv(gdb->client, gdb->packet_buffer + gdb->pending_data, MAX_GDB_PACKET_SIZE, 0); + + if (bytes_read > 0) { + gdb->pending_data += bytes_read; + } + + + return bytes_read; +} + +void gdb_mark_read(struct gdb* gdb, int handled_bytes) { + if (handled_bytes <= gdb->pending_data) { + gdb->pending_data -= handled_bytes; + } else { + gdb->pending_data = 0; + } + + if (gdb->pending_data) { + for (int i = 0; i < gdb->pending_data; i++) { + gdb->packet_buffer[i] = gdb->packet_buffer[i + handled_bytes]; + } + } +} + +bool gdb_parse_packet(const char* input, int len, const char** command_start, const char** command_end) { + const char* string_end = input + len; + + while (input < string_end) { + if (*input == '$') { + input++; + *command_start = input; + break; + } + input++; + } + + while (input < string_end) { + if (*input == '#') { + *command_end = input; + return true; + } else { + input++; + } + } + + return false; +} + +CEN64_THREAD_RETURN_TYPE gdb_thread(void *opaque) { + cen64_thread_setname(NULL, "gdb"); + struct gdb *gdb = (struct gdb *) opaque; + + cen64_mutex_lock(&gdb->client_mutex); + + // wait until first breakpoint is hit before entering loop + if (gdb->flags & GDB_FLAGS_INITIAL) { + cen64_cv_wait(&gdb->client_semaphore, &gdb->client_mutex); + } else { + cen64_mutex_lock(&gdb->client_mutex); + } + + while (gdb->flags & GDB_FLAGS_CONNECTED) { + int bytes_read = gdb_read(gdb); + + debug("rec: %.*s\n", bytes_read, gdb->packet_buffer + gdb->pending_data - bytes_read); + + if (bytes_read <= 0) { + gdb->flags &= ~GDB_FLAGS_CONNECTED; + printf("Debug socket closed\n"); + break; + } + + const char* command_start; + const char* command_end; + + bool did_handle = false; + + do { + int handled_bytes = 0; + + int search_end = gdb->pending_data; + + if (gdb_parse_packet(gdb->packet_buffer, gdb->pending_data, &command_start, &command_end)) { + send(gdb->client, "+", strlen("+"), 0); + gdb_handle_packet(gdb, command_start, command_end); + + // +3, 1 byte for the '#' and 2 additional bytes for the checksum + handled_bytes = (command_end - gdb->packet_buffer) + 3; + search_end = command_start - gdb->packet_buffer; + } + + int at; + + for (at = 0; at < search_end && gdb->packet_buffer[at] != '$'; at++) { + if (gdb->packet_buffer[at] == 0x03) { + vr4300_signal_break(gdb->device->vr4300); + } + } + + if (at > handled_bytes) { + handled_bytes = at; + } + + bytes_read -= handled_bytes; + gdb_mark_read(gdb, handled_bytes); + } while (did_handle); + + } + + return CEN64_THREAD_RETURN_VAL; +} + +cen64_cold void gdb_handle_breakpoint(void* data, enum vr4300_debug_break_reason reason) { + struct gdb* gdb = (struct gdb*)data; + + debug("Stopping at 0x%08x\n", (uint32_t)vr4300_get_pc(gdb->device->vr4300)); + + if (!(gdb->flags & GDB_FLAGS_CONNECTED)) { + return; + } else if (gdb->flags & GDB_FLAGS_INITIAL) { + gdb->flags &= ~GDB_FLAGS_INITIAL; + vr4300_remove_breakpoint(gdb->device->vr4300, 0xFFFFFFFF80000000ULL); + cen64_cv_signal(&gdb->client_semaphore); + } else { + gdb_send_stop_reply(gdb, reason == VR4300_DEBUG_BREAK_REASON_BREAKPOINT); + } + + cen64_mutex_lock(&gdb->client_mutex); + gdb->flags |= GDB_FLAGS_PAUSED; + cen64_cv_wait(&gdb->client_semaphore, &gdb->client_mutex); + gdb->flags &= ~GDB_FLAGS_PAUSED; + + if (!(gdb->flags & GDB_FLAGS_CONNECTED)) { + vr4300_connect_debugger(gdb->device->vr4300, NULL, NULL); + } +} + +cen64_cold struct gdb* gdb_alloc() { + struct gdb* result = malloc(sizeof(struct gdb)); + memset(result, 0, sizeof(struct gdb)); + return result; +} + +cen64_cold bool gdb_init(struct gdb* gdb, struct cen64_device* device, const char* host) { + int port; + if (sscanf(host, "localhost:%d", &port) != 1) { + printf("debugger error: -debug flag must be followed by locahost:\n"); + return false; + } + + if (port < 0 || port > 0xffff) { + printf("debugger error: -debug port must be a value between 0-65535\n"); + return false; + } + + if ((gdb->socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { + printf("debugger error: could create socket\n"); + return false; + } + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + //addr.sin_addr.s_addr = IPADDR_ANY; + addr.sin_port = htons((unsigned short)port); + + int flag = 1; + if (-1 == setsockopt(gdb->socket, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag))) { + printf("debugger error: could not open port %d\n", (int)port); + return false; + } + + if(bind(gdb->socket, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + printf("debugger error: could not open port %d\n", (int)port); + return false; + } + + if(listen(gdb->socket, 1) < 0) { + printf("debugger error: could not listen on port %d\n", (int)port); + return false; + } + + printf("Waiting for gdb connection on port %d\n", (int)port); + + if((gdb->client = accept(gdb->socket, NULL, NULL)) < 0) { + printf("debugger error: could not connect to client on port %d\n", (int)port); + return false; + } + + gdb->device = device; + device_connect_debugger(device, gdb, &gdb_handle_breakpoint); + + gdb->flags = GDB_FLAGS_INITIAL | GDB_FLAGS_CONNECTED; + vr4300_set_breakpoint(device->vr4300, 0xFFFFFFFF80000000ULL); + + if (cen64_mutex_create(&gdb->client_mutex)) { + printf("Failed to create gdb client semaphore.\n"); + return false; + } + + if (cen64_cv_create(&gdb->client_semaphore)) { + cen64_mutex_destroy(&gdb->client_mutex); + printf("Failed to create gdb client semaphore.\n"); + return false; + } + + if (cen64_thread_create(&gdb->thread, gdb_thread, gdb)) { + cen64_mutex_destroy(&gdb->client_mutex); + cen64_cv_destroy(&gdb->client_semaphore); + printf("Failed to create gdb thread.\n"); + return false; + } + + return true; +} + +cen64_cold void gdb_destroy(struct gdb* gdb) { + gdb->flags = 0; + + shutdown(gdb->client, 2); + shutdown(gdb->socket, 2); + + cen64_thread_join(&gdb->thread); + cen64_mutex_destroy(&gdb->client_mutex); + cen64_cv_destroy(&gdb->client_semaphore); + + gdb->device = NULL; + free(gdb); +} \ No newline at end of file diff --git a/gdb/gdb.h b/gdb/gdb.h new file mode 100644 index 0000000..cd71630 --- /dev/null +++ b/gdb/gdb.h @@ -0,0 +1,42 @@ +// +// gdb.h: gdb remote debugger implementation +// +// CEN64: Cycle-Accurate Nintendo 64 Emulator. +// Copyright (C) 2015, Tyler J. Stachecki. +// +// This file is subject to the terms and conditions defined in +// 'LICENSE', which is part of this source code package. +// + +#ifndef _gdb_h__ +#define _gdb_h__ +#include "common.h" +#include "vr4300/interface.h" +#include "device/device.h" + +#define MAX_GDB_PACKET_SIZE 0x4000 + +enum gdb_flags { + GDB_FLAGS_INITIAL = 0x1, + GDB_FLAGS_CONNECTED = 0x2, + GDB_FLAGS_PAUSED = 0x4, +}; + +struct gdb { + int socket; + int client; + struct cen64_device* device; + int pending_data; + char packet_buffer[MAX_GDB_PACKET_SIZE*2]; + char output_buffer[MAX_GDB_PACKET_SIZE]; + int flags; + cen64_thread thread; + cen64_mutex client_mutex; + cen64_cv client_semaphore; +}; + +cen64_cold struct gdb* gdb_alloc(); +cen64_cold bool gdb_init(struct gdb* gdb, struct cen64_device* device, const char* host); +cen64_cold void gdb_destroy(struct gdb* gdb); + +#endif \ No newline at end of file diff --git a/gdb/protocol.c b/gdb/protocol.c new file mode 100644 index 0000000..cd680e8 --- /dev/null +++ b/gdb/protocol.c @@ -0,0 +1,351 @@ +// +// protocol.c: gdb message parsing and responding +// +// CEN64: Cycle-Accurate Nintendo 64 Emulator. +// Copyright (C) 2015, Tyler J. Stachecki. +// +// This file is subject to the terms and conditions defined in +// 'LICENSE', which is part of this source code package. +// + +#include "gdb/protocol.h" +#include "gdb/gdb.h" +#include "vr4300/cpu.h" + +#include + +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#endif + +#define GDB_GET_EXC_CODE(cause) (((cause) >> 2) & 0x1f) +#define GDB_TRANSLATE_PC(pc) ((uint64_t)(pc) | 0xFFFFFFFF00000000ULL) +#define GDB_GLOBAL_THREAD_ID 1 +#define GDB_STR_STARTS_WITH(str, const_str) (strncmp(str, const_str, sizeof const_str - 1) == 0) + +static int gdb_signals[32] = { + 2, // SIGINT + 11, // SIGSEGV + 11, // SIGSEGV + 11, // SIGSEGV + 11, // SIGSEGV + 11, // SIGSEGV + 10, // SIGBUS + 10, // SIGBUS + 12, // SIGSYS + 5, // SIGTRAP + 4, // SIGILL + 30, // SIGUSR1 + 8, // SIGFPE + 5, // SIGTRAP + 0, // reserved + 8, // SIGFPE + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved + 5, // SIGTRAP + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved +}; + +int gdb_read_hex_digit(char character) { + if (character >= 'a' && character <= 'f') { + return 10 + character - 'a'; + } else if (character >= 'A' && character <= 'F') { + return 10 + character - 'A'; + } else if (character >= '0' && character <= '9') { + return character - '0'; + } else { + return -1; + } +} + +uint32_t gdb_parse_hex(const char* src, int max_bytes) { + uint32_t result = 0; + int current_char; + int max_characters = max_bytes * 2; + + for (current_char = 0; current_char < max_characters; ++current_char) { + int digit = gdb_read_hex_digit(*src); + + if (digit != -1) { + result = (result << 4) + digit; + } else { + break; + } + + ++src; + } + + return result; +} + +static char gdb_hex_letters[16] = "0123456789abcdef"; + +char* gdb_write_hex64(char* target, uint64_t data, int data_size) { + int shift = data_size * 8 - 4; + + while (shift >= 0) { + *target++ = gdb_hex_letters[(data >> shift) & 0xF]; + shift -= 4; + } + + return target; +} + +int gdb_apply_checksum(char* message) { + char* message_start = message; + if (*message == '$') { + ++message; + } + + unsigned char checksum = 0; + while (*message) + { + if (*message == '#') { + ++message; + break; + } + + checksum += (unsigned char)*message; + ++message; + } + + sprintf(message, "%02x", checksum); + + return (message - message_start) + 2; +} + +void gdb_send(struct gdb* gdb) { + int messageLen = gdb_apply_checksum(gdb->output_buffer); + send(gdb->client, gdb->output_buffer, messageLen, 0); + debug("send: %.*s\n", messageLen, gdb->output_buffer); +} + +void gdb_send_literal(struct gdb* gdb, const char* message) { + send(gdb->client, message, strlen(message), 0); + debug("send: %s\n", message); +} + +void gdb_handle_query(struct gdb* gdb, const char* command_start, const char *command_end) { + if (GDB_STR_STARTS_WITH(command_start, "qSupported")) { + strcpy(gdb->output_buffer, "$PacketSize=4000;vContSupported+;swbreak+#"); + gdb_send(gdb); + } else if (GDB_STR_STARTS_WITH(command_start, "qTStatus")) { + gdb_send_literal(gdb, "$#00"); + } else if (GDB_STR_STARTS_WITH(command_start, "qfThreadInfo")) { + sprintf(gdb->output_buffer, "$m%x#", GDB_GLOBAL_THREAD_ID); + gdb_send(gdb); + } else if (GDB_STR_STARTS_WITH(command_start, "qsThreadInfo")) { + strcpy(gdb->output_buffer, "$l#"); + gdb_send(gdb); + } else if (GDB_STR_STARTS_WITH(command_start, "qAttached")) { + strcpy(gdb->output_buffer, "$0#"); + gdb_send(gdb); + } else if (GDB_STR_STARTS_WITH(command_start, "qC")) { + sprintf(gdb->output_buffer, "$QC%x#", GDB_GLOBAL_THREAD_ID); + gdb_send(gdb); + } else if (GDB_STR_STARTS_WITH(command_start, "qTfV")) { + gdb_send_literal(gdb, "$#00"); + } else if (GDB_STR_STARTS_WITH(command_start, "qTfP")) { + gdb_send_literal(gdb, "$#00"); + } else if (GDB_STR_STARTS_WITH(command_start, "qOffsets")) { + sprintf(gdb->output_buffer, "$Text=%x;Data=%x;Bss=%x#", 0, 0, 0); + gdb_send(gdb); + } else if (GDB_STR_STARTS_WITH(command_start, "qSymbol")) { + gdb_send_literal(gdb, "$OK#9a"); + } else if (GDB_STR_STARTS_WITH(command_start, "qThreadExtraInfo")) { + strcpy(gdb->output_buffer, "$746872656164#"); + gdb_send(gdb); + } else { + gdb_send_literal(gdb, "$#00"); + } +} + +void gdb_handle_v(struct gdb* gdb, const char* command_start, const char *command_end) { + if (GDB_STR_STARTS_WITH(command_start, "vMustReplyEmpty")) { + gdb_send_literal(gdb, "$#00"); + } else if (GDB_STR_STARTS_WITH(command_start, "vCont")) { + if (command_start[5] == '?') { + strcpy(gdb->output_buffer, "$c;t#"); + gdb_send(gdb); + } else { + switch (command_start[6]) + { + case 'c': + cen64_cv_signal(&gdb->client_semaphore); + break; + case 't': + vr4300_signal_break(gdb->device->vr4300); + break; + } + } + } else if (GDB_STR_STARTS_WITH(command_start, "vKill")) { + gdb->flags &= ~GDB_FLAGS_CONNECTED; + gdb->flags &= ~GDB_FLAGS_PAUSED; + cen64_cv_signal(&gdb->client_semaphore); + gdb_send_literal(gdb, "$OK#9a"); + } else { + gdb_send_literal(gdb, "$#00"); + } +} + +void gdb_reply_registers(struct gdb* gdb) { + char* current = gdb->output_buffer; + *current++ = '$'; + + // R0 + current = gdb_write_hex64(current, 0, sizeof(uint64_t)); + for (int i = VR4300_REGISTER_AT; i <= VR4300_REGISTER_RA; i++) { + current = gdb_write_hex64(current, vr4300_get_register(gdb->device->vr4300, i), sizeof(uint64_t)); + } + + current = gdb_write_hex64(current, vr4300_get_register(gdb->device->vr4300, VR4300_CP0_REGISTER_STATUS), sizeof(uint64_t)); + current = gdb_write_hex64(current, vr4300_get_register(gdb->device->vr4300, VR4300_REGISTER_LO), sizeof(uint64_t)); + current = gdb_write_hex64(current, vr4300_get_register(gdb->device->vr4300, VR4300_REGISTER_HI), sizeof(uint64_t)); + current = gdb_write_hex64(current, vr4300_get_register(gdb->device->vr4300, VR4300_CP0_REGISTER_BADVADDR), sizeof(uint64_t)); + current = gdb_write_hex64(current, vr4300_get_register(gdb->device->vr4300, VR4300_CP0_REGISTER_CAUSE), sizeof(uint64_t)); + current += sprintf(current, "%08x%08x", 0, (int32_t)vr4300_get_pc(gdb->device->vr4300)); + + for (int i = VR4300_REGISTER_CP1_0; i <= VR4300_REGISTER_CP1_31; i++) { + current = gdb_write_hex64(current, vr4300_get_register(gdb->device->vr4300, i), sizeof(uint64_t)); + } + + current = gdb_write_hex64(current, vr4300_get_register(gdb->device->vr4300, VR4300_CP1_FCR31), sizeof(uint64_t)); + + *current++ = '#'; + *current++ = '\0'; + + gdb_send(gdb); +} + +void gdb_reply_memory(struct gdb* gdb, const char* command_start, const char *command_end) { + char* current = gdb->output_buffer; + *current++ = '$'; + + const char* len_text = command_start + 1; + + while (*len_text != ',' && len_text != command_end) { + ++len_text; + } + + int32_t addr = gdb_parse_hex(command_start + 1, 4); + int32_t len = gdb_parse_hex(len_text + 1, 4); + + int32_t alignedAddr = addr & ~0x3; + int32_t byteShift = addr - alignedAddr; + + for (int curr = 0; curr < len + byteShift; curr += 4) { + uint32_t word = 0; + int32_t vaddr = alignedAddr + curr; + + if (!vr4300_read_word_vaddr(gdb->device->vr4300, vaddr, &word)) { + debug("Bad vaddr %08x\n", vaddr); + } + + // if (curr > 0x20) { + // word = curr; + // } + + current = gdb_write_hex64(current, word, sizeof(uint32_t)); + } + + if (byteShift) { + for (int curr = 0; curr < len * 2; curr++) { + gdb->output_buffer[curr + 1] = gdb->output_buffer[curr + 1 + byteShift * 2]; + } + } + + current = gdb->output_buffer + 1 + len * 2; + + *current++ = '#'; + *current++ = '\0'; + + gdb_send(gdb); +} + +void gdb_handle_packet(struct gdb* gdb, const char* command_start, const char* command_end) { + switch (*command_start) { + case 'q': + gdb_handle_query(gdb, command_start, command_end); + break; + case 'v': + gdb_handle_v(gdb, command_start, command_end); + break; + case 'H': + gdb_send_literal(gdb, "$OK#9a"); + break; + case '!': + gdb_send_literal(gdb, "$#00"); + break; + case '?': + gdb_send_stop_reply(gdb, false); + break; + case 'g': + gdb_reply_registers(gdb); + break; + case 'm': + gdb_reply_memory(gdb, command_start, command_end); + break; + case 'D': + gdb->flags &= ~GDB_FLAGS_CONNECTED; + gdb_send_literal(gdb, "$OK#9a");; + break; + case 'z': + case 'Z': + { + if (command_start[1] == '0') { + uint64_t addr = GDB_TRANSLATE_PC(gdb_parse_hex(&command_start[3], 4)); + + if (*command_start == 'z') { + vr4300_remove_breakpoint(gdb->device->vr4300, addr); + } else { + vr4300_set_breakpoint(gdb->device->vr4300, addr); + } + + return gdb_send_literal(gdb, "$OK#9a"); + } else { + gdb_send_literal(gdb, "$#00"); + } + break; + } + default: + gdb_send_literal(gdb, "$#00"); + } +} + +cen64_cold void gdb_send_stop_reply(struct gdb* gdb, bool is_breakpoint) { + char* current = gdb->output_buffer; + int exc_code; + + if (is_breakpoint) { + exc_code = 9; + } else { + exc_code = GDB_GET_EXC_CODE(vr4300_get_register(gdb->device->vr4300, VR4300_CP0_REGISTER_CAUSE)); + } + current += sprintf(current, "$T%02x", gdb_signals[exc_code]); + if (is_breakpoint) { + current += sprintf(current, "swbreak:"); + } + current += sprintf(current, "thread:%d;", GDB_GLOBAL_THREAD_ID); + *current++ = '#'; + *current++ = '\0'; + gdb_send(gdb); +} \ No newline at end of file diff --git a/gdb/protocol.h b/gdb/protocol.h new file mode 100644 index 0000000..c6c6075 --- /dev/null +++ b/gdb/protocol.h @@ -0,0 +1,21 @@ +// +// protocol.h: gdb message parsing and responding +// +// CEN64: Cycle-Accurate Nintendo 64 Emulator. +// Copyright (C) 2015, Tyler J. Stachecki. +// +// This file is subject to the terms and conditions defined in +// 'LICENSE', which is part of this source code package. +// + +#ifndef _gdb_protocol_h__ +#define _gdb_protocol_h__ +#include "common.h" + +struct gdb; + +cen64_cold void gdb_send_stop_reply(struct gdb* gdb, bool is_breakpoint); +cen64_cold void gdb_handle_packet(struct gdb* gdb, const char* command_start, const char* command_end); + + +#endif