/*	$NetBSD: db_trace.c,v 1.13 2013/10/19 14:03:04 skrll Exp $	*/

/*	$OpenBSD: db_interface.c,v 1.16 2001/03/22 23:31:45 mickey Exp $	*/

/*
 * Copyright (c) 1999-2003 Michael Shalayeff
 * All rights reserved.
 *
 * 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 AUTHOR ``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 AUTHOR OR HIS RELATIVES 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 MIND, 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>
__KERNEL_RCSID(0, "$NetBSD: db_trace.c,v 1.13 2013/10/19 14:03:04 skrll Exp $");

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

#include <machine/db_machdep.h>

#include <ddb/db_access.h>
#include <ddb/db_sym.h>
#include <ddb/db_interface.h>
#include <ddb/db_proc.h>

void
db_stack_trace_print(db_expr_t addr, bool have_addr, db_expr_t count,
    const char *modif, void (*pr)(const char *, ...))
{
	register_t *fp, pc, rp;
#if 0
	bool kernel_only = true;
#endif
	bool trace_thread = false;
	bool lwpaddr = false;
	db_sym_t sym;
	db_expr_t off;
	const char *name;
	const char *cp = modif;
	char c;

	if (count < 0)
		count = 65536;

	while ((c = *cp++) != 0) {
		if (c == 'a') {
			lwpaddr = true;
			trace_thread = true;
		}
		if (c == 't')
			trace_thread = true;
#if 0
		if (c == 'u')
			kernel_only = false;
#endif
	}

	if (!have_addr) {
		fp = (register_t *)ddb_regs.tf_r3;
		pc = ddb_regs.tf_iioq_head;
		rp = ddb_regs.tf_rp;
	} else {
		if (trace_thread) {
			proc_t p;
			lwp_t l;
			pid_t pid;

			if (lwpaddr) {
				db_read_bytes(addr, sizeof(l), (char *)&l);
				db_read_bytes((db_addr_t)l.l_proc, sizeof(p),
				    (char *)&p);
				(*pr)("trace: pid %d ", p.p_pid);
			} else {
				proc_t *pp;

				pid = (pid_t)addr;
				(*pr)("trace: pid %d ", pid);
				pp = db_proc_find(pid);
				if (pp == NULL) {
					(*pr)("not found\n");
					return;
				}
				db_read_bytes((db_addr_t)pp, sizeof(p),
				    (char *)&p);
				addr = (db_addr_t)p.p_lwps.lh_first;
				db_read_bytes(addr, sizeof(l), (char *)&l);
			}
			(*pr)("lid %d ", l.l_lid);
#ifdef _KERNEL
			if (l.l_proc == curproc && (lwp_t *)addr == curlwp) {
				fp = (int *)ddb_regs.tf_r3;
				pc = ddb_regs.tf_iioq_head;
				rp = ddb_regs.tf_rp;
			} else
#endif
			{
				struct pcb *pcb = lwp_getpcb(&l);
				register_t sp;
				/* cpu_switchto fp, and return point */
				db_read_bytes((db_addr_t)&pcb->pcb_ksp,
				    sizeof(sp), (char *)&sp);
				fp = (register_t *)(sp -
				    (HPPA_FRAME_SIZE + 16*4));
				pc = 0;

				db_read_bytes((db_addr_t)&fp[-5], sizeof(rp),
				    (char *)&rp);
			}
			(*pr)("at %p\n", fp);
		} else {
			pc = 0;
			fp = (register_t *)addr;
			db_read_bytes((db_addr_t)&fp[-5], sizeof(rp),
			    (char *)&rp);
		}
	}

	while (fp && count--) {
		register_t *newfp;
#ifdef DDB_DEBUG
		pr(">> %08x %08x %08x\t", fp, pc, rp);
#endif

		if (USERMODE(pc))
			return;

		if (pc) {
			sym = db_search_symbol(pc, DB_STGY_ANY, &off);
			db_symbol_values (sym, &name, NULL);

			pr("%s() at ", name);
			db_printsym(pc, DB_STGY_PROC, pr);
			pr("\n");
		}

		db_read_bytes((db_addr_t)&fp[0], sizeof(newfp), (char *)&newfp);

		/*
		 * if a terminal frame then report the trapframe and continue
		 * after it (if not the last one).
		 */
		if (!newfp) {
			register_t scargs[5];
			struct trapframe tf, *ktf;
			int scoff;

			/* Stack space for syscall args */
			scoff = HPPA_FRAME_ROUND(HPPA_FRAME_SIZE +
			    HPPA_FRAME_MAXARGS);
			ktf = (struct trapframe *)((char *)fp - scoff -
			    sizeof(tf));

			db_read_bytes((db_addr_t)((char *)fp - scoff),
			     sizeof(scargs), (char *)&scargs);
			db_read_bytes((db_addr_t)ktf, sizeof(tf), (char *)&tf);

			if (tf.tf_flags & TFF_SYS)
				pr("-- syscall #%d(%x, %x, %x, %x, ...) (%p)\n",
				    tf.tf_t1, scargs[1], scargs[2],
				    scargs[3], scargs[4], ktf);
			else
				pr("-- trap #%d (%p) %s\n", tf.tf_flags & 0x3f,
				    ktf, (tf.tf_flags & T_USER)? " from user" :
				    "");

			if (!(tf.tf_flags & TFF_LAST)) {
				fp = (register_t *)tf.tf_r3;
				pc = tf.tf_iioq_head;
				rp = tf.tf_rp;
			} else {
				pc = 0;
				fp = 0;
			}
		} else {
			/* next frame */
			fp = newfp;
			pc = rp;
			db_read_bytes((db_addr_t)&fp[-5], sizeof(rp),
			    (char *)&rp);
		}
	}

	if (count && pc) {
		db_printsym(pc, DB_STGY_XTRN, pr);
		pr(":\n");
	}
}
