diff --git a/src/config.h b/src/config.h index e695b015..f43da040 100644 --- a/src/config.h +++ b/src/config.h @@ -163,7 +163,9 @@ #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 */ diff --git a/src/core/config.c b/src/core/config.c index 018f084a..42026827 100644 --- a/src/core/config.c +++ b/src/core/config.c @@ -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 index 00000000..2fecd5f6 --- /dev/null +++ b/src/core/gdbserial.c @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2008 Stefan Hajnoczi . + * + * 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 +#include +#include + +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, +}; diff --git a/src/core/gdbstub.c b/src/core/gdbstub.c index df50dc81..c331b85b 100644 --- a/src/core/gdbstub.c +++ b/src/core/gdbstub.c @@ -24,22 +24,24 @@ */ #include -#include #include +#include #include #include -#include -#include +#include #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 index 00000000..fb381644 --- /dev/null +++ b/src/core/gdbudp.c @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2008 Stefan Hajnoczi . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 index 00000000..74167525 --- /dev/null +++ b/src/hci/commands/gdbstub_cmd.c @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2008 Stefan Hajnoczi . + * + * 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 +#include +#include +#include + +/** @file + * + * GDB stub command + * + */ + +/** + * "gdbstub" command syntax message + * + * @v argv Argument list + */ +static void gdbstub_syntax ( char **argv ) { + printf ( "Usage:\n" + " %s [...]\n" + "\n" + "Start remote debugging using one of the following transports:\n" + " serial use serial port (if compiled in)\n" + " udp 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 index 00000000..adc7e382 --- /dev/null +++ b/src/include/gpxe/gdbstub.h @@ -0,0 +1,64 @@ +#ifndef _GPXE_GDBSTUB_H +#define _GPXE_GDBSTUB_H + +/** @file + * + * GDB remote debugging + * + */ + +#include +#include + +/** + * 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 */ diff --git a/src/tests/gdbstub_test.gdb b/src/tests/gdbstub_test.gdb index c0c59644..c86d4f2a 100755 --- a/src/tests/gdbstub_test.gdb +++ b/src/tests/gdbstub_test.gdb @@ -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