fa76c6f9ba
On PowerPC, this is needed in order for the debugger to find out the memory offset where the kernel image was loaded on the remote target. This fixes symbol resolution when remote debugging a PowerPC kernel. Reviewed by: cem Differential Revision: https://reviews.freebsd.org/D22767
842 lines
19 KiB
C
842 lines
19 KiB
C
/*-
|
|
* 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 <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kdb.h>
|
|
#include <sys/kernel.h>
|
|
#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>
|
|
|
|
#include <gdb/gdb.h>
|
|
#include <gdb/gdb_int.h>
|
|
|
|
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
|
|
* <?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 %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, "<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 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();
|
|
#ifdef __powerpc__
|
|
} else if (gdb_rx_equal("Offsets")) {
|
|
gdb_cpu_do_offsets();
|
|
#endif
|
|
} 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);
|
|
}
|