From owner-svn-src-head@freebsd.org Thu Aug 22 00:34:12 2019 Return-Path: Delivered-To: svn-src-head@mailman.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.nyi.freebsd.org (Postfix) with ESMTP id 1B670D8DFB; Thu, 22 Aug 2019 00:34:12 +0000 (UTC) (envelope-from cem@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) server-signature RSA-PSS (4096 bits) client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 46DQWl6zbFz3JVK; Thu, 22 Aug 2019 00:34:11 +0000 (UTC) (envelope-from cem@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id D13C923E7D; Thu, 22 Aug 2019 00:34:11 +0000 (UTC) (envelope-from cem@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id x7M0YBUo088033; Thu, 22 Aug 2019 00:34:11 GMT (envelope-from cem@FreeBSD.org) Received: (from cem@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id x7M0YBOw088032; Thu, 22 Aug 2019 00:34:11 GMT (envelope-from cem@FreeBSD.org) Message-Id: <201908220034.x7M0YBOw088032@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: cem set sender to cem@FreeBSD.org using -f From: Conrad Meyer Date: Thu, 22 Aug 2019 00:34:11 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r351368 - head/sys/gdb X-SVN-Group: head X-SVN-Commit-Author: cem X-SVN-Commit-Paths: head/sys/gdb X-SVN-Commit-Revision: 351368 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 22 Aug 2019 00:34:12 -0000 Author: cem Date: Thu Aug 22 00:34:11 2019 New Revision: 351368 URL: https://svnweb.freebsd.org/changeset/base/351368 Log: gdb(4): Implement qXfer:threads:read This streams out an XML document over several GDB packets describing all threads in the system; their ids, name, and any loosely defined "extra info" we feel like including. For now, I have included a string version of the run state, similar to some of the DDB logic to stringify thread state. The benefit of supporting this in addition to the qfThreadInfo/qsThreadInfo packing is that in this mode, the host gdb does not ask for every thread's "qThreadExtraInfo," saving per-thread round-trips on "info threads." To use this feature, (k)gdb needs to be built with the --with-expat option. I would encourage enabling this option by default in our GDB port, if it is not already. Finally, there is another optional attribute you can specify per-thread called a "handle." Handles are arbitrarily long sequences of bytes, represented in the XML as hexadecimal. It is unclear to me how or if GDB actually uses handles for anything. So I have left them out. Modified: head/sys/gdb/gdb_main.c Modified: head/sys/gdb/gdb_main.c ============================================================================== --- head/sys/gdb/gdb_main.c Thu Aug 22 00:33:22 2019 (r351367) +++ head/sys/gdb/gdb_main.c Thu Aug 22 00:34:11 2019 (r351368) @@ -36,6 +36,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include @@ -252,11 +253,12 @@ gdb_do_qsupported(uint32_t *feat) */ gdb_tx_varhex(GDB_BUFSZ + strlen("$#nn") - 1); + gdb_tx_str(";qXfer:threads:read+"); + /* * Future consideration: * - vCont * - multiprocess - * - qXfer:threads:read */ gdb_tx_end(); return; @@ -266,7 +268,306 @@ error: 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 %ju\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 int gdb_trap(int type, int code) { jmp_buf jb; @@ -425,6 +726,8 @@ gdb_trap(int type, int code) 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())