mirror of
https://git.FreeBSD.org/src.git
synced 2025-01-12 14:29:28 +00:00
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.
This commit is contained in:
parent
c4fbbfaa47
commit
5555afa14e
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=351368
@ -36,6 +36,7 @@ __FBSDID("$FreeBSD$");
|
||||
#include <sys/pcpu.h>
|
||||
#include <sys/proc.h>
|
||||
#include <sys/reboot.h>
|
||||
#include <sys/sbuf.h>
|
||||
|
||||
#include <machine/gdb_machdep.h>
|
||||
#include <machine/kdb.h>
|
||||
@ -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,6 +268,305 @@ gdb_do_qsupported(uint32_t *feat)
|
||||
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
|
||||
* <?xml?> 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, /* '<thread' */
|
||||
XML_THREAD_ID, /* ' id="xxx"' */
|
||||
XML_THREAD_CORE, /* ' core="yyy"' */
|
||||
XML_THREAD_NAME, /* ' name="zzz"' */
|
||||
XML_THREAD_EXTRA, /* '> ...' */
|
||||
XML_END_THREAD, /* '</thread>' */
|
||||
XML_SENT_END_THREADS, /* '</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, "<threads>");
|
||||
}
|
||||
|
||||
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, "<thread");
|
||||
continue;
|
||||
|
||||
case XML_THREAD_ID:
|
||||
ctx.next_step = XML_THREAD_CORE;
|
||||
sbuf_printf(&ctx.qXfer.sb, " id=\"%jx\"",
|
||||
(uintmax_t)ctx.iter->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, "</thread>");
|
||||
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, "</threads>");
|
||||
}
|
||||
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)
|
||||
{
|
||||
@ -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())
|
||||
|
Loading…
Reference in New Issue
Block a user