47b0c9943c
backend improves over the old GDB support in the following ways: o Unified implementation with minimal MD code. o A simple interface for devices to register themselves as debug ports, ala consoles. o Compression by using run-length encoding. o Implements GDB threading support.
262 lines
6.4 KiB
C
262 lines
6.4 KiB
C
/*
|
|
* 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 <machine/gdb_machdep.h>
|
|
#include <machine/kdb.h>
|
|
|
|
#include <gdb/gdb.h>
|
|
#include <gdb/gdb_int.h>
|
|
|
|
static dbbe_init_f gdb_init;
|
|
static dbbe_trap_f gdb_trap;
|
|
|
|
KDB_BACKEND(gdb, gdb_init, NULL, gdb_trap);
|
|
|
|
GDB_DBGPORT(null, NULL, NULL, NULL, NULL, NULL, NULL);
|
|
SET_DECLARE(gdb_dbgport_set, struct gdb_dbgport);
|
|
|
|
struct gdb_dbgport *gdb_cur = NULL;
|
|
|
|
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;
|
|
else
|
|
cur_pri = -1;
|
|
return (cur_pri);
|
|
}
|
|
|
|
static int
|
|
gdb_trap(int type, int code)
|
|
{
|
|
struct thread *thr_iter;
|
|
|
|
/*
|
|
* 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('S');
|
|
gdb_tx_hex(gdb_cpu_signal(type, code), 2);
|
|
gdb_tx_end();
|
|
break;
|
|
case 'c': { /* Continue. */
|
|
uintmax_t addr;
|
|
if (!gdb_rx_varhex(&addr))
|
|
gdb_cpu_setreg(GDB_REG_PC, addr);
|
|
kdb_cpu_clear_singlestep();
|
|
return (1);
|
|
}
|
|
case 'C': { /* Continue with signal. */
|
|
uintmax_t addr, sig;
|
|
if (!gdb_rx_varhex(&sig) && gdb_rx_char() == ';' &&
|
|
!gdb_rx_varhex(&addr))
|
|
gdb_cpu_setreg(GDB_REG_PC, addr);
|
|
kdb_cpu_clear_singlestep();
|
|
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;
|
|
gdb_rx_char();
|
|
gdb_rx_varhex(&tid);
|
|
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. */
|
|
kdb_cpu_clear_singlestep();
|
|
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. */
|
|
uintmax_t reg, val;
|
|
if (gdb_rx_varhex(®) || gdb_rx_char() != '=' ||
|
|
gdb_rx_varhex(&val)) {
|
|
gdb_tx_err(EINVAL);
|
|
break;
|
|
}
|
|
gdb_cpu_setreg(reg, val);
|
|
gdb_tx_ok();
|
|
break;
|
|
}
|
|
case 'q': /* General query. */
|
|
if (gdb_rx_equal("fThreadInfo")) {
|
|
thr_iter = kdb_thr_first();
|
|
gdb_tx_begin('m');
|
|
gdb_tx_hex((long)thr_iter->td_tid, 8);
|
|
gdb_tx_end();
|
|
} else if (gdb_rx_equal("sThreadInfo")) {
|
|
if (thr_iter == NULL) {
|
|
gdb_tx_err(ENXIO);
|
|
break;
|
|
}
|
|
thr_iter = kdb_thr_next(thr_iter);
|
|
if (thr_iter != NULL) {
|
|
gdb_tx_begin('m');
|
|
gdb_tx_hex((long)thr_iter->td_tid, 8);
|
|
gdb_tx_end();
|
|
} else {
|
|
gdb_tx_begin('l');
|
|
gdb_tx_end();
|
|
}
|
|
} else if (!gdb_cpu_query())
|
|
gdb_tx_empty();
|
|
break;
|
|
case 's': { /* Step. */
|
|
uintmax_t addr;
|
|
if (!gdb_rx_varhex(&addr))
|
|
gdb_cpu_setreg(GDB_REG_PC, addr);
|
|
kdb_cpu_set_singlestep();
|
|
return (1);
|
|
}
|
|
case 'S': { /* Step with signal. */
|
|
uintmax_t addr, sig;
|
|
if (!gdb_rx_varhex(&sig) && gdb_rx_char() == ';' &&
|
|
!gdb_rx_varhex(&addr))
|
|
gdb_cpu_setreg(GDB_REG_PC, addr);
|
|
kdb_cpu_set_singlestep();
|
|
return (1);
|
|
}
|
|
case 'T': { /* Thread alive. */
|
|
intmax_t tid;
|
|
gdb_rx_varhex(&tid);
|
|
if (kdb_thr_lookup(tid) != NULL)
|
|
gdb_tx_ok();
|
|
else
|
|
gdb_tx_err(ENOENT);
|
|
break;
|
|
}
|
|
case -1:
|
|
/* Empty command. Treat as unknown command. */
|
|
/* FALLTHROUGH */
|
|
default:
|
|
/* Unknown command. Send empty response. */
|
|
gdb_tx_empty();
|
|
break;
|
|
}
|
|
}
|
|
return (0);
|
|
}
|