#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: mips_stacktrace.c,v 1.9 2021/04/06 13:11:22 simonb Exp $");
#ifdef _KERNEL_OPT
#include "opt_ddb.h"
#include "opt_kgdb.h"
#endif
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <mips/locore.h>
#include <mips/mips_opcode.h>
#include <mips/regnum.h>
#include <mips/stacktrace.h>
#if defined(_KMEMUSER) && !defined(DDB)
#define DDB 1
#endif
#ifdef DDB
#include <machine/db_machdep.h>
#include <ddb/db_sym.h>
#include <ddb/db_user.h>
#include <ddb/db_access.h>
#endif
#ifdef KGDB
#include <sys/kgdb.h>
#endif
#ifndef DDB_TRACE
#if defined(DEBUG) || defined(DDB) || defined(KGDB) || defined(geo)
extern char start[], edata[], verylocore[];
#ifdef MIPS1
extern char mips1_kern_gen_exception[];
extern char mips1_user_gen_exception[];
extern char mips1_kern_intr[];
extern char mips1_user_intr[];
extern char mips1_systemcall[];
#endif
#ifdef MIPS3
extern char mips3_kern_gen_exception[];
extern char mips3_user_gen_exception[];
extern char mips3_kern_intr[];
extern char mips3_user_intr[];
extern char mips3_systemcall[];
#endif
#ifdef MIPS32
extern char mips32_kern_gen_exception[];
extern char mips32_user_gen_exception[];
extern char mips32_kern_intr[];
extern char mips32_user_intr[];
extern char mips32_systemcall[];
#endif
#ifdef MIPS32R2
extern char mips32r2_kern_gen_exception[];
extern char mips32r2_user_gen_exception[];
extern char mips32r2_kern_intr[];
extern char mips32r2_user_intr[];
extern char mips32r2_systemcall[];
#endif
#ifdef MIPS64
extern char mips64_kern_gen_exception[];
extern char mips64_user_gen_exception[];
extern char mips64_kern_intr[];
extern char mips64_user_intr[];
extern char mips64_systemcall[];
#endif
#ifdef MIPS64R2
extern char mips64r2_kern_gen_exception[];
extern char mips64r2_user_gen_exception[];
extern char mips64r2_kern_intr[];
extern char mips64r2_user_intr[];
extern char mips64r2_systemcall[];
#endif
#define MIPS_JR_RA 0x03e00008
#define MIPS_JR_K0 0x03400008
#define MIPS_ERET 0x42000018
int main(void *);
#define Name(_fn) { (void*)_fn, # _fn }
static const struct { void *addr; const char *name;} names[] = {
#ifdef _KERNEL
Name(stacktrace),
Name(stacktrace_subr),
Name(main),
Name(trap),
#ifdef MIPS1
Name(mips1_kern_gen_exception),
Name(mips1_user_gen_exception),
Name(mips1_systemcall),
Name(mips1_kern_intr),
Name(mips1_user_intr),
#endif
#if defined(MIPS3)
Name(mips3_kern_gen_exception),
Name(mips3_user_gen_exception),
Name(mips3_systemcall),
Name(mips3_kern_intr),
Name(mips3_user_intr),
#endif
#if defined(MIPS32)
Name(mips32_kern_gen_exception),
Name(mips32_user_gen_exception),
Name(mips32_systemcall),
Name(mips32_kern_intr),
Name(mips32_user_intr),
#endif
#if defined(MIPS32R2)
Name(mips32r2_kern_gen_exception),
Name(mips32r2_user_gen_exception),
Name(mips32r2_systemcall),
Name(mips32r2_kern_intr),
Name(mips32r2_user_intr),
#endif
#if defined(MIPS64)
Name(mips64_kern_gen_exception),
Name(mips64_user_gen_exception),
Name(mips64_systemcall),
Name(mips64_kern_intr),
Name(mips64_user_intr),
#endif
#if defined(MIPS64R2)
Name(mips64r2_kern_gen_exception),
Name(mips64r2_user_gen_exception),
Name(mips64r2_systemcall),
Name(mips64r2_kern_intr),
Name(mips64r2_user_intr),
#endif
Name(cpu_idle),
Name(cpu_switchto),
#endif
{0, 0}
};
bool
kdbpeek(vaddr_t addr, unsigned *valp)
{
if (addr & 3) {
printf("kdbpeek: unaligned address %#"PRIxVADDR"\n", addr);
return false;
} else if (addr == 0) {
printf("kdbpeek: NULL\n");
return false;
} else {
#if _KERNEL
*valp = *(unsigned *)addr;
#else
db_read_bytes((db_addr_t)addr, sizeof(unsigned), (char *)valp);
#endif
return true;
}
}
mips_reg_t
kdbrpeek(vaddr_t addr, size_t n)
{
mips_reg_t rc = 0;
if (addr & (n - 1)) {
printf("kdbrpeek: unaligned address %#"PRIxVADDR"\n", addr);
#if _KERNEL
stacktrace();
#endif
rc = -1;
} else if (addr == 0) {
printf("kdbrpeek: NULL\n");
rc = 0xdeadfeed;
} else {
if (sizeof(mips_reg_t) == 8 && n == 8)
#if _KERNEL
rc = *(int64_t *)addr;
else
rc = *(int32_t *)addr;
#else
db_read_bytes((db_addr_t)addr, sizeof(int64_t), (char *)&rc);
else
db_read_bytes((db_addr_t)addr, sizeof(int32_t), (char *)&rc);
#endif
}
return rc;
}
static const char *
fn_name(vaddr_t addr)
{
static char buf[17];
int i = 0;
#ifdef DDB
db_expr_t diff;
db_sym_t sym;
const char *symname;
#endif
#ifdef DDB
diff = 0;
symname = NULL;
sym = db_search_symbol(addr, DB_STGY_ANY, &diff);
db_symbol_values(sym, &symname, 0);
if (symname && diff == 0)
return (symname);
#endif
for (i = 0; names[i].name; i++)
if (names[i].addr == (void*)addr)
return (names[i].name);
snprintf(buf, sizeof(buf), "%#"PRIxVADDR, addr);
return (buf);
}
void
stacktrace_subr(mips_reg_t a0, mips_reg_t a1, mips_reg_t a2, mips_reg_t a3,
vaddr_t pc, vaddr_t sp, vaddr_t fp, vaddr_t ra,
void (*printfn)(const char*, ...))
{
vaddr_t va, subr;
unsigned instr, mask;
InstFmt i;
int more, stksize;
unsigned int frames = 0;
int foundframesize = 0;
mips_reg_t regs[32] = {
[_R_ZERO] = 0,
[_R_A0] = a0, [_R_A1] = a1, [_R_A2] = a2, [_R_A3] = a3,
[_R_RA] = ra,
};
loop:
stksize = 0;
subr = 0;
mask = 1;
if (frames++ > 100) {
(*printfn)("\nstackframe count exceeded\n");
goto finish;
}
if ((sp & (sizeof(sp)-1)) || (intptr_t)sp >= 0) {
(*printfn)("SP 0x%x: not in kernel\n", sp);
ra = 0;
subr = 0;
goto done;
}
if (pc & 3 || (intptr_t)pc >= 0 || (intptr_t)pc >= (intptr_t)edata) {
(*printfn)("PC 0x%x: not in kernel space\n", pc);
ra = 0;
goto done;
}
#if defined(DDB) && defined(_KERNEL)
if (ksyms_available()) {
db_expr_t diff;
db_sym_t sym;
diff = 0;
sym = db_search_symbol(pc, DB_STGY_ANY, &diff);
if (sym != DB_SYM_NULL && diff == 0) {
if (!kdbpeek(pc - 2 * sizeof(unsigned), &instr))
return;
i.word = instr;
if (i.JType.op == OP_JAL) {
sym = db_search_symbol(pc - sizeof(int),
DB_STGY_ANY, &diff);
if (sym != DB_SYM_NULL && diff != 0)
diff += sizeof(int);
}
}
if (sym == DB_SYM_NULL) {
ra = 0;
goto done;
}
va = pc - diff;
} else {
#endif
va = pc;
do {
va -= sizeof(int);
#ifdef _KERNEL
if (va <= (vaddr_t)verylocore)
goto finish;
#endif
if (!kdbpeek(va, &instr))
return;
if (instr == MIPS_ERET)
goto mips3_eret;
} while (instr != MIPS_JR_RA && instr != MIPS_JR_K0);
va += sizeof(int);
mips3_eret:
va += sizeof(int);
instr = 0;
while (instr == 0) {
if (!kdbpeek(va, &instr))
return;
va += sizeof(int);
}
#if defined(DDB) && defined(_KERNEL)
}
#endif
subr = va;
stksize = 0;
more = 3;
mask &= 0x40ff0001;
foundframesize = 0;
for (va = subr; more; va += sizeof(int),
more = (more == 3) ? 3 : more - 1) {
if (va >= pc)
break;
if (!kdbpeek(va, &instr))
return;
i.word = instr;
switch (i.JType.op) {
case OP_SPECIAL:
switch (i.RType.func) {
case OP_JR:
case OP_JALR:
more = 2;
break;
case OP_ADD:
case OP_ADDU:
case OP_DADD:
case OP_DADDU:
if (!(mask & (1 << i.RType.rd))
|| !(mask & (1 << i.RType.rt)))
break;
if (i.RType.rd != _R_ZERO)
break;
mask |= (1 << i.RType.rs);
regs[i.RType.rs] = regs[i.RType.rt];
if (i.RType.func >= OP_DADD)
break;
regs[i.RType.rs] = (int32_t)regs[i.RType.rs];
break;
case OP_SYSCALL:
case OP_BREAK:
more = 1;
break;
}
break;
case OP_REGIMM:
case OP_J:
case OP_JAL:
case OP_BEQ:
case OP_BNE:
case OP_BLEZ:
case OP_BGTZ:
more = 2;
break;
case OP_COP0:
case OP_COP1:
case OP_COP2:
case OP_COP3:
switch (i.RType.rs) {
case OP_BCx:
case OP_BCy:
more = 2;
};
break;
case OP_SW:
#if !defined(__mips_o32)
case OP_SD:
#endif
{
size_t size = (i.JType.op == OP_SW) ? 4 : 8;
if (i.IType.rs != _R_SP)
break;
switch (i.IType.rt) {
case _R_A0:
case _R_A1:
case _R_A2:
case _R_A3:
case _R_S0:
case _R_S1:
case _R_S2:
case _R_S3:
case _R_S4:
case _R_S5:
case _R_S6:
case _R_S7:
case _R_S8:
case _R_RA:
regs[i.IType.rt] =
kdbrpeek(sp + (int16_t)i.IType.imm, size);
mask |= (1 << i.IType.rt);
break;
}
break;
}
case OP_ADDI:
case OP_ADDIU:
#if !defined(__mips_o32)
case OP_DADDI:
case OP_DADDIU:
#endif
if (i.IType.rs != _R_SP || i.IType.rt != _R_SP)
break;
if (!foundframesize) {
stksize = - ((short)i.IType.imm);
foundframesize = 1;
}
break;
}
}
done:
if (mask & (1 << _R_RA))
ra = regs[_R_RA];
(*printfn)("%#"PRIxVADDR": %s+%#"PRIxVADDR" (%#"PRIxREGISTER","
"%#"PRIxREGISTER",%#"PRIxREGISTER",%#"PRIxREGISTER") "
"ra %#"PRIxVADDR" sz %d\n",
sp, fn_name(subr), pc - subr,
regs[_R_A0], regs[_R_A1], regs[_R_A2], regs[_R_A3],
ra, stksize);
if (ra) {
if (pc == ra && stksize == 0)
(*printfn)("stacktrace: loop!\n");
else {
pc = ra;
sp += stksize;
ra = 0;
goto loop;
}
} else {
finish:
#ifdef _KERNEL
(*printfn)("User-level: pid %d.%d\n",
curlwp->l_proc->p_pid, curlwp->l_lid);
#else
(*printfn)("User-level: FIXME\n");
#endif
}
}
#endif
#endif