diff options
-rw-r--r-- | share/man/man4/Makefile | 1 | ||||
-rw-r--r-- | share/man/man4/ddb.4 | 9 | ||||
-rw-r--r-- | share/man/man4/netgdb.4 | 147 | ||||
-rw-r--r-- | sys/amd64/conf/GENERIC | 1 | ||||
-rw-r--r-- | sys/conf/NOTES | 10 | ||||
-rw-r--r-- | sys/conf/files | 1 | ||||
-rw-r--r-- | sys/conf/options | 6 | ||||
-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 | ||||
-rw-r--r-- | sys/i386/conf/GENERIC | 1 | ||||
-rw-r--r-- | sys/kern/subr_kdb.c | 1 | ||||
-rw-r--r-- | sys/net/debugnet.c | 16 | ||||
-rw-r--r-- | sys/net/debugnet.h | 22 | ||||
-rw-r--r-- | sys/net/debugnet_inet.c | 2 | ||||
-rw-r--r-- | sys/net/debugnet_int.h | 1 | ||||
-rw-r--r-- | sys/sys/kdb.h | 3 | ||||
-rw-r--r-- | sys/sys/param.h | 2 |
21 files changed, 757 insertions, 18 deletions
diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index bbe57ed2c4e5..eeddbdd77696 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -307,6 +307,7 @@ MAN= aac.4 \ net80211.4 \ netdump.4 \ netfpga10g_nf10bmac.4 \ + netgdb.4 \ netgraph.4 \ netintro.4 \ netmap.4 \ diff --git a/share/man/man4/ddb.4 b/share/man/man4/ddb.4 index f7704b25e43f..70d61f1343e2 100644 --- a/share/man/man4/ddb.4 +++ b/share/man/man4/ddb.4 @@ -1211,6 +1211,15 @@ Finally, the .Ic netdump command does not provide any way to configure compression or encryption. .Pp +.It Ic netgdb Fl s Ar server Oo Fl g Ar gateway Fl c Ar client Fl i Ar iface Oc +Initiate a +.Xr netgdb 4 +session with the provided parameters. +.Pp +.Ic netgdb +has identical limitations to +.Ic netdump . +.Pp .It Ic capture on .It Ic capture off .It Ic capture reset diff --git a/share/man/man4/netgdb.4 b/share/man/man4/netgdb.4 new file mode 100644 index 000000000000..aaa2a2801ca7 --- /dev/null +++ b/share/man/man4/netgdb.4 @@ -0,0 +1,147 @@ +.\"- +.\" Copyright (c) 2019 Conrad Meyer <cem@FreeBSD.org> +.\" +.\" 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$ +.\" +.Dd October 17, 2019 +.Dt NETGDB 4 +.Os +.Sh NAME +.Nm netgdb +.Nd protocol for debugging the kernel with GDB over the network +.Sh SYNOPSIS +NetGDB support is compiled by default, if DDB, GDB, and INET are enabled. +To build a kernel without it, add the following line to your kernel +configuration file: +.Bd -ragged -offset indent +.Cd "nooptions NETGDB" +.Ed +.Sh DESCRIPTION +.Nm +is a UDP-based protocol for communicating with a remote GDB client via an +intermediary proxy. +.Pp +A +.Nm +session is started by using the +.Ic netgdb Fl s Ar server Oo Fl g Ar gateway Fl c Ar client Fl i Ar iface Oc +command in +.Xr ddb 4 +to connect to a proxy server. +When the connection is made, the proxy server logs a message that a +.Nm +client has connected. +It subsequently establishes a TCP listening socket and logs a message +specifying which port it is listening on. +Then it waits for a GDB client to connect. +The GDB command to connect is: +.Bd -ragged -offset indent +.Ic target remote Aq Ar proxyip:proxyport +.Ed +.Pp +At this point, the server proxies traffic back and forth between +.Nm +and the ordinary GDB client, speaking the ordinary GDB remote protocol. +The +.Nm +session is identical to any other kernel GDB sesssion from the perspective +of the GDB debugger. +.Sh IMPLEMENTATION NOTES +The UDP protocol is based on the same packet structure and a subset of the +exact same message types as +.Xr netdump 4 . +It uses the +.Dv HERALD , +.Dv DATA ( née VMCORE ) , +and +.Dv FINISHED +message types. +Like +.Xr netdump 4 , +the client's initial +.Dv HERALD +message is acknowledged from a random source port, and the client sends +subsequent communication to that port. +.Pp +Unlike +.Xr netdump 4 , +the initial +.Dv HERALD +port is 20025. +Additionally, +the proxy server sends responses to the source port of the client's initial +.Dv HERALD , +rather than a separate reserved port. +.Nm +message and acknowledgements are bidirectional. +The sequence number and acknowledgement protocol is otherwise identical to +the unidirectional version used by netdump; it just runs in both directions. +Acknowledgements are sent to and from the same addresses and ports as +regular messages. +.Pp +The first version of the +.Nm +protocol uses the protocol number +.Dv Sq 0x2515f095 +in the 32-bit +.Va aux2 +parameter of the initial +.Dv HERALD +message. +.Pp +The list of supported network drivers and protocol families is identical to +that of +.Xr netdump 4 . +.Sh DIAGNOSTICS +The following variable is available via both +.Xr sysctl 8 +and +.Xr loader 8 +(as a tunable): +.Bl -tag -width "indent" +.It Va debug.gdb.netgdb.debug +Control debug message verbosity. +Debug messages are disabled by default. +They may be enabled by setting the variable to a non-zero value. +.El +.Sh SEE ALSO +.Xr ddb 4 , +.Xr gdb 4 , +.Xr netdump 4 +.Sh HISTORY +.Nm +first appeared in +.Fx 13.0 . +.Sh BUGS +.Nm +may only be used after the kernel has panicked, due to limitations in the +treatment of locking primitives under +.Xr ddb 4 . +.Sh SECURITY CONSIDERATIONS +Version 1 of the +.Nm +protocol has no security properties whatsoever. +All messages are sent and acknowledged in cleartext, and no message +authentication codes are used to prevent attackers from forging messages. +It is absolutely inappropriate for use across the public internet. diff --git a/sys/amd64/conf/GENERIC b/sys/amd64/conf/GENERIC index a3e75a7ac136..30246adb4857 100644 --- a/sys/amd64/conf/GENERIC +++ b/sys/amd64/conf/GENERIC @@ -113,6 +113,7 @@ options GZIO # gzip-compressed kernel and user dumps options ZSTDIO # zstd-compressed kernel and user dumps options DEBUGNET # debugnet networking options NETDUMP # netdump(4) client support +options NETGDB # netgdb(4) client support # Make an SMP-capable kernel by default options SMP # Symmetric MultiProcessor Kernel diff --git a/sys/conf/NOTES b/sys/conf/NOTES index 7892a2391bc8..002d809eae5d 100644 --- a/sys/conf/NOTES +++ b/sys/conf/NOTES @@ -1038,13 +1038,17 @@ options TCP_SIGNATURE #include support for RFC 2385 # a smooth scheduling of the traffic. options DUMMYNET +# The DEBUGNET option enables a basic debug/panic-time networking API. It +# is used by NETDUMP and NETGDB. +options DEBUGNET + # The NETDUMP option enables netdump(4) client support in the kernel. # This allows a panicking kernel to transmit a kernel dump to a remote host. options NETDUMP -# The DEBUGNET option enables a basic debug/panic-time networking API. It -# is used by NETDUMP. -options DEBUGNET +# The NETGDB option enables netgdb(4) support in the kernel. This allows a +# panicking kernel to be debugged as a GDB remote over the network. +options NETGDB ##################################################################### # FILESYSTEM OPTIONS diff --git a/sys/conf/files b/sys/conf/files index d910a1f0ce17..e47fe27466cf 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -3546,6 +3546,7 @@ fs/tmpfs/tmpfs_subr.c optional tmpfs gdb/gdb_cons.c optional gdb gdb/gdb_main.c optional gdb gdb/gdb_packet.c optional gdb +gdb/netgdb.c optional ddb debugnet gdb netgdb inet geom/bde/g_bde.c optional geom_bde geom/bde/g_bde_crypt.c optional geom_bde geom/bde/g_bde_lock.c optional geom_bde diff --git a/sys/conf/options b/sys/conf/options index d9ea0a821cb3..0600bf52203b 100644 --- a/sys/conf/options +++ b/sys/conf/options @@ -319,10 +319,12 @@ NFS_ROOT opt_nfsroot.h # SMB/CIFS requester NETSMB opt_netsmb.h -# Enable netdump(4) client support. -NETDUMP opt_global.h # Enable debugnet(4) networking support. DEBUGNET opt_global.h +# Enable netdump(4) client support. +NETDUMP opt_global.h +# Enable netgdb(4) support. +NETGDB opt_global.h # Options used only in subr_param.c. HZ opt_param.h 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. */ diff --git a/sys/i386/conf/GENERIC b/sys/i386/conf/GENERIC index 4d29e889defd..0aef50a08ea0 100644 --- a/sys/i386/conf/GENERIC +++ b/sys/i386/conf/GENERIC @@ -101,6 +101,7 @@ options GZIO # gzip-compressed kernel and user dumps options ZSTDIO # zstd-compressed kernel and user dumps options DEBUGNET # debugnet networking options NETDUMP # netdump(4) client support +options NETGDB # netgdb(4) client support # To make an SMP kernel, the next two lines are needed options SMP # Symmetric MultiProcessor Kernel diff --git a/sys/kern/subr_kdb.c b/sys/kern/subr_kdb.c index f6f105743245..07a2c7da5a7e 100644 --- a/sys/kern/subr_kdb.c +++ b/sys/kern/subr_kdb.c @@ -77,7 +77,6 @@ static int kdb_break_to_debugger = KDB_BREAK_TO_DEBUGGER; static int kdb_alt_break_to_debugger = KDB_ALT_BREAK_TO_DEBUGGER; KDB_BACKEND(null, NULL, NULL, NULL, NULL); -SET_DECLARE(kdb_dbbe_set, struct kdb_dbbe); static int kdb_sysctl_available(SYSCTL_HANDLER_ARGS); static int kdb_sysctl_current(SYSCTL_HANDLER_ARGS); diff --git a/sys/net/debugnet.c b/sys/net/debugnet.c index c42812709e9c..160fd1ebaeca 100644 --- a/sys/net/debugnet.c +++ b/sys/net/debugnet.c @@ -183,7 +183,7 @@ debugnet_udp_output(struct debugnet_pcb *pcb, struct mbuf *m) return (debugnet_ip_output(pcb, m)); } -static int +int debugnet_ack_output(struct debugnet_pcb *pcb, uint32_t seqno /* net endian */) { struct debugnet_ack *dn_ack; @@ -330,7 +330,7 @@ retransmit: printf(". "); goto retransmit; } - debugnet_network_poll(pcb->dp_ifp); + debugnet_network_poll(pcb); DELAY(500); if (pcb->dp_state == DN_STATE_REMOTE_CLOSED) return (ECONNRESET); @@ -577,8 +577,11 @@ done: * driver directly for packets. */ void -debugnet_network_poll(struct ifnet *ifp) +debugnet_network_poll(struct debugnet_pcb *pcb) { + struct ifnet *ifp; + + ifp = pcb->dp_ifp; ifp->if_debugnet_methods->dn_poll(ifp, 1000); } @@ -610,6 +613,7 @@ int debugnet_connect(const struct debugnet_conn_params *dcp, struct debugnet_pcb **pcb_out) { + struct debugnet_proto_aux herald_auxdata; struct debugnet_pcb *pcb; struct ifnet *ifp; int error; @@ -738,8 +742,12 @@ debugnet_connect(const struct debugnet_conn_params *dcp, } MPASS(pcb->dp_state == DN_STATE_HAVE_GW_MAC); + herald_auxdata = (struct debugnet_proto_aux) { + .dp_offset_start = dcp->dc_herald_offset, + .dp_aux2 = dcp->dc_herald_aux2, + }; error = debugnet_send(pcb, DEBUGNET_HERALD, dcp->dc_herald_data, - dcp->dc_herald_datalen, NULL); + dcp->dc_herald_datalen, &herald_auxdata); if (error != 0) { printf("%s: failed to herald debugnet server\n", __func__); goto cleanup; diff --git a/sys/net/debugnet.h b/sys/net/debugnet.h index 87fc9ba0a07c..7b323ad113d7 100644 --- a/sys/net/debugnet.h +++ b/sys/net/debugnet.h @@ -108,6 +108,14 @@ struct debugnet_conn_params { uint32_t dc_herald_datalen; /* + * Consistent with debugnet_send(), aux paramaters to debugnet + * functions are provided host-endian (but converted to + * network endian on the wire). + */ + uint32_t dc_herald_aux2; + uint64_t dc_herald_offset; + + /* * If NULL, debugnet is a unidirectional channel from panic machine to * remote server (like netdump). * @@ -123,12 +131,14 @@ struct debugnet_conn_params { * If the handler frees the mbuf chain, it should set the mbuf pointer * to NULL. Otherwise, the debugnet input framework will free the * chain. + * + * The handler should ACK receieved packets with debugnet_ack_output. */ void (*dc_rx_handler)(struct debugnet_pcb *, struct mbuf **); }; /* - * Open a unidirectional stream to the specified server's herald port. + * Open a stream to the specified server's herald port. * * If all goes well, the server will send ACK from a different port to our ack * port. This allows servers to somewhat gracefully handle multiple debugnet @@ -178,6 +188,16 @@ debugnet_sendempty(struct debugnet_pcb *pcb, uint32_t mhtype) } /* + * Full-duplex RX should ACK received messages. + */ +int debugnet_ack_output(struct debugnet_pcb *, uint32_t seqno /*net endian*/); + +/* + * Check and/or wait for further packets. + */ +void debugnet_network_poll(struct debugnet_pcb *); + +/* * PCB accessors. */ diff --git a/sys/net/debugnet_inet.c b/sys/net/debugnet_inet.c index e57b4810653f..983c5add533f 100644 --- a/sys/net/debugnet_inet.c +++ b/sys/net/debugnet_inet.c @@ -407,7 +407,7 @@ restart: return (error); for (polls = 0; polls < debugnet_npolls && pcb->dp_state < DN_STATE_HAVE_GW_MAC; polls++) { - debugnet_network_poll(pcb->dp_ifp); + debugnet_network_poll(pcb); DELAY(500); } if (pcb->dp_state >= DN_STATE_HAVE_GW_MAC) diff --git a/sys/net/debugnet_int.h b/sys/net/debugnet_int.h index 1e723c4d93f9..00a4e0c25c75 100644 --- a/sys/net/debugnet_int.h +++ b/sys/net/debugnet_int.h @@ -87,7 +87,6 @@ SYSCTL_DECL(_net_debugnet); int debugnet_ether_output(struct mbuf *, struct ifnet *, struct ether_addr, u_short); void debugnet_handle_udp(struct debugnet_pcb *, struct mbuf **); -void debugnet_network_poll(struct ifnet *); #ifdef INET int debugnet_arp_gw(struct debugnet_pcb *); diff --git a/sys/sys/kdb.h b/sys/sys/kdb.h index 75f8343b8428..a70e88627718 100644 --- a/sys/sys/kdb.h +++ b/sys/sys/kdb.h @@ -31,6 +31,7 @@ #ifndef _SYS_KDB_H_ #define _SYS_KDB_H_ +#include <sys/linker_set.h> #include <machine/setjmp.h> struct pcb; @@ -61,6 +62,8 @@ struct kdb_dbbe { }; \ DATA_SET(kdb_dbbe_set, name##_dbbe) +SET_DECLARE(kdb_dbbe_set, struct kdb_dbbe); + extern u_char kdb_active; /* Non-zero while in debugger. */ extern int debugger_on_trap; /* enter the debugger on trap. */ extern struct kdb_dbbe *kdb_dbbe; /* Default debugger backend or NULL. */ diff --git a/sys/sys/param.h b/sys/sys/param.h index 561015c0568b..2c1f8b46305d 100644 --- a/sys/sys/param.h +++ b/sys/sys/param.h @@ -60,7 +60,7 @@ * in the range 5 to 9. */ #undef __FreeBSD_version -#define __FreeBSD_version 1300052 /* Master, propagated to newvers */ +#define __FreeBSD_version 1300053 /* Master, propagated to newvers */ /* * __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD, |