aboutsummaryrefslogtreecommitdiff
path: root/sys/gdb
diff options
context:
space:
mode:
authorConrad Meyer <cem@FreeBSD.org>2019-10-17 21:33:01 +0000
committerConrad Meyer <cem@FreeBSD.org>2019-10-17 21:33:01 +0000
commitdda17b3672f2c7f661699a69ea4462710a52480d (patch)
treefbc063b744e51a6513553568dd2481e81ebcf50b /sys/gdb
parent092bacb2c4277cb9aae662f82ceeb4fabfdd818a (diff)
downloadsrc-dda17b3672f2c7f661699a69ea4462710a52480d.tar.gz
src-dda17b3672f2c7f661699a69ea4462710a52480d.zip
Notes
Diffstat (limited to 'sys/gdb')
-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
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", &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. */