#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: kgdb_stub.c,v 1.31 2022/10/26 23:26:57 riastradh Exp $");
#include "opt_ddb.h"
#include "opt_kgdb.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kgdb.h>
#undef DEBUG_KGDB
#ifdef DEBUG_KGDB
#define DPRINTF(x) printf x
#else
#define DPRINTF(x)
#endif
#ifndef KGDB_DEV
#define KGDB_DEV NODEV
#endif
#ifndef KGDB_DEVRATE
#define KGDB_DEVRATE 19200
#endif
dev_t kgdb_dev = KGDB_DEV;
int kgdb_rate = KGDB_DEVRATE;
int kgdb_active = 0;
int kgdb_debug_init = 0;
int kgdb_debug_panic = 0;
label_t *kgdb_recover = 0;
static int (*kgdb_getc)(void *);
static void (*kgdb_putc)(void *, int);
static void *kgdb_ioarg;
static u_char buffer[KGDB_BUFLEN];
static kgdb_reg_t gdb_regs[KGDB_NUMREGS];
#define GETC() kgdb_waitc(kgdb_ioarg)
#define PUTC(c) (*kgdb_putc)(kgdb_ioarg, c)
static int
kgdb_waitc(void *arg)
{
int c;
while ((c = (*kgdb_getc)(arg)) == -1)
continue;
return c;
}
#ifdef DDB
#include <ddb/db_extern.h>
#else
void (*db_trap_callback)(int);
#endif
void kgdb_voidop(void);
__weak_alias(kgdb_entry_notice, kgdb_voidop);
void
kgdb_voidop(void)
{
return;
}
static void
kgdb_copy(void *vsrc, void *vdst, int len)
{
char *src = vsrc;
char *dst = vdst;
while (--len >= 0)
*dst++ = *src++;
}
#if 0
static void
kgdb_zero(void *vptr, int len)
{
char *ptr = vptr;
while (--len >= 0)
*ptr++ = (char) 0;
}
#endif
static int
digit2i(u_char c)
{
if (c >= '0' && c <= '9')
return (c - '0');
else if (c >= 'a' && c <= 'f')
return (c - 'a' + 10);
else if (c >= 'A' && c <= 'F')
return (c - 'A' + 10);
else
return (-1);
}
static u_char
i2digit(int n)
{
return (hexdigits[n & 0x0f]);
}
static void
mem2hex(void *vdst, void *vsrc, int len)
{
u_char *dst = vdst;
u_char *src = vsrc;
while (len--) {
*dst++ = i2digit(*src >> 4);
*dst++ = i2digit(*src++);
}
*dst = '\0';
}
static u_char *
hex2mem(void *vdst, u_char *src, int maxlen)
{
u_char *dst = vdst;
int msb, lsb;
while (*src && maxlen--) {
msb = digit2i(*src++);
if (msb < 0)
return (src - 1);
lsb = digit2i(*src++);
if (lsb < 0)
return (NULL);
*dst++ = (msb << 4) | lsb;
}
return (src);
}
static vaddr_t
hex2i(u_char **srcp)
{
char *src = *srcp;
vaddr_t r = 0;
int nibble;
while ((nibble = digit2i(*src)) >= 0) {
r *= 16;
r += nibble;
src++;
}
*srcp = src;
return (r);
}
static void
kgdb_send(const u_char *bp)
{
const u_char *p;
u_char csum, c;
DPRINTF(("kgdb_send: %s\n", bp));
do {
p = bp;
PUTC(KGDB_START);
for (csum = 0; (c = *p); p++) {
PUTC(c);
csum += c;
}
PUTC(KGDB_END);
PUTC(i2digit(csum >> 4));
PUTC(i2digit(csum));
} while ((c = GETC() & 0x7f) == KGDB_BADP);
}
static int
kgdb_recv(u_char *bp, int maxlen)
{
u_char *p;
int c, csum, tmpcsum;
int len;
DPRINTF(("kgdb_recv: "));
do {
p = bp;
csum = len = 0;
while ((c = GETC()) != KGDB_START)
DPRINTF(("%c",c));
DPRINTF(("%c Start ",c));
while ((c = GETC()) != KGDB_END && len < maxlen) {
DPRINTF(("%c",c));
c &= 0x7f;
csum += c;
*p++ = c;
len++;
}
csum &= 0xff;
*p = '\0';
DPRINTF(("%c End ", c));
if (len >= maxlen) {
DPRINTF(("Long- "));
PUTC(KGDB_BADP);
continue;
}
tmpcsum = csum;
c = GETC();
DPRINTF(("%c",c));
csum -= digit2i(c) * 16;
c = GETC();
DPRINTF(("%c",c));
csum -= digit2i(c);
if (csum == 0) {
DPRINTF(("Good+ "));
PUTC(KGDB_GOODP);
if (bp[2] == ':') {
DPRINTF(("Seq %c%c ", bp[0], bp[1]));
PUTC(bp[0]);
PUTC(bp[1]);
len -= 3;
kgdb_copy(bp + 3, bp, len);
}
break;
}
DPRINTF((" Bad(wanted %x, off by %d)- ", tmpcsum, csum));
__USE(tmpcsum);
PUTC(KGDB_BADP);
} while (1);
DPRINTF(("kgdb_recv: %s\n", bp));
return (len);
}
void
kgdb_attach(int (*getfn)(void *), void (*putfn)(void *, int), void *ioarg)
{
kgdb_getc = getfn;
kgdb_putc = putfn;
kgdb_ioarg = ioarg;
}
int
kgdb_trap(int type, db_regs_t *regs)
{
label_t jmpbuf;
vaddr_t addr;
size_t len;
u_char *p;
kgdb_entry_notice(type, regs);
if (kgdb_dev == NODEV || kgdb_getc == NULL) {
return (0);
}
db_clear_single_step(regs);
if (db_trap_callback)
db_trap_callback(1);
if (kgdb_recover != 0) {
printf("kgdb: caught trap 0x%x at %p\n",
type, (void*)PC_REGS(regs));
kgdb_send("E0E");
longjmp(kgdb_recover);
}
if (kgdb_active == 0) {
if (!IS_BREAKPOINT_TRAP(type, 0)) {
if (db_trap_callback)
db_trap_callback(0);
return (0);
}
#ifdef FIXUP_PC_AFTER_BREAK
FIXUP_PC_AFTER_BREAK(regs);
#endif
#ifdef PC_ADVANCE
PC_ADVANCE(regs);
#else
PC_REGS(regs) += BKPT_SIZE;
#endif
kgdb_active = 1;
} else {
snprintf(buffer, sizeof(buffer), "S%02x", kgdb_signal(type));
kgdb_send(buffer);
}
kgdb_getregs(regs, gdb_regs);
(void)setjmp((kgdb_recover = &jmpbuf));
for (;;) {
kgdb_recv(buffer, sizeof(buffer));
switch (buffer[0]) {
default:
kgdb_send("");
continue;
case KGDB_SIGNAL:
snprintf(buffer, sizeof(buffer), "S%02x",
kgdb_signal(type));
kgdb_send(buffer);
continue;
case KGDB_REG_R:
mem2hex(buffer, gdb_regs, sizeof(gdb_regs));
kgdb_send(buffer);
continue;
case KGDB_REG_W:
p = hex2mem(gdb_regs, buffer + 1, sizeof(gdb_regs));
if (p == NULL || *p != '\0')
kgdb_send("E01");
else {
kgdb_setregs(regs, gdb_regs);
kgdb_send("OK");
}
continue;
case KGDB_MEM_R:
p = buffer + 1;
addr = hex2i(&p);
if (*p++ != ',') {
kgdb_send("E02");
continue;
}
len = hex2i(&p);
if (*p != '\0') {
kgdb_send("E03");
continue;
}
if (len > sizeof(buffer) / 2) {
kgdb_send("E04");
continue;
}
if (kgdb_acc(addr, len) == 0) {
kgdb_send("E05");
continue;
}
char *ptr = (char *)buffer + sizeof(buffer) / 2;
db_read_bytes(addr, len, ptr);
mem2hex(buffer, ptr, len);
kgdb_send(buffer);
continue;
case KGDB_MEM_W:
p = buffer + 1;
addr = hex2i(&p);
if (*p++ != ',') {
kgdb_send("E06");
continue;
}
len = hex2i(&p);
if (*p++ != ':') {
kgdb_send("E07");
continue;
}
if (len > (sizeof(buffer) - (p - buffer))) {
kgdb_send("E08");
continue;
}
p = hex2mem(buffer, p, sizeof(buffer));
if (p == NULL) {
kgdb_send("E09");
continue;
}
if (kgdb_acc(addr, len) == 0) {
kgdb_send("E0A");
continue;
}
db_write_bytes(addr, len, (char *)buffer);
kgdb_send("OK");
continue;
case KGDB_DETACH:
case KGDB_KILL:
kgdb_active = 0;
printf("kgdb detached\n");
db_clear_single_step(regs);
kgdb_send("OK");
goto out;
case KGDB_CONT:
if (buffer[1]) {
p = buffer + 1;
addr = hex2i(&p);
if (*p) {
kgdb_send("E0B");
continue;
}
PC_REGS(regs) = addr;
DPRINTF(("kgdb: continuing at %08lx\n", addr));
} else {
DPRINTF((
"kgdb: continuing at old address %08lx\n",
(vaddr_t)PC_REGS(regs)));
}
db_clear_single_step(regs);
goto out;
case KGDB_STEP:
if (buffer[1]) {
p = buffer + 1;
addr = hex2i(&p);
if (*p) {
kgdb_send("E0B");
continue;
}
PC_REGS(regs) = addr;
}
db_set_single_step(regs);
goto out;
}
}
out:
if (db_trap_callback)
db_trap_callback(0);
kgdb_recover = 0;
return (1);
}
int
kgdb_disconnected(void)
{
return 1;
}