/*	$NetBSD: mips_stacktrace.c,v 1.9 2021/04/06 13:11:22 simonb Exp $	*/

/*
 * Copyright (c) 1988 University of Utah.
 * Copyright (c) 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * the Systems Programming Group of the University of Utah Computer
 * Science Department and Ralph Campbell.
 *
 * 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.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
 *
 * from: NetBSD: trap.c,v 1.255 2020/07/13 09:00:40 simonb Exp
 * from: Utah Hdr: trap.c 1.32 91/04/06
 *
 *	@(#)trap.c	8.5 (Berkeley) 1/11/94
 */

#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	/* instruction code for jr ra */
#define	MIPS_JR_K0	0x03400008	/* instruction code for jr k0 */
#define	MIPS_ERET	0x42000018	/* instruction code for eret */

int main(void *);	/* XXX */

/*
 * Functions ``special'' enough to print by name
 */
#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	/*  r2000 family  (mips-I CPU) */
	Name(mips1_kern_gen_exception),
	Name(mips1_user_gen_exception),
	Name(mips1_systemcall),
	Name(mips1_kern_intr),
	Name(mips1_user_intr),
#endif	/* MIPS1 */

#if defined(MIPS3)			/* r4000 family (mips-III CPU) */
	Name(mips3_kern_gen_exception),
	Name(mips3_user_gen_exception),
	Name(mips3_systemcall),
	Name(mips3_kern_intr),
	Name(mips3_user_intr),
#endif	/* MIPS3 */

#if defined(MIPS32)			/* MIPS32 family (mips-III CPU) */
	Name(mips32_kern_gen_exception),
	Name(mips32_user_gen_exception),
	Name(mips32_systemcall),
	Name(mips32_kern_intr),
	Name(mips32_user_intr),
#endif	/* MIPS32 */

#if defined(MIPS32R2)			/* MIPS32R2 family (mips-III CPU) */
	Name(mips32r2_kern_gen_exception),
	Name(mips32r2_user_gen_exception),
	Name(mips32r2_systemcall),
	Name(mips32r2_kern_intr),
	Name(mips32r2_user_intr),
#endif	/* MIPS32R2 */

#if defined(MIPS64)			/* MIPS64 family (mips-III CPU) */
	Name(mips64_kern_gen_exception),
	Name(mips64_user_gen_exception),
	Name(mips64_systemcall),
	Name(mips64_kern_intr),
	Name(mips64_user_intr),
#endif	/* MIPS64 */

#if defined(MIPS64R2)			/* MIPS64R2 family (mips-III CPU) */
	Name(mips64r2_kern_gen_exception),
	Name(mips64r2_user_gen_exception),
	Name(mips64r2_systemcall),
	Name(mips64r2_kern_intr),
	Name(mips64r2_user_intr),
#endif	/* MIPS64R2 */

	Name(cpu_idle),
	Name(cpu_switchto),
#endif /* _KERNEL */
	{0, 0}
};


bool
kdbpeek(vaddr_t addr, unsigned *valp)
{
	if (addr & 3) {
		printf("kdbpeek: unaligned address %#"PRIxVADDR"\n", addr);
		/* We might have been called from DDB, so do not go there. */
		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
		/* We might have been called from DDB, so do not go there. */
		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;
}

/*
 * Map a function address to a string name, if known; or a hex string.
 */
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);
}

/*
 * Do a stack backtrace.
 * (*printfn)()  prints the output to either the system log,
 * the console, or both.
 */
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,
	};

/* Jump here when done with a frame, to start a new one */
loop:
	stksize = 0;
	subr = 0;
	mask = 1;
	if (frames++ > 100) {
		(*printfn)("\nstackframe count exceeded\n");
		/* return breaks stackframe-size heuristics with gcc -O2 */
		goto finish;	/*XXX*/
	}

	/* check for bad SP: could foul up next frame */
	if ((sp & (sizeof(sp)-1)) || (intptr_t)sp >= 0) {
		(*printfn)("SP 0x%x: not in kernel\n", sp);
		ra = 0;
		subr = 0;
		goto done;
	}

	/* Check for bad PC */
	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;

		/*
		 * Check the kernel symbol table to see the beginning of
		 * the current subroutine.
		 */
		diff = 0;
		sym = db_search_symbol(pc, DB_STGY_ANY, &diff);
		if (sym != DB_SYM_NULL && diff == 0) {
			/* check func(foo) __attribute__((__noreturn__)) case */
			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 /* DDB && _KERNEL */
		/*
		 * Find the beginning of the current subroutine by
		 * scanning backwards from the current PC for the end
		 * of the previous subroutine.
		 *
		 * XXX This won't work well because nowadays gcc is so
		 *     aggressive as to reorder instruction blocks for
		 *     branch-predict. (i.e. 'jr ra' wouldn't indicate
		 *     the end of subroutine)
		 */
		va = pc;
		do {
			va -= sizeof(int);
#ifdef _KERNEL /* XXX crash */
			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);
		/* skip back over branch & delay slot */
		va += sizeof(int);
mips3_eret:
		va += sizeof(int);
		/* skip over nulls which might separate .o files */
		instr = 0;
		while (instr == 0) {
			if (!kdbpeek(va, &instr))
				return;
			va += sizeof(int);
		}
#if defined(DDB) && defined(_KERNEL)
	}
#endif /* DDB && _KERNEL */
	subr = va;

	/* scan forwards to find stack size and any saved registers */
	stksize = 0;
	more = 3;
	mask &= 0x40ff0001;	/* if s0-s8 are valid, leave then as valid */
	foundframesize = 0;
	for (va = subr; more; va += sizeof(int),
			      more = (more == 3) ? 3 : more - 1) {
		/* stop if hit our current position */
		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; /* stop after next instruction */
				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; /* stop now */
				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; /* stop after next instruction */
			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; /* stop after next instruction */
			};
			break;

		case OP_SW:
#if !defined(__mips_o32)
		case OP_SD:
#endif
		{
			size_t size = (i.JType.op == OP_SW) ? 4 : 8;

			/* look for saved registers on the stack */
			if (i.IType.rs != _R_SP)
				break;
			switch (i.IType.rt) {
			case _R_A0: /* a0 */
			case _R_A1: /* a1 */
			case _R_A2: /* a2 */
			case _R_A3: /* a3 */
			case _R_S0: /* s0 */
			case _R_S1: /* s1 */
			case _R_S2: /* s2 */
			case _R_S3: /* s3 */
			case _R_S4: /* s4 */
			case _R_S5: /* s5 */
			case _R_S6: /* s6 */
			case _R_S7: /* s7 */
			case _R_S8: /* s8 */
			case _R_RA: /* 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
			/* look for stack pointer adjustment */
			if (i.IType.rs != _R_SP || i.IType.rt != _R_SP)
				break;
			/* don't count pops for mcount */
			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 /* DEBUG || DDB || KGDB || geo */
#endif /* DDB_TRACE */
