#include <sys/param.h>
#include <sys/systm.h>
#include <machine/cpu.h>
#include <machine/db_machdep.h>
#include <ddb/db_variables.h>
#include <ddb/db_output.h>
#include <ddb/db_sym.h>
#include <ddb/db_command.h>
#include <ddb/db_access.h>
#include <ddb/db_interface.h>
#ifdef DEBUG
#define DPRINTF(stmt) printf stmt
#else
#define DPRINTF(stmt) do { } while (0)
#endif
static inline u_int
br_dest(vaddr_t addr, u_int inst)
{
inst = (inst & 0x03ffffff) << 2;
if (inst & 0x08000000)
inst |= 0xf0000000;
return (addr + inst);
}
int frame_is_sane(db_regs_t *regs, int);
const char *m88k_exception_name(u_int vector);
u_int db_trace_get_val(vaddr_t addr, u_int *ptr);
#define JMPN_R1(I) ((I) == 0xf400c401)
#define JMP_R1(I) ((I) == 0xf400c001)
#define IMM16VAL(I) ((I) & 0x0000ffff)
#define SUBU_R31_R31_IMM(I) (((I) & 0xffff0000) == 0x67ff0000U)
#define ST_R30_R31_IMM(I) (((I) & 0xffff0000) == 0x27df0000U)
extern label_t *db_recover;
#define N(s, x) {s, (long *)&ddb_regs.x, FCN_NULL}
struct db_variable db_regs[] = {
N("r1", r[1]), N("r2", r[2]), N("r3", r[3]), N("r4", r[4]),
N("r5", r[5]), N("r6", r[6]), N("r7", r[7]), N("r8", r[8]),
N("r9", r[9]), N("r10", r[10]), N("r11", r[11]), N("r12", r[12]),
N("r13", r[13]), N("r14", r[14]), N("r15", r[15]), N("r16", r[16]),
N("r17", r[17]), N("r18", r[18]), N("r19", r[19]), N("r20", r[20]),
N("r21", r[21]), N("r22", r[22]), N("r23", r[23]), N("r24", r[24]),
N("r25", r[25]), N("r26", r[26]), N("r27", r[27]), N("r28", r[28]),
N("r29", r[29]), N("r30", r[30]), N("r31", r[31]), N("epsr", epsr),
N("sxip", sxip), N("snip", snip), N("sfip", sfip), N("ssbr", ssbr),
N("dmt0", dmt0), N("dmd0", dmd0), N("dma0", dma0), N("dmt1", dmt1),
N("dmd1", dmd1), N("dma1", dma1), N("dmt2", dmt2), N("dmd2", dmd2),
N("dma2", dma2), N("fpecr", fpecr),N("fphs1", fphs1),N("fpls1", fpls1),
N("fphs2", fphs2), N("fpls2", fpls2),N("fppt", fppt), N("fprh", fprh),
N("fprl", fprl), N("fpit", fpit), N("fpsr", fpsr), N("fpcr", fpcr),
};
#undef N
struct db_variable *db_eregs = db_regs + nitems(db_regs);
#define TRASHES 0x001
#define STORE 0x002
#define LOAD 0x004
#define DOUBLE 0x008
#define FLOW_CTRL 0x010
#define DELAYED 0x020
#define JSR 0x040
#define BSR 0x080
static u_int
m88k_instruction_info(u_int32_t instruction)
{
static const struct {
u_int32_t mask, value;
u_int flags;
} *ptr, control[] = {
{ 0xf0000000U, 0x00000000U, TRASHES | STORE | LOAD},
{ 0xec000000U, 0x00000000U, TRASHES | LOAD | DOUBLE},
{ 0xe0000000U, 0x00000000U, TRASHES | LOAD},
{ 0xfc000000U, 0x20000000U, STORE | DOUBLE},
{ 0xf0000000U, 0x20000000U, STORE},
{ 0xc0000000U, 0x40000000U, TRASHES},
{ 0xfc004000U, 0x80004000U, TRASHES},
{ 0xfc004000U, 0x80000000U, 0},
{ 0xfc008060U, 0x84000000U, TRASHES},
{ 0xfc008060U, 0x84000020U, TRASHES | DOUBLE},
{ 0xfc000000U, 0xcc000000U, FLOW_CTRL | DELAYED | BSR},
{ 0xfc000000U, 0xc8000000U, FLOW_CTRL | BSR},
{ 0xe4000000U, 0xc4000000U, FLOW_CTRL | DELAYED},
{ 0xe4000000U, 0xc0000000U, FLOW_CTRL},
{ 0xfc000000U, 0xec000000U, FLOW_CTRL | DELAYED},
{ 0xfc000000U, 0xe8000000U, FLOW_CTRL},
{ 0xfc00c000U, 0xf0008000U, TRASHES},
{ 0xfc00c000U, 0xf000c000U, 0},
{ 0xfc00f0e0U, 0xf4002000U, 0},
{ 0xfc00cce0U, 0xf4000000U, TRASHES | DOUBLE},
{ 0xfc00c0e0U, 0xf4000000U, TRASHES},
{ 0xfc00c0e0U, 0xf4004000U, TRASHES},
{ 0xfc00c3e0U, 0xf4008000U, TRASHES},
{ 0xfc00ffe0U, 0xf400cc00U, FLOW_CTRL | DELAYED | JSR},
{ 0xfc00ffe0U, 0xf400c800U, FLOW_CTRL | JSR},
{ 0xfc00ffe0U, 0xf400c400U, FLOW_CTRL | DELAYED},
{ 0xfc00ffe0U, 0xf400c000U, FLOW_CTRL},
{ 0xfc00fbe0U, 0xf400e800U, TRASHES},
{ 0xfc00ffe0U, 0xf400f800U, 0},
{ 0xfc00ffe0U, 0xf400fc00U, FLOW_CTRL},
{ 0xfc000000U, 0xf8000000U, 0},
};
for (ptr = &control[0]; ptr < &control[nitems(control)]; ptr++)
if ((instruction & ptr->mask) == ptr->value)
return ptr->flags;
return 0;
}
static int
hex_value_needs_0x(u_int value)
{
int c;
int have_a_hex_digit = 0;
if (value <= 9)
return (0);
while (value != 0) {
c = value & 0xf;
value >>= 4;
if (c > 9)
have_a_hex_digit = 1;
}
if (have_a_hex_digit == 0)
return (1);
if (c > 9)
return (1);
return (0);
}
int
frame_is_sane(db_regs_t *regs, int quiet)
{
if (badaddr((vaddr_t)regs, 4) || badaddr((vaddr_t)®s->fpit, 4)) {
if (quiet == 0)
db_printf("[WARNING: frame at %p : unreadable]\n", regs);
return 0;
}
if (regs->r[0] != 0) {
if (quiet == 0)
db_printf("[WARNING: frame at %p : r[0] != 0]\n", regs);
return 0;
}
if (regs->r[31] == 0 || (regs->r[31] & 3) != 0) {
if (quiet == 0)
db_printf("[WARNING: frame at %p : r[31] == 0 or not word aligned]\n", regs);
return 0;
}
#ifdef M88100
if (CPU_IS88100) {
#if 0
if ((regs->sxip & XIP_E) != 0)
goto out;
#endif
if ((regs->snip & ~NIP_ADDR) != NIP_V)
goto out;
if ((regs->sfip & ~FIP_ADDR) != FIP_V)
goto out;
}
#endif
if (regs->epsr & PSR_BO)
goto out;
return ((regs->epsr & PSR_MODE) ? 1 : 2);
out:
if (quiet == 0)
db_printf("[WARNING: not an exception frame?]\n");
return (0);
}
const char *
m88k_exception_name(u_int vector)
{
switch (vector) {
default:
case 0: return "Reset";
case 1: return "Interrupt";
case 2: return "Instruction Access Exception";
case 3: return "Data Access Exception";
case 4: return "Misaligned Access Exception";
case 5: return "Unimplemented Opcode Exception";
case 6: return "Privilege Violation";
case 7: return "Bounds Check";
case 8: return "Integer Divide Exception";
case 9: return "Integer Overflow Exception";
case 10: return "Error Exception";
case 11: return "Non Maskable Interrupt";
case 114: return "FPU precise";
case 115: return "FPU imprecise";
case DDB_ENTRY_BKPT_NO:
return "ddb break";
case DDB_ENTRY_TRACE_NO:
return "ddb trace";
case DDB_ENTRY_TRAP_NO:
return "ddb trap";
case 451: return "Syscall";
}
}
u_int
db_trace_get_val(vaddr_t addr, u_int *ptr)
{
label_t db_jmpbuf;
label_t *prev = db_recover;
if (setjmp((db_recover = &db_jmpbuf)) != 0) {
db_recover = prev;
return 0;
} else {
db_read_bytes(addr, 4, (char *)ptr);
db_recover = prev;
return 1;
}
}
#define FIRST_CALLPRESERVED_REG 14
#define LAST_CALLPRESERVED_REG 29
#define FIRST_ARG_REG 2
#define LAST_ARG_REG 9
#define RETURN_VAL_REG 1
static u_int global_saved_list = 0x0;
static u_int local_saved_list = 0x0;
static u_int trashed_list = 0x0;
static u_int saved_reg[32];
#define reg_bit(reg) 1 << (reg)
static void
save_reg(int reg, u_int value)
{
reg &= 0x1f;
if (trashed_list & reg_bit(reg))
return;
saved_reg[reg] = value;
global_saved_list |= reg_bit(reg);
local_saved_list |= reg_bit(reg);
}
#define mark_reg_trashed(reg) trashed_list |= reg_bit((reg) & 0x1f)
#define have_global_reg(reg) (global_saved_list & reg_bit(reg))
#define have_local_reg(reg) (local_saved_list & reg_bit(reg))
#define clear_local_saved_regs() local_saved_list = trashed_list = 0
#define clear_global_saved_regs() local_saved_list = global_saved_list = 0
#define saved_reg_value(reg) saved_reg[(reg)]
static void
print_args(void)
{
int reg, last_arg;
for (last_arg = LAST_ARG_REG; last_arg >= FIRST_ARG_REG; last_arg--)
if (have_local_reg(last_arg))
break;
if (last_arg < FIRST_ARG_REG)
return;
db_printf("(");
for (reg = FIRST_ARG_REG; ; reg++) {
if (!have_local_reg(reg))
db_printf("?");
else {
u_int value = saved_reg_value(reg);
db_printf("%s%x", hex_value_needs_0x(value) ?
"0x" : "", value);
}
if (reg == last_arg)
break;
else
db_printf(", ");
}
db_printf(")");
}
#define JUMP_SOURCE_IS_BAD 0
#define JUMP_SOURCE_IS_OK 1
#define JUMP_SOURCE_IS_UNLIKELY 2
static int
is_jump_source_ok(vaddr_t return_to, vaddr_t jump_to)
{
u_int flags;
uint32_t instruction;
if (!db_trace_get_val(return_to - 8, &instruction))
return JUMP_SOURCE_IS_BAD;
flags = m88k_instruction_info(instruction);
if ((flags & (FLOW_CTRL | DELAYED)) == (FLOW_CTRL | DELAYED) &&
(flags & (JSR | BSR)) != 0) {
if ((flags & JSR) != 0)
return JUMP_SOURCE_IS_OK;
if (br_dest(return_to - 8, instruction) == jump_to)
return JUMP_SOURCE_IS_OK;
else
return JUMP_SOURCE_IS_UNLIKELY;
}
if (!db_trace_get_val(return_to - 4, &instruction))
return JUMP_SOURCE_IS_BAD;
flags = m88k_instruction_info(instruction);
if ((flags & (FLOW_CTRL | DELAYED)) == FLOW_CTRL &&
(flags & (JSR | BSR)) != 0) {
if ((flags & JSR) != 0)
return JUMP_SOURCE_IS_OK;
if (br_dest(return_to - 4, instruction) == jump_to)
return JUMP_SOURCE_IS_OK;
else
return JUMP_SOURCE_IS_UNLIKELY;
}
return JUMP_SOURCE_IS_UNLIKELY;
}
static int next_address_likely_wrong = 0;
#define FRAME_PLAY 8
static vaddr_t
stack_decode(vaddr_t addr, vaddr_t *stack, int (*pr)(const char *, ...))
{
Elf_Sym *proc;
db_expr_t offset_from_proc;
uint instructions_to_search;
vaddr_t check_addr;
vaddr_t function_addr;
uint32_t r31;
uint32_t inst;
vaddr_t ret_addr;
u_int tried_to_save_r1 = 0;
vaddr_t str30_addr = 0;
vaddr_t last_subu_addr = 0;
proc = db_search_symbol(addr, DB_STGY_PROC, &offset_from_proc);
if (offset_from_proc == addr)
proc = NULL;
if (proc != NULL) {
const char *names = NULL;
db_symbol_values(proc, &names, &function_addr);
if (names == NULL)
return 0;
} else {
instructions_to_search = 400;
for (check_addr = addr; instructions_to_search-- != 0;
check_addr -= 4) {
if (!db_trace_get_val(check_addr, &inst))
break;
if (ST_R30_R31_IMM(inst)) {
DPRINTF(("{st r30 found at %p}\n",
check_addr));
str30_addr = (vaddr_t)check_addr;
} else if (SUBU_R31_R31_IMM(inst)) {
DPRINTF(("{subu r31 found at %p}\n",
check_addr));
if (str30_addr == (vaddr_t)check_addr + 4)
break;
else
last_subu_addr = (vaddr_t)check_addr;
}
if (JMP_R1(inst) || JMPN_R1(inst)) {
if (last_subu_addr != 0) {
check_addr = last_subu_addr;
break;
} else
return 0;
}
}
if (instructions_to_search == 0)
return 0;
function_addr = check_addr;
}
DPRINTF(("{start of function at %p}\n", function_addr));
if (addr == function_addr)
return 0;
if (!db_trace_get_val(function_addr, &inst))
return 0;
if (!SUBU_R31_R31_IMM(inst))
return 0;
DPRINTF(("{orig r31 is %p}\n", *stack));
*stack += IMM16VAL(inst);
if (function_addr == (vaddr_t)&panic) {
check_addr = function_addr + 4;
instructions_to_search = (addr - check_addr) / 4;
while (instructions_to_search-- != 0) {
if (!db_trace_get_val(check_addr, &inst))
break;
if (SUBU_R31_R31_IMM(inst)) {
DPRINTF(("{variadic subu r31 found at %p}\n",
check_addr));
*stack += IMM16VAL(inst);
}
check_addr += 4;
}
}
clear_local_saved_regs();
check_addr = function_addr;
r31 = *stack;
DPRINTF(("{entry r31 would be %p}\n", r31));
for (instructions_to_search = (addr - check_addr) / sizeof(uint32_t);
instructions_to_search-- != 0; check_addr += 4) {
uint32_t s1, d;
uint flags;
if (!db_trace_get_val(check_addr, &inst))
break;
if (SUBU_R31_R31_IMM(inst)) {
r31 -= IMM16VAL(inst);
DPRINTF(("{adjust r31 to %p at %p}\n",
r31, check_addr));
}
flags = m88k_instruction_info(inst);
s1 = (inst >> 16) & 0x1f;
d = (inst >> 21) & 0x1f;
if ((flags & STORE) && s1 == 31 ) {
u_int value;
if (!have_local_reg(d)) {
if (d == 1)
tried_to_save_r1 = r31 + IMM16VAL(inst);
DPRINTF(("{r%d saved at %p to %p}\n",
d, check_addr, r31 + IMM16VAL(inst)));
if (db_trace_get_val(r31 + IMM16VAL(inst),
&value))
save_reg(d, value);
}
if ((flags & DOUBLE) && !have_local_reg(d + 1)) {
if (d == 0)
tried_to_save_r1 = r31 +
IMM16VAL(inst) + 4;
DPRINTF(("{r%d saved at %p to %p}\n",
d + 1, check_addr, r31 + IMM16VAL(inst)));
if (db_trace_get_val(r31 + IMM16VAL(inst) + 4,
&value))
save_reg(d + 1, value);
}
}
if (flags & TRASHES) {
mark_reg_trashed(d);
if (flags & DOUBLE)
mark_reg_trashed(d + 1);
}
if ((flags & FLOW_CTRL) && instructions_to_search != 0)
instructions_to_search = (flags & DELAYED) ? 1 : 0;
}
if (!have_local_reg(1)) {
if (tried_to_save_r1 != 0) {
(*pr)(" <return value of next fcn unreadable in %08x>\n",
tried_to_save_r1);
}
return 0;
}
ret_addr = saved_reg_value(1);
if (ret_addr != 0) {
switch (is_jump_source_ok(ret_addr, function_addr)) {
case JUMP_SOURCE_IS_OK:
break;
case JUMP_SOURCE_IS_BAD:
return 0;
case JUMP_SOURCE_IS_UNLIKELY:
next_address_likely_wrong = 1;
break;
}
}
return ret_addr;
}
static void
db_stack_trace_cmd2(db_regs_t *regs, int (*pr)(const char *, ...))
{
vaddr_t stack;
u_int depth=1;
vaddr_t where;
u_int ft;
u_int pair[2];
int i;
if ((ft = frame_is_sane(regs, 1)) == 0) {
(*pr)("Register frame 0x%x is suspicious; skipping trace\n", regs);
return;
}
if (ft == 2)
return;
where = PC_REGS(regs);
stack = regs->r[31];
(*pr)("stack base = 0x%x\n", stack);
(*pr)("(0) ");
db_printsym(where, DB_STGY_PROC, pr);
clear_global_saved_regs();
if ((where = stack_decode(where, &stack, pr)) == 0) {
where = regs->r[1];
(*pr)("(stackless)");
} else
print_args();
(*pr)("\n");
do {
(*pr)("(%d)%c", depth++, next_address_likely_wrong ? '?' : ' ');
next_address_likely_wrong = 0;
db_printsym(where, DB_STGY_PROC, pr);
where = stack_decode(where, &stack, pr);
print_args();
(*pr)("\n");
} while (where);
stack &= ~7;
i = FRAME_PLAY;
while (i) {
if (badaddr((vaddr_t)stack, 4) ||
badaddr((vaddr_t)(stack + 4), 4))
break;
db_read_bytes((vaddr_t)stack, 2 * sizeof(int), (char *)pair);
if (pair[0] == pair[1]) {
if (pair[0] != stack+8) {
#if 0
if (!badaddr((vaddr_t)pair[0], 4) &&
pair[0] != 0)
(*pr)("stack_trace:found pair 0x%x but != to stack+8\n",
pair[0]);
#endif
} else if (frame_is_sane((db_regs_t*)pair[0], 1) != 0) {
struct trapframe *frame =
(struct trapframe *)pair[0];
(*pr)("-------------- %s [EF: 0x%x] -------------\n",
m88k_exception_name(frame->tf_vector),
frame);
db_stack_trace_cmd2(&frame->tf_regs, pr);
return;
}
}
stack += 8;
i--;
}
}
void
db_stack_trace_print(db_expr_t addr, int have_addr, db_expr_t count,
char *modif, int (*pr)(const char *, ...))
{
enum {
Default, Stack, Frame
} style = Default;
db_regs_t frame;
db_regs_t *regs;
union {
db_regs_t *frame;
db_expr_t num;
} arg;
arg.num = addr;
while (modif && *modif) {
switch (*modif++) {
case 's': style = Stack ; break;
case 'f': style = Frame ; break;
default:
(*pr)("unknown trace modifier [%c]\n", modif[-1]);
case 'h':
(*pr)("usage: trace/[MODIFIER] [ARG]\n");
(*pr)(" s = ARG is a stack pointer\n");
(*pr)(" f = ARG is a frame pointer\n");
return;
}
}
if (!have_addr && style != Default) {
(*pr)("expecting argument with /s or /f\n");
return;
}
if (have_addr && style == Default)
style = Frame;
switch (style) {
case Default:
regs = &ddb_regs;
break;
case Frame:
regs = arg.frame;
break;
case Stack:
{
u_int val1, val2, sxip;
u_int ptr;
bzero((void *)&frame, sizeof(frame));
#define REASONABLE_FRAME_DISTANCE 2048
for (ptr = arg.num;; ptr += 4) {
if (db_trace_get_val(ptr, &val1) == 0) {
(*pr)("can't read from %x, aborting.\n", ptr);
return;
}
if (val1 <= ptr ||
(val1 & 3) ||
val1 > (ptr + REASONABLE_FRAME_DISTANCE))
continue;
if (db_trace_get_val(ptr, &sxip) == 0) {
(*pr)("can't read from %x, aborting.\n", ptr);
return;
}
if (sxip == 0 || !db_trace_get_val(sxip, &val2))
continue;
if (db_trace_get_val(val1, &val2) == 0) {
(*pr)("can't read from %x, aborting.\n", val1);
continue;
}
if (val2 == 0x12345678 &&
db_trace_get_val(val1 - 4, &val2) &&
val2 == val1 &&
db_trace_get_val(val1 - 8, &val2) &&
val2 == val1) {
(*pr)("%x looks like a frame, accepting %x\n",val1,ptr);
break;
}
if (val2 > val1 && (val2 & 3) == 0) {
(*pr)("*%x = %x looks like a stack frame pointer, accepting %x\n", val1, val2, ptr);
break;
}
}
frame.r[31] = ptr;
frame.epsr = 0x800003f0U;
#ifdef M88100
if (CPU_IS88100) {
frame.sxip = sxip | XIP_V;
frame.snip = frame.sxip + 4;
frame.sfip = frame.snip + 4;
}
#endif
(*pr)("[r31=%x, %sxip=%x]\n", frame.r[31],
CPU_IS88110 ? "e" : "s", frame.sxip);
regs = &frame;
}
break;
}
db_stack_trace_cmd2(regs, pr);
}