Index: head/sys/gdb/gdb.h =================================================================== --- head/sys/gdb/gdb.h (revision 353701) +++ head/sys/gdb/gdb.h (revision 353702) @@ -1,70 +1,73 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2004 Marcel Moolenaar * All rights reserved. * * 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 ``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 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$ */ #ifndef _GDB_GDB_H_ #define _GDB_GDB_H_ typedef int gdb_checkc_f(void); typedef int gdb_getc_f(void); typedef void gdb_init_f(void); typedef int gdb_probe_f(void); typedef void gdb_putc_f(int); typedef void gdb_term_f(void); struct gdb_dbgport { const char *gdb_name; gdb_getc_f *gdb_getc; gdb_init_f *gdb_init; gdb_probe_f *gdb_probe; 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_DBGP_FEAT_RELIABLE 0x2 /* The debugport promises it is a + reliable transport, which allows GDB + acks to be turned off. */ #define GDB_DBGPORT(name, probe, init, term, getc, putc) \ static struct gdb_dbgport name##_gdb_dbgport = { \ .gdb_name = #name, \ .gdb_probe = probe, \ .gdb_init = init, \ .gdb_term = term, \ .gdb_getc = getc, \ .gdb_putc = putc, \ }; \ DATA_SET(gdb_dbgport_set, name##_gdb_dbgport) #endif /* !_GDB_GDB_H_ */ Index: head/sys/gdb/gdb_int.h =================================================================== --- head/sys/gdb/gdb_int.h (revision 353701) +++ head/sys/gdb/gdb_int.h (revision 353702) @@ -1,137 +1,153 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2004 Marcel Moolenaar * All rights reserved. * * 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 ``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 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$ */ #ifndef _GDB_GDB_INT_H_ #define _GDB_GDB_INT_H_ #include "opt_ddb.h" #include #ifdef DDB #include #endif #ifndef EOF #define EOF (-1) #endif SYSCTL_DECL(_debug_gdb); extern struct gdb_dbgport *gdb_cur; extern int gdb_listening; void gdb_consinit(void); extern char *gdb_rxp; extern size_t gdb_rxsz; extern char *gdb_txp; +extern bool gdb_ackmode; + #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); int gdb_rx_varhex(uintmax_t *); static __inline int gdb_rx_char(void) { int c; if (gdb_rxsz > 0) { c = *gdb_rxp++; gdb_rxsz--; } else c = EOF; return (c); } void gdb_tx_begin(char); int gdb_tx_end(void); int gdb_tx_mem(const unsigned char *, size_t); void gdb_tx_reg(int); bool gdb_txbuf_has_capacity(size_t); int gdb_rx_bindata(unsigned char *data, size_t datalen, size_t *amt); int gdb_search_mem(const unsigned char *addr, size_t size, const unsigned char *pat, size_t patlen, const unsigned char **found); static __inline void gdb_tx_char(char c) { *gdb_txp++ = c; } static __inline int gdb_tx_empty(void) { gdb_tx_begin('\0'); return (gdb_tx_end()); } static __inline void gdb_tx_hex(uintmax_t n, int sz) { gdb_txp += sprintf(gdb_txp, "%0*jx", sz, n); } static __inline int gdb_tx_err(int err) { gdb_tx_begin('E'); gdb_tx_hex(err, 2); return (gdb_tx_end()); } static __inline int gdb_tx_ok(void) { gdb_tx_begin('O'); gdb_tx_char('K'); return (gdb_tx_end()); } static __inline void gdb_tx_str(const char *s) { while (*s) *gdb_txp++ = *s++; } static __inline void gdb_tx_varhex(uintmax_t n) { gdb_txp += sprintf(gdb_txp, "%jx", n); +} + +static __inline void +gdb_nack(void) +{ + if (gdb_ackmode) + gdb_cur->gdb_putc('-'); +} + +static __inline void +gdb_ack(void) +{ + if (gdb_ackmode) + gdb_cur->gdb_putc('+'); } #endif /* !_GDB_GDB_INT_H_ */ Index: head/sys/gdb/gdb_main.c =================================================================== --- head/sys/gdb/gdb_main.c (revision 353701) +++ head/sys/gdb/gdb_main.c (revision 353702) @@ -1,810 +1,837 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2004 Marcel Moolenaar * All rights reserved. * * 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 AUTHORS ``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 AUTHORS 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include SYSCTL_NODE(_debug, OID_AUTO, gdb, CTLFLAG_RW, 0, "GDB settings"); static dbbe_init_f gdb_init; static dbbe_trap_f gdb_trap; KDB_BACKEND(gdb, gdb_init, NULL, NULL, gdb_trap); static struct gdb_dbgport null_gdb_dbgport; DATA_SET(gdb_dbgport_set, null_gdb_dbgport); SET_DECLARE(gdb_dbgport_set, struct gdb_dbgport); struct gdb_dbgport *gdb_cur = NULL; int gdb_listening = 0; +bool gdb_ackmode = true; static unsigned char gdb_bindata[64]; #ifdef DDB bool gdb_return_to_ddb = false; #endif static int gdb_init(void) { struct gdb_dbgport *dp, **iter; int cur_pri, pri; gdb_cur = NULL; cur_pri = -1; SET_FOREACH(iter, gdb_dbgport_set) { dp = *iter; pri = (dp->gdb_probe != NULL) ? dp->gdb_probe() : -1; dp->gdb_active = (pri >= 0) ? 0 : -1; if (pri > cur_pri) { cur_pri = pri; gdb_cur = dp; } } if (gdb_cur != NULL) { printf("GDB: debug ports:"); SET_FOREACH(iter, gdb_dbgport_set) { dp = *iter; if (dp->gdb_active == 0) printf(" %s", dp->gdb_name); } printf("\n"); } else printf("GDB: no debug ports present\n"); if (gdb_cur != NULL) { gdb_cur->gdb_init(); printf("GDB: current port: %s\n", gdb_cur->gdb_name); } if (gdb_cur != NULL) { cur_pri = (boothowto & RB_GDB) ? 2 : 0; gdb_consinit(); } else cur_pri = -1; return (cur_pri); } static void gdb_do_mem_search(void) { size_t patlen; intmax_t addr, size; const unsigned char *found; if (gdb_rx_varhex(&addr) || gdb_rx_char() != ';' || gdb_rx_varhex(&size) || gdb_rx_char() != ';' || gdb_rx_bindata(gdb_bindata, sizeof(gdb_bindata), &patlen)) { gdb_tx_err(EINVAL); return; } if (gdb_search_mem((char *)(uintptr_t)addr, size, gdb_bindata, patlen, &found)) { if (found == 0ULL) gdb_tx_begin('0'); else { gdb_tx_begin('1'); gdb_tx_char(','); gdb_tx_hex((intmax_t)(uintptr_t)found, 8); } gdb_tx_end(); } else gdb_tx_err(EIO); } static void gdb_do_threadinfo(struct thread **thr_iter) { static struct thread * const done_sentinel = (void *)(uintptr_t)1; static const size_t tidsz_hex = sizeof(lwpid_t) * 2; size_t tds_sent; if (*thr_iter == NULL) { gdb_tx_err(ENXIO); return; } if (*thr_iter == done_sentinel) { gdb_tx_begin('l'); *thr_iter = NULL; goto sendit; } gdb_tx_begin('m'); for (tds_sent = 0; *thr_iter != NULL && gdb_txbuf_has_capacity(tidsz_hex + 1); *thr_iter = kdb_thr_next(*thr_iter), tds_sent++) { if (tds_sent > 0) gdb_tx_char(','); gdb_tx_varhex((*thr_iter)->td_tid); } /* * Can't send EOF and "some" in same packet, so set a sentinel to send * EOF when GDB asks us next. */ if (*thr_iter == NULL && tds_sent > 0) *thr_iter = done_sentinel; sendit: gdb_tx_end(); } #define BIT(n) (1ull << (n)) enum { GDB_MULTIPROCESS, GDB_SWBREAK, GDB_HWBREAK, GDB_QRELOCINSN, GDB_FORK_EVENTS, GDB_VFORK_EVENTS, GDB_EXEC_EVENTS, GDB_VCONT_SUPPORTED, GDB_QTHREADEVENTS, GDB_NO_RESUMED, }; static const char * const gdb_feature_names[] = { [GDB_MULTIPROCESS] = "multiprocess", [GDB_SWBREAK] = "swbreak", [GDB_HWBREAK] = "hwbreak", [GDB_QRELOCINSN] = "qRelocInsn", [GDB_FORK_EVENTS] = "fork-events", [GDB_VFORK_EVENTS] = "vfork-events", [GDB_EXEC_EVENTS] = "exec-events", [GDB_VCONT_SUPPORTED] = "vContSupported", [GDB_QTHREADEVENTS] = "QThreadEvents", [GDB_NO_RESUMED] = "no-resumed", }; static void gdb_do_qsupported(uint32_t *feat) { char *tok, *delim, ok; size_t i, toklen; /* Parse supported host features */ *feat = 0; if (gdb_rx_char() != ':') goto error; while (gdb_rxsz > 0) { tok = gdb_rxp; delim = strchrnul(gdb_rxp, ';'); toklen = (delim - tok); gdb_rxp += toklen; gdb_rxsz -= toklen; if (*delim != '\0') { *delim = '\0'; gdb_rxp += 1; gdb_rxsz -= 1; } if (toklen < 2) goto error; ok = tok[toklen - 1]; if (ok != '-' && ok != '+') { /* * GDB only has one KV-pair feature, and we don't * support it, so ignore and move on. */ if (strchr(tok, '=') != NULL) continue; /* Not a KV-pair, and not a +/- flag? Malformed. */ goto error; } if (ok != '+') continue; tok[toklen - 1] = '\0'; for (i = 0; i < nitems(gdb_feature_names); i++) if (strcmp(gdb_feature_names[i], tok) == 0) break; if (i == nitems(gdb_feature_names)) { /* Unknown GDB feature. */ continue; } *feat |= BIT(i); } /* Send a supported feature list back */ gdb_tx_begin(0); gdb_tx_str("PacketSize"); gdb_tx_char('='); /* * We don't buffer framing bytes, but we do need to retain a byte for a * trailing nul. */ gdb_tx_varhex(GDB_BUFSZ + strlen("$#nn") - 1); gdb_tx_str(";qXfer:threads:read+"); /* + * If the debugport is a reliable transport, request No Ack mode from + * the server. The server may or may not choose to enter No Ack mode. + * https://sourceware.org/gdb/onlinedocs/gdb/Packet-Acknowledgment.html + */ + if (gdb_cur->gdb_dbfeatures & GDB_DBGP_FEAT_RELIABLE) + gdb_tx_str(";QStartNoAckMode+"); + + /* * Future consideration: * - vCont * - multiprocess */ gdb_tx_end(); return; error: *feat = 0; gdb_tx_err(EINVAL); } /* * A qXfer_context provides a vaguely generic way to generate a multi-packet * response on the fly, making some assumptions about the size of sbuf writes * vs actual packet length constraints. A non-byzantine gdb host should allow * hundreds of bytes per packet or more. * * Upper layers are considered responsible for escaping the four forbidden * characters '# $ } *'. */ struct qXfer_context { struct sbuf sb; size_t last_offset; bool flushed; bool lastmessage; char xfer_buf[GDB_BUFSZ]; }; static int qXfer_drain(void *v, const char *buf, int len) { struct qXfer_context *qx; if (len < 0) return (-EINVAL); qx = v; if (qx->flushed) { /* * Overflow. We lost some message. Maybe the packet size is * ridiculously small. */ printf("%s: Overflow in qXfer detected.\n", __func__); return (-ENOBUFS); } qx->last_offset += len; qx->flushed = true; if (qx->lastmessage) gdb_tx_begin('l'); else gdb_tx_begin('m'); memcpy(gdb_txp, buf, len); gdb_txp += len; gdb_tx_end(); return (len); } static int init_qXfer_ctx(struct qXfer_context *qx, uintmax_t len) { /* Protocol (max) length field includes framing overhead. */ if (len < sizeof("$m#nn")) return (ENOSPC); len -= 4; len = ummin(len, GDB_BUFSZ - 1); qx->last_offset = 0; qx->flushed = false; qx->lastmessage = false; sbuf_new(&qx->sb, qx->xfer_buf, len, SBUF_FIXEDLEN); sbuf_set_drain(&qx->sb, qXfer_drain, qx); return (0); } /* * dst must be 2x strlen(max_src) + 1. * * Squashes invalid XML characters down to _. Sorry. Then escapes for GDB. */ static void qXfer_escape_xmlattr_str(char *dst, size_t dstlen, const char *src) { static const char *forbidden = "#$}*"; size_t i; char c; for (i = 0; i < dstlen - 1 && *src != 0; src++, i++) { c = *src; /* XML attr filter */ if (c < 32) c = '_'; /* We assume attributes will be "" quoted. */ if (c == '<' || c == '&' || c == '"') c = '_'; /* GDB escape. */ if (strchr(forbidden, c) != NULL) { *dst++ = '}'; c ^= 0x20; } *dst++ = c; } if (*src != 0) printf("XXX%s: overflow; API misuse\n", __func__); *dst = 0; } /* * Dynamically generate qXfer:threads document, one packet at a time. * * The format is loosely described[0], although it does not seem that the * mentioned on that page is required. * * [0]: https://sourceware.org/gdb/current/onlinedocs/gdb/Thread-List-Format.html */ static void do_qXfer_threads_read(void) { /* Kludgy context */ static struct { struct qXfer_context qXfer; /* Kludgy state machine */ struct thread *iter; enum { XML_START_THREAD, /* ' ...' */ XML_END_THREAD, /* '' */ XML_SENT_END_THREADS, /* '' */ } next_step; } ctx; static char td_name_escape[MAXCOMLEN * 2 + 1]; const char *name_src; uintmax_t offset, len; int error; /* Annex part must be empty. */ if (gdb_rx_char() != ':') goto misformed_request; if (gdb_rx_varhex(&offset) != 0 || gdb_rx_char() != ',' || gdb_rx_varhex(&len) != 0) goto misformed_request; /* * Validate resume xfers. */ if (offset != 0) { if (offset != ctx.qXfer.last_offset) { printf("%s: Resumed offset %ju != expected %zu\n", __func__, offset, ctx.qXfer.last_offset); error = ESPIPE; goto request_error; } ctx.qXfer.flushed = false; } if (offset == 0) { ctx.iter = kdb_thr_first(); ctx.next_step = XML_START_THREAD; error = init_qXfer_ctx(&ctx.qXfer, len); if (error != 0) goto request_error; sbuf_cat(&ctx.qXfer.sb, ""); } while (!ctx.qXfer.flushed && ctx.iter != NULL) { switch (ctx.next_step) { case XML_START_THREAD: ctx.next_step = XML_THREAD_ID; sbuf_cat(&ctx.qXfer.sb, "td_tid); continue; case XML_THREAD_CORE: ctx.next_step = XML_THREAD_NAME; if (ctx.iter->td_oncpu != NOCPU) { sbuf_printf(&ctx.qXfer.sb, " core=\"%d\"", ctx.iter->td_oncpu); } continue; case XML_THREAD_NAME: ctx.next_step = XML_THREAD_EXTRA; if (ctx.iter->td_name[0] != 0) name_src = ctx.iter->td_name; else if (ctx.iter->td_proc != NULL && ctx.iter->td_proc->p_comm[0] != 0) name_src = ctx.iter->td_proc->p_comm; else continue; qXfer_escape_xmlattr_str(td_name_escape, sizeof(td_name_escape), name_src); sbuf_printf(&ctx.qXfer.sb, " name=\"%s\"", td_name_escape); continue; case XML_THREAD_EXTRA: ctx.next_step = XML_END_THREAD; sbuf_putc(&ctx.qXfer.sb, '>'); if (ctx.iter->td_state == TDS_RUNNING) sbuf_cat(&ctx.qXfer.sb, "Running"); else if (ctx.iter->td_state == TDS_RUNQ) sbuf_cat(&ctx.qXfer.sb, "RunQ"); else if (ctx.iter->td_state == TDS_CAN_RUN) sbuf_cat(&ctx.qXfer.sb, "CanRun"); else if (TD_ON_LOCK(ctx.iter)) sbuf_cat(&ctx.qXfer.sb, "Blocked"); else if (TD_IS_SLEEPING(ctx.iter)) sbuf_cat(&ctx.qXfer.sb, "Sleeping"); else if (TD_IS_SWAPPED(ctx.iter)) sbuf_cat(&ctx.qXfer.sb, "Swapped"); else if (TD_AWAITING_INTR(ctx.iter)) sbuf_cat(&ctx.qXfer.sb, "IthreadWait"); else if (TD_IS_SUSPENDED(ctx.iter)) sbuf_cat(&ctx.qXfer.sb, "Suspended"); else sbuf_cat(&ctx.qXfer.sb, "???"); continue; case XML_END_THREAD: ctx.next_step = XML_START_THREAD; sbuf_cat(&ctx.qXfer.sb, ""); ctx.iter = kdb_thr_next(ctx.iter); continue; /* * This one isn't part of the looping state machine, * but GCC complains if you leave an enum value out of the * select. */ case XML_SENT_END_THREADS: /* NOTREACHED */ break; } } if (ctx.qXfer.flushed) return; if (ctx.next_step != XML_SENT_END_THREADS) { ctx.next_step = XML_SENT_END_THREADS; sbuf_cat(&ctx.qXfer.sb, ""); } if (ctx.qXfer.flushed) return; ctx.qXfer.lastmessage = true; sbuf_finish(&ctx.qXfer.sb); sbuf_delete(&ctx.qXfer.sb); ctx.qXfer.last_offset = 0; return; misformed_request: /* * GDB "General-Query-Packets.html" qXfer-read anchor specifically * documents an E00 code for malformed requests or invalid annex. * Non-zero codes indicate invalid offset or "error reading the data." */ error = 0; request_error: gdb_tx_err(error); return; } /* * A set of standardized transfers from "special data areas." * * We've already matched on "qXfer:" and advanced the rx packet buffer past * that bit. Parse out the rest of the packet and generate an appropriate * response. */ static void do_qXfer(void) { if (!gdb_rx_equal("threads:")) goto unrecognized; if (!gdb_rx_equal("read:")) goto unrecognized; do_qXfer_threads_read(); return; unrecognized: gdb_tx_empty(); 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) { jmp_buf jb; struct thread *thr_iter; void *prev_jb; uint32_t host_features; prev_jb = kdb_jmpbuf(jb); if (setjmp(jb) != 0) { printf("%s bailing, hopefully back to ddb!\n", __func__); gdb_listening = 0; (void)kdb_jmpbuf(prev_jb); return (1); } gdb_listening = 0; + gdb_ackmode = true; + /* * Send a T packet. We currently do not support watchpoints (the * awatch, rwatch or watch elements). */ gdb_tx_begin('T'); gdb_tx_hex(gdb_cpu_signal(type, code), 2); gdb_tx_varhex(GDB_REG_PC); gdb_tx_char(':'); gdb_tx_reg(GDB_REG_PC); gdb_tx_char(';'); gdb_tx_str("thread:"); gdb_tx_varhex((long)kdb_thread->td_tid); gdb_tx_char(';'); gdb_tx_end(); /* XXX check error condition. */ thr_iter = NULL; while (gdb_rx_begin() == 0) { /* printf("GDB: got '%s'\n", gdb_rxp); */ switch (gdb_rx_char()) { case '?': /* Last signal. */ gdb_tx_begin('T'); gdb_tx_hex(gdb_cpu_signal(type, code), 2); gdb_tx_str("thread:"); gdb_tx_varhex((long)kdb_thread->td_tid); gdb_tx_char(';'); gdb_tx_end(); break; case 'c': { /* Continue. */ uintmax_t addr; register_t pc; if (!gdb_rx_varhex(&addr)) { pc = addr; gdb_cpu_setreg(GDB_REG_PC, &pc); } kdb_cpu_clear_singlestep(); gdb_listening = 1; return (1); } case 'C': { /* Continue with signal. */ uintmax_t addr, sig; register_t pc; if (!gdb_rx_varhex(&sig) && gdb_rx_char() == ';' && !gdb_rx_varhex(&addr)) { pc = addr; gdb_cpu_setreg(GDB_REG_PC, &pc); } kdb_cpu_clear_singlestep(); gdb_listening = 1; return (1); } case 'D': { /* Detach */ gdb_tx_ok(); gdb_handle_detach(); return (1); } case 'g': { /* Read registers. */ size_t r; gdb_tx_begin(0); for (r = 0; r < GDB_NREGS; r++) gdb_tx_reg(r); gdb_tx_end(); break; } case 'G': /* Write registers. */ gdb_tx_err(0); break; case 'H': { /* Set thread. */ intmax_t tid; struct thread *thr; /* Ignore 'g' (general) or 'c' (continue) flag. */ (void) gdb_rx_char(); if (gdb_rx_varhex(&tid)) { gdb_tx_err(EINVAL); break; } if (tid > 0) { thr = kdb_thr_lookup(tid); if (thr == NULL) { gdb_tx_err(ENOENT); break; } kdb_thr_select(thr); } gdb_tx_ok(); break; } case 'k': /* Kill request. */ gdb_handle_detach(); return (1); case 'm': { /* Read memory. */ uintmax_t addr, size; if (gdb_rx_varhex(&addr) || gdb_rx_char() != ',' || gdb_rx_varhex(&size)) { gdb_tx_err(EINVAL); break; } gdb_tx_begin(0); if (gdb_tx_mem((char *)(uintptr_t)addr, size)) gdb_tx_end(); else gdb_tx_err(EIO); break; } case 'M': { /* Write memory. */ uintmax_t addr, size; if (gdb_rx_varhex(&addr) || gdb_rx_char() != ',' || gdb_rx_varhex(&size) || gdb_rx_char() != ':') { gdb_tx_err(EINVAL); break; } if (gdb_rx_mem((char *)(uintptr_t)addr, size) == 0) gdb_tx_err(EIO); else gdb_tx_ok(); break; } case 'P': { /* Write register. */ char *val; uintmax_t reg; val = gdb_rxp; if (gdb_rx_varhex(®) || gdb_rx_char() != '=' || !gdb_rx_mem(val, gdb_cpu_regsz(reg))) { gdb_tx_err(EINVAL); break; } gdb_cpu_setreg(reg, val); gdb_tx_ok(); break; } case 'q': /* General query. */ if (gdb_rx_equal("C")) { gdb_tx_begin('Q'); gdb_tx_char('C'); gdb_tx_varhex((long)kdb_thread->td_tid); gdb_tx_end(); } else if (gdb_rx_equal("Supported")) { gdb_do_qsupported(&host_features); } else if (gdb_rx_equal("fThreadInfo")) { thr_iter = kdb_thr_first(); gdb_do_threadinfo(&thr_iter); } else if (gdb_rx_equal("sThreadInfo")) { gdb_do_threadinfo(&thr_iter); } else if (gdb_rx_equal("Xfer:")) { do_qXfer(); } else if (gdb_rx_equal("Search:memory:")) { gdb_do_mem_search(); } else if (!gdb_cpu_query()) + gdb_tx_empty(); + break; + case 'Q': + if (gdb_rx_equal("StartNoAckMode")) { + if ((gdb_cur->gdb_dbfeatures & + GDB_DBGP_FEAT_RELIABLE) == 0) { + /* + * Shouldn't happen if we didn't + * advertise support. Reject. + */ + gdb_tx_empty(); + break; + } + gdb_ackmode = false; + gdb_tx_ok(); + } else gdb_tx_empty(); break; case 's': { /* Step. */ uintmax_t addr; register_t pc; if (!gdb_rx_varhex(&addr)) { pc = addr; gdb_cpu_setreg(GDB_REG_PC, &pc); } kdb_cpu_set_singlestep(); gdb_listening = 1; return (1); } case 'S': { /* Step with signal. */ uintmax_t addr, sig; register_t pc; if (!gdb_rx_varhex(&sig) && gdb_rx_char() == ';' && !gdb_rx_varhex(&addr)) { pc = addr; gdb_cpu_setreg(GDB_REG_PC, &pc); } kdb_cpu_set_singlestep(); gdb_listening = 1; return (1); } case 'T': { /* Thread alive. */ intmax_t tid; if (gdb_rx_varhex(&tid)) { gdb_tx_err(EINVAL); break; } if (kdb_thr_lookup(tid) != NULL) gdb_tx_ok(); else gdb_tx_err(ENOENT); break; } case EOF: /* Empty command. Treat as unknown command. */ /* FALLTHROUGH */ default: /* Unknown command. Send empty response. */ gdb_tx_empty(); break; } } (void)kdb_jmpbuf(prev_jb); return (0); } Index: head/sys/gdb/gdb_packet.c =================================================================== --- head/sys/gdb/gdb_packet.c (revision 353701) +++ head/sys/gdb/gdb_packet.c (revision 353702) @@ -1,428 +1,446 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2004 Marcel Moolenaar * All rights reserved. * * 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 ``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 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include static char gdb_rxbuf[GDB_BUFSZ]; char *gdb_rxp = NULL; size_t gdb_rxsz = 0; /* * 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' : \ 10 + (((c) < 'a') ? (c) - 'A' : (c) - 'a')) #define N2C(n) (((n) < 10) ? (n) + '0' : (n) + 'a' - 10) /* * Get a single character */ static int gdb_getc(void) { int c; do c = gdb_cur->gdb_getc(); while (c == -1); 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 { printf("using longjmp, hope it works!\n"); kdb_reenter(); } } return (c); } /* * Functions to receive and extract from a packet. */ int gdb_rx_begin(void) { int c, cksum; gdb_rxp = NULL; do { /* * Wait for the start character, ignore all others. * XXX needs a timeout. */ while ((c = gdb_getc()) != '$') ; /* Read until a # or end of buffer is found. */ cksum = 0; gdb_rxsz = 0; while (gdb_rxsz < sizeof(gdb_rxbuf) - 1) { c = gdb_getc(); if (c == '#') break; gdb_rxbuf[gdb_rxsz++] = c; cksum += c; } gdb_rxbuf[gdb_rxsz] = 0; cksum &= 0xff; /* Bail out on a buffer overflow. */ if (c != '#') { - gdb_cur->gdb_putc('-'); + gdb_nack(); return (ENOSPC); } + /* + * In Not-AckMode, we can assume reliable transport and neither + * need to verify checksums nor send Ack/Nack. + */ + if (!gdb_ackmode) + break; + c = gdb_getc(); cksum -= (C2N(c) << 4) & 0xf0; c = gdb_getc(); cksum -= C2N(c) & 0x0f; - gdb_cur->gdb_putc((cksum == 0) ? '+' : '-'); - if (cksum != 0) + if (cksum == 0) { + gdb_ack(); + } else { + gdb_nack(); printf("GDB: packet `%s' has invalid checksum\n", gdb_rxbuf); + } } while (cksum != 0); gdb_rxp = gdb_rxbuf; return (0); } int gdb_rx_equal(const char *str) { int len; len = strlen(str); if (len > gdb_rxsz || strncmp(str, gdb_rxp, len) != 0) return (0); gdb_rxp += len; gdb_rxsz -= len; return (1); } int gdb_rx_mem(unsigned char *addr, size_t size) { unsigned char *p; void *prev; void *wctx; jmp_buf jb; size_t cnt; int ret; unsigned char c; if (size * 2 != gdb_rxsz) return (-1); wctx = gdb_begin_write(); prev = kdb_jmpbuf(jb); ret = setjmp(jb); if (ret == 0) { p = addr; cnt = size; while (cnt-- > 0) { c = (C2N(gdb_rxp[0]) << 4) & 0xf0; c |= C2N(gdb_rxp[1]) & 0x0f; *p++ = c; gdb_rxsz -= 2; gdb_rxp += 2; } kdb_cpu_sync_icache(addr, size); } (void)kdb_jmpbuf(prev); gdb_end_write(wctx); return ((ret == 0) ? 1 : 0); } int gdb_rx_varhex(uintmax_t *vp) { uintmax_t v; int c, neg; c = gdb_rx_char(); neg = (c == '-') ? 1 : 0; if (neg == 1) c = gdb_rx_char(); if (!isxdigit(c)) { gdb_rxp -= ((c == -1) ? 0 : 1) + neg; gdb_rxsz += ((c == -1) ? 0 : 1) + neg; return (-1); } v = 0; do { v <<= 4; v += C2N(c); c = gdb_rx_char(); } while (isxdigit(c)); if (c != EOF) { gdb_rxp--; gdb_rxsz++; } *vp = (neg) ? -v : v; return (0); } /* * Function to build and send a package. */ void gdb_tx_begin(char tp) { gdb_txp = gdb_txbuf; if (tp != '\0') 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) { const char *p; int runlen; unsigned char c, cksum; do { if (gdb_cur->gdb_sendpacket != NULL) { gdb_tx_sendpacket(); goto getack; } gdb_cur->gdb_putc('$'); cksum = 0; p = gdb_txbuf; while (p < gdb_txp) { /* Send a character and start run-length encoding. */ c = *p++; gdb_cur->gdb_putc(c); cksum += c; runlen = 0; /* Determine run-length and update checksum. */ while (p < gdb_txp && *p == c) { runlen++; p++; } /* Emit the run-length encoded string. */ while (runlen >= 97) { gdb_cur->gdb_putc('*'); cksum += '*'; gdb_cur->gdb_putc(97+29); cksum += 97+29; runlen -= 97; if (runlen > 0) { gdb_cur->gdb_putc(c); cksum += c; runlen--; } } if (runlen == 1) { gdb_cur->gdb_putc(c); cksum += c; runlen--; } if (runlen == 0) continue; /* Don't emit '$', '#', '+' or '-'. */ if (runlen == 7) { gdb_cur->gdb_putc(c); cksum += c; runlen--; } if (runlen == 6 || runlen == 14 || runlen == 16) { gdb_cur->gdb_putc(c); cksum += c; runlen--; } gdb_cur->gdb_putc('*'); cksum += '*'; gdb_cur->gdb_putc(runlen+29); cksum += runlen+29; } gdb_cur->gdb_putc('#'); c = cksum >> 4; gdb_cur->gdb_putc(N2C(c)); c = cksum & 0x0f; gdb_cur->gdb_putc(N2C(c)); getack: + /* + * In NoAckMode, it is assumed that the underlying transport is + * reliable and thus neither conservant sends acknowledgements; + * there is nothing to wait for here. + */ + if (!gdb_ackmode) + break; + c = gdb_getc(); } while (c != '+'); return (0); } int gdb_tx_mem(const unsigned char *addr, size_t size) { void *prev; jmp_buf jb; int ret; prev = kdb_jmpbuf(jb); ret = setjmp(jb); if (ret == 0) { while (size-- > 0) { *gdb_txp++ = N2C(*addr >> 4); *gdb_txp++ = N2C(*addr & 0x0f); addr++; } } (void)kdb_jmpbuf(prev); return ((ret == 0) ? 1 : 0); } void gdb_tx_reg(int regnum) { unsigned char *regp; size_t regsz; regp = gdb_cpu_getreg(regnum, ®sz); if (regp == NULL) { /* Register unavailable. */ while (regsz--) { gdb_tx_char('x'); gdb_tx_char('x'); } } else gdb_tx_mem(regp, regsz); } bool gdb_txbuf_has_capacity(size_t req) { return (((char *)gdb_txbuf + sizeof(gdb_txbuf) - gdb_txp) >= req); } /* Read binary data up until the end of the packet or until we have datalen decoded bytes */ int gdb_rx_bindata(unsigned char *data, size_t datalen, size_t *amt) { int c; *amt = 0; while (*amt < datalen) { c = gdb_rx_char(); if (c == EOF) break; /* Escaped character up next */ if (c == '}') { /* Malformed packet. */ if ((c = gdb_rx_char()) == EOF) return (1); c ^= 0x20; } *(data++) = c & 0xff; (*amt)++; } return (0); } int gdb_search_mem(const unsigned char *addr, size_t size, const unsigned char *pat, size_t patlen, const unsigned char **found) { void *prev; jmp_buf jb; int ret; prev = kdb_jmpbuf(jb); ret = setjmp(jb); if (ret == 0) *found = memmem(addr, size, pat, patlen); (void)kdb_jmpbuf(prev); return ((ret == 0) ? 1 : 0); } Index: head/sys/gdb/netgdb.c =================================================================== --- head/sys/gdb/netgdb.c (revision 353701) +++ head/sys/gdb/netgdb.c (revision 353702) @@ -1,406 +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 :' 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 __FBSDID("$FreeBSD$"); #include "opt_ddb.h" #ifndef DDB #error "NetGDB cannot be used without DDB at this time" #endif #include #include #include #include #include #include #include #ifdef DDB #include #include #include #endif #include #include #include #include #include #include #include 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, + .gdb_dbfeatures = GDB_DBGP_FEAT_WANTTERM | GDB_DBGP_FEAT_RELIABLE, }; 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 [-g -i ] * * 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 */