mirror of
https://git.planet-casio.com/Lephenixnoir/gint.git
synced 2024-12-29 13:03:36 +01:00
gdb: first implementation with basic memory and register read support
This commit is contained in:
parent
dcb876dfe0
commit
0bea485f4b
4 changed files with 382 additions and 0 deletions
|
@ -70,6 +70,8 @@ set(SOURCES_COMMON
|
||||||
src/fs/fugue/fugue_rmdir.c
|
src/fs/fugue/fugue_rmdir.c
|
||||||
src/fs/fugue/fugue_unlink.c
|
src/fs/fugue/fugue_unlink.c
|
||||||
src/fs/fugue/util.c
|
src/fs/fugue/util.c
|
||||||
|
# GDB remote serial protocol
|
||||||
|
src/gdb/gdb.c
|
||||||
# Interrupt Controller driver
|
# Interrupt Controller driver
|
||||||
src/intc/intc.c
|
src/intc/intc.c
|
||||||
src/intc/inth.s
|
src/intc/inth.s
|
||||||
|
|
31
include/gint/gdb.h
Normal file
31
include/gint/gdb.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
//---
|
||||||
|
// gint:gdb - GDB remote serial protocol
|
||||||
|
//---
|
||||||
|
|
||||||
|
#ifndef GINT_GDB
|
||||||
|
#define GINT_GDB
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Error codes for GDB functions */
|
||||||
|
enum {
|
||||||
|
GDB_NO_INTERFACE = -1,
|
||||||
|
GDB_ALREADY_STARTED = -2,
|
||||||
|
GDB_USB_ERROR = -3,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* gdb_start(): Start the GDB remote serial protocol server
|
||||||
|
|
||||||
|
This function will start the GDB remote serial protocol implementation and
|
||||||
|
block until the program is resumed from the connected debugger.
|
||||||
|
It currently only supports USB communication and will fail if USB is already
|
||||||
|
in use.*/
|
||||||
|
int gdb_start(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* GINT_GDB */
|
299
src/gdb/gdb.c
Normal file
299
src/gdb/gdb.c
Normal file
|
@ -0,0 +1,299 @@
|
||||||
|
#include <gint/cpu.h>
|
||||||
|
#include <gint/gdb.h>
|
||||||
|
#include <gint/usb-ff-bulk.h>
|
||||||
|
#include <gint/usb.h>
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "gdb_private.h"
|
||||||
|
|
||||||
|
static void gdb_hexlify(char* output_string, const uint8_t* input_buffer, size_t input_size)
|
||||||
|
{
|
||||||
|
const char* hex = "0123456789ABCDEF";
|
||||||
|
for (size_t i = 0; i < input_size; i++) {
|
||||||
|
uint8_t byte = input_buffer[i];
|
||||||
|
output_string[i*2 + 0] = hex[(byte & 0xF0) >> 4];
|
||||||
|
output_string[i*2 + 1] = hex[byte & 0x0F];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO : bug in fxlibc ? strtoul doesn't support uppercase
|
||||||
|
static uint32_t gdb_unhexlify(const char* input_string)
|
||||||
|
{
|
||||||
|
size_t input_length = strlen(input_string);
|
||||||
|
uint32_t ret = 0;
|
||||||
|
for (size_t i = 0; i < input_length; i++) {
|
||||||
|
uint8_t nibble_hex = tolower(input_string[i]);
|
||||||
|
uint8_t nibble = nibble_hex >= 'a' && nibble_hex <= 'f' ? nibble_hex - 'a' + 10 :
|
||||||
|
nibble_hex >= '0' && nibble_hex <= '9' ? nibble_hex - '0' : 0;
|
||||||
|
ret = (ret << 4) | nibble;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool gdb_started = false;
|
||||||
|
|
||||||
|
static void gdb_send(const char *data, size_t size)
|
||||||
|
{
|
||||||
|
usb_fxlink_header_t header;
|
||||||
|
usb_fxlink_fill_header(&header, "gdb", "remote", size);
|
||||||
|
|
||||||
|
int pipe = usb_ff_bulk_output();
|
||||||
|
usb_write_sync(pipe, &header, sizeof(header), false);
|
||||||
|
usb_write_sync(pipe, data, size, false);
|
||||||
|
usb_commit_sync(pipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char gdb_recv_buffer[1024];
|
||||||
|
static size_t gdb_recv_buffer_size = 0;
|
||||||
|
static ssize_t gdb_recv(char *buffer, size_t buffer_size)
|
||||||
|
{
|
||||||
|
if (gdb_recv_buffer_size >= buffer_size) {
|
||||||
|
memcpy(buffer, gdb_recv_buffer, buffer_size);
|
||||||
|
memmove(gdb_recv_buffer, &gdb_recv_buffer[buffer_size], gdb_recv_buffer_size - buffer_size);
|
||||||
|
gdb_recv_buffer_size -= buffer_size;
|
||||||
|
return buffer_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
usb_fxlink_header_t header;
|
||||||
|
while (!usb_fxlink_handle_messages(&header)) {
|
||||||
|
sleep();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO : should we abort or find a way to gracefully shutdown the debugger ?
|
||||||
|
if (strncmp(header.application, "gdb", 16) == 0
|
||||||
|
&& strncmp(header.type, "remote", 16) == 0) {
|
||||||
|
if (header.size > sizeof(gdb_recv_buffer) - gdb_recv_buffer_size) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
usb_read_sync(usb_ff_bulk_input(), &gdb_recv_buffer[gdb_recv_buffer_size], header.size, false);
|
||||||
|
gdb_recv_buffer_size += header.size;
|
||||||
|
return gdb_recv(buffer, buffer_size);
|
||||||
|
} else {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t gdb_recv_packet(char* buffer, size_t buffer_size)
|
||||||
|
{
|
||||||
|
char read_char;
|
||||||
|
|
||||||
|
// Waiting for packet start '$'
|
||||||
|
do {
|
||||||
|
if (gdb_recv(&read_char, 1) != 1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} while (read_char != '$');
|
||||||
|
|
||||||
|
uint8_t checksum = 0;
|
||||||
|
size_t packet_len = 0;
|
||||||
|
while (true) {
|
||||||
|
if (gdb_recv(&read_char, 1) != 1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (read_char != '#') {
|
||||||
|
// -1 to ensure space for a NULL terminator
|
||||||
|
if (packet_len >= (buffer_size - 1)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
buffer[packet_len++] = read_char;
|
||||||
|
checksum += read_char;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer[packet_len] = '\0';
|
||||||
|
|
||||||
|
char read_checksum_hex[3];
|
||||||
|
if (gdb_recv(read_checksum_hex, 2) != 2) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
read_checksum_hex[2] = '\0';
|
||||||
|
uint8_t read_checksum = gdb_unhexlify(read_checksum_hex);
|
||||||
|
|
||||||
|
if (read_checksum != checksum) {
|
||||||
|
read_char = '-';
|
||||||
|
gdb_send(&read_char, 1);
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
read_char = '+';
|
||||||
|
gdb_send(&read_char, 1);
|
||||||
|
return packet_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t gdb_send_packet(const char* packet, size_t packet_length)
|
||||||
|
{
|
||||||
|
if (packet == NULL || packet_length == 0) {
|
||||||
|
// Empty packet
|
||||||
|
gdb_send("$#00", 4);
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t buffer_length = packet_length + 1 + 4;
|
||||||
|
// TODO : find if it's more efficient to malloc+copy on each packet or send 3 small fxlink messages
|
||||||
|
char* buffer = malloc(buffer_length);
|
||||||
|
|
||||||
|
uint8_t checksum = 0;
|
||||||
|
for (size_t i = 0; i < packet_length; i++) {
|
||||||
|
checksum += packet[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[0] = '$';
|
||||||
|
memcpy(&buffer[1], packet, packet_length);
|
||||||
|
snprintf(&buffer[buffer_length - 4], 4, "#%02X", checksum);
|
||||||
|
|
||||||
|
// -1 to not send the NULL terminator of snprintf
|
||||||
|
gdb_send(buffer, buffer_length - 1);
|
||||||
|
free(buffer);
|
||||||
|
return buffer_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gdb_send_stop_reply(void)
|
||||||
|
{
|
||||||
|
gdb_send_packet("S05", 3); // SIGTRAP
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gdb_handle_query_packet(const char* packet)
|
||||||
|
{
|
||||||
|
if (strncmp("qSupported", packet, 10) == 0) {
|
||||||
|
const char* qsupported_ans = "PacketSize=255;qXfer:memory-map:read";
|
||||||
|
gdb_send_packet(qsupported_ans, strlen(qsupported_ans));
|
||||||
|
} else if (strncmp("qXfer:memory-map:read::", packet, 23) == 0) {
|
||||||
|
/* TODO : Implement qXfer and memory map XML
|
||||||
|
* https://sourceware.org/gdb/onlinedocs/gdb/Memory-Map-Format.html#Memory-Map-Format
|
||||||
|
* Required for enforcing hbreak : https://sourceware.org/gdb/onlinedocs/gdb/Set-Breaks.html
|
||||||
|
*/
|
||||||
|
gdb_send_packet(NULL, 0);
|
||||||
|
} else {
|
||||||
|
gdb_send_packet(NULL, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gdb_handle_read_general_registers(gdb_cpu_state_t* cpu_state)
|
||||||
|
{
|
||||||
|
char reply_buffer[23*8];
|
||||||
|
if (!cpu_state) {
|
||||||
|
memset(reply_buffer, 'x', sizeof(reply_buffer));
|
||||||
|
memcpy(&reply_buffer[offsetof(gdb_cpu_state_t, reg.pc)*2],
|
||||||
|
"A0000000", 8); // pc needs to be set to make GDB happy
|
||||||
|
} else {
|
||||||
|
gdb_hexlify(reply_buffer, (uint8_t*)cpu_state->regs,
|
||||||
|
sizeof(cpu_state->regs));
|
||||||
|
}
|
||||||
|
gdb_send_packet(reply_buffer, sizeof(reply_buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gdb_handle_read_register(gdb_cpu_state_t* cpu_state, const char* packet)
|
||||||
|
{
|
||||||
|
uint8_t register_id = gdb_unhexlify(&packet[1]);
|
||||||
|
char reply_buffer[8];
|
||||||
|
if (!cpu_state || register_id >= sizeof(cpu_state->regs)/sizeof(uint32_t)) {
|
||||||
|
memset(reply_buffer, 'x', sizeof(reply_buffer));
|
||||||
|
} else {
|
||||||
|
gdb_hexlify(reply_buffer, (uint8_t*)&cpu_state->regs[register_id],
|
||||||
|
sizeof(cpu_state->regs[register_id]));
|
||||||
|
}
|
||||||
|
gdb_send_packet(reply_buffer, sizeof(reply_buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gdb_handle_read_memory(const char* packet)
|
||||||
|
{
|
||||||
|
char address_hex[16] = {0}, size_hex[16] = {0};
|
||||||
|
void* read_address;
|
||||||
|
size_t read_size;
|
||||||
|
|
||||||
|
packet++; // consume 'm'
|
||||||
|
for (size_t i = 0; i < sizeof(address_hex); i++) {
|
||||||
|
address_hex[i] = *(packet++); // consume address
|
||||||
|
if (*packet == ',') break;
|
||||||
|
}
|
||||||
|
packet++; // consume ','
|
||||||
|
for (size_t i = 0; i < sizeof(size_hex); i++) {
|
||||||
|
size_hex[i] = *(packet++); // consume size
|
||||||
|
if (*packet == '\0') break;
|
||||||
|
}
|
||||||
|
|
||||||
|
read_address = (void*) gdb_unhexlify(address_hex);
|
||||||
|
read_size = (size_t) gdb_unhexlify(size_hex);
|
||||||
|
|
||||||
|
// TODO : Detect invalid reads and prevent TLB misses
|
||||||
|
char *reply_buffer = malloc(read_size * 2);
|
||||||
|
gdb_hexlify(reply_buffer, read_address, read_size);
|
||||||
|
gdb_send_packet(reply_buffer, read_size * 2);
|
||||||
|
free(reply_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gdb_main(gdb_cpu_state_t* cpu_state)
|
||||||
|
{
|
||||||
|
if (cpu_state != NULL) {
|
||||||
|
gdb_send_stop_reply();
|
||||||
|
}
|
||||||
|
while (1) {
|
||||||
|
char packet_buffer[256];
|
||||||
|
ssize_t packet_size = gdb_recv_packet(packet_buffer, sizeof(packet_buffer));
|
||||||
|
if (packet_size <= 0) {
|
||||||
|
// TODO : Should we break or log on recv error ?
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (packet_buffer[0]) {
|
||||||
|
case '?': // Halt reason
|
||||||
|
gdb_send_stop_reply();
|
||||||
|
break;
|
||||||
|
case 'q':
|
||||||
|
gdb_handle_query_packet(packet_buffer);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'g':
|
||||||
|
gdb_handle_read_general_registers(cpu_state);
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
gdb_handle_read_register(cpu_state, packet_buffer);
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
gdb_handle_read_memory(packet_buffer);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// case 'G': // Write general register
|
||||||
|
// case 'P': // Write register
|
||||||
|
// case 'M': // Write memory
|
||||||
|
// case 'z': // Insert hbreak
|
||||||
|
// case 'Z': // Remove hbreak
|
||||||
|
// case 'k': // Kill request
|
||||||
|
// case 's': // Single step
|
||||||
|
|
||||||
|
case 'c': // Continue
|
||||||
|
return;
|
||||||
|
|
||||||
|
default: // Unsupported packet
|
||||||
|
gdb_send_packet(NULL, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int gdb_start(void)
|
||||||
|
{
|
||||||
|
if (gdb_started) {
|
||||||
|
return GDB_ALREADY_STARTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usb_is_open()) {
|
||||||
|
return GDB_NO_INTERFACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
usb_interface_t const *interfaces[] = { &usb_ff_bulk, NULL };
|
||||||
|
if (usb_open(interfaces, GINT_CALL_NULL) < 0) {
|
||||||
|
return GDB_USB_ERROR;
|
||||||
|
}
|
||||||
|
usb_open_wait();
|
||||||
|
|
||||||
|
gdb_started = true;
|
||||||
|
gdb_main(NULL);
|
||||||
|
return 0;
|
||||||
|
}
|
50
src/gdb/gdb_private.h
Normal file
50
src/gdb/gdb_private.h
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
//---
|
||||||
|
// gint:gdb:gdb-private - Private definitions for the GDB implementation
|
||||||
|
//---
|
||||||
|
|
||||||
|
#ifndef GINT_GDB_PRIVATE
|
||||||
|
#define GINT_GDB_PRIVATE
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* gdb_cpu_state_t: State of the CPU when breaking
|
||||||
|
This struct keep the same register indices as those declared by GDB to allow
|
||||||
|
easy R/W without needing a "translation" table.
|
||||||
|
See : https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/sh-tdep.c;
|
||||||
|
h=c402961b80a0b4589243023ea5362d43f644a9ec;hb=4f3e26ac6ee31f7bc4b04abd
|
||||||
|
8bdb944e7f1fc5d2#l327
|
||||||
|
*/
|
||||||
|
// TODO : Should we expose r*b*, ssr, spc ? are they double-saved when breaking
|
||||||
|
// inside an interrupt handler ?
|
||||||
|
typedef struct {
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
uint32_t r0;
|
||||||
|
uint32_t r1;
|
||||||
|
uint32_t r2;
|
||||||
|
uint32_t r3;
|
||||||
|
uint32_t r4;
|
||||||
|
uint32_t r5;
|
||||||
|
uint32_t r6;
|
||||||
|
uint32_t r7;
|
||||||
|
uint32_t r8;
|
||||||
|
uint32_t r9;
|
||||||
|
uint32_t r10;
|
||||||
|
uint32_t r11;
|
||||||
|
uint32_t r12;
|
||||||
|
uint32_t r13;
|
||||||
|
uint32_t r14;
|
||||||
|
uint32_t r15;
|
||||||
|
uint32_t pc;
|
||||||
|
uint32_t pr;
|
||||||
|
uint32_t gbr;
|
||||||
|
uint32_t vbr;
|
||||||
|
uint32_t mach;
|
||||||
|
uint32_t macl;
|
||||||
|
uint32_t sr;
|
||||||
|
} reg;
|
||||||
|
uint32_t regs[23];
|
||||||
|
};
|
||||||
|
} gdb_cpu_state_t;
|
||||||
|
|
||||||
|
#endif /* GINT_GDB_PRIVATE */
|
Loading…
Reference in a new issue