/* $NetBSD: db_trace.c,v 1.39 2023/11/21 22:19:12 thorpej Exp $ */

/*-
 * Copyright (c) 1999 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
 * NASA Ames Research Center.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Ross Harvey.
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */

#include <sys/cdefs.h>			/* RCS ID & Copyright macro defns */

__KERNEL_RCSID(0, "$NetBSD: db_trace.c,v 1.39 2023/11/21 22:19:12 thorpej Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>

#include <machine/alpha.h>
#include <machine/db_machdep.h>

#include <machine/alpha_instruction.h>

#include <ddb/db_sym.h>
#include <ddb/db_access.h>
#include <ddb/db_variables.h>
#include <ddb/db_output.h>
#include <ddb/db_interface.h>

/*
 * Information about the `standard' Alpha function prologue.
 */
struct prologue_info {
	int	pi_reg_offset[32]; /* offset of registers in stack frame */
	uint32_t pi_regmask;	   /* which registers are in frame */
	int	pi_frame_size;	   /* frame size */
};

/*
 * Decode the function prologue for the function we're in, and note
 * which registers are stored where, and how large the stack frame is.
 */
static void
decode_prologue(db_addr_t callpc, db_addr_t func,
    struct prologue_info *pi, void (*pr)(const char *, ...))
{
	long signed_immediate;
	alpha_instruction ins;
	db_addr_t pc;

	pi->pi_regmask = 0;
	pi->pi_frame_size = 0;

#define	CHECK_FRAMESIZE							\
do {									\
	if (pi->pi_frame_size != 0) {					\
		(*pr)("frame size botch: adjust register offsets?\n"); \
	}								\
} while (0)

	for (pc = func; pc < callpc; pc += sizeof(alpha_instruction)) {
		db_read_bytes(pc, sizeof(ins.bits), (char *)&ins.bits);

		if (ins.mem_format.opcode == op_lda &&
		    ins.mem_format.ra == 30 &&
		    ins.mem_format.rb == 30) {
			/*
			 * GCC 2.7-style stack adjust:
			 *
			 *	lda	sp, -64(sp)
			 */
			signed_immediate = (long)ins.mem_format.displacement;
			/*
			 * The assumption here is that a positive
			 * stack offset is the function epilogue,
			 * which may come before callpc when an
			 * aggressive optimizer (like GCC 3.3 or later)
			 * has moved part of the function "out of
			 * line", past the epilogue. Therefore, ignore
			 * the positive offset so that
			 * pi->pi_frame_size has the correct value
			 * when we reach callpc.
			 */
			if (signed_immediate <= 0) {
				CHECK_FRAMESIZE;
				pi->pi_frame_size += -signed_immediate;
			}
		} else if (ins.operate_lit_format.opcode == op_arit &&
			   ins.operate_lit_format.function == op_subq &&
			   ins.operate_lit_format.ra == 30 &&
			   ins.operate_lit_format.rc == 30) {
			/*
			 * EGCS-style stack adjust:
			 *
			 *	subq	sp, 64, sp
			 */
			CHECK_FRAMESIZE;
			pi->pi_frame_size += ins.operate_lit_format.literal;
		} else if (ins.mem_format.opcode == op_stq &&
			   ins.mem_format.rb == 30 &&
			   ins.mem_format.ra != 31) {
			/* Store of (non-zero) register onto the stack. */
			signed_immediate = (long)ins.mem_format.displacement;
			pi->pi_regmask |= 1 << ins.mem_format.ra;
			pi->pi_reg_offset[ins.mem_format.ra] = signed_immediate;
		}
	}
}

static void
decode_syscall(int number, void (*pr)(const char *, ...))
{
	(*pr)(" (%d)", number);
}

void
db_stack_trace_print(db_expr_t addr, bool have_addr, db_expr_t count,
    const char *modif, void (*pr)(const char *, ...))
{

	db_stack_trace_print_ra(/*ra*/0, /*have_ra*/false, addr, have_addr,
	    count, modif, pr);
}

void
db_stack_trace_print_ra(db_expr_t ra, bool have_ra,
    db_expr_t addr, bool have_addr,
    db_expr_t count,
    const char *modif, void (*pr)(const char *, ...))
{
	db_addr_t callpc, frame, symval;
	struct prologue_info pi;
	db_expr_t diff;
	db_sym_t sym;
	u_long tfps;
	const char *symname;
	struct pcb *pcbp;
	const char *cp = modif;
	struct trapframe *tf;
	bool ra_from_tf;
	u_long last_ipl = ~0L;
	char c;
	bool trace_thread = false;
	bool lwpaddr = false;

	while ((c = *cp++) != 0) {
		trace_thread |= c == 't';
		trace_thread |= c == 'a';
		lwpaddr |= c == 'a';
	}

	if (!have_addr) {
		addr = DDB_REGS->tf_regs[FRAME_SP] - FRAME_SIZE * 8;
		tf = (struct trapframe *)addr;
		callpc = db_alpha_tf_reg(tf, FRAME_PC);
		frame = (db_addr_t)tf + FRAME_SIZE * 8;
		ra_from_tf = true;
	} else {
#ifdef _KERNEL
		struct proc *p = NULL;
		struct lwp *l = NULL;
#else
		struct proc pstore, *p = &pstore;
		struct lwp lstore, *l = &lstore;
#endif /* _KERNEL */

		if (trace_thread) {
			if (lwpaddr) {
#ifdef _KERNEL
				l = (struct lwp *)addr;
				p = l->l_proc;
#else
				db_read_bytes(addr, sizeof(*l), (char *)l);
				db_read_bytes((db_addr_t)l->l_proc,
				    sizeof(*p), (char *)p);
#endif /* _KERNEL */
				(*pr)("trace: pid %d ", p->p_pid);
			} else {
#ifdef _KERNEL
				(*pr)("trace: pid %d ", (int)addr);
				p = proc_find_raw(addr);
				if (p == NULL) {
					(*pr)("not found\n");
					return;
				}
				l = LIST_FIRST(&p->p_lwps);
				KASSERT(l != NULL);
#else
				(*pr)("no proc_find_raw() in crash\n");
				return;
#endif /* _KERNEL */
			}
			(*pr)("lid %d ", l->l_lid);
			pcbp = lwp_getpcb(l);
			addr = db_alpha_read_saved_reg(&pcbp->pcb_hw.apcb_ksp);
			callpc = db_alpha_read_saved_reg(&pcbp->pcb_context[7]);
			(*pr)("at 0x%lx\n", addr);
		} else if (have_ra) {
			callpc = ra;
			(*pr)("at 0x%lx pc 0x%lx\n", addr, callpc);
		} else {
			(*pr)("alpha trace requires known PC =eject=\n");
			return;
		}
		frame = addr;
		tf = NULL;
		ra_from_tf = false;
	}

	while (count--) {
		sym = db_search_symbol(callpc, DB_STGY_ANY, &diff);
		if (sym == DB_SYM_NULL)
			break;

		db_symbol_values(sym, &symname, (db_expr_t *)&symval);

		if (callpc < symval) {
			(*pr)("symbol botch: callpc 0x%lx < "
			    "func 0x%lx (%s)\n", callpc, symval, symname);
			return;
		}

		/*
		 * If the previous RA pointed at the kernel thread
		 * backstop, then we are at the root of the call
		 * graph.
		 */
		if (db_alpha_sym_is_backstop(symval)) {
			(*pr)("--- kernel thread backstop ---\n");
			break;
		}

		/*
		 * XXX Printing out arguments is Hard.  We'd have to
		 * keep lots of state as we traverse the frame, figuring
		 * out where the arguments to the function are stored
		 * on the stack.
		 *
		 * Even worse, they may be stored to the stack _after_
		 * being modified in place; arguments are passed in
		 * registers.
		 *
		 * So, in order for this to work reliably, we pretty much
		 * have to have a kernel built with `cc -g':
		 *
		 *	- The debugging symbols would tell us where the
		 *	  arguments are, how many there are, if there were
		 *	  any passed on the stack, etc.
		 *
		 *	- Presumably, the compiler would be careful to
		 *	  store the argument registers on the stack before
		 *	  modifying the registers, so that a debugger could
		 *	  know what those values were upon procedure entry.
		 *
		 * Because of this, we don't bother.  We've got most of the
		 * benefit of back tracking without the arguments, and we
		 * could get the arguments if we use a remote source-level
		 * debugger (for serious debugging).
		 */
		(*pr)("%s() at ", symname);
		db_printsym(callpc, DB_STGY_PROC, pr);
		(*pr)("\n");

		/*
		 * If we are in a trap vector, frame points to a
		 * trapframe.
		 */
		if (db_alpha_sym_is_trap(symval)) {
			tf = (struct trapframe *)frame;

			(*pr)("--- %s", db_alpha_trapsym_description(symval));

			tfps = db_alpha_tf_reg(tf, FRAME_PS);
			if (db_alpha_sym_is_syscall(symval)) {
				decode_syscall(db_alpha_tf_reg(tf, FRAME_V0),
				    pr);
			}
			if ((tfps & ALPHA_PSL_IPL_MASK) != last_ipl) {
				last_ipl = tfps & ALPHA_PSL_IPL_MASK;
				if (! db_alpha_sym_is_syscall(symval)) {
					(*pr)(" (from ipl %ld)", last_ipl);
				}
			}
			(*pr)(" ---\n");
			if (tfps & ALPHA_PSL_USERMODE) {
				(*pr)("--- user mode ---\n");
				break;	/* Terminate search.  */
			}
			callpc = db_alpha_tf_reg(tf, FRAME_PC);
			frame = (db_addr_t)tf + FRAME_SIZE * 8;
			ra_from_tf = true;
			continue;
		}

		/*
		 * This is a bit trickier; we must decode the function
		 * prologue to find the saved RA.
		 *
		 * XXX How does this interact w/ alloca()?!
		 */
		decode_prologue(callpc, symval, &pi, pr);
		if ((pi.pi_regmask & (1 << 26)) == 0) {
			/*
			 * No saved RA found.  We might have RA from
			 * the trap frame, however (e.g trap occurred
			 * in a leaf call).  If not, we've found the
			 * root of the call graph.
			 */
			if (ra_from_tf) {
				callpc = db_alpha_tf_reg(tf, FRAME_RA);
			} else {
				(*pr)("--- root of call graph ---\n");
				break;
			}
		} else {
			unsigned long reg;

			db_read_bytes(frame + pi.pi_reg_offset[26],
			    sizeof(reg), (char *)&reg);
			callpc = reg;
		}
		frame += pi.pi_frame_size;
		ra_from_tf = false;
	}
}
