diff options
author | Conrad Meyer <cem@FreeBSD.org> | 2019-10-17 21:33:01 +0000 |
---|---|---|
committer | Conrad Meyer <cem@FreeBSD.org> | 2019-10-17 21:33:01 +0000 |
commit | dda17b3672f2c7f661699a69ea4462710a52480d (patch) | |
tree | fbc063b744e51a6513553568dd2481e81ebcf50b /sys/gdb | |
parent | 092bacb2c4277cb9aae662f82ceeb4fabfdd818a (diff) | |
download | src-dda17b3672f2c7f661699a69ea4462710a52480d.tar.gz src-dda17b3672f2c7f661699a69ea4462710a52480d.zip |
Notes
Diffstat (limited to 'sys/gdb')
-rw-r--r-- | sys/gdb/gdb.h | 8 | ||||
-rw-r--r-- | sys/gdb/gdb_int.h | 11 | ||||
-rw-r--r-- | sys/gdb/gdb_main.c | 29 | ||||
-rw-r--r-- | sys/gdb/gdb_packet.c | 54 | ||||
-rw-r--r-- | sys/gdb/netgdb.c | 406 | ||||
-rw-r--r-- | sys/gdb/netgdb.h | 44 |
6 files changed, 548 insertions, 4 deletions
diff --git a/sys/gdb/gdb.h b/sys/gdb/gdb.h index 0a7ba9c4b56a..3a570da7d75c 100644 --- a/sys/gdb/gdb.h +++ b/sys/gdb/gdb.h @@ -46,8 +46,16 @@ struct gdb_dbgport { gdb_putc_f *gdb_putc; gdb_term_f *gdb_term; int gdb_active; + void (*gdb_sendpacket)(const void *, size_t); + int gdb_dbfeatures; }; +#define GDB_DBGP_FEAT_WANTTERM 0x1 /* Want gdb_term() invocation when + leaving GDB. gdb_term has been + deadcode and never invoked for so + long I don't want to just blindly + start invoking it without opt-in. */ + #define GDB_DBGPORT(name, probe, init, term, getc, putc) \ static struct gdb_dbgport name##_gdb_dbgport = { \ .gdb_name = #name, \ diff --git a/sys/gdb/gdb_int.h b/sys/gdb/gdb_int.h index b4b28bd47b0b..2bea90ccf3e8 100644 --- a/sys/gdb/gdb_int.h +++ b/sys/gdb/gdb_int.h @@ -31,8 +31,14 @@ #ifndef _GDB_GDB_INT_H_ #define _GDB_GDB_INT_H_ +#include "opt_ddb.h" + #include <sys/sysctl.h> +#ifdef DDB +#include <ddb/ddb.h> +#endif + #ifndef EOF #define EOF (-1) #endif @@ -48,6 +54,11 @@ extern char *gdb_rxp; extern size_t gdb_rxsz; extern char *gdb_txp; +#ifdef DDB +/* If set, return to DDB when controlling GDB detaches. */ +extern bool gdb_return_to_ddb; +#endif + int gdb_rx_begin(void); int gdb_rx_equal(const char *); int gdb_rx_mem(unsigned char *, size_t); diff --git a/sys/gdb/gdb_main.c b/sys/gdb/gdb_main.c index e9de5a332b0a..134a5a339513 100644 --- a/sys/gdb/gdb_main.c +++ b/sys/gdb/gdb_main.c @@ -60,6 +60,10 @@ int gdb_listening = 0; static unsigned char gdb_bindata[64]; +#ifdef DDB +bool gdb_return_to_ddb = false; +#endif + static int gdb_init(void) { @@ -569,6 +573,26 @@ unrecognized: return; } +static void +gdb_handle_detach(void) +{ + kdb_cpu_clear_singlestep(); + gdb_listening = 0; + + if (gdb_cur->gdb_dbfeatures & GDB_DBGP_FEAT_WANTTERM) + gdb_cur->gdb_term(); + +#ifdef DDB + if (!gdb_return_to_ddb) + return; + + gdb_return_to_ddb = false; + + if (kdb_dbbe_select("ddb") != 0) + printf("The ddb backend could not be selected.\n"); +#endif +} + static int gdb_trap(int type, int code) { @@ -638,7 +662,7 @@ gdb_trap(int type, int code) } case 'D': { /* Detach */ gdb_tx_ok(); - kdb_cpu_clear_singlestep(); + gdb_handle_detach(); return (1); } case 'g': { /* Read registers. */ @@ -675,8 +699,7 @@ gdb_trap(int type, int code) break; } case 'k': /* Kill request. */ - kdb_cpu_clear_singlestep(); - gdb_listening = 1; + gdb_handle_detach(); return (1); case 'm': { /* Read memory. */ uintmax_t addr, size; diff --git a/sys/gdb/gdb_packet.c b/sys/gdb/gdb_packet.c index 2a8f982ba49c..e740b2c9442e 100644 --- a/sys/gdb/gdb_packet.c +++ b/sys/gdb/gdb_packet.c @@ -45,7 +45,27 @@ __FBSDID("$FreeBSD$"); static char gdb_rxbuf[GDB_BUFSZ]; char *gdb_rxp = NULL; size_t gdb_rxsz = 0; -static char gdb_txbuf[GDB_BUFSZ]; + +/* + * The goal here is to allow in-place framing without making the math around + * 'gdb_txbuf' more complicated. A generous reading of union special rule for + * "common initial sequence" suggests this may be valid in standard C99 and + * later. + */ +static union { + struct _midbuf { + char mb_pad1; + char mb_buf[GDB_BUFSZ]; + char mb_pad2[4]; + } __packed txu_midbuf; + /* sizeof includes trailing nul byte and this is intentional. */ + char txu_fullbuf[GDB_BUFSZ + sizeof("$#..")]; +} gdb_tx_u; +#define gdb_txbuf gdb_tx_u.txu_midbuf.mb_buf +#define gdb_tx_fullbuf gdb_tx_u.txu_fullbuf +_Static_assert(sizeof(gdb_tx_u.txu_midbuf) == sizeof(gdb_tx_u.txu_fullbuf) && + offsetof(struct _midbuf, mb_buf) == 1, + "assertions necessary for correctness"); char *gdb_txp = NULL; /* Used in inline functions. */ #define C2N(c) (((c) < 'A') ? (c) - '0' : \ @@ -68,6 +88,9 @@ gdb_getc(void) if (c == CTRL('C')) { printf("Received ^C; trying to switch back to ddb.\n"); + if (gdb_cur->gdb_dbfeatures & GDB_DBGP_FEAT_WANTTERM) + gdb_cur->gdb_term(); + if (kdb_dbbe_select("ddb") != 0) printf("The ddb backend could not be selected.\n"); else { @@ -218,6 +241,29 @@ gdb_tx_begin(char tp) gdb_tx_char(tp); } +/* + * Take raw packet buffer and perform typical GDB packet framing, but not run- + * length encoding, before forwarding to driver ::gdb_sendpacket() routine. + */ +static void +gdb_tx_sendpacket(void) +{ + size_t msglen, i; + unsigned char csum; + + msglen = gdb_txp - gdb_txbuf; + + /* Add GDB packet framing */ + gdb_tx_fullbuf[0] = '$'; + + csum = 0; + for (i = 0; i < msglen; i++) + csum += (unsigned char)gdb_txbuf[i]; + snprintf(&gdb_tx_fullbuf[1 + msglen], 4, "#%02x", (unsigned)csum); + + gdb_cur->gdb_sendpacket(gdb_tx_fullbuf, msglen + 4); +} + int gdb_tx_end(void) { @@ -226,6 +272,11 @@ gdb_tx_end(void) unsigned char c, cksum; do { + if (gdb_cur->gdb_sendpacket != NULL) { + gdb_tx_sendpacket(); + goto getack; + } + gdb_cur->gdb_putc('$'); cksum = 0; @@ -284,6 +335,7 @@ gdb_tx_end(void) c = cksum & 0x0f; gdb_cur->gdb_putc(N2C(c)); +getack: c = gdb_getc(); } while (c != '+'); diff --git a/sys/gdb/netgdb.c b/sys/gdb/netgdb.c new file mode 100644 index 000000000000..de069903c5de --- /dev/null +++ b/sys/gdb/netgdb.c @@ -0,0 +1,406 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Isilon Systems, LLC. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * netgdb.c + * FreeBSD subsystem supporting debugging the FreeBSD kernel over the network. + * + * There are three pieces necessary to use NetGDB. + * + * First, a dedicated proxy server must be running to accept connections from + * both NetGDB and gdb(1), and pass bidirectional traffic between the two + * protocols. + * + * Second, The NetGDB client is activated much like ordinary 'gdb' and + * similarly to 'netdump' in ddb(4). Like other debugnet(4) clients + * (netdump(4)), the network interface on the route to the proxy server must be + * online and support debugnet(4). + * + * Finally, the remote (k)gdb(1) uses 'target remote <proxy>:<port>' to connect + * to the proxy server. + * + * NetGDBv1 speaks the literal GDB remote serial protocol, and uses a 1:1 + * relationship between GDB packets and plain debugnet packets. There is no + * encryption utilized to keep debugging sessions private, so this is only + * appropriate for local segments or trusted networks. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_ddb.h" +#ifndef DDB +#error "NetGDB cannot be used without DDB at this time" +#endif + +#include <sys/param.h> +#include <sys/kdb.h> +#include <sys/sbuf.h> +#include <sys/socket.h> +#include <sys/sysctl.h> +#include <sys/ttydefaults.h> + +#include <machine/gdb_machdep.h> + +#ifdef DDB +#include <ddb/ddb.h> +#include <ddb/db_command.h> +#include <ddb/db_lex.h> +#endif + +#include <net/debugnet.h> +#include <net/if.h> +#include <net/if_var.h> +#include <net/route.h> + +#include <gdb/gdb.h> +#include <gdb/gdb_int.h> +#include <gdb/netgdb.h> + +FEATURE(netgdb, "NetGDB support"); +SYSCTL_NODE(_debug_gdb, OID_AUTO, netgdb, CTLFLAG_RD, NULL, + "NetGDB parameters"); + +static unsigned netgdb_debug; +SYSCTL_UINT(_debug_gdb_netgdb, OID_AUTO, debug, CTLFLAG_RWTUN, + &netgdb_debug, 0, + "Debug message verbosity (0: off; 1: on)"); + +#define NETGDB_DEBUG(f, ...) do { \ + if (netgdb_debug > 0) \ + printf(("%s [%s:%d]: " f), __func__, __FILE__, __LINE__, ## \ + __VA_ARGS__); \ +} while (false) + +static void netgdb_fini(void); + +/* Runtime state. */ +static char netgdb_rxbuf[GDB_BUFSZ + 16]; /* Some overhead for framing. */ +static struct sbuf netgdb_rxsb; +static ssize_t netgdb_rx_off; + +static struct debugnet_pcb *netgdb_conn; +static struct gdb_dbgport *netgdb_prev_dbgport; +static int *netgdb_prev_kdb_inactive; + +/* TODO(CEM) disable ack mode */ + +/* + * Receive non-TX ACK packets on the client port. + * + * The mbuf chain will have all non-debugnet framing headers removed + * (ethernet, inet, udp). It will start with a debugnet_msg_hdr, of + * which the header is guaranteed to be contiguous. If m_pullup is + * used, the supplied in-out mbuf pointer should be updated + * appropriately. + * + * If the handler frees the mbuf chain, it should set the mbuf pointer + * to NULL. Otherwise, the debugnet input framework will free the + * chain. + */ +static void +netgdb_rx(struct debugnet_pcb *pcb, struct mbuf **mb) +{ + const struct debugnet_msg_hdr *dnh; + struct mbuf *m; + uint32_t rlen, count; + int error; + + m = *mb; + dnh = mtod(m, const void *); + + if (ntohl(dnh->mh_type) == DEBUGNET_FINISHED) { + sbuf_putc(&netgdb_rxsb, CTRL('C')); + return; + } + + if (ntohl(dnh->mh_type) != DEBUGNET_DATA) { + printf("%s: Got unexpected debugnet message %u\n", + __func__, ntohl(dnh->mh_type)); + return; + } + + rlen = ntohl(dnh->mh_len); +#define _SBUF_FREESPACE(s) ((s)->s_size - ((s)->s_len + 1)) + if (_SBUF_FREESPACE(&netgdb_rxsb) < rlen) { + NETGDB_DEBUG("Backpressure: Not ACKing RX of packet that " + "would overflow our buffer (%zd/%zd used).\n", + netgdb_rxsb.s_len, netgdb_rxsb.s_size); + return; + } +#undef _SBUF_FREESPACE + + error = debugnet_ack_output(pcb, dnh->mh_seqno); + if (error != 0) { + printf("%s: Couldn't ACK rx packet %u; %d\n", __func__, + ntohl(dnh->mh_seqno), error); + /* + * Sender will re-xmit, and assuming the condition is + * transient, we'll process the packet's contentss later. + */ + return; + } + + m_adj(m, sizeof(*dnh)); + dnh = NULL; + + /* + * Inlined m_apply -- why isn't there a macro or inline function + * version? + */ + while (m != NULL && m->m_len == 0) + m = m->m_next; + while (rlen > 0) { + MPASS(m != NULL && m->m_len >= 0); + count = min((uint32_t)m->m_len, rlen); + (void)sbuf_bcat(&netgdb_rxsb, mtod(m, const void *), count); + rlen -= count; + m = m->m_next; + } +} + +/* + * The following routines implement a pseudo GDB debugport (an emulated serial + * driver that the MI gdb(4) code does I/O with). + */ + +static int +netgdb_dbg_getc(void) +{ + int c; + + while (true) { + /* Pull bytes off any currently cached packet first. */ + if (netgdb_rx_off < sbuf_len(&netgdb_rxsb)) { + c = netgdb_rxsb.s_buf[netgdb_rx_off]; + netgdb_rx_off++; + break; + } + + /* Reached EOF? Reuse buffer. */ + sbuf_clear(&netgdb_rxsb); + netgdb_rx_off = 0; + + /* Check for CTRL-C on console/serial, if any. */ + if (netgdb_prev_dbgport != NULL) { + c = netgdb_prev_dbgport->gdb_getc(); + if (c == CTRL('C')) + break; + } + + debugnet_network_poll(netgdb_conn); + } + + if (c == CTRL('C')) { + netgdb_fini(); + /* Caller gdb_getc() will print that we got ^C. */ + } + return (c); +} + +static void +netgdb_dbg_sendpacket(const void *buf, size_t len) +{ + struct debugnet_proto_aux aux; + int error; + + MPASS(len <= UINT32_MAX); + + /* + * GDB packet boundaries matter. debugnet_send() fragments a single + * request into many sequential debugnet messages. Mark full packet + * length and offset for potential reassembly by the proxy. + */ + aux = (struct debugnet_proto_aux) { + .dp_aux2 = len, + }; + + error = debugnet_send(netgdb_conn, DEBUGNET_DATA, buf, len, &aux); + if (error != 0) { + printf("%s: Network error: %d; trying to switch back to ddb.\n", + __func__, error); + netgdb_fini(); + + if (kdb_dbbe_select("ddb") != 0) + printf("The ddb backend could not be selected.\n"); + else { + printf("using longjmp, hope it works!\n"); + kdb_reenter(); + } + } + +} + +/* Just used for + / - GDB-level ACKs. */ +static void +netgdb_dbg_putc(int i) +{ + char c; + + c = i; + netgdb_dbg_sendpacket(&c, 1); + +} + +static struct gdb_dbgport netgdb_gdb_dbgport = { + .gdb_name = "netgdb", + .gdb_getc = netgdb_dbg_getc, + .gdb_putc = netgdb_dbg_putc, + .gdb_term = netgdb_fini, + .gdb_sendpacket = netgdb_dbg_sendpacket, + .gdb_dbfeatures = GDB_DBGP_FEAT_WANTTERM, +}; + +static void +netgdb_init(void) +{ + struct kdb_dbbe *be, **iter; + + /* + * Force enable GDB. (If no other debugports were registered at boot, + * KDB thinks it doesn't exist.) + */ + SET_FOREACH(iter, kdb_dbbe_set) { + be = *iter; + if (strcmp(be->dbbe_name, "gdb") != 0) + continue; + if (be->dbbe_active == -1) { + netgdb_prev_kdb_inactive = &be->dbbe_active; + be->dbbe_active = 0; + } + break; + } + + /* Force netgdb debugport. */ + netgdb_prev_dbgport = gdb_cur; + gdb_cur = &netgdb_gdb_dbgport; + + sbuf_new(&netgdb_rxsb, netgdb_rxbuf, sizeof(netgdb_rxbuf), + SBUF_FIXEDLEN); + netgdb_rx_off = 0; +} + +static void +netgdb_fini(void) +{ + + /* TODO: tear down conn gracefully? */ + if (netgdb_conn != NULL) { + debugnet_free(netgdb_conn); + netgdb_conn = NULL; + } + + sbuf_delete(&netgdb_rxsb); + + gdb_cur = netgdb_prev_dbgport; + + if (netgdb_prev_kdb_inactive != NULL) { + *netgdb_prev_kdb_inactive = -1; + netgdb_prev_kdb_inactive = NULL; + } +} + +#ifdef DDB +/* + * Usage: netgdb -s <server> [-g <gateway -c <localip> -i <interface>] + * + * Order is not significant. + * + * Currently, this command does not support configuring encryption or + * compression. + */ +DB_FUNC(netgdb, db_netgdb_cmd, db_cmd_table, CS_OWN, NULL) +{ + struct debugnet_ddb_config params; + struct debugnet_conn_params dcp; + struct debugnet_pcb *pcb; + int error; + + if (panicstr == NULL) { + /* TODO: This limitation should be removed in future work. */ + printf("%s: netgdb is currently limited to use only after a " + "panic. Sorry.\n", __func__); + return; + } + + error = debugnet_parse_ddb_cmd("netgdb", ¶ms); + if (error != 0) { + db_printf("Error configuring netgdb: %d\n", error); + return; + } + + /* + * Must initialize netgdb_rxsb before debugnet_connect(), because we + * might be getting rx handler callbacks from the send->poll path + * during debugnet_connect(). + */ + netgdb_init(); + + if (!params.dd_has_client) + params.dd_client = INADDR_ANY; + if (!params.dd_has_gateway) + params.dd_gateway = INADDR_ANY; + + dcp = (struct debugnet_conn_params) { + .dc_ifp = params.dd_ifp, + .dc_client = params.dd_client, + .dc_server = params.dd_server, + .dc_gateway = params.dd_gateway, + .dc_herald_port = NETGDB_HERALDPORT, + .dc_client_port = NETGDB_CLIENTPORT, + .dc_herald_aux2 = NETGDB_PROTO_V1, + .dc_rx_handler = netgdb_rx, + }; + + error = debugnet_connect(&dcp, &pcb); + if (error != 0) { + printf("failed to contact netgdb server: %d\n", error); + netgdb_fini(); + return; + } + + netgdb_conn = pcb; + + if (kdb_dbbe_select("gdb") != 0) { + db_printf("The remote GDB backend could not be selected.\n"); + netgdb_fini(); + return; + } + + /* + * Mark that we are done in ddb(4). Return -> kdb_trap() should + * re-enter with the new backend. + */ + db_cmd_loop_done = 1; + gdb_return_to_ddb = true; + db_printf("(detaching GDB will return control to DDB)\n"); +#if 0 + /* Aspirational, but does not work reliably. */ + db_printf("(ctrl-c will return control to ddb)\n"); +#endif +} +#endif /* DDB */ diff --git a/sys/gdb/netgdb.h b/sys/gdb/netgdb.h new file mode 100644 index 000000000000..36f369312290 --- /dev/null +++ b/sys/gdb/netgdb.h @@ -0,0 +1,44 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Isilon Systems, LLC. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#pragma once + +/* + * Protocol information, for use by the userspace proxy server. + * + * It might make sense to allow not hardcoding these parameters as future work + * (e.g., for use in environments with arbitrary port filtering). + * + * The herald port is only used for initial handshake. The proxy server will + * choose a different remote port to connect back to the NetGDB client on by + * sending the HERALD ACK from that other port. + */ +#define NETGDB_HERALDPORT 20025 +#define NETGDB_CLIENTPORT 20026 + +#define NETGDB_PROTO_V1 0x2515f095 /* Rolled a 2^32 sided die. */ |