#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: db_interface.c,v 1.98 2023/10/26 10:41:03 andvar Exp $");
#ifdef _KERNEL_OPT
#include "opt_ddb.h"
#include "opt_kgdb.h"
#include "opt_multiprocessor.h"
#endif
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/cpu.h>
#include <sys/reboot.h>
#include <sys/systm.h>
#include <sys/lwp.h>
#include <dev/cons.h>
#include <uvm/uvm.h>
#include <machine/db_machdep.h>
#include <machine/locore.h>
#include <ddb/db_access.h>
#include <ddb/db_active.h>
#include <ddb/ddbvar.h>
#if defined(DDB) || defined(_KMEMUSER)
#include <ddb/db_user.h>
#include <ddb/db_command.h>
#include <ddb/db_sym.h>
#include <ddb/db_variables.h>
#include <ddb/db_extern.h>
#include <ddb/db_output.h>
#include <ddb/db_interface.h>
#endif
#ifdef KGDB
#include <ddb/db_interface.h>
#endif
#include <machine/instr.h>
#if defined(_KERNEL)
#include <machine/promlib.h>
#endif
#include <machine/ctlreg.h>
#include <machine/pmap.h>
#if defined(_KERNEL)
#include <sparc/sparc/asm.h>
#include "fb.h"
void
db_read_bytes(vaddr_t addr, size_t size, char *data)
{
char *src;
src = (char *)addr;
while (size-- > 0)
*data++ = *src++;
}
void
db_write_bytes(vaddr_t addr, size_t size, const char *data)
{
char *dst;
dst = (char *)addr;
while (size-- > 0) {
if ((dst >= (char *)VM_MIN_KERNEL_ADDRESS) && (dst < etext))
pmap_writetext(dst, *data);
else
*dst = *data;
dst++, data++;
}
}
#endif
#if defined(DDB)
void
cpu_Debugger(void)
{
__asm("ta 0x81");
sparc_noop();
}
#endif
#if defined(DDB) || defined(_KMEMUSER)
int db_active = 0;
#ifdef _KERNEL
void kdb_kbd_trap(struct trapframe *);
void db_prom_cmd(db_expr_t, bool, db_expr_t, const char *);
void db_page_cmd(db_expr_t, bool, db_expr_t, const char *);
void db_proc_cmd(db_expr_t, bool, db_expr_t, const char *);
void db_dump_pcb(db_expr_t, bool, db_expr_t, const char *);
#endif
#ifdef MULTIPROCESSOR
void db_cpu_cmd(db_expr_t, bool, db_expr_t, const char *);
void db_xcall_cmd(db_expr_t, bool, db_expr_t, const char *);
#endif
#ifdef _KERNEL
void
kdb_kbd_trap(struct trapframe *tf)
{
if (db_active == 0 && (boothowto & RB_KDB)) {
printf("\n\nkernel: keyboard interrupt\n");
kdb_trap(-1, tf);
}
}
#endif
struct cpu_info *ddb_cpuinfo;
#ifdef MULTIPROCESSOR
#define NOCPU -1
static int db_suspend_others(void);
static void db_resume_others(void);
void ddb_suspend(struct trapframe *);
void mp_pause_cpus_ddb(void);
void mp_resume_cpus_ddb(void);
__cpu_simple_lock_t db_lock;
int ddb_cpu = NOCPU;
static int
db_suspend_others(void)
{
int cpu_me = cpu_number();
int win;
__cpu_simple_lock(&db_lock);
if (ddb_cpu == NOCPU)
ddb_cpu = cpu_me;
win = (ddb_cpu == cpu_me);
__cpu_simple_unlock(&db_lock);
if (win)
mp_pause_cpus_ddb();
return win;
}
static void
db_resume_others(void)
{
mp_resume_cpus_ddb();
__cpu_simple_lock(&db_lock);
ddb_cpu = NOCPU;
__cpu_simple_unlock(&db_lock);
}
void
ddb_suspend(struct trapframe *tf)
{
volatile db_regs_t dbregs;
dbregs.db_tf = *tf;
dbregs.db_fr = *(struct frame *)tf->tf_out[6];
cpuinfo.ci_ddb_regs = &dbregs;
while (cpuinfo.flags & CPUFLG_PAUSED) ;
cpuinfo.ci_ddb_regs = NULL;
}
#endif
#if defined(DDB) || defined(KGDB)
int
kdb_trap(int type, struct trapframe *tf)
{
db_regs_t dbregs;
int s;
#if NFB > 0
fb_unblank();
#endif
switch (type) {
case T_BREAKPOINT:
case -1:
break;
default:
if (!db_onpanic && db_recover==0)
return (0);
printf("kernel: %s trap\n", trap_type[type & 0xff]);
if (db_recover != 0) {
db_error("Faulted in DDB; continuing...\n");
}
}
#ifdef MULTIPROCESSOR
if (!db_suspend_others()) {
ddb_suspend(tf);
return 1;
}
#endif
dbregs.db_tf = *tf;
dbregs.db_fr = *(struct frame *)tf->tf_out[6];
ddb_cpuinfo = curcpu();
curcpu()->ci_ddb_regs = ddb_regp = &dbregs;
s = splhigh();
db_active++;
cnpollc(true);
db_trap(type, 0);
cnpollc(false);
db_active--;
splx(s);
*(struct frame *)tf->tf_out[6] = dbregs.db_fr;
*tf = dbregs.db_tf;
curcpu()->ci_ddb_regs = ddb_regp = 0;
ddb_cpuinfo = NULL;
#ifdef MULTIPROCESSOR
db_resume_others();
#endif
return (1);
}
#endif
#ifdef _KERNEL
void
db_proc_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif)
{
struct lwp *l;
struct proc *p;
l = curlwp;
if (have_addr)
l = (struct lwp *) addr;
if (l == NULL) {
db_printf("no current process\n");
return;
}
p = l->l_proc;
db_printf("LWP %p: ", l);
db_printf("PID:%d.%d CPU:%d stat:%d vmspace:%p", p->p_pid,
l->l_lid, l->l_cpu->ci_cpuid, l->l_stat, p->p_vmspace);
if (!P_ZOMBIE(p))
db_printf(" ctx: %p cpuset %x",
p->p_vmspace->vm_map.pmap->pm_ctx,
p->p_vmspace->vm_map.pmap->pm_cpuset);
db_printf("\npmap:%p wchan:%p pri:%d epri:%d\n",
p->p_vmspace->vm_map.pmap,
l->l_wchan, l->l_priority, lwp_eprio(l));
db_printf("maxsaddr:%p ssiz:%d pg or %llxB\n",
p->p_vmspace->vm_maxsaddr, p->p_vmspace->vm_ssize,
(unsigned long long)ctob(p->p_vmspace->vm_ssize));
db_printf("profile timer: %lld sec %ld nsec\n",
p->p_stats->p_timer[ITIMER_PROF].it_value.tv_sec,
p->p_stats->p_timer[ITIMER_PROF].it_value.tv_nsec);
db_printf("pcb: %p\n", lwp_getpcb(l));
return;
}
void
db_dump_pcb(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif)
{
struct pcb *pcb;
char bits[64];
int i;
if (have_addr)
pcb = (struct pcb *) addr;
else
pcb = curcpu()->curpcb;
snprintb(bits, sizeof(bits), PSR_BITS, pcb->pcb_psr);
db_printf("pcb@%p sp:%p pc:%p psr:%s onfault:%p\nfull windows:\n",
pcb, (void *)(long)pcb->pcb_sp, (void *)(long)pcb->pcb_pc,
bits, (void *)pcb->pcb_onfault);
for (i=0; i<pcb->pcb_nsaved; i++) {
db_printf("win %d: at %llx local, in\n", i,
(unsigned long long)pcb->pcb_rw[i+1].rw_in[6]);
db_printf("%16llx %16llx %16llx %16llx\n",
(unsigned long long)pcb->pcb_rw[i].rw_local[0],
(unsigned long long)pcb->pcb_rw[i].rw_local[1],
(unsigned long long)pcb->pcb_rw[i].rw_local[2],
(unsigned long long)pcb->pcb_rw[i].rw_local[3]);
db_printf("%16llx %16llx %16llx %16llx\n",
(unsigned long long)pcb->pcb_rw[i].rw_local[4],
(unsigned long long)pcb->pcb_rw[i].rw_local[5],
(unsigned long long)pcb->pcb_rw[i].rw_local[6],
(unsigned long long)pcb->pcb_rw[i].rw_local[7]);
db_printf("%16llx %16llx %16llx %16llx\n",
(unsigned long long)pcb->pcb_rw[i].rw_in[0],
(unsigned long long)pcb->pcb_rw[i].rw_in[1],
(unsigned long long)pcb->pcb_rw[i].rw_in[2],
(unsigned long long)pcb->pcb_rw[i].rw_in[3]);
db_printf("%16llx %16llx %16llx %16llx\n",
(unsigned long long)pcb->pcb_rw[i].rw_in[4],
(unsigned long long)pcb->pcb_rw[i].rw_in[5],
(unsigned long long)pcb->pcb_rw[i].rw_in[6],
(unsigned long long)pcb->pcb_rw[i].rw_in[7]);
}
}
void
db_prom_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif)
{
prom_abort();
}
void
db_page_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif)
{
if (!have_addr) {
db_printf("Need paddr for page\n");
return;
}
db_printf("pa %llx pg %p\n", (unsigned long long)addr,
PHYS_TO_VM_PAGE(addr));
}
#endif
#if defined(MULTIPROCESSOR)
void
db_cpu_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif)
{
struct cpu_info *ci;
if (!have_addr) {
cpu_debug_dump();
return;
}
if ((addr < 0) || (addr >= sparc_ncpus)) {
db_printf("%ld: CPU out of range\n", addr);
return;
}
ci = cpus[addr];
if (ci == NULL) {
db_printf("CPU %ld not configured\n", addr);
return;
}
if (ci != curcpu()) {
if (!(ci->flags & CPUFLG_PAUSED)) {
db_printf("CPU %ld not paused\n", addr);
return;
}
}
if (ci->ci_ddb_regs == 0) {
db_printf("CPU %ld has no saved regs\n", addr);
return;
}
db_printf("using CPU %ld", addr);
ddb_regp = __UNVOLATILE(ci->ci_ddb_regs);
ddb_cpuinfo = ci;
}
void
db_xcall_cmd(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif)
{
cpu_xcall_dump();
}
#endif
const struct db_command db_machine_command_table[] = {
#ifdef _KERNEL
{ DDB_ADD_CMD("prom", db_prom_cmd, 0,
"Enter the Sun PROM monitor.",NULL,NULL) },
{ DDB_ADD_CMD("page", db_page_cmd, 0,
"Display the address of a struct vm_page given a physical address",
"pa", " pa:\tphysical address to look up") },
{ DDB_ADD_CMD("proc", db_proc_cmd, 0,
"Display some information about an LWP",
"[addr]"," addr:\tstruct lwp address (curlwp otherwise)") },
{ DDB_ADD_CMD("pcb", db_dump_pcb, 0,
"Display information about a struct pcb",
"[address]",
" address:\tthe struct pcb to print (curpcb otherwise)") },
#endif
#ifdef MULTIPROCESSOR
{ DDB_ADD_CMD("cpu", db_cpu_cmd, 0,
"switch to another cpu's registers", "cpu-no", NULL) },
{ DDB_ADD_CMD("xcall", db_xcall_cmd, 0,
"show xcall information on all cpus", NULL, NULL) },
#endif
{ DDB_END_CMD },
};
#endif
db_addr_t
db_branch_taken(int inst, db_addr_t pc, db_regs_t *regs)
{
union instr insn;
db_addr_t npc = ddb_regp->db_tf.tf_npc;
insn.i_int = inst;
if (insn.i_any.i_op != IOP_OP2 || insn.i_branch.i_annul != 1)
return npc;
switch (insn.i_op2.i_op2) {
case IOP2_Bicc:
case IOP2_FBfcc:
case IOP2_BPcc:
case IOP2_FBPfcc:
case IOP2_CBccc:
switch (insn.i_branch.i_cond)
{
case Icc_A:
return pc + ((inst << 10) >> 8);
default:
return npc + 4;
}
case IOP2_BPr:
return npc + 4;
default:
#ifdef _KERNEL
panic("branch_taken() on non-branch");
#else
printf("branch_taken() on non-branch\n");
return 0;
#endif
}
}
bool
db_inst_branch(int inst)
{
union instr insn;
insn.i_int = inst;
if (insn.i_any.i_op != IOP_OP2)
return false;
switch (insn.i_op2.i_op2) {
case IOP2_BPcc:
case IOP2_Bicc:
case IOP2_BPr:
case IOP2_FBPfcc:
case IOP2_FBfcc:
case IOP2_CBccc:
return true;
default:
return false;
}
}
bool
db_inst_call(int inst)
{
union instr insn;
insn.i_int = inst;
switch (insn.i_any.i_op) {
case IOP_CALL:
return true;
case IOP_reg:
return (insn.i_op3.i_op3 == IOP3_JMPL) && !db_inst_return(inst);
default:
return false;
}
}
bool
db_inst_unconditional_flow_transfer(int inst)
{
union instr insn;
insn.i_int = inst;
if (db_inst_call(inst))
return true;
if (insn.i_any.i_op != IOP_OP2)
return false;
switch (insn.i_op2.i_op2)
{
case IOP2_BPcc:
case IOP2_Bicc:
case IOP2_FBPfcc:
case IOP2_FBfcc:
case IOP2_CBccc:
return insn.i_branch.i_cond == Icc_A;
default:
return false;
}
}
bool
db_inst_return(int inst)
{
return (inst == I_JMPLri(I_G0, I_O7, 8) ||
inst == I_JMPLri(I_G0, I_I7, 8));
}
bool
db_inst_trap_return(int inst)
{
union instr insn;
insn.i_int = inst;
return (insn.i_any.i_op == IOP_reg &&
insn.i_op3.i_op3 == IOP3_RETT);
}
int
db_inst_load(int inst)
{
union instr insn;
insn.i_int = inst;
if (insn.i_any.i_op != IOP_mem)
return 0;
switch (insn.i_op3.i_op3) {
case IOP3_LD:
case IOP3_LDUB:
case IOP3_LDUH:
case IOP3_LDD:
case IOP3_LDSB:
case IOP3_LDSH:
case IOP3_LDSTUB:
case IOP3_SWAP:
case IOP3_LDA:
case IOP3_LDUBA:
case IOP3_LDUHA:
case IOP3_LDDA:
case IOP3_LDSBA:
case IOP3_LDSHA:
case IOP3_LDSTUBA:
case IOP3_SWAPA:
case IOP3_LDF:
case IOP3_LDFSR:
case IOP3_LDDF:
case IOP3_LFC:
case IOP3_LDCSR:
case IOP3_LDDC:
return 1;
default:
return 0;
}
}
int
db_inst_store(int inst)
{
union instr insn;
insn.i_int = inst;
if (insn.i_any.i_op != IOP_mem)
return 0;
switch (insn.i_op3.i_op3) {
case IOP3_ST:
case IOP3_STB:
case IOP3_STH:
case IOP3_STD:
case IOP3_LDSTUB:
case IOP3_SWAP:
case IOP3_STA:
case IOP3_STBA:
case IOP3_STHA:
case IOP3_STDA:
case IOP3_LDSTUBA:
case IOP3_SWAPA:
case IOP3_STF:
case IOP3_STFSR:
case IOP3_STDFQ:
case IOP3_STDF:
case IOP3_STC:
case IOP3_STCSR:
case IOP3_STDCQ:
case IOP3_STDC:
return 1;
default:
return 0;
}
}