From 0bea485f4b33bc7d9d48a00be411e1c650535c09 Mon Sep 17 00:00:00 2001 From: redoste Date: Sun, 21 May 2023 19:49:23 +0200 Subject: [PATCH 01/17] gdb: first implementation with basic memory and register read support --- CMakeLists.txt | 2 + include/gint/gdb.h | 31 +++++ src/gdb/gdb.c | 299 ++++++++++++++++++++++++++++++++++++++++++ src/gdb/gdb_private.h | 50 +++++++ 4 files changed, 382 insertions(+) create mode 100644 include/gint/gdb.h create mode 100644 src/gdb/gdb.c create mode 100644 src/gdb/gdb_private.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2723835..5449fb4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,6 +70,8 @@ set(SOURCES_COMMON src/fs/fugue/fugue_rmdir.c src/fs/fugue/fugue_unlink.c src/fs/fugue/util.c + # GDB remote serial protocol + src/gdb/gdb.c # Interrupt Controller driver src/intc/intc.c src/intc/inth.s diff --git a/include/gint/gdb.h b/include/gint/gdb.h new file mode 100644 index 0000000..41b6e3c --- /dev/null +++ b/include/gint/gdb.h @@ -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 */ diff --git a/src/gdb/gdb.c b/src/gdb/gdb.c new file mode 100644 index 0000000..5b95416 --- /dev/null +++ b/src/gdb/gdb.c @@ -0,0 +1,299 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +#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; +} diff --git a/src/gdb/gdb_private.h b/src/gdb/gdb_private.h new file mode 100644 index 0000000..04474ee --- /dev/null +++ b/src/gdb/gdb_private.h @@ -0,0 +1,50 @@ +//--- +// gint:gdb:gdb-private - Private definitions for the GDB implementation +//--- + +#ifndef GINT_GDB_PRIVATE +#define GINT_GDB_PRIVATE + +#include + +/* 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 */ From aa0ff7b10b5fefa31994c75894f54483548cf236 Mon Sep 17 00:00:00 2001 From: redoste Date: Wed, 24 May 2023 17:24:08 +0200 Subject: [PATCH 02/17] ubc: basic User Break Controller driver --- CMakeLists.txt | 3 + include/gint/gdb.h | 42 +++++++++++++ include/gint/mpu/ubc.h | 87 +++++++++++++++++++++++++++ include/gint/ubc.h | 54 +++++++++++++++++ src/gdb/gdb.c | 2 - src/gdb/gdb_private.h | 50 ---------------- src/ubc/ubc.S | 89 +++++++++++++++++++++++++++ src/ubc/ubc.c | 132 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 407 insertions(+), 52 deletions(-) create mode 100644 include/gint/mpu/ubc.h create mode 100644 include/gint/ubc.h delete mode 100644 src/gdb/gdb_private.h create mode 100644 src/ubc/ubc.S create mode 100644 src/ubc/ubc.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 5449fb4..bc90558 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,6 +124,9 @@ set(SOURCES_COMMON src/tmu/inth-tmu.s src/tmu/sleep.c src/tmu/tmu.c + # UBC driver + src/ubc/ubc.c + src/ubc/ubc.S # USB driver src/usb/asyncio.c src/usb/classes/ff-bulk.c diff --git a/include/gint/gdb.h b/include/gint/gdb.h index 41b6e3c..acdedc8 100644 --- a/include/gint/gdb.h +++ b/include/gint/gdb.h @@ -9,6 +9,8 @@ extern "C" { #endif +#include + /* Error codes for GDB functions */ enum { GDB_NO_INTERFACE = -1, @@ -16,6 +18,46 @@ enum { GDB_USB_ERROR = -3, }; +/* 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; + /* gdb_start(): Start the GDB remote serial protocol server This function will start the GDB remote serial protocol implementation and diff --git a/include/gint/mpu/ubc.h b/include/gint/mpu/ubc.h new file mode 100644 index 0000000..4dacb87 --- /dev/null +++ b/include/gint/mpu/ubc.h @@ -0,0 +1,87 @@ +//--- +// gint:mpu:ubc - User Break Controller +//--- + +#ifndef GINT_MPU_UBC +#define GINT_MPU_UBC + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef volatile struct +{ + lword_union(CBR0, /* Match condition setting 0 */ + uint32_t MFE :1; /* Match Flag Enable */ + uint32_t AIE :1; /* ASID Enable */ + uint32_t MFI :6; /* Match Flag Specify */ + uint32_t AIV :8; /* ASID Specify */ + uint32_t :1; + uint32_t SZ :3; /* Operand Size Select */ + uint32_t :4; + uint32_t CD :2; /* Bus Select */ + uint32_t ID :2; /* Instruction Fetch / Operand Access Select */ + uint32_t :1; + uint32_t RW :2; /* Bus Command Select */ + uint32_t CE :1; /* Channel Enable */ + ); + lword_union(CRR0, /* Match operation setting 0 */ + uint32_t :30; + uint32_t PCB :1; /* PC Break Select */ + uint32_t BIE :1; /* Break Enable */ + ); + uint32_t CAR0; /* Match address setting 0 */ + uint32_t CAMR0; /* Match address mask setting 0 */ + pad(0x10); + + + lword_union(CBR1, /* Match condition setting 1 */ + uint32_t MFE :1; /* Match Flag Enable */ + uint32_t AIE :1; /* ASID Enable */ + uint32_t MFI :6; /* Match Flag Specify */ + uint32_t AIV :8; /* ASID Specify */ + uint32_t DBE :1; /* Data Value Enable */ + uint32_t SZ :3; /* Operand Size Select */ + uint32_t ETBE :1; /* Execution Count Value Enable */ + uint32_t :3; + uint32_t CD :2; /* Bus Select */ + uint32_t ID :2; /* Instruction Fetch / Operand Access Select */ + uint32_t :1; + uint32_t RW :2; /* Bus Command Select */ + uint32_t CE :1; /* Channel Enable */ + ); + lword_union(CRR1, /* Match operation setting 1 */ + uint32_t :30; + uint32_t PCB :1; /* PC Break Select */ + uint32_t BIE :1; /* Break Enable */ + ); + uint32_t CAR1; /* Match address setting 1 */ + uint32_t CAMR1; /* Match address mask setting 1 */ + uint32_t CDR1; /* Match data setting 1 */ + uint32_t CDMR1; /* Match data mask setting 1 */ + lword_union(CETR1, /* Execution count break 1 */ + uint32_t :20; + uint32_t CET :12; /* Execution Count */ + ); + pad(0x5c4); + + lword_union(CCMFR, /* Channel match flag */ + uint32_t :30; + uint32_t MF1 :1; /* Channel 1 Condition Match Flag */ + uint32_t MF0 :1; /* Channel 0 Condition Match Flag */ + ); + pad(0x1c); + lword_union(CBCR, /* Break control */ + uint32_t :31; + uint32_t UBDE :1; /* User Break Debugging Support Function Enable */ + ); +} GPACKED(4) sh7305_ubc_t; +#define SH7305_UBC (*(sh7305_ubc_t *)0xff200000) + +#ifdef __cplusplus +} +#endif + +#endif /* GINT_MPU_UBC */ diff --git a/include/gint/ubc.h b/include/gint/ubc.h new file mode 100644 index 0000000..d858948 --- /dev/null +++ b/include/gint/ubc.h @@ -0,0 +1,54 @@ +//--- +// gint:ubc - User Break Controller driver +//--- + +#ifndef GINT_UBC +#define GINT_UBC + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Read and write the DBR register */ +void ubc_setDBR(void* DBR); +void* ubc_getDBR(void); + +/* ubc_dbh(): Low level UBC debug handler + The handler will backup the current CPU state and call ubc_debug_handler(). */ +void ubc_dbh(void); +/* ubc_debug_handler(): C level UBC debug handler + The execution will be redirected to the handler set by the user or the add-in + will panic in case no handler has been set. */ +void ubc_debug_handler(gdb_cpu_state_t* cpu_state); +/* ubc_set_debug_handler(): Set user debug handler + Set a custom debug handler that will be called when a break condition from + the UBC is reached. */ +void ubc_set_debug_handler(void (*h)(gdb_cpu_state_t*)); + +/* UBC Breakpoint types */ +typedef enum { + UBC_BREAK_BEFORE, /* Break before the instruction is executed */ + UBC_BREAK_AFTER, /* Break after the instruction is executed : + at this point PC will point to the next instruction. */ +} ubc_break_mode_t; + +/* ubc_set_breakpoint(): Set a breakpoint in a UBC channel and enable it + Return false when an invalid channel number is provided, true if the + breakpoint was correctly set up. */ +bool ubc_set_breakpoint(int channel, void* break_address, ubc_break_mode_t break_mode); +/* ubc_get_break_address(): Get a breakpoint address if it's enabled + If the channel is disabled the function will return false and *break_address + will not be updated. */ +bool ubc_get_break_address(int channel, void** break_address); +/* ubc_disable_channel(): Disable a UBC channel + Return true on success. If an invalid channel number is provided, it will + return false. */ +bool ubc_disable_channel(int channel); + +#ifdef __cplusplus +} +#endif + +#endif /* GINT_UBC */ diff --git a/src/gdb/gdb.c b/src/gdb/gdb.c index 5b95416..68782ab 100644 --- a/src/gdb/gdb.c +++ b/src/gdb/gdb.c @@ -8,8 +8,6 @@ #include #include -#include "gdb_private.h" - static void gdb_hexlify(char* output_string, const uint8_t* input_buffer, size_t input_size) { const char* hex = "0123456789ABCDEF"; diff --git a/src/gdb/gdb_private.h b/src/gdb/gdb_private.h deleted file mode 100644 index 04474ee..0000000 --- a/src/gdb/gdb_private.h +++ /dev/null @@ -1,50 +0,0 @@ -//--- -// gint:gdb:gdb-private - Private definitions for the GDB implementation -//--- - -#ifndef GINT_GDB_PRIVATE -#define GINT_GDB_PRIVATE - -#include - -/* 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 */ diff --git a/src/ubc/ubc.S b/src/ubc/ubc.S new file mode 100644 index 0000000..da31041 --- /dev/null +++ b/src/ubc/ubc.S @@ -0,0 +1,89 @@ +.global _ubc_setDBR +.global _ubc_getDBR +.text + +_ubc_setDBR: + ldc r4, dbr + rts + nop + +_ubc_getDBR: + stc dbr, r0 + rts + nop + +.global _ubc_dbh +_ubc_dbh: + /* We backup registers in the correct order to build gdb_cpu_state_t */ + stc.l ssr, @-r15 + sts.l macl, @-r15 + sts.l mach, @-r15 + stc.l vbr, @-r15 + stc.l gbr, @-r15 + sts.l pr, @-r15 + stc.l spc, @-r15 + stc.l sgr, @-r15 + mov.l r14, @-r15 + mov.l r13, @-r15 + mov.l r12, @-r15 + mov.l r11, @-r15 + mov.l r10, @-r15 + mov.l r9, @-r15 + mov.l r8, @-r15 + stc.l R7_BANK, @-r15 + stc.l R6_BANK, @-r15 + stc.l R5_BANK, @-r15 + stc.l R4_BANK, @-r15 + stc.l R3_BANK, @-r15 + stc.l R2_BANK, @-r15 + stc.l R1_BANK, @-r15 + stc.l R0_BANK, @-r15 + + /* Enable interrupts and switch register bank + Original SR is kept in r8 */ + stc sr, r8 + mov r8, r1 + mov.l .sr_mask, r0 + and r0, r1 + ldc r1, sr + + mov r15, r4 + mov.l .handler, r0 + jsr @r0 + nop + + /* Restore original SR to access the correct register bank */ + ldc r8, sr + + ldc.l @r15+, R0_BANK + ldc.l @r15+, R1_BANK + ldc.l @r15+, R2_BANK + ldc.l @r15+, R3_BANK + ldc.l @r15+, R4_BANK + ldc.l @r15+, R5_BANK + ldc.l @r15+, R6_BANK + ldc.l @r15+, R7_BANK + mov.l @r15+, r8 + mov.l @r15+, r9 + mov.l @r15+, r10 + mov.l @r15+, r11 + mov.l @r15+, r12 + mov.l @r15+, r13 + mov.l @r15+, r14 + ldc.l @r15+, sgr + ldc.l @r15+, spc + lds.l @r15+, pr + ldc.l @r15+, gbr + ldc.l @r15+, vbr + lds.l @r15+, mach + lds.l @r15+, macl + ldc.l @r15+, ssr + rte + nop + +.align 4 +.handler: .long _ubc_debug_handler +.sr_mask: .long ~0x300000f0 /* IMASK = 0 : mask no interrupts + BL = 0 : do not block interrupts + RB = 0 : use register BANK0 + */ diff --git a/src/ubc/ubc.c b/src/ubc/ubc.c new file mode 100644 index 0000000..26b8021 --- /dev/null +++ b/src/ubc/ubc.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include + +#define UBC SH7305_UBC +#define POWER SH7305_POWER + +static bool hpowered(void) +{ + return POWER.MSTPCR0.UDB == 0; +} + +static void hpoweron(void) +{ + // Power on the UBC via MSTPCR0 + POWER.MSTPCR0.UDB = 0; + + // Set the DBR register + ubc_setDBR(ubc_dbh); + + // Disable break channel 0 and 1 + UBC.CBR0.CE = 0; + UBC.CBR1.CE = 0; + + // Enable user break debugging support (i.e. usage of the DBR register) + UBC.CBCR.UBDE = 1; +} + +static void hpoweroff(void) +{ + POWER.MSTPCR0.UDB = 1; + + UBC.CBR0.CE = 0; + UBC.CBR1.CE = 0; +} + +#define UBC_BREAK_CHANNEL(CBR, CRR, CAR, CAMR) do { \ + UBC.CBR.MFE = 0; /* Don't include Match Flag in match condition */ \ + UBC.CBR.AIE = 0; /* Don't include ASID check in match condition */ \ + UBC.CBR.MFI = 0; /* Default value of MFI is reserved, make it legal */ \ + UBC.CBR.SZ = 0; /* Match on any operand size */ \ + UBC.CBR.CD = 0; /* Match on operand bus access */ \ + UBC.CBR.ID = 1; /* Match on instruction fetch */ \ + UBC.CBR.RW = 1; /* Match on read cycle */ \ + \ + UBC.CRR.PCB = pcb; /* Set PC break {before,after} instruction execution */ \ + UBC.CRR.BIE = 1; /* Break when channel matches */ \ + \ + UBC.CAR = (uint32_t)break_address; /* Match address */ \ + UBC.CAMR = 0; /* Match mask (0 bits are included) */ \ + UBC.CBR.CE = 1; /* Enable channel */ \ +} while (0) +bool ubc_set_breakpoint(int channel, void* break_address, ubc_break_mode_t break_mode) +{ + if (!hpowered()) + hpoweron(); + + uint32_t pcb = break_mode == UBC_BREAK_AFTER ? 1 : 0; + if (channel == 0) { + UBC_BREAK_CHANNEL(CBR0, CRR0, CAR0, CAMR0); + return true; + } else if (channel == 1) { + UBC_BREAK_CHANNEL(CBR1, CRR1, CAR1, CAMR1); + return true; + } else { + return false; + } +} +#undef UBC_BREAK_CHANNEL + +bool ubc_get_break_address(int channel, void** break_address) +{ + if (!hpowered()) + hpoweron(); + + if (channel == 0 && UBC.CBR0.CE) { + *break_address = (void*) UBC.CAR0; + return true; + } else if (channel == 1 && UBC.CBR1.CE) { + *break_address = (void*) UBC.CAR1; + return true; + } else { + return false; + } +} + +bool ubc_disable_channel(int channel) +{ + if (!hpowered()) + hpoweron(); + + if (channel == 0) { + UBC.CBR0.CE = 0; + return true; + } else if (channel == 1) { + UBC.CBR1.CE = 0; + return true; + } else { + return false; + } +} + +static void (*ubc_application_debug_handler)(gdb_cpu_state_t*) = NULL; +void ubc_debug_handler(gdb_cpu_state_t* cpu_state) +{ + // Clear match flags + UBC.CCMFR.lword = 0; + + if (ubc_application_debug_handler != NULL) { + ubc_application_debug_handler(cpu_state); + } else { + // TODO : Should we add a custom panic code ? + gint_panic(-1); + } +} + +// TODO : Should we use the struct designed for GDB or make it more generic ? +void ubc_set_debug_handler(void (*h)(gdb_cpu_state_t*)) { + ubc_application_debug_handler = h; +} + +gint_driver_t drv_ubc = { + .name = "UBC", + .hpowered = hpowered, + .hpoweron = hpoweron, + .hpoweroff = hpoweroff, + .flags = GINT_DRV_SHARED, +}; +GINT_DECLARE_DRIVER(30, drv_ubc); From 238bccddbe79cf7356aab5129d00d87ddfb032f3 Mon Sep 17 00:00:00 2001 From: redoste Date: Wed, 24 May 2023 17:41:13 +0200 Subject: [PATCH 03/17] gdb: add hw-breakpoint and single step support using the UBC --- include/gint/gdb.h | 6 ++ src/gdb/gdb.c | 135 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 137 insertions(+), 4 deletions(-) diff --git a/include/gint/gdb.h b/include/gint/gdb.h index acdedc8..1f4c958 100644 --- a/include/gint/gdb.h +++ b/include/gint/gdb.h @@ -66,6 +66,12 @@ typedef struct { in use.*/ int gdb_start(void); +/* gdb_main(): Main GDB loop + + This function handles GDB messages sent over USB and will mutate the cpu_state + struct, memory and the UBC configuration accordingly. */ +void gdb_main(gdb_cpu_state_t* cpu_state); + #ifdef __cplusplus } #endif diff --git a/src/gdb/gdb.c b/src/gdb/gdb.c index 68782ab..0a73b10 100644 --- a/src/gdb/gdb.c +++ b/src/gdb/gdb.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -226,11 +227,128 @@ static void gdb_handle_read_memory(const char* packet) free(reply_buffer); } -static void gdb_main(gdb_cpu_state_t* cpu_state) +static bool gdb_parse_hardware_breakpoint_packet(const char* packet, void** read_address) +{ + packet++; // consume 'z' or 'Z' + if (*packet != '1') { // hardware breakpoint + return false; + } + packet++; // consume '1' + packet++; // consume ',' + + char address_hex[16] = {0}, kind_hex[16] = {0}; + 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(kind_hex); i++) { + kind_hex[i] = *(packet++); // consume kind + if (*packet == '\0' || *packet == ';') break; + } + + *read_address = (void*) gdb_unhexlify(address_hex); + uint32_t read_kind = gdb_unhexlify(kind_hex); + + if (read_kind != 2) { // SuperH instructions are 2 bytes long + return false; + } + + return true; +} + +static void gdb_handle_insert_hardware_breakpoint(const char* packet) +{ + void* read_address; + if (!gdb_parse_hardware_breakpoint_packet(packet, &read_address)) { + gdb_send_packet(NULL, 0); + return; + } + + void *channel0_addr, *channel1_addr; + bool channel0_used = ubc_get_break_address(0, &channel0_addr); + bool channel1_used = ubc_get_break_address(1, &channel1_addr); + + /* As stated by GDB doc : "the operations should be implemented in an idempotent way." + * Thus we first check if the breakpoint is already placed in one of the UBC channel. + */ + if ((channel0_used && channel0_addr == read_address) || + (channel1_used && channel1_addr == read_address)) { + gdb_send_packet("OK", 2); + } else if (!channel0_used) { + ubc_set_breakpoint(0, read_address, UBC_BREAK_BEFORE); + gdb_send_packet("OK", 2); + } else if (!channel1_used) { + ubc_set_breakpoint(1, read_address, UBC_BREAK_BEFORE); + gdb_send_packet("OK", 2); + } else { + /* TODO : We should find a proper way to inform GDB that we are + * limited by the number of UBC channels. + */ + gdb_send_packet(NULL, 0); + } +} + +static void gdb_handle_remove_hardware_breakpoint(const char* packet) +{ + void* read_address; + if (!gdb_parse_hardware_breakpoint_packet(packet, &read_address)) { + gdb_send_packet(NULL, 0); + return; + } + + void *channel0_addr, *channel1_addr; + bool channel0_used = ubc_get_break_address(0, &channel0_addr); + bool channel1_used = ubc_get_break_address(1, &channel1_addr); + + if (channel0_used && channel0_addr == read_address) { + ubc_disable_channel(0); + } + if (channel1_used && channel1_addr == read_address) { + ubc_disable_channel(1); + } + gdb_send_packet("OK", 2); +} + +static struct { + bool single_stepped; + bool channel0_used; + bool channel1_used; + void* channel0_addr; + void* channel1_addr; +} gdb_single_step_backup = { false }; +static void gdb_handle_single_step(gdb_cpu_state_t* cpu_state) +{ + gdb_single_step_backup.channel0_used = ubc_get_break_address(0, &gdb_single_step_backup.channel0_addr); + gdb_single_step_backup.channel1_used = ubc_get_break_address(1, &gdb_single_step_backup.channel1_addr); + + ubc_disable_channel(0); + ubc_set_breakpoint(1, (void*)cpu_state->reg.pc, UBC_BREAK_AFTER); + + gdb_single_step_backup.single_stepped = true; +} + +void gdb_main(gdb_cpu_state_t* cpu_state) { if (cpu_state != NULL) { gdb_send_stop_reply(); } + + if (gdb_single_step_backup.single_stepped) { + if (gdb_single_step_backup.channel0_used) { + ubc_set_breakpoint(0, gdb_single_step_backup.channel0_addr, UBC_BREAK_BEFORE); + } else { + ubc_disable_channel(0); + } + if (gdb_single_step_backup.channel1_used) { + ubc_set_breakpoint(1, gdb_single_step_backup.channel1_addr, UBC_BREAK_BEFORE); + } else { + ubc_disable_channel(1); + } + + gdb_single_step_backup.single_stepped = false; + } + while (1) { char packet_buffer[256]; ssize_t packet_size = gdb_recv_packet(packet_buffer, sizeof(packet_buffer)); @@ -260,11 +378,18 @@ static void gdb_main(gdb_cpu_state_t* cpu_state) // 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 'Z': + gdb_handle_insert_hardware_breakpoint(packet_buffer); + break; + case 'z': + gdb_handle_remove_hardware_breakpoint(packet_buffer); + break; + + case 's': + gdb_handle_single_step(cpu_state); + return; case 'c': // Continue return; @@ -291,6 +416,8 @@ int gdb_start(void) } usb_open_wait(); + ubc_set_debug_handler(gdb_main); + gdb_started = true; gdb_main(NULL); return 0; From eab3184dbbf79c8acb641b0e01ca91e9ec30c7d1 Mon Sep 17 00:00:00 2001 From: redoste Date: Wed, 24 May 2023 22:22:54 +0200 Subject: [PATCH 04/17] gdb: add "kill request" packet support by abort()ing the add-in --- src/gdb/gdb.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/gdb/gdb.c b/src/gdb/gdb.c index 0a73b10..ab02de0 100644 --- a/src/gdb/gdb.c +++ b/src/gdb/gdb.c @@ -378,7 +378,10 @@ void gdb_main(gdb_cpu_state_t* cpu_state) // case 'G': // Write general register // case 'P': // Write register // case 'M': // Write memory - // case 'k': // Kill request + + case 'k': // Kill request + abort(); + return; case 'Z': gdb_handle_insert_hardware_breakpoint(packet_buffer); From 3aa11b4252235b3ebcff292622dc7c48b233214e Mon Sep 17 00:00:00 2001 From: redoste Date: Wed, 24 May 2023 23:07:41 +0200 Subject: [PATCH 05/17] gdb: add register write support --- src/gdb/gdb.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/src/gdb/gdb.c b/src/gdb/gdb.c index ab02de0..8e84550 100644 --- a/src/gdb/gdb.c +++ b/src/gdb/gdb.c @@ -20,9 +20,8 @@ static void gdb_hexlify(char* output_string, const uint8_t* input_buffer, size_t } // TODO : bug in fxlibc ? strtoul doesn't support uppercase -static uint32_t gdb_unhexlify(const char* input_string) +static uint32_t gdb_unhexlify_sized(const char* input_string, size_t input_length) { - 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]); @@ -33,6 +32,11 @@ static uint32_t gdb_unhexlify(const char* input_string) return ret; } +static uint32_t gdb_unhexlify(const char* input_string) +{ + return gdb_unhexlify_sized(input_string, strlen(input_string)); +} + static bool gdb_started = false; static void gdb_send(const char *data, size_t size) @@ -200,6 +204,59 @@ static void gdb_handle_read_register(gdb_cpu_state_t* cpu_state, const char* pac gdb_send_packet(reply_buffer, sizeof(reply_buffer)); } +static void gdb_handle_write_general_registers(gdb_cpu_state_t* cpu_state, const char* packet) +{ + if (!cpu_state) { + gdb_send_packet(NULL, 0); + return; + } + + packet++; // consume 'G' + + // Let's not handle incomplete 'G' packets as they're rarely used anyway + if (strlen(packet) != sizeof(cpu_state->regs)*2) { + gdb_send_packet(NULL, 0); + return; + } + + for (size_t i = 0; i < sizeof(cpu_state->regs)/sizeof(uint32_t); i++) { + cpu_state->regs[i] = gdb_unhexlify_sized(&packet[i * sizeof(uint32_t) * 2], + sizeof(uint32_t) * 2); + } + gdb_send_packet("OK", 2); +} + +static void gdb_handle_write_register(gdb_cpu_state_t* cpu_state, const char* packet) +{ + if (!cpu_state) { + gdb_send_packet(NULL, 0); + return; + } + + char register_id_hex[16] = {0}, value_hex[16] = {0}; + + packet++; // consume 'P' + for (size_t i = 0; i < sizeof(register_id_hex); i++) { + register_id_hex[i] = *(packet++); // consume register id + if (*packet == '=') break; + } + packet++; // consume '=' + for (size_t i = 0; i < sizeof(value_hex); i++) { + value_hex[i] = *(packet++); // consume register value + if (*packet == '\0') break; + } + + uint32_t register_id = gdb_unhexlify(register_id_hex); + uint32_t value = gdb_unhexlify(value_hex); + + if (register_id >= sizeof(cpu_state->regs)/sizeof(uint32_t)) { + gdb_send_packet(NULL, 0); + } else { + cpu_state->regs[register_id] = value; + gdb_send_packet("OK", 2); + } +} + static void gdb_handle_read_memory(const char* packet) { char address_hex[16] = {0}, size_hex[16] = {0}; @@ -374,9 +431,13 @@ void gdb_main(gdb_cpu_state_t* cpu_state) case 'm': gdb_handle_read_memory(packet_buffer); break; + case 'G': + gdb_handle_write_general_registers(cpu_state, packet_buffer); + break; + case 'P': + gdb_handle_write_register(cpu_state, packet_buffer); + break; - // case 'G': // Write general register - // case 'P': // Write register // case 'M': // Write memory case 'k': // Kill request From 2f6ee59d432608bfa8e28e3b50e98a4f489a9981 Mon Sep 17 00:00:00 2001 From: redoste Date: Wed, 24 May 2023 23:30:29 +0200 Subject: [PATCH 06/17] gdb: add memory write support --- src/gdb/gdb.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/gdb/gdb.c b/src/gdb/gdb.c index 8e84550..5a92d8a 100644 --- a/src/gdb/gdb.c +++ b/src/gdb/gdb.c @@ -284,6 +284,34 @@ static void gdb_handle_read_memory(const char* packet) free(reply_buffer); } +static void gdb_handle_write_memory(const char* packet) +{ + char address_hex[16] = {0}, size_hex[16] = {0}; + uint8_t* 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 == ':') break; + } + packet++; // consume ':' + + read_address = (uint8_t*) gdb_unhexlify(address_hex); + read_size = (size_t) gdb_unhexlify(size_hex); + + // TODO : Detect invalid writes and prevent TLB misses + for (size_t i = 0; i < read_size; i++) { + read_address[i] = (uint8_t)gdb_unhexlify_sized(&packet[i * 2], 2); + } + gdb_send_packet("OK", 2); +} + static bool gdb_parse_hardware_breakpoint_packet(const char* packet, void** read_address) { packet++; // consume 'z' or 'Z' @@ -437,8 +465,9 @@ void gdb_main(gdb_cpu_state_t* cpu_state) case 'P': gdb_handle_write_register(cpu_state, packet_buffer); break; - - // case 'M': // Write memory + case 'M': + gdb_handle_write_memory(packet_buffer); + break; case 'k': // Kill request abort(); From eca05ec64c5fce755aab435414a4321704cf7215 Mon Sep 17 00:00:00 2001 From: redoste Date: Thu, 25 May 2023 22:16:58 +0200 Subject: [PATCH 07/17] gdb: send memory map XML to GDB to enforce hw-breakpoints --- src/gdb/gdb.c | 57 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/src/gdb/gdb.c b/src/gdb/gdb.c index 5a92d8a..59baf29 100644 --- a/src/gdb/gdb.c +++ b/src/gdb/gdb.c @@ -161,17 +161,62 @@ static void gdb_send_stop_reply(void) gdb_send_packet("S05", 3); // SIGTRAP } +static void gdb_handle_qXfer_packet(const char* packet, const char* data, size_t data_size) +{ + char offset_hex[16] = {0}, length_hex[16] = {0}; + for (size_t i = 0; i < sizeof(offset_hex); i++) { + offset_hex[i] = *(packet++); // consume offset + if (*packet == ',') break; + } + packet++; // consume ',' + for (size_t i = 0; i < sizeof(length_hex); i++) { + length_hex[i] = *(packet++); // consume length + if (*packet == '\0') break; + } + + size_t offset = (size_t)gdb_unhexlify(offset_hex); + size_t length = (size_t)gdb_unhexlify(length_hex); + + if (offset >= data_size) { + gdb_send_packet("l", 1); + } else if (offset + length >= data_size) { + char *reply_buffer = malloc(data_size - offset + 1); + reply_buffer[0] = 'l'; + memcpy(&reply_buffer[1], &data[offset], data_size - offset); + gdb_send_packet(reply_buffer, data_size - offset + 1); + free(reply_buffer); + } else { + char *reply_buffer = malloc(length + 1); + reply_buffer[0] = 'm'; + memcpy(&reply_buffer[1], &data[offset], length); + gdb_send_packet(reply_buffer, length + 1); + free(reply_buffer); + } +} + +/* We implement the memory-map qXfer extension to mark add-in memory as read-only + * and enforce hardware breakpoints. + * See : https://sourceware.org/gdb/onlinedocs/gdb/Memory-Map-Format.html + * https://sourceware.org/gdb/onlinedocs/gdb/Set-Breaks.html + */ +// TODO : Should we mark other regions as ROM ? +static const char gdb_memory_map_xml[] = "" +"" +"" + "" // add-in rom + "" // add-in ram + "" // fx-CG50 stack + "" // Prizm stack +""; + 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"; + 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); + // -1 to not send the NULL terminator + gdb_handle_qXfer_packet(&packet[23], gdb_memory_map_xml, sizeof(gdb_memory_map_xml) - 1); } else { gdb_send_packet(NULL, 0); } From 76c82beec65bd9f08c9b11dc64dbbb37dcd9dd13 Mon Sep 17 00:00:00 2001 From: redoste Date: Sat, 27 May 2023 18:36:15 +0200 Subject: [PATCH 08/17] intc: allow user-space handlers to access the interrupted context This workaround using a gint_call_t with an odd address is not realy the cleanest idea but it helps keep the existing intc_generic_handler in the 32 bytes size limit of the VBR space. --- include/gint/defs/call.h | 13 +++++++++++++ include/gint/gint.h | 19 +++++++++++++++++++ include/gint/intc.h | 5 +++++ src/kernel/inth.S | 28 +++++++++++++++++++++------- 4 files changed, 58 insertions(+), 7 deletions(-) diff --git a/include/gint/defs/call.h b/include/gint/defs/call.h index 8870884..cd442cb 100644 --- a/include/gint/defs/call.h +++ b/include/gint/defs/call.h @@ -144,6 +144,19 @@ typedef struct { /* GINT_CALL_NULL: Empty function call */ #define GINT_CALL_NULL ((gint_call_t){ .function = NULL, .args = {} }) +/* GINT_CALL_FLAG(): Build an indirect call to an odd address + + This is used as a workaround to have an extra flag in gint_call_t structures + without altering their size and exploits the fact that SuperH code has to be + aligned on even addresses. + It is up to the caller to notice the presence of the flag and realign the + address properly. + + This flag is currently only checked by gint_inth_callback to specify that the + called function will take the interrupt context as its first argument. */ +#define GINT_CALL_FLAG(func, ...) \ + GINT_CALL((void*)((uint32_t)(func) | 1), __VA_ARGS__) + /* gint_call(): Perform an indirect call */ static GINLINE int gint_call(gint_call_t cb) { diff --git a/include/gint/gint.h b/include/gint/gint.h index 13b7e1d..0ff0f75 100644 --- a/include/gint/gint.h +++ b/include/gint/gint.h @@ -105,10 +105,29 @@ static GINLINE void *gint_inthandler(int code, void const *h, size_t size) { .callback: .long _gint_inth_callback + The gint_call_t object can be built with GINT_CALL_FLAG to specify that the + called function should get a pointer to a gint_inth_callback_context_t + strcture as its first argument. + @call Address of a gint_call_t object Returns the return value of the callback. */ extern int (*gint_inth_callback)(gint_call_t const *call); +/* gint_inth_callback_context_t: Context of the interrupted function when an + interrupt is handled by gint_inth_callback. */ +typedef struct { + uint32_t ssr; + uint32_t spc; + uint32_t r7; + uint32_t r6; + uint32_t r5; + uint32_t r4; + uint32_t r3; + uint32_t r2; + uint32_t r1; + uint32_t r0; +} gint_inth_callback_context_t; + /* gint_set_quit_handler(): Setup a call to be invoked when leaving the add-in This function sets up the provided GINT_CALL() to be invoked when the diff --git a/include/gint/intc.h b/include/gint/intc.h index 9b117f0..ee4a9e3 100644 --- a/include/gint/intc.h +++ b/include/gint/intc.h @@ -130,6 +130,11 @@ void *intc_handler(int event_code, void const *handler, size_t size); the numerous constraints of intc_handler(), at the cost of always going back to userspace and a small time overhead. + Since gint_inth_callback is used to do the heavy lifting of setting up a sane + context for C code, the gint_call_t object can be built with GINT_CALL_FLAG + if the called function expects a gint_inth_callback_context_t structure as its + first argument. + @event_code Identifier of the interrupt block @function Function to use as a handler Returns true on success, false if the event code is invalid. */ diff --git a/src/kernel/inth.S b/src/kernel/inth.S index 4823cd7..1634211 100644 --- a/src/kernel/inth.S +++ b/src/kernel/inth.S @@ -209,11 +209,15 @@ _gint_inth_callback_reloc: stc.l r7_bank, @-r15 stc.l spc, @-r15 stc.l ssr, @-r15 + /* We backup the address of the gint_inth_callback_context_t built on + the stack to the user bank. */ + ldc r15, r4_bank + stc.l sr, @-r15 /* Save some values to user bank; once we enable interrupts, the kernel bank might be overwritten at any moment. */ - ldc r4, r0_bank + ldc r4, r3_bank /* Enable interrupts and go back to user bank. On SH4, SR.IMASK is set to the level of the current interrupt, which makes sure we can only @@ -243,15 +247,25 @@ _gint_inth_callback_reloc: .load_sr: ldc r1, sr - /* We are now in the user bank with r0 set. Perform the call. We want + /* We are now in the user bank with r3 set. Perform the call. We want to forward the return value to kernel bank, but this bank can be changed at any moment since interrupts are enabled. */ sts.l pr, @-r15 - mov.l @(4, r0), r4 - mov.l @(8, r0), r5 - mov.l @(12, r0), r6 - mov.l @(16, r0), r7 - mov.l @r0, r0 + + /* We only set r4 to the value of the first argument in the gint_call_t + if the address is even (i.e. GINT_CALL_FLAG() was not used). */ + mov.l @r3, r0 + tst #1, r0 + bf .do_not_set_r4 + mov.l @(4, r3), r4 +.do_not_set_r4: + /* And we make sure to realign the address in case it was odd. */ + mov #0xfe, r2 + and r2, r0 + + mov.l @(8, r3), r5 + mov.l @(12, r3), r6 + mov.l @(16, r3), r7 jsr @r0 nop lds.l @r15+, pr From 21ff5c1d5328255e1a727720705c77a438bb95aa Mon Sep 17 00:00:00 2001 From: redoste Date: Sat, 27 May 2023 18:52:05 +0200 Subject: [PATCH 09/17] usb: expose the context of the interrupted function on USB interrupt --- include/gint/usb.h | 8 ++++++++ src/usb/usb.c | 12 +++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/include/gint/usb.h b/include/gint/usb.h index 479d28d..e727bfe 100644 --- a/include/gint/usb.h +++ b/include/gint/usb.h @@ -517,6 +517,14 @@ uint16_t usb_dc_string(uint16_t const *literal, size_t len); This is mostly used by the driver to answer GET_DESCRIPTOR requests. */ usb_dc_string_t *usb_dc_string_get(uint16_t id); +//--- +// USB interrupts +//--- + +/* usb_interrupt_context: Context of the function interrupted by a USB interrupt + The pointer is set back to NULL when the interrupt is finished being handled. */ +extern gint_inth_callback_context_t* usb_interrupt_context; + #ifdef __cplusplus } #endif diff --git a/src/usb/usb.c b/src/usb/usb.c index 14adb76..3d065b0 100644 --- a/src/usb/usb.c +++ b/src/usb/usb.c @@ -12,7 +12,7 @@ #define USB SH7305_USB -static void usb_interrupt_handler(void); +static void usb_interrupt_handler(gint_inth_callback_context_t* interrupt_context); /* Shorthand to clear a bit in INTSTS0 */ #define INTSTS0_clear(field_name) { \ @@ -213,7 +213,7 @@ int usb_open(usb_interface_t const **interfaces, gint_call_t callback) USB.NRDYSTS = 0x0000; USB.BEMPSTS = 0x0000; - intc_handler_function(0xa20, GINT_CALL(usb_interrupt_handler)); + intc_handler_function(0xa20, GINT_CALL_FLAG(usb_interrupt_handler)); intc_priority(INTC_USB, 8); /* Pull D+ up to 3.3V, notifying connection when possible. Read @@ -251,8 +251,12 @@ void usb_close(void) // Userspace interrupt handler //--- -static void usb_interrupt_handler(void) +gint_inth_callback_context_t* usb_interrupt_context; + +static void usb_interrupt_handler(gint_inth_callback_context_t* interrupt_context) { + usb_interrupt_context = interrupt_context; + GUNUSED static char const * const device_st[] = { "powered", "default", "address", "configured", "suspended-powered", "suspended-default", "suspended-address", @@ -315,6 +319,8 @@ static void usb_interrupt_handler(void) /* Restore PIPESEL which can have been used for transfers */ USB.PIPESEL.word = pipesel; + + usb_interrupt_context = NULL; } //--- From 33dae5d218059749274c4d443375213170e75384 Mon Sep 17 00:00:00 2001 From: redoste Date: Sat, 27 May 2023 18:59:24 +0200 Subject: [PATCH 10/17] ubc: add a global lock to inform if a UBC break is being handled --- include/gint/ubc.h | 4 ++++ src/ubc/ubc.S | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/include/gint/ubc.h b/include/gint/ubc.h index d858948..97f6371 100644 --- a/include/gint/ubc.h +++ b/include/gint/ubc.h @@ -27,6 +27,10 @@ void ubc_debug_handler(gdb_cpu_state_t* cpu_state); the UBC is reached. */ void ubc_set_debug_handler(void (*h)(gdb_cpu_state_t*)); +/* ubc_dbh_lock: Lock set by ubc_dbh() when a UBC break is currently being + handled. */ +extern uint8_t ubc_dbh_lock; + /* UBC Breakpoint types */ typedef enum { UBC_BREAK_BEFORE, /* Break before the instruction is executed */ diff --git a/src/ubc/ubc.S b/src/ubc/ubc.S index da31041..fc956f6 100644 --- a/src/ubc/ubc.S +++ b/src/ubc/ubc.S @@ -39,6 +39,11 @@ _ubc_dbh: stc.l R1_BANK, @-r15 stc.l R0_BANK, @-r15 + /* We set the ubc_dbh_lock before enabling interrupts */ + mov.l .ubc_dbh_lock, r0 + mov #1, r1 + mov.b r1, @r0 + /* Enable interrupts and switch register bank Original SR is kept in r8 */ stc sr, r8 @@ -55,6 +60,12 @@ _ubc_dbh: /* Restore original SR to access the correct register bank */ ldc r8, sr + /* We can release the ubc_dbh_lock now that interrupts have been + disabled */ + mov.l .ubc_dbh_lock, r0 + mov #0, r1 + mov.b r1, @r0 + ldc.l @r15+, R0_BANK ldc.l @r15+, R1_BANK ldc.l @r15+, R2_BANK @@ -83,7 +94,12 @@ _ubc_dbh: .align 4 .handler: .long _ubc_debug_handler +.ubc_dbh_lock: .long _ubc_dbh_lock .sr_mask: .long ~0x300000f0 /* IMASK = 0 : mask no interrupts BL = 0 : do not block interrupts RB = 0 : use register BANK0 */ + +.data +.global _ubc_dbh_lock +_ubc_dbh_lock: .byte 0 From 02a97719ac7de322a1a39ba6d10f60373482a0e8 Mon Sep 17 00:00:00 2001 From: redoste Date: Sat, 27 May 2023 19:06:29 +0200 Subject: [PATCH 11/17] gdb: break when a message is recived during execution This behaviour implements support for breaking the add-in during execution by pressing ^C in GDB without setting a breakpoint beforehand. --- src/gdb/gdb.c | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/gdb/gdb.c b/src/gdb/gdb.c index 59baf29..1ec6ee5 100644 --- a/src/gdb/gdb.c +++ b/src/gdb/gdb.c @@ -37,7 +37,11 @@ static uint32_t gdb_unhexlify(const char* input_string) return gdb_unhexlify_sized(input_string, strlen(input_string)); } -static bool gdb_started = false; +static enum { + GDB_STATE_STOPPED, + GDB_STATE_FIRST_BREAK, + GDB_STATE_STARTED, +} GPACKEDENUM gdb_state = GDB_STATE_STOPPED; static void gdb_send(const char *data, size_t size) { @@ -460,10 +464,6 @@ static void gdb_handle_single_step(gdb_cpu_state_t* cpu_state) void gdb_main(gdb_cpu_state_t* cpu_state) { - if (cpu_state != NULL) { - gdb_send_stop_reply(); - } - if (gdb_single_step_backup.single_stepped) { if (gdb_single_step_backup.channel0_used) { ubc_set_breakpoint(0, gdb_single_step_backup.channel0_addr, UBC_BREAK_BEFORE); @@ -479,6 +479,10 @@ void gdb_main(gdb_cpu_state_t* cpu_state) gdb_single_step_backup.single_stepped = false; } + 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)); @@ -538,9 +542,28 @@ void gdb_main(gdb_cpu_state_t* cpu_state) } } +static void gdb_notifier_function(void) +{ + // We ignore fxlink notifications when we're already inside GDB code. + if (ubc_dbh_lock || gdb_state != GDB_STATE_STARTED) + return; + + // We make sure we are called during a USB interrupt. + if (usb_interrupt_context == NULL) + return; + + // And we make sure an other step break is not already set up. + if (gdb_single_step_backup.single_stepped) + return; + + gdb_cpu_state_t fake_state = {}; + fake_state.reg.pc = usb_interrupt_context->spc; + gdb_handle_single_step(&fake_state); +} + int gdb_start(void) { - if (gdb_started) { + if (gdb_state != GDB_STATE_STOPPED) { return GDB_ALREADY_STARTED; } @@ -553,10 +576,12 @@ int gdb_start(void) return GDB_USB_ERROR; } usb_open_wait(); + usb_fxlink_set_notifier(gdb_notifier_function); ubc_set_debug_handler(gdb_main); - gdb_started = true; + gdb_state = GDB_STATE_FIRST_BREAK; gdb_main(NULL); + gdb_state = GDB_STATE_STARTED; return 0; } From 9c22ecea8dfb87d05f640915e24296f2372a997e Mon Sep 17 00:00:00 2001 From: redoste Date: Tue, 30 May 2023 00:53:56 +0200 Subject: [PATCH 12/17] gdb: prevent TLB misses during arbitrary memory RW operations --- src/gdb/gdb.c | 62 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/src/gdb/gdb.c b/src/gdb/gdb.c index 1ec6ee5..4d6184b 100644 --- a/src/gdb/gdb.c +++ b/src/gdb/gdb.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -306,10 +307,13 @@ static void gdb_handle_write_register(gdb_cpu_state_t* cpu_state, const char* pa } } +static volatile bool gdb_tlbh_enable = false; +static volatile bool gdb_tlbh_caught = false; + static void gdb_handle_read_memory(const char* packet) { char address_hex[16] = {0}, size_hex[16] = {0}; - void* read_address; + uint8_t* read_address; size_t read_size; packet++; // consume 'm' @@ -323,13 +327,24 @@ static void gdb_handle_read_memory(const char* packet) if (*packet == '\0') break; } - read_address = (void*) gdb_unhexlify(address_hex); + read_address = (uint8_t*) 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); + + gdb_tlbh_enable = true; + gdb_tlbh_caught = false; + for (size_t i = 0; i < read_size && !gdb_tlbh_caught; i++) { + gdb_hexlify(&reply_buffer[i * 2], &read_address[i], 1); + } + gdb_tlbh_enable = false; + + if (gdb_tlbh_caught) { + gdb_send_packet("E22", 3); // EINVAL + gdb_tlbh_caught = false; + } else { + gdb_send_packet(reply_buffer, read_size * 2); + } free(reply_buffer); } @@ -354,11 +369,19 @@ static void gdb_handle_write_memory(const char* packet) read_address = (uint8_t*) gdb_unhexlify(address_hex); read_size = (size_t) gdb_unhexlify(size_hex); - // TODO : Detect invalid writes and prevent TLB misses - for (size_t i = 0; i < read_size; i++) { + gdb_tlbh_enable = true; + gdb_tlbh_caught = false; + for (size_t i = 0; i < read_size && !gdb_tlbh_caught; i++) { read_address[i] = (uint8_t)gdb_unhexlify_sized(&packet[i * 2], 2); } - gdb_send_packet("OK", 2); + gdb_tlbh_enable = false; + + if (gdb_tlbh_caught) { + gdb_send_packet("E22", 3); // EINVAL + gdb_tlbh_caught = false; + } else { + gdb_send_packet("OK", 2); + } } static bool gdb_parse_hardware_breakpoint_packet(const char* packet, void** read_address) @@ -561,6 +584,26 @@ static void gdb_notifier_function(void) gdb_handle_single_step(&fake_state); } +/* TODO : break and let the debugger attempt to fix the issue before panicking + * in user code + */ +static int gdb_panic_handler(uint32_t code) +{ + // We make sure we currently want to handle TLB misses + if (!gdb_tlbh_enable) + return 1; + + // We only handle TLB miss reads (0x040) and writes (0x060) + if (code != 0x040 && code != 0x060) + return 1; + + gdb_tlbh_caught = true; + + // We skip the offending instruction and continue + gint_exc_skip(1); + return 0; +} + int gdb_start(void) { if (gdb_state != GDB_STATE_STOPPED) { @@ -578,6 +621,9 @@ int gdb_start(void) usb_open_wait(); usb_fxlink_set_notifier(gdb_notifier_function); + // TODO : Should we detect if other panic or debug handlers are setup ? + gint_exc_catch(gdb_panic_handler); + ubc_set_debug_handler(gdb_main); gdb_state = GDB_STATE_FIRST_BREAK; From 0b7d8d68006f32ce36d8cd94b66c2e3db6ced7e3 Mon Sep 17 00:00:00 2001 From: redoste Date: Sun, 4 Jun 2023 17:51:08 +0200 Subject: [PATCH 13/17] ubc: ignore breaks when no debug handler is set --- include/gint/ubc.h | 4 ++-- src/ubc/ubc.c | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/include/gint/ubc.h b/include/gint/ubc.h index 97f6371..fa1af78 100644 --- a/include/gint/ubc.h +++ b/include/gint/ubc.h @@ -19,8 +19,8 @@ void* ubc_getDBR(void); The handler will backup the current CPU state and call ubc_debug_handler(). */ void ubc_dbh(void); /* ubc_debug_handler(): C level UBC debug handler - The execution will be redirected to the handler set by the user or the add-in - will panic in case no handler has been set. */ + The execution will be redirected to the handler set by the user or the break + will be ignored in case no handler has been set. */ void ubc_debug_handler(gdb_cpu_state_t* cpu_state); /* ubc_set_debug_handler(): Set user debug handler Set a custom debug handler that will be called when a break condition from diff --git a/src/ubc/ubc.c b/src/ubc/ubc.c index 26b8021..104879d 100644 --- a/src/ubc/ubc.c +++ b/src/ubc/ubc.c @@ -111,10 +111,9 @@ void ubc_debug_handler(gdb_cpu_state_t* cpu_state) if (ubc_application_debug_handler != NULL) { ubc_application_debug_handler(cpu_state); - } else { - // TODO : Should we add a custom panic code ? - gint_panic(-1); } + // For now we will ignore breaks when no debug handler is set + // TODO : Should we log or panic when no debug handler is set ? } // TODO : Should we use the struct designed for GDB or make it more generic ? From 6efcfa6018ba3f02c5855952034a23048495b863 Mon Sep 17 00:00:00 2001 From: redoste Date: Sun, 4 Jun 2023 17:57:49 +0200 Subject: [PATCH 14/17] ubc: panic when trying to break in code using register bank 1 --- src/kernel/exch.c | 2 ++ src/ubc/ubc.S | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/kernel/exch.c b/src/kernel/exch.c index 1efcfbc..c061f21 100644 --- a/src/kernel/exch.c +++ b/src/kernel/exch.c @@ -78,6 +78,7 @@ GNORETURN static void gint_default_panic(GUNUSED uint32_t code) if(code == 0x1040) name = "Add-in too large"; if(code == 0x1060) name = "Memory init failed"; if(code == 0x1080) name = "Stack overflow"; + if(code == 0x10a0) name = "UBC in bank 1 code"; if(name[0]) dtext(1, 9, name); else dprint(1, 9, "%03x", code); @@ -118,6 +119,7 @@ GNORETURN static void gint_default_panic(GUNUSED uint32_t code) if(code == 0x1040) name = "Add-in not fully mapped (too large)"; if(code == 0x1060) name = "Memory initialization failed (heap)"; if(code == 0x1080) name = "Stack overflow during world switch"; + if(code == 0x10a0) name = "UBC break in register bank 1 code"; dprint(6, 25, "%03x %s", code, name); diff --git a/src/ubc/ubc.S b/src/ubc/ubc.S index fc956f6..734bdb3 100644 --- a/src/ubc/ubc.S +++ b/src/ubc/ubc.S @@ -14,6 +14,12 @@ _ubc_getDBR: .global _ubc_dbh _ubc_dbh: + /* We don't support breaking in a context where register bank 1 is used */ + stc ssr, r0 + mov.l .sr_rb1_mask, r1 + tst r0, r1 + bf .dbh_panic + /* We backup registers in the correct order to build gdb_cpu_state_t */ stc.l ssr, @-r15 sts.l macl, @-r15 @@ -92,9 +98,25 @@ _ubc_dbh: rte nop +.dbh_panic: + stc sr, r1 + mov.l .sr_mask, r0 + and r0, r1 + ldc r1, sr + + mov.l .panic_code, r4 + + mov.l .panic, r0 + mov.l @r0, r0 + jmp @r0 + nop + .align 4 .handler: .long _ubc_debug_handler .ubc_dbh_lock: .long _ubc_dbh_lock +.panic_code: .long 0x10a0 +.panic: .long _gint_exc_panic +.sr_rb1_mask: .long (1 << 29) .sr_mask: .long ~0x300000f0 /* IMASK = 0 : mask no interrupts BL = 0 : do not block interrupts RB = 0 : use register BANK0 From 7d3663483f2cf30da0382f42c785ecf178088c58 Mon Sep 17 00:00:00 2001 From: redoste Date: Sun, 4 Jun 2023 18:37:27 +0200 Subject: [PATCH 15/17] gdb: break before panicking in user code --- src/gdb/gdb.c | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/gdb/gdb.c b/src/gdb/gdb.c index 4d6184b..c124afe 100644 --- a/src/gdb/gdb.c +++ b/src/gdb/gdb.c @@ -474,13 +474,13 @@ static struct { void* channel0_addr; void* channel1_addr; } gdb_single_step_backup = { false }; -static void gdb_handle_single_step(gdb_cpu_state_t* cpu_state) +static void gdb_handle_single_step(uint32_t pc, ubc_break_mode_t break_mode) { gdb_single_step_backup.channel0_used = ubc_get_break_address(0, &gdb_single_step_backup.channel0_addr); gdb_single_step_backup.channel1_used = ubc_get_break_address(1, &gdb_single_step_backup.channel1_addr); ubc_disable_channel(0); - ubc_set_breakpoint(1, (void*)cpu_state->reg.pc, UBC_BREAK_AFTER); + ubc_set_breakpoint(1, (void*)pc, break_mode); gdb_single_step_backup.single_stepped = true; } @@ -553,7 +553,7 @@ void gdb_main(gdb_cpu_state_t* cpu_state) break; case 's': - gdb_handle_single_step(cpu_state); + gdb_handle_single_step(cpu_state->reg.pc, UBC_BREAK_AFTER); return; case 'c': // Continue return; @@ -579,29 +579,35 @@ static void gdb_notifier_function(void) if (gdb_single_step_backup.single_stepped) return; - gdb_cpu_state_t fake_state = {}; - fake_state.reg.pc = usb_interrupt_context->spc; - gdb_handle_single_step(&fake_state); + gdb_handle_single_step(usb_interrupt_context->spc, UBC_BREAK_AFTER); } -/* TODO : break and let the debugger attempt to fix the issue before panicking - * in user code - */ static int gdb_panic_handler(uint32_t code) { - // We make sure we currently want to handle TLB misses - if (!gdb_tlbh_enable) - return 1; + // If we are currently expecting to handle TLB misses + if (gdb_tlbh_enable) { + // We only handle TLB miss reads (0x040) and writes (0x060) + if (code != 0x040 && code != 0x060) + return 1; - // We only handle TLB miss reads (0x040) and writes (0x060) - if (code != 0x040 && code != 0x060) - return 1; + gdb_tlbh_caught = true; - gdb_tlbh_caught = true; + // We skip the offending instruction and continue + gint_exc_skip(1); + return 0; + } + // If we are in user code, let's break + else if (!ubc_dbh_lock && gdb_state == GDB_STATE_STARTED) { + // We make sure an other step break is not already set up + if (gdb_single_step_backup.single_stepped) + return 1; - // We skip the offending instruction and continue - gint_exc_skip(1); - return 0; + uint32_t spc; + __asm__("stc spc, %0" : "=r"(spc)); + gdb_handle_single_step(spc, UBC_BREAK_BEFORE); + return 0; + } + return 1; } int gdb_start(void) From 5087a911010081885385986e997d2846060ec88e Mon Sep 17 00:00:00 2001 From: redoste Date: Sun, 4 Jun 2023 19:32:53 +0200 Subject: [PATCH 16/17] gdb: make memory map XML more generic to ensure fx-9860G III support --- src/gdb/gdb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gdb/gdb.c b/src/gdb/gdb.c index c124afe..f32cb1c 100644 --- a/src/gdb/gdb.c +++ b/src/gdb/gdb.c @@ -209,7 +209,7 @@ static const char gdb_memory_map_xml[] = "" "" "" "" // add-in rom - "" // add-in ram + "" // add-in ram "" // fx-CG50 stack "" // Prizm stack ""; From 6f53fa78428a38a0fc84f1de69796c76549b0454 Mon Sep 17 00:00:00 2001 From: redoste Date: Sun, 4 Jun 2023 19:35:13 +0200 Subject: [PATCH 17/17] gdb: move recv buffer to the heap to support fx-9860G III smaller .data --- src/gdb/gdb.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/gdb/gdb.c b/src/gdb/gdb.c index f32cb1c..54931f2 100644 --- a/src/gdb/gdb.c +++ b/src/gdb/gdb.c @@ -55,7 +55,8 @@ static void gdb_send(const char *data, size_t size) usb_commit_sync(pipe); } -static char gdb_recv_buffer[1024]; +static char *gdb_recv_buffer = NULL; +static const size_t gdb_recv_buffer_capacity = 256; static size_t gdb_recv_buffer_size = 0; static ssize_t gdb_recv(char *buffer, size_t buffer_size) { @@ -74,7 +75,7 @@ static ssize_t gdb_recv(char *buffer, size_t buffer_size) // 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) { + if (header.size > gdb_recv_buffer_capacity - gdb_recv_buffer_size) { abort(); } usb_read_sync(usb_ff_bulk_input(), &gdb_recv_buffer[gdb_recv_buffer_size], header.size, false); @@ -620,6 +621,10 @@ int gdb_start(void) return GDB_NO_INTERFACE; } + if (!gdb_recv_buffer) { + gdb_recv_buffer = malloc(gdb_recv_buffer_capacity); + } + usb_interface_t const *interfaces[] = { &usb_ff_bulk, NULL }; if (usb_open(interfaces, GINT_CALL_NULL) < 0) { return GDB_USB_ERROR;