aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--share/man/man4/Makefile1
-rw-r--r--share/man/man4/ddb.49
-rw-r--r--share/man/man4/netgdb.4147
-rw-r--r--sys/amd64/conf/GENERIC1
-rw-r--r--sys/conf/NOTES10
-rw-r--r--sys/conf/files1
-rw-r--r--sys/conf/options6
-rw-r--r--sys/gdb/gdb.h8
-rw-r--r--sys/gdb/gdb_int.h11
-rw-r--r--sys/gdb/gdb_main.c29
-rw-r--r--sys/gdb/gdb_packet.c54
-rw-r--r--sys/gdb/netgdb.c406
-rw-r--r--sys/gdb/netgdb.h44
-rw-r--r--sys/i386/conf/GENERIC1
-rw-r--r--sys/kern/subr_kdb.c1
-rw-r--r--sys/net/debugnet.c16
-rw-r--r--sys/net/debugnet.h22
-rw-r--r--sys/net/debugnet_inet.c2
-rw-r--r--sys/net/debugnet_int.h1
-rw-r--r--sys/sys/kdb.h3
-rw-r--r--sys/sys/param.h2
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", &params);
+ 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,