mirror of
https://git.planet-casio.com/Lephenixnoir/gint.git
synced 2024-12-28 04:23:36 +01:00
gdb: allow gdb stub to automatically start when a crash occurs
The user can still gdb_start() + gdb_main(NULL) manually, which allows e.g. early setup of breakpoints. The start_on_except mechanism is the lazier method where we just run the add-in normally and start fxsdk gdb on the PC *after* a crash occurs.
This commit is contained in:
parent
4623d790cc
commit
d9414bb6f2
5 changed files with 93 additions and 46 deletions
|
@ -11,19 +11,12 @@ extern "C" {
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
/* Error codes for GDB functions */
|
|
||||||
enum {
|
|
||||||
GDB_NO_INTERFACE = -1,
|
|
||||||
GDB_ALREADY_STARTED = -2,
|
|
||||||
GDB_USB_ERROR = -3,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* gdb_cpu_state_t: State of the CPU when breaking
|
/* gdb_cpu_state_t: State of the CPU when breaking
|
||||||
This struct keep the same register indices as those declared by GDB to allow
|
This struct keep the same register indices as those declared by GDB to allow
|
||||||
easy R/W without needing a "translation" table.
|
easy R/W without needing a "translation" table.
|
||||||
See : https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/sh-tdep.c;
|
See : https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/sh-tdep.c;
|
||||||
h=c402961b80a0b4589243023ea5362d43f644a9ec;hb=4f3e26ac6ee31f7bc4b04abd
|
h=c402961b80a0b4589243023ea5362d43f644a9ec;hb=4f3e26ac6ee31f7bc4b04abd
|
||||||
8bdb944e7f1fc5d2#l327
|
8bdb944e7f1fc5d2#l361
|
||||||
*/
|
*/
|
||||||
// TODO : Should we expose r*b*, ssr, spc ? are they double-saved when breaking
|
// TODO : Should we expose r*b*, ssr, spc ? are they double-saved when breaking
|
||||||
// inside an interrupt handler ?
|
// inside an interrupt handler ?
|
||||||
|
@ -60,17 +53,27 @@ typedef struct {
|
||||||
|
|
||||||
/* gdb_start(): Start the GDB remote serial protocol server
|
/* gdb_start(): Start the GDB remote serial protocol server
|
||||||
|
|
||||||
This function will start the GDB remote serial protocol implementation and
|
This function starts the GDB stub (program that communicates with GDB). It
|
||||||
block until the program is resumed from the connected debugger.
|
takes over USB, and returns once communication is established with a GDB
|
||||||
It currently only supports USB communication and will fail if USB is already
|
instance on the computer. Normally this is called by the panic handler after
|
||||||
in use.*/
|
a crash. It can also be called manually, in which case it should be followed
|
||||||
|
by a call to gdb_main() with the current state (NULL if you don't have any).
|
||||||
|
|
||||||
|
If the GDB stub is already started, this is a no-op. Returns 0 on success
|
||||||
|
and a negative value if the USB setup fails. */
|
||||||
int gdb_start(void);
|
int gdb_start(void);
|
||||||
|
|
||||||
/* gdb_main(): Main GDB loop
|
/* gdb_main(): Main GDB loop
|
||||||
|
Main loop for when the program is in a paused state. Communicates with GDB
|
||||||
|
over USB and mutates the cpu_state struct, memory and the UBC configuration
|
||||||
|
accordingly. Returns once the program should resume. */
|
||||||
|
void gdb_main(gdb_cpu_state_t *cpu_state);
|
||||||
|
|
||||||
This function handles GDB messages sent over USB and will mutate the cpu_state
|
/* gdb_start_on_exception(): Set the GDB stub to autostart on crashes
|
||||||
struct, memory and the UBC configuration accordingly. */
|
This function sets a crash handler that starts the GDB stub whenever a
|
||||||
void gdb_main(gdb_cpu_state_t* cpu_state);
|
System ERROR occurs. */
|
||||||
|
// TODO: Get some visible signal on-screen when that happens
|
||||||
|
void gdb_start_on_exception(void);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,9 @@ void usb_open_wait(void);
|
||||||
/* usb_is_open(): Check whether the USB link is active */
|
/* usb_is_open(): Check whether the USB link is active */
|
||||||
bool usb_is_open(void);
|
bool usb_is_open(void);
|
||||||
|
|
||||||
|
/* usb_is_open_interface(): Check whether a particular interface is open */
|
||||||
|
bool usb_is_open_interface(usb_interface_t const *interface);
|
||||||
|
|
||||||
/* usb_close(): Close the USB link
|
/* usb_close(): Close the USB link
|
||||||
|
|
||||||
This function closes the link opened by usb_open(), and notifies the host of
|
This function closes the link opened by usb_open(), and notifies the host of
|
||||||
|
|
|
@ -38,11 +38,7 @@ static uint32_t gdb_unhexlify(const char* input_string)
|
||||||
return gdb_unhexlify_sized(input_string, strlen(input_string));
|
return gdb_unhexlify_sized(input_string, strlen(input_string));
|
||||||
}
|
}
|
||||||
|
|
||||||
static enum {
|
static bool gdb_started = false;
|
||||||
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)
|
static void gdb_send(const char *data, size_t size)
|
||||||
{
|
{
|
||||||
|
@ -55,6 +51,16 @@ static void gdb_send(const char *data, size_t size)
|
||||||
usb_commit_sync(pipe);
|
usb_commit_sync(pipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void gdb_send_start(void)
|
||||||
|
{
|
||||||
|
usb_fxlink_header_t header;
|
||||||
|
usb_fxlink_fill_header(&header, "gdb", "start", 0);
|
||||||
|
|
||||||
|
int pipe = usb_ff_bulk_output();
|
||||||
|
usb_write_sync(pipe, &header, sizeof(header), false);
|
||||||
|
usb_commit_sync(pipe);
|
||||||
|
}
|
||||||
|
|
||||||
static char *gdb_recv_buffer = NULL;
|
static char *gdb_recv_buffer = NULL;
|
||||||
static const size_t gdb_recv_buffer_capacity = 256;
|
static const size_t gdb_recv_buffer_capacity = 256;
|
||||||
static size_t gdb_recv_buffer_size = 0;
|
static size_t gdb_recv_buffer_size = 0;
|
||||||
|
@ -488,6 +494,13 @@ static void gdb_handle_single_step(uint32_t pc, ubc_break_mode_t break_mode)
|
||||||
|
|
||||||
void gdb_main(gdb_cpu_state_t* cpu_state)
|
void gdb_main(gdb_cpu_state_t* cpu_state)
|
||||||
{
|
{
|
||||||
|
if (!gdb_started && gdb_start()) {
|
||||||
|
// TODO: Visual signal "GDB stub error"
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Visual signal "GDB stub idle"
|
||||||
|
|
||||||
if (gdb_single_step_backup.single_stepped) {
|
if (gdb_single_step_backup.single_stepped) {
|
||||||
if (gdb_single_step_backup.channel0_used) {
|
if (gdb_single_step_backup.channel0_used) {
|
||||||
ubc_set_breakpoint(0, gdb_single_step_backup.channel0_addr, UBC_BREAK_BEFORE);
|
ubc_set_breakpoint(0, gdb_single_step_backup.channel0_addr, UBC_BREAK_BEFORE);
|
||||||
|
@ -508,6 +521,8 @@ void gdb_main(gdb_cpu_state_t* cpu_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
|
// TODO: Visual signal "GDB stub communicating"
|
||||||
|
|
||||||
char packet_buffer[256];
|
char packet_buffer[256];
|
||||||
ssize_t packet_size = gdb_recv_packet(packet_buffer, sizeof(packet_buffer));
|
ssize_t packet_size = gdb_recv_packet(packet_buffer, sizeof(packet_buffer));
|
||||||
if (packet_size <= 0) {
|
if (packet_size <= 0) {
|
||||||
|
@ -515,6 +530,8 @@ void gdb_main(gdb_cpu_state_t* cpu_state)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Visual signal "GDB stub working"
|
||||||
|
|
||||||
switch (packet_buffer[0]) {
|
switch (packet_buffer[0]) {
|
||||||
case '?': // Halt reason
|
case '?': // Halt reason
|
||||||
gdb_send_stop_reply();
|
gdb_send_stop_reply();
|
||||||
|
@ -544,7 +561,6 @@ void gdb_main(gdb_cpu_state_t* cpu_state)
|
||||||
|
|
||||||
case 'k': // Kill request
|
case 'k': // Kill request
|
||||||
abort();
|
abort();
|
||||||
return;
|
|
||||||
|
|
||||||
case 'Z':
|
case 'Z':
|
||||||
gdb_handle_insert_hardware_breakpoint(packet_buffer);
|
gdb_handle_insert_hardware_breakpoint(packet_buffer);
|
||||||
|
@ -555,21 +571,27 @@ void gdb_main(gdb_cpu_state_t* cpu_state)
|
||||||
|
|
||||||
case 's':
|
case 's':
|
||||||
gdb_handle_single_step(cpu_state->reg.pc, UBC_BREAK_AFTER);
|
gdb_handle_single_step(cpu_state->reg.pc, UBC_BREAK_AFTER);
|
||||||
return;
|
goto ret;
|
||||||
case 'c': // Continue
|
case 'c': // Continue
|
||||||
return;
|
goto ret;
|
||||||
|
|
||||||
default: // Unsupported packet
|
default: // Unsupported packet
|
||||||
gdb_send_packet(NULL, 0);
|
gdb_send_packet(NULL, 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Visual signal "GDB stub idle"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret:
|
||||||
|
// We're started after the first round of exchanges
|
||||||
|
gdb_started = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gdb_notifier_function(void)
|
static void gdb_notifier_function(void)
|
||||||
{
|
{
|
||||||
// We ignore fxlink notifications when we're already inside GDB code.
|
// We ignore fxlink notifications when we're already inside GDB code.
|
||||||
if (ubc_dbh_lock || gdb_state != GDB_STATE_STARTED)
|
if (ubc_dbh_lock || !gdb_started)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// We make sure we are called during a USB interrupt.
|
// We make sure we are called during a USB interrupt.
|
||||||
|
@ -585,7 +607,7 @@ static void gdb_notifier_function(void)
|
||||||
|
|
||||||
static int gdb_panic_handler(uint32_t code)
|
static int gdb_panic_handler(uint32_t code)
|
||||||
{
|
{
|
||||||
// If we are currently expecting to handle TLB misses
|
// Catch memory access errors from GDB trying to read/print stuff
|
||||||
if (gdb_tlbh_enable) {
|
if (gdb_tlbh_enable) {
|
||||||
// We only handle TLB miss reads (0x040) and writes (0x060)
|
// We only handle TLB miss reads (0x040) and writes (0x060)
|
||||||
if (code != 0x040 && code != 0x060)
|
if (code != 0x040 && code != 0x060)
|
||||||
|
@ -598,11 +620,12 @@ static int gdb_panic_handler(uint32_t code)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// If we are in user code, let's break
|
// If we are in user code, let's break
|
||||||
else if (!ubc_dbh_lock && gdb_state == GDB_STATE_STARTED) {
|
else if (!ubc_dbh_lock) {
|
||||||
// We make sure an other step break is not already set up
|
// We make sure an other step break is not already set up
|
||||||
if (gdb_single_step_backup.single_stepped)
|
if (gdb_single_step_backup.single_stepped)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
// TODO: This only works for re-execution type exceptions
|
||||||
uint32_t spc;
|
uint32_t spc;
|
||||||
__asm__("stc spc, %0" : "=r"(spc));
|
__asm__("stc spc, %0" : "=r"(spc));
|
||||||
gdb_handle_single_step(spc, UBC_BREAK_BEFORE);
|
gdb_handle_single_step(spc, UBC_BREAK_BEFORE);
|
||||||
|
@ -613,32 +636,35 @@ static int gdb_panic_handler(uint32_t code)
|
||||||
|
|
||||||
int gdb_start(void)
|
int gdb_start(void)
|
||||||
{
|
{
|
||||||
if (gdb_state != GDB_STATE_STOPPED) {
|
if (gdb_started)
|
||||||
return GDB_ALREADY_STARTED;
|
return 0;
|
||||||
|
|
||||||
|
if(usb_is_open() && !usb_is_open_interface(&usb_ff_bulk))
|
||||||
|
usb_close();
|
||||||
|
|
||||||
|
if(!usb_is_open()) {
|
||||||
|
usb_interface_t const *interfaces[] = { &usb_ff_bulk, NULL };
|
||||||
|
if(usb_open(interfaces, GINT_CALL_NULL) < 0)
|
||||||
|
return -1;
|
||||||
|
usb_open_wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usb_is_open()) {
|
usb_fxlink_set_notifier(gdb_notifier_function);
|
||||||
return GDB_NO_INTERFACE;
|
gdb_send_start();
|
||||||
}
|
|
||||||
|
|
||||||
if (!gdb_recv_buffer) {
|
if (!gdb_recv_buffer) {
|
||||||
gdb_recv_buffer = malloc(gdb_recv_buffer_capacity);
|
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;
|
|
||||||
}
|
|
||||||
usb_open_wait();
|
|
||||||
usb_fxlink_set_notifier(gdb_notifier_function);
|
|
||||||
|
|
||||||
// TODO : Should we detect if other panic or debug handlers are setup ?
|
// TODO : Should we detect if other panic or debug handlers are setup ?
|
||||||
gint_exc_catch(gdb_panic_handler);
|
gint_exc_catch(gdb_panic_handler);
|
||||||
|
|
||||||
ubc_set_debug_handler(gdb_main);
|
ubc_set_debug_handler(gdb_main);
|
||||||
|
|
||||||
gdb_state = GDB_STATE_FIRST_BREAK;
|
|
||||||
gdb_main(NULL);
|
|
||||||
gdb_state = GDB_STATE_STARTED;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void gdb_start_on_exception(void)
|
||||||
|
{
|
||||||
|
gint_exc_catch(gdb_panic_handler);
|
||||||
|
ubc_set_debug_handler(gdb_main);
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
//---
|
//---
|
||||||
|
|
||||||
/* Current configuration: list of interfaces and endpoint assignments */
|
/* Current configuration: list of interfaces and endpoint assignments */
|
||||||
static usb_interface_t const *conf_if[16];
|
#define MAX_INTERFACES 16 /* actually 15 */
|
||||||
|
static usb_interface_t const *conf_if[MAX_INTERFACES];
|
||||||
static endpoint_t *conf_ep;
|
static endpoint_t *conf_ep;
|
||||||
|
|
||||||
GCONSTRUCTOR static void usb_alloc(void)
|
GCONSTRUCTOR static void usb_alloc(void)
|
||||||
|
@ -86,7 +87,7 @@ static int find_pipe(int type)
|
||||||
int usb_configure_solve(usb_interface_t const **interfaces)
|
int usb_configure_solve(usb_interface_t const **interfaces)
|
||||||
{
|
{
|
||||||
/* Reset the previous configuration */
|
/* Reset the previous configuration */
|
||||||
memset(conf_if, 0, 16 * sizeof *conf_if);
|
memset(conf_if, 0, MAX_INTERFACES * sizeof *conf_if);
|
||||||
memset(conf_ep, 0, 32 * sizeof *conf_ep);
|
memset(conf_ep, 0, 32 * sizeof *conf_ep);
|
||||||
|
|
||||||
/* Next interface number to assign */
|
/* Next interface number to assign */
|
||||||
|
@ -98,7 +99,8 @@ int usb_configure_solve(usb_interface_t const **interfaces)
|
||||||
|
|
||||||
for(int i = 0; interfaces[i]; i++)
|
for(int i = 0; interfaces[i]; i++)
|
||||||
{
|
{
|
||||||
if(i == 16) return USB_OPEN_TOO_MANY_INTERFACES;
|
if(i == MAX_INTERFACES - 1)
|
||||||
|
return USB_OPEN_TOO_MANY_INTERFACES;
|
||||||
usb_interface_t const *intf = interfaces[i];
|
usb_interface_t const *intf = interfaces[i];
|
||||||
|
|
||||||
conf_if[next_interface++] = intf;
|
conf_if[next_interface++] = intf;
|
||||||
|
@ -180,7 +182,7 @@ void usb_configure_log(void)
|
||||||
{
|
{
|
||||||
#ifdef GINT_USB_DEBUG
|
#ifdef GINT_USB_DEBUG
|
||||||
/* Log the final configuration */
|
/* Log the final configuration */
|
||||||
for(int i = 0; i < 16 && conf_if[i]; i++)
|
for(int i = 0; conf_if[i]; i++)
|
||||||
USB_LOG("Interface #%d: %p\n", i, conf_if[i]);
|
USB_LOG("Interface #%d: %p\n", i, conf_if[i]);
|
||||||
|
|
||||||
for(int i = 0; i < 32; i++)
|
for(int i = 0; i < 32; i++)
|
||||||
|
|
|
@ -229,6 +229,19 @@ bool usb_is_open(void)
|
||||||
return usb_open_status;
|
return usb_open_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool usb_is_open_interface(usb_interface_t const *interface)
|
||||||
|
{
|
||||||
|
if(!usb_is_open())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
usb_interface_t const * const *open = usb_configure_interfaces();
|
||||||
|
for(int i = 0; open[i]; i++) {
|
||||||
|
if(open[i] == interface)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void usb_open_wait(void)
|
void usb_open_wait(void)
|
||||||
{
|
{
|
||||||
while(!usb_open_status) sleep();
|
while(!usb_open_status) sleep();
|
||||||
|
|
Loading…
Reference in a new issue