[GDB] Remote debugging over UDP
authorStefan Hajnoczi <stefanha@gmail.com>
Wed, 11 Jun 2008 11:12:46 +0000 (12:12 +0100)
committerMichael Brown <mcb30@etherboot.org>
Mon, 30 Jun 2008 18:19:48 +0000 (19:19 +0100)
This commit implements GDB over UDP.  Using UDP is more complex than
serial and has required some restructuring.

The GDB stub is now built using one or both of GDBSERIAL and GDBUDP
config.h options.

To enter the debugger, execute the gPXE shell command:
gdbstub <transport> [<options>...]

Where <transport> is "serial" or "udp".  For "udp", the name of a
configured network device is required:
gdbstub udp net0

The GDB stub listens on UDP port 43770 by default.

src/config.h
src/core/config.c
src/core/gdbserial.c [new file with mode: 0644]
src/core/gdbstub.c
src/core/gdbudp.c [new file with mode: 0644]
src/hci/commands/gdbstub_cmd.c [new file with mode: 0644]
src/include/gpxe/gdbstub.h [new file with mode: 0644]
src/tests/gdbstub_test.gdb

index e695b01..f43da04 100644 (file)
 #undef BUILD_ID                /* Include a custom build ID string,
                                 * e.g "test-foo" */
 #undef NULL_TRAP               /* Attempt to catch NULL function calls */
-#undef GDBSTUB                 /* Remote GDB debugging */
+#undef GDBSERIAL               /* Remote GDB debugging over serial */
+#undef GDBUDP                  /* Remote GDB debugging over UDP
+                                * (both may be set) */
 
 /* @END general.h */
 
index 018f084..4202682 100644 (file)
@@ -195,6 +195,13 @@ REQUIRE_OBJECT ( sanboot_cmd );
 #ifdef NULL_TRAP
 REQUIRE_OBJECT ( nulltrap );
 #endif
-#ifdef GDBSTUB
+#ifdef GDBSERIAL
 REQUIRE_OBJECT ( gdbidt );
+REQUIRE_OBJECT ( gdbserial );
+REQUIRE_OBJECT ( gdbstub_cmd );
+#endif
+#ifdef GDBUDP
+REQUIRE_OBJECT ( gdbidt );
+REQUIRE_OBJECT ( gdbudp );
+REQUIRE_OBJECT ( gdbstub_cmd );
 #endif
diff --git a/src/core/gdbserial.c b/src/core/gdbserial.c
new file mode 100644 (file)
index 0000000..2fecd5f
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2008 Stefan Hajnoczi <stefanha@gmail.com>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <assert.h>
+#include <gpxe/serial.h>
+#include <gpxe/gdbstub.h>
+
+struct gdb_transport serial_gdb_transport __gdb_transport;
+
+static size_t gdbserial_recv ( char *buf, size_t len ) {
+       assert ( len > 0 );
+       buf [ 0 ] = serial_getc();
+       return 1;
+}
+
+static void gdbserial_send ( const char *buf, size_t len ) {
+       while ( len-- > 0 ) {
+               serial_putc ( *buf++ );
+       }
+}
+
+struct gdb_transport serial_gdb_transport __gdb_transport = {
+       .name = "serial",
+       .recv = gdbserial_recv,
+       .send = gdbserial_send,
+};
index df50dc8..c331b85 100644 (file)
  */
 
 #include <stdlib.h>
-#include <stddef.h>
 #include <stdio.h>
+#include <string.h>
 #include <ctype.h>
 #include <byteswap.h>
-#include <gpxe/process.h>
-#include <gpxe/serial.h>
+#include <gpxe/gdbstub.h>
 #include "gdbmach.h"
 
 enum {
-       POSIX_EINVAL = 0x1c /* used to report bad arguments to GDB */
+       POSIX_EINVAL = 0x1c,  /* used to report bad arguments to GDB */
+       SIZEOF_PAYLOAD = 256, /* buffer size of GDB payload data */
 };
 
 struct gdbstub {
+       struct gdb_transport *trans;
+       int exit_handler; /* leave interrupt handler */
+
        int signo;
        gdbreg_t *regs;
-       int exit_handler; /* leave interrupt handler */
 
        void ( * parse ) ( struct gdbstub *stub, char ch );
        uint8_t cksum1;
@@ -47,10 +49,15 @@ struct gdbstub {
        /* Buffer for payload data when parsing a packet.  Once the
         * packet has been received, this buffer is used to hold
         * the reply payload. */
-       char payload [ 256 ];
-       int len;
+       char buf [ SIZEOF_PAYLOAD + 4 ]; /* $...PAYLOAD...#XX */
+       char *payload;                   /* start of payload */
+       int len;                         /* length of payload */
 };
 
+/* Transports */
+static struct gdb_transport gdb_transport_start[0] __table_start ( struct gdb_transport, gdb_transports );
+static struct gdb_transport gdb_transport_end[0] __table_end ( struct gdb_transport, gdb_transports );
+
 /* Packet parser states */
 static void gdbstub_state_new ( struct gdbstub *stub, char ch );
 static void gdbstub_state_data ( struct gdbstub *stub, char ch );
@@ -132,29 +139,13 @@ static uint8_t gdbstub_cksum ( char *data, int len ) {
        return cksum;
 }
 
-static int gdbstub_getchar ( struct gdbstub *stub ) {
-       if ( stub->exit_handler ) {
-               return -1;
-       }
-       return serial_getc();
-}
-
-static void gdbstub_putchar ( struct gdbstub * stub __unused, char ch ) {
-       serial_putc ( ch );
-}
-
 static void gdbstub_tx_packet ( struct gdbstub *stub ) {
        uint8_t cksum = gdbstub_cksum ( stub->payload, stub->len );
-       int i;
-
-       gdbstub_putchar ( stub, '$' );
-       for ( i = 0; i < stub->len; i++ ) {
-               gdbstub_putchar ( stub, stub->payload [ i ] );
-       }
-       gdbstub_putchar ( stub, '#' );
-       gdbstub_putchar ( stub, gdbstub_to_hex_digit ( cksum >> 4 ) );
-       gdbstub_putchar ( stub, gdbstub_to_hex_digit ( cksum ) );
-
+       stub->buf [ 0 ] = '$';
+       stub->buf [ stub->len + 1 ] = '#';
+       stub->buf [ stub->len + 2 ] = gdbstub_to_hex_digit ( cksum >> 4 );
+       stub->buf [ stub->len + 3 ] = gdbstub_to_hex_digit ( cksum );
+       stub->trans->send ( stub->buf, stub->len + 4 );
        stub->parse = gdbstub_state_wait_ack;
 }
 
@@ -229,7 +220,7 @@ static void gdbstub_read_mem ( struct gdbstub *stub ) {
                gdbstub_send_errno ( stub, POSIX_EINVAL );
                return;
        }
-       args [ 1 ] = ( args [ 1 ] < sizeof stub->payload / 2 ) ? args [ 1 ] : sizeof stub->payload / 2;
+       args [ 1 ] = ( args [ 1 ] < SIZEOF_PAYLOAD / 2 ) ? args [ 1 ] : SIZEOF_PAYLOAD / 2;
        gdbstub_to_hex_buf ( stub->payload, ( char * ) args [ 0 ], args [ 1 ] );
        stub->len = args [ 1 ] * 2;
        gdbstub_tx_packet ( stub );
@@ -306,7 +297,7 @@ static void gdbstub_state_data ( struct gdbstub *stub, char ch ) {
                stub->len = 0; /* retry new packet */
        } else {
                /* If the length exceeds our buffer, let the checksum fail */
-               if ( stub->len < ( int ) sizeof stub->payload ) {
+               if ( stub->len < SIZEOF_PAYLOAD ) {
                        stub->payload [ stub->len++ ] = ch;
                }
        }
@@ -325,12 +316,12 @@ static void gdbstub_state_cksum2 ( struct gdbstub *stub, char ch ) {
        their_cksum = stub->cksum1 + gdbstub_from_hex_digit ( ch );
        our_cksum = gdbstub_cksum ( stub->payload, stub->len );
        if ( their_cksum == our_cksum ) {
-               gdbstub_putchar ( stub, '+' );
+               stub->trans->send ( "+", 1 );
                if ( stub->len > 0 ) {
                        gdbstub_rx_packet ( stub );
                }
        } else {
-               gdbstub_putchar ( stub, '-' );
+               stub->trans->send ( "-", 1 );
        }
 }
 
@@ -351,23 +342,37 @@ static struct gdbstub stub = {
 };
 
 __cdecl void gdbstub_handler ( int signo, gdbreg_t *regs ) {
-       int ch;
+       char packet [ SIZEOF_PAYLOAD + 4 ];
+       size_t len, i;
+
+       /* A transport must be set up */
+       if ( !stub.trans ) {
+               return;
+       }
+
        stub.signo = signo;
        stub.regs = regs;
        stub.exit_handler = 0;
        gdbstub_report_signal ( &stub );
-       while ( ( ch = gdbstub_getchar( &stub ) ) != -1 ) {
-               gdbstub_parse ( &stub, ch );
+       while ( !stub.exit_handler && ( len = stub.trans->recv ( packet, sizeof ( packet ) ) ) > 0 ) {
+               for ( i = 0; i < len; i++ ) {
+                       gdbstub_parse ( &stub, packet [ i ] );
+               }
        }
 }
 
-/* Activity monitor to detect packets from GDB when we are not active */
-static void gdbstub_activity_step ( struct process *process __unused ) {
-       if ( serial_ischar() ) {
-               gdbmach_breakpoint();
+struct gdb_transport *find_gdb_transport ( const char *name ) {
+       struct gdb_transport *trans;
+       for ( trans = gdb_transport_start; trans < gdb_transport_end; trans++ ) {
+               if ( strcmp ( trans->name, name ) == 0 ) {
+                       return trans;
+               }
        }
+       return NULL;
 }
 
-struct process gdbstub_activity_process __permanent_process = {
-       .step = gdbstub_activity_step,
-};
+void gdbstub_start ( struct gdb_transport *trans ) {
+       stub.trans = trans;
+       stub.payload = &stub.buf [ 1 ];
+       gdbmach_breakpoint();
+}
diff --git a/src/core/gdbudp.c b/src/core/gdbudp.c
new file mode 100644 (file)
index 0000000..fb38164
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2008 Stefan Hajnoczi <stefanha@gmail.com>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <byteswap.h>
+#include <gpxe/iobuf.h>
+#include <gpxe/in.h>
+#include <gpxe/if_arp.h>
+#include <gpxe/if_ether.h>
+#include <gpxe/ip.h>
+#include <gpxe/udp.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/gdbstub.h>
+
+struct gdb_transport udp_gdb_transport __gdb_transport;
+
+static struct net_device *netdev;
+static uint8_t dest_eth[ETH_ALEN];
+static uint8_t source_eth[ETH_ALEN];
+static struct sockaddr_in dest_addr;
+static struct sockaddr_in source_addr;
+
+static void gdbudp_ensure_netdev_open ( struct net_device *netdev ) {
+       if ( ( netdev->state & NETDEV_OPEN) == 0 ) {
+               netdev_open ( netdev );
+       }
+       /* TODO forcing the netdev to be open is useful when
+        * gPXE closes the netdev between breakpoints.  Should
+        * we restore the state of the netdev, i.e. closed,
+        * before leaving the interrupt handler? */
+}
+
+static size_t gdbudp_recv ( char *buf, size_t len ) {
+       struct io_buffer *iob;
+       struct ethhdr *ethhdr;
+       struct arphdr *arphdr;
+       struct iphdr *iphdr;
+       struct udp_header *udphdr;
+       size_t payload_len;
+
+       assert ( netdev );
+       gdbudp_ensure_netdev_open ( netdev );
+
+       for ( ; ; ) {
+               while ( ( iob = netdev_rx_dequeue ( netdev ) ) != NULL ) {
+                       if ( iob_len ( iob ) > sizeof ( *ethhdr ) + sizeof ( *iphdr ) + sizeof ( *udphdr ) + len ) {
+                               goto bad_packet;
+                       }
+
+                       /* Ethernet header */
+                       ethhdr = iob->data;
+                       iob_pull ( iob, sizeof ( *ethhdr ) );
+                       if ( ethhdr->h_protocol == htons ( ETH_P_ARP ) ) {
+                               /* Handle ARP requests so the client can connect to us */
+                               arphdr = iob->data;
+                               if ( arphdr->ar_hrd != htons ( ARPHRD_ETHER ) ||
+                                               arphdr->ar_pro != htons ( ETH_P_IP ) ||
+                                               arphdr->ar_hln != ETH_ALEN ||
+                                               arphdr->ar_pln != sizeof ( struct in_addr ) ||
+                                               arphdr->ar_op != htons ( ARPOP_REQUEST ) ||
+                                               memcmp ( arp_target_pa ( arphdr ), &source_addr.sin_addr.s_addr, sizeof ( struct in_addr ) ) ) {
+                                       goto bad_packet;
+                               }
+
+                               /* Generate an ARP reply */
+                               arphdr->ar_op = htons ( ARPOP_REPLY );
+                               memswap ( arp_sender_pa ( arphdr ), arp_target_pa ( arphdr ), sizeof ( struct in_addr ) );
+                               memcpy ( arp_target_ha ( arphdr ), arp_sender_ha ( arphdr ), ETH_ALEN );
+                               memcpy ( arp_sender_ha ( arphdr ), source_eth, ETH_ALEN );
+
+                               /* Fix up ethernet header */
+                               ethhdr = iob_push ( iob, sizeof ( *ethhdr ) );
+                               memswap ( ethhdr->h_source, ethhdr->h_dest, ETH_ALEN );
+
+                               netdev_tx ( netdev, iob );
+                               continue; /* no need to free iob */
+                       }
+                       if ( ethhdr->h_protocol != htons ( ETH_P_IP ) ) {
+                               goto bad_packet;
+                       }
+
+                       /* IP header */
+                       iphdr = iob->data;
+                       iob_pull ( iob, sizeof ( *iphdr ) );
+                       if ( iphdr->protocol != IP_UDP || iphdr->dest.s_addr != source_addr.sin_addr.s_addr ) {
+                               goto bad_packet;
+                       }
+
+                       /* UDP header */
+                       udphdr = iob->data;
+                       if ( udphdr->dest != source_addr.sin_port ) {
+                               goto bad_packet;
+                       }
+
+                       /* Learn the remote connection details */
+                       memcpy ( dest_eth, ethhdr->h_source, ETH_ALEN );
+                       dest_addr.sin_addr.s_addr = iphdr->src.s_addr;
+                       dest_addr.sin_port = udphdr->src;
+
+                       /* Payload */
+                       payload_len = ntohs ( udphdr->len );
+                       if ( payload_len < sizeof ( *udphdr ) ||
+                                       payload_len > iob_len ( iob ) ) {
+                               goto bad_packet;
+                       }
+                       payload_len -= sizeof ( *udphdr );
+                       iob_pull ( iob, sizeof ( *udphdr ) );
+                       memcpy ( buf, iob->data, payload_len );
+
+                       free_iob ( iob );
+                       return payload_len;
+
+bad_packet:
+                       free_iob ( iob );
+               }
+               netdev_poll ( netdev );
+       }
+}
+
+static void gdbudp_send ( const char *buf, size_t len ) {
+       struct io_buffer *iob;
+       struct ethhdr *ethhdr;
+       struct iphdr *iphdr;
+       struct udp_header *udphdr;
+
+       /* Check that we are connected */
+       if ( dest_addr.sin_port == 0 ) {
+               return;
+       }
+
+       assert ( netdev );
+       gdbudp_ensure_netdev_open ( netdev );
+
+       iob = alloc_iob ( sizeof ( *ethhdr ) + sizeof ( *iphdr ) + sizeof ( *udphdr ) + len );
+       if ( !iob ) {
+               return;
+       }
+
+       /* Payload */
+       iob_reserve ( iob, sizeof ( *ethhdr ) + sizeof ( *iphdr ) + sizeof ( *udphdr ) );
+       memcpy ( iob_put ( iob, len ), buf, len );
+
+       /* UDP header */
+       udphdr = iob_push ( iob, sizeof ( *udphdr ) );
+       udphdr->src = source_addr.sin_port;
+       udphdr->dest = dest_addr.sin_port;
+       udphdr->len = htons ( iob_len ( iob ) );
+       udphdr->chksum = 0; /* optional and we are not using it */
+
+       /* IP header */
+       iphdr = iob_push ( iob, sizeof ( *iphdr ) );
+       memset ( iphdr, 0, sizeof ( *iphdr ) );
+       iphdr->verhdrlen = ( IP_VER | ( sizeof ( *iphdr ) / 4 ) );
+       iphdr->service = IP_TOS;
+       iphdr->len = htons ( iob_len ( iob ) ); 
+       iphdr->ttl = IP_TTL;
+       iphdr->protocol = IP_UDP;
+       iphdr->dest.s_addr = dest_addr.sin_addr.s_addr;
+       iphdr->src.s_addr = source_addr.sin_addr.s_addr;
+       iphdr->chksum = tcpip_chksum ( iphdr, sizeof ( *iphdr ) );
+
+       /* Ethernet header */
+       ethhdr = iob_push ( iob, sizeof ( *ethhdr ) );
+       memcpy ( ethhdr->h_dest, dest_eth, ETH_ALEN );
+       memcpy ( ethhdr->h_source, source_eth, ETH_ALEN );
+       ethhdr->h_protocol = htons ( ETH_P_IP );
+
+       netdev_tx ( netdev, iob );
+}
+
+static int gdbudp_init ( int argc, char **argv ) {
+       struct settings *settings;
+
+       if ( argc != 1 ) {
+               printf ( "udp: missing <interface> argument\n" );
+               return 1;
+       }
+
+       netdev = find_netdev ( argv[0] );
+       if ( !netdev ) {
+               printf ( "%s: no such interface\n", argv[0] );
+               return 1;
+       }
+
+       if ( !netdev_link_ok ( netdev ) ) {
+               printf ( "%s: link not up\n", argv[0] );
+               return 1;
+       }
+
+       /* Load network settings from device.  We keep the MAC address,
+        * IP address, and UDP port.  The MAC and IP could be fetched
+        * from the network device each time they are used in rx/tx.
+        * Storing a separate copy makes it possible to use different
+        * MAC/IP settings than the network stack. */
+       memcpy ( source_eth, netdev->ll_addr, ETH_ALEN );
+       source_addr.sin_port = htons ( 43770 ); /* TODO default port */
+       settings = netdev_settings ( netdev );
+       fetch_ipv4_setting ( settings, &ip_setting, &source_addr.sin_addr );
+       if ( source_addr.sin_addr.s_addr == 0 ) {
+               printf ( "%s: no IP address configured\n", argv[0] );
+               return 1;
+       }
+
+       return 0;
+}
+
+struct gdb_transport udp_gdb_transport __gdb_transport = {
+       .name = "udp",
+       .init = gdbudp_init,
+       .send = gdbudp_send,
+       .recv = gdbudp_recv,
+};
diff --git a/src/hci/commands/gdbstub_cmd.c b/src/hci/commands/gdbstub_cmd.c
new file mode 100644 (file)
index 0000000..7416752
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2008 Stefan Hajnoczi <stefanha@gmail.com>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <getopt.h>
+#include <gpxe/command.h>
+#include <gpxe/gdbstub.h>
+
+/** @file
+ *
+ * GDB stub command
+ *
+ */
+
+/**
+ * "gdbstub" command syntax message
+ *
+ * @v argv             Argument list
+ */
+static void gdbstub_syntax ( char **argv ) {
+       printf ( "Usage:\n"
+                "  %s <transport> [<options>...]\n"
+                "\n"
+                "Start remote debugging using one of the following transports:\n"
+                "  serial           use serial port (if compiled in)\n"
+                "  udp <interface>  use UDP over network interface (if compiled in)\n",
+                argv[0] );
+}
+
+/**
+ * The "gdbstub" command
+ *
+ * @v argc             Argument count
+ * @v argv             Argument list
+ * @ret rc             Exit code
+ */
+static int gdbstub_exec ( int argc, char **argv ) {
+       static struct option longopts[] = {
+               { "help", 0, NULL, 'h' },
+               { NULL, 0, NULL, 0 },
+       };
+       const char *trans_name;
+       struct gdb_transport *trans;
+       int c;
+
+       /* Parse options */
+       while ( ( c = getopt_long ( argc, argv, "h", longopts, NULL ) ) >= 0 ){
+               switch ( c ) {
+               case 'h':
+                       /* Display help text */
+               default:
+                       /* Unrecognised/invalid option */
+                       gdbstub_syntax ( argv );
+                       return 1;
+               }
+       }
+
+       /* At least one argument */
+       if ( optind == argc ) {
+               gdbstub_syntax ( argv );
+               return 1;
+       }
+
+       trans_name = argv[optind++];
+
+       /* Initialise transport */
+       trans = find_gdb_transport ( trans_name );
+       if ( !trans ) {
+               printf ( "%s: no such transport (is it compiled in?)\n", trans_name );
+               return 1;
+       }
+
+       if ( trans->init ) {
+               if ( trans->init ( argc - optind, &argv[optind] ) != 0 ) {
+                       return 1;
+               }
+       }
+
+       /* Enter GDB stub */
+       gdbstub_start ( trans );
+       return 0;
+}
+
+/** GDB stub commands */
+struct command gdbstub_commands[] __command = {
+       {
+               .name = "gdbstub",
+               .exec = gdbstub_exec,
+       },
+};
diff --git a/src/include/gpxe/gdbstub.h b/src/include/gpxe/gdbstub.h
new file mode 100644 (file)
index 0000000..adc7e38
--- /dev/null
@@ -0,0 +1,64 @@
+#ifndef _GPXE_GDBSTUB_H
+#define _GPXE_GDBSTUB_H
+
+/** @file
+ *
+ * GDB remote debugging
+ *
+ */
+
+#include <stdint.h>
+#include <gpxe/tables.h>
+
+/**
+ * A transport mechanism for the GDB protocol
+ *
+ */
+struct gdb_transport {
+       /** Transport name */
+       const char *name;
+       /**
+        * Set up the transport given a list of arguments
+        *
+        * @v argc Number of arguments
+        * @v argv Argument list
+        * @ret Return status code
+        *
+        * Note that arguments start at argv[0].
+        */
+       int ( * init ) ( int argc, char **argv );
+       /**
+        * Perform a blocking read
+        *
+        * @v buf Buffer
+        * @v len Size of buffer
+        * @ret Number of bytes read into buffer
+        */
+       size_t ( * recv ) ( char *buf, size_t len );
+       /**
+        * Write, may block
+        *
+        * @v buf Buffer
+        * @v len Size of buffer
+        */
+       void ( * send ) ( const char *buf, size_t len );
+};
+
+#define __gdb_transport __table ( struct gdb_transport, gdb_transports, 01 )
+
+/**
+ * Look up GDB transport by name
+ *
+ * @v name Name of transport
+ * @ret GDB transport or NULL
+ */
+extern struct gdb_transport *find_gdb_transport ( const char *name );
+
+/**
+ * Break into the debugger using the given transport
+ *
+ * @v trans GDB transport
+ */
+extern void gdbstub_start ( struct gdb_transport *trans );
+
+#endif /* _GPXE_GDBSTUB_H */
index c0c5964..c86d4f2 100755 (executable)
@@ -3,16 +3,16 @@
 # Run:
 #   make bin/gpxe.hd.tmp
 #   make
-#   tests/gdbstub_test.gdb
+#   gdb
+#   (gdb) target remote :TCPPORT
+#   OR
+#   (gdb) target remote udp:IP:UDPPORT
+#   (gdb) source tests/gdbstub_test.gdb
 
 define gpxe_load_symbols
        file bin/gpxe.hd.tmp
 end
 
-define gpxe_connect
-       target remote localhost:4444
-end
-
 define gpxe_assert
        if $arg0 != $arg1
                echo FAIL $arg2\n
@@ -78,7 +78,6 @@ define gpxe_test_step
 end
 
 gpxe_load_symbols
-gpxe_connect
 gpxe_start_tests
 gpxe_test_regs_read
 gpxe_test_regs_write