Implement gdb debugger

This commit is contained in:
James Lambert 2021-01-10 17:07:29 -07:00
parent 2865d107e4
commit ee3d2fcc47
6 changed files with 694 additions and 0 deletions

View file

@ -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}

14
cen64.c
View file

@ -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);

260
gdb/gdb.c Normal file
View file

@ -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 <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#else
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#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:<port number>\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);
}

42
gdb/gdb.h Normal file
View file

@ -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

351
gdb/protocol.c Normal file
View file

@ -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 <inttypes.h>
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#else
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#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);
}

21
gdb/protocol.h Normal file
View file

@ -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