#include <sys/cdefs.h>
#include "opt_ddb.h"
#ifndef DDB
#error "NetGDB cannot be used without DDB at this time"
#endif
#include <sys/errno.h>
#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 | CTLFLAG_MPSAFE, 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);
static char netgdb_rxbuf[GDB_BUFSZ + 16];
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;
static int
netgdb_rx(struct mbuf *m)
{
uint32_t rlen, count;
rlen = m->m_pkthdr.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 (ENOBUFS);
}
#undef _SBUF_FREESPACE
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;
}
return (0);
}
static void
netgdb_finish(void)
{
sbuf_putc(&netgdb_rxsb, CTRL('C'));
}
static int
netgdb_dbg_getc(void)
{
int c;
while (true) {
if (netgdb_rx_off < sbuf_len(&netgdb_rxsb)) {
c = netgdb_rxsb.s_buf[netgdb_rx_off];
netgdb_rx_off++;
break;
}
sbuf_clear(&netgdb_rxsb);
netgdb_rx_off = 0;
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();
}
return (c);
}
static void
netgdb_dbg_sendpacket(const void *buf, size_t len)
{
struct debugnet_proto_aux aux;
int error;
MPASS(len <= UINT32_MAX);
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();
}
}
}
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 | GDB_DBGP_FEAT_RELIABLE,
};
static void
netgdb_init(void)
{
struct kdb_dbbe *be, **iter;
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;
}
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)
{
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
DB_COMMAND_FLAGS(netgdb, db_netgdb_cmd, CS_OWN)
{
struct debugnet_ddb_config params;
struct debugnet_conn_params dcp;
struct debugnet_pcb *pcb;
char proxy_buf[INET_ADDRSTRLEN];
int error;
if (!KERNEL_PANICKED()) {
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;
}
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,
.dc_finish_handler = netgdb_finish,
};
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;
}
db_cmd_loop_done = 1;
gdb_return_to_ddb = true;
db_printf("(detaching GDB will return control to DDB)\n");
const in_addr_t *proxy_addr = debugnet_get_server_addr(netgdb_conn);
const uint16_t proxy_port = debugnet_get_server_port(netgdb_conn) + 1;
inet_ntop(AF_INET, proxy_addr, proxy_buf, sizeof(proxy_buf));
if (inet_ntop(AF_INET, proxy_addr, proxy_buf, sizeof(proxy_buf)) == NULL) {
db_printf("Connected to proxy. "
"Use target remote <proxy address>:%hu to begin debugging.\n",
proxy_port);
} else {
db_printf("Connected to proxy. "
"Use target remote %s:%hu to begin debugging.\n",
proxy_buf, proxy_port);
}
#if 0
db_printf("(ctrl-c will return control to ddb)\n");
#endif
}
#endif