db_trace.c   [plain text]


/*
 * Copyright (c) 2000-2006 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 * 
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */
/*
 * @OSF_COPYRIGHT@
 */

#include <string.h>

#include <mach/boolean.h>
#include <mach/machine.h>

#include <vm/vm_map.h>

#include <kern/thread.h>
#include <kern/processor.h>
#include <kern/task.h>

#include <ppc/cpu_internal.h>
#include <ppc/exception.h>

#include <machine/asm.h>
#include <machine/db_machdep.h>
#include <machine/setjmp.h>

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

extern jmp_buf_t *db_recover;

struct savearea ddb_null_kregs;

extern vm_offset_t vm_min_inks_addr;	/* set by db_clone_symtabXXX */

#define DB_NUMARGS_MAX	5

#define	INFIXEDSTACK(va)	0							\

#define INKERNELSTACK(va, th) 1

struct db_ppc_frame {
	struct db_ppc_frame	*f_frame;
	int			pad1;
	uint32_t	f_retaddr;
	int			pad3;
	int			pad4;
	int			pad5;
	uint32_t	f_arg[DB_NUMARGS_MAX];
};

#define	TRAP		1
#define	INTERRUPT	2
#define SYSCALL		3

db_addr_t	db_user_trap_symbol_value = 0;
db_addr_t	db_kernel_trap_symbol_value = 0;
db_addr_t	db_interrupt_symbol_value = 0;
db_addr_t	db_return_to_iret_symbol_value = 0;
db_addr_t	db_syscall_symbol_value = 0;
boolean_t	db_trace_symbols_found = FALSE;

static int db_ppc_reg_value(
			struct db_variable	* vp,
			db_expr_t		* val,
			int			flag,
			db_var_aux_param_t	ap);
static void db_find_trace_symbols(void);
static int db_numargs(
			struct db_ppc_frame	*fp,
			task_t			task);
static boolean_t db_find_arg(
			struct db_ppc_frame	*frame,
			db_addr_t		calleepc,
			task_t			task,
			int			narg,
			db_addr_t		*arg);
static void db_nextframe(
			struct db_ppc_frame	**lfp,
			struct db_ppc_frame	**fp,
			db_addr_t		*ip,
			int			frame_type,
			thread_act_t		thr_act,
			db_addr_t		linkpc);

/*
 * Machine register set.
 */
struct db_variable db_regs[] = {
	/* XXX "pc" is an alias to "srr0"... */
	{
		.name = "pc",
		.valuep = &ddb_regs.save_srr0,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "srr0",
		.valuep = &ddb_regs.save_srr0,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "srr1",
		.valuep = &ddb_regs.save_srr1,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r0",
		.valuep = &ddb_regs.save_r0,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r1",
		.valuep = &ddb_regs.save_r1,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r2",
		.valuep = &ddb_regs.save_r2,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r3",
		.valuep = &ddb_regs.save_r3,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r4",
		.valuep = &ddb_regs.save_r4,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r5",
		.valuep = &ddb_regs.save_r5,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r6",
		.valuep = &ddb_regs.save_r6,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r7",
		.valuep = &ddb_regs.save_r7,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r8",
		.valuep = &ddb_regs.save_r8,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r9",
		.valuep = &ddb_regs.save_r9,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r10",
		.valuep = &ddb_regs.save_r10,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r11",
		.valuep = &ddb_regs.save_r11,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r12",
		.valuep = &ddb_regs.save_r12,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r13",
		.valuep = &ddb_regs.save_r13,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r14",
		.valuep = &ddb_regs.save_r14,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r15",
		.valuep = &ddb_regs.save_r15,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r16",
		.valuep = &ddb_regs.save_r16,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r17",
		.valuep = &ddb_regs.save_r17,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r18",
		.valuep = &ddb_regs.save_r18,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r19",
		.valuep = &ddb_regs.save_r19,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r20",
		.valuep = &ddb_regs.save_r20,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r21",
		.valuep = &ddb_regs.save_r21,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r22",
		.valuep = &ddb_regs.save_r22,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r23",
		.valuep = &ddb_regs.save_r23,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r24",
		.valuep = &ddb_regs.save_r24,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r25",
		.valuep = &ddb_regs.save_r25,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r26",
		.valuep = &ddb_regs.save_r26,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r27",
		.valuep = &ddb_regs.save_r27,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r28",
		.valuep = &ddb_regs.save_r28,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r29",
		.valuep = &ddb_regs.save_r29,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r30",
		.valuep = &ddb_regs.save_r30,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "r31",
		.valuep = &ddb_regs.save_r31,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "cr",
		.valuep = (db_expr_t *)&ddb_regs.save_cr,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "xer",
		.valuep = &ddb_regs.save_xer,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "lr",
		.valuep = &ddb_regs.save_lr,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
	{
		.name = "ctr",
		.valuep = &ddb_regs.save_ctr,
		.fcn = db_ppc_reg_value,
		.min_level = 0,
		.max_level = 0,
		.low = 0,
		.high = 0,
		.hidden_level = TRUE,
	},
};
struct db_variable *db_eregs = db_regs + sizeof(db_regs)/sizeof(db_regs[0]);

int
db_ppc_reg_value(
	struct	db_variable	*vp,
	db_expr_t		*valuep,
	int			flag,
	db_var_aux_param_t	ap)
{
	db_expr_t *dp = 0;
	db_expr_t null_reg = 0;
	uint32_t *dp32;
	thread_act_t thr_act = ap->thr_act;
	unsigned int cpu;

	if (db_option(ap->modif, 'u')) {
		if (thr_act == THR_ACT_NULL) {
			if ((thr_act = current_thread()) == THR_ACT_NULL)
				db_error("no user registers\n");
		}
		if (thr_act == current_thread()) {
			if (IS_USER_TRAP((&ddb_regs))) dp = vp->valuep;
			else if (INFIXEDSTACK(ddb_regs.save_r1))
				db_error("cannot get/set user registers in nested interrupt\n");
		}
	} 
	else {
		if (thr_act == THR_ACT_NULL || thr_act == current_thread()) {
			dp = vp->valuep;
		} 
		else {
			if (thr_act->kernel_stack) {
				for (cpu = 0; cpu < real_ncpus; cpu++) {
					if (cpu_to_processor(cpu)->state == PROCESSOR_RUNNING &&
							cpu_to_processor(cpu)->active_thread == thr_act &&
							PerProcTable[cpu].ppe_vaddr->db_saved_state) {

						dp = (db_expr_t)(((uint32_t)(PerProcTable[cpu].ppe_vaddr->db_saved_state)) +
								(((uint32_t) vp->valuep) -
								 (uint32_t) &ddb_regs));
						break;
					}
				}

				if (dp == 0)
					dp = &null_reg;
			} 
			else {
				/* only PC is valid */
				if (vp->valuep == &ddb_regs.save_srr0)
					dp = (db_expr_t *)&thr_act->continuation;
				else
					dp = &null_reg;
			}
		}
	}
	if (dp == 0) {
		if (!db_option(ap->modif, 'u')) {
			for (cpu = 0; cpu < real_ncpus; cpu++) {
				if (cpu_to_processor(cpu)->state == PROCESSOR_RUNNING &&
						cpu_to_processor(cpu)->active_thread == thr_act &&
						PerProcTable[cpu].ppe_vaddr->db_saved_state) {
					dp = (int *) (((int)(PerProcTable[cpu].ppe_vaddr->db_saved_state)) +
							(((int) vp->valuep) - (int) &ddb_regs));
					break;
				}
			}
		}
		if (dp == 0) {
			if (!thr_act || thr_act->machine.pcb == 0)
				db_error("no pcb\n");
			dp = (int *)((int)thr_act->machine.pcb + ((int)vp->valuep - (int)&ddb_regs));
		}
	}

	if(vp->valuep == (db_expr_t *)&ddb_regs.save_cr) {	/* Is this the CR we are doing? */
		dp32 = (uint32_t *)dp;						/* Make this easier */
		if (flag == DB_VAR_SET)
			*dp32 = *valuep;
		else
			*valuep = *dp32;
	}
	else {											/* Normal 64-bit registers */
		if (flag == DB_VAR_SET)
			*dp = *valuep;
		else
			*valuep = *(unsigned long long *)dp;
	}

	return 0;
}


void
db_find_trace_symbols(void)
{
	db_expr_t	value;
	boolean_t	found_some;

	found_some = FALSE;
	if (db_value_of_name(CC_SYM_PREFIX "thandler", &value)) {
		db_user_trap_symbol_value = (db_addr_t) value;
		found_some = TRUE;
	}
	if (db_value_of_name(CC_SYM_PREFIX "thandler", &value)) {
		db_kernel_trap_symbol_value = (db_addr_t) value;
		found_some = TRUE;
	}
	if (db_value_of_name(CC_SYM_PREFIX "ihandler", &value)) {
		db_interrupt_symbol_value = (db_addr_t) value;
		found_some = TRUE;
	}
#if 0
	if (db_value_of_name(CC_SYM_PREFIX "return_to_iret", &value)) {
		db_return_to_iret_symbol_value = (db_addr_t) value;
		found_some = TRUE;
	}
#endif
	if (db_value_of_name(CC_SYM_PREFIX "thandler", &value)) {
		db_syscall_symbol_value = (db_addr_t) value;
		found_some = TRUE;
	}
	if (found_some) 
		db_trace_symbols_found = TRUE;
}

int
db_numargs(
	struct db_ppc_frame	*fp,
	task_t			task)
{
	return DB_NUMARGS_MAX;
}

boolean_t
db_find_arg(
	struct db_ppc_frame 	*fp,
	db_addr_t		calleepc,
	task_t			task,
	int			narg,
	db_addr_t		*arg)
{
	db_addr_t	argp;
	db_addr_t	calleep;
	db_addr_t   	offset;
	int		i;
	int		inst;
	char 		*name;

#if	0
	db_find_task_sym_and_offset(calleepc, &name, &offset, task);
	calleep = calleepc-offset;

	for (i = 0; calleep < calleepc; i++, calleep++) {
		if (!DB_CHECK_ACCESS((int) calleep, 4, task)) {
			continue;
		}
		inst = db_get_task_value(calleep, 4, FALSE, task);
		if ((inst & 0xffff0000) == (0x907f0000 + (narg << 21)) ||
				(inst & 0xffff0000) == (0x90610000 + (narg << 21))) {
			argp = (db_addr_t) &(fp->f_arg[narg]);
			*arg = argp;
			return TRUE;
		}
	}
#endif
	return FALSE;
}

extern int	TRAP_TYPES;
/* 
 * Figure out the next frame up in the call stack.  
 * For trap(), we print the address of the faulting instruction and 
 *   proceed with the calling frame.  We return the ip that faulted.
 *   If the trap was caused by jumping through a bogus pointer, then
 *   the next line in the backtrace will list some random function as 
 *   being called.  It should get the argument list correct, though.  
 *   It might be possible to dig out from the next frame up the name
 *   of the function that faulted, but that could get hairy.
 */
void
db_nextframe(
	struct db_ppc_frame	**lfp,		/* in/out */
	struct db_ppc_frame	**fp,		/* in/out */
	db_addr_t		*ip,		/* out */
	int			frame_type,	/* in */
	thread_act_t		thr_act,
	db_addr_t		linkpc)		/* in */
{
	struct savearea *saved_regs;

	task_t task = (thr_act != THR_ACT_NULL)? thr_act->task: TASK_NULL;

	switch(frame_type) {
	case TRAP:
		db_printf(">>>>> trap <<<<<\n");
		goto miss_frame;
		break;
	case INTERRUPT:
		if (*lfp == 0) {
			db_printf(">>>>> interrupt <<<<<\n");
			goto miss_frame;
		}
		db_printf(">>>>> interrupt <<<<<\n");
		goto miss_frame;
		break;
	case SYSCALL:
		if (thr_act != THR_ACT_NULL && thr_act->machine.pcb) {
			*ip = (db_addr_t) thr_act->machine.pcb->save_srr0;
			*fp = (struct db_ppc_frame *) (thr_act->machine.pcb->save_r1);
			break;
		}
		/* falling down for unknown case */
	default:
miss_frame:
		if(!pmap_find_phys(kernel_pmap, (addr64_t)*fp)) {	/* Check if this is valid */
			db_printf("Frame not mapped %08X\n",*fp);		/* Say not found */
			*fp = 0;										/* Show not found */
			break;											/* Out of here */
		}

		if ((*fp)->f_frame)
			*ip = (db_addr_t)
				db_get_task_value((int)&(*fp)->f_frame->f_retaddr,
						4, FALSE, task);
		else
			*ip = (db_addr_t) 
				db_get_task_value((int)&(*fp)->f_retaddr,
						4, FALSE, task);

		*lfp = *fp;
		*fp = (struct db_ppc_frame *)
			db_get_task_value((int)&(*fp)->f_frame, 4, FALSE, task);
		break;
	}
}

void
db_stack_trace_cmd(
	db_expr_t	addr,
	boolean_t	have_addr,
	db_expr_t	count,
	char		*modif)
{
	struct db_ppc_frame *frame, *lastframe;
	db_addr_t	callpc, linkpc, lastcallpc;
	int		frame_type;
	boolean_t	kernel_only = TRUE;
	boolean_t	trace_thread = FALSE;
	boolean_t	trace_all_threads = FALSE;
	int		thcount = 0;
	char		*filename;
	int		linenum;
	task_t		task;
	thread_act_t	th, top_act;
	int		user_frame;
	int		frame_count;
	jmp_buf_t	*prev;
	jmp_buf_t	db_jmp_buf;
	queue_entry_t	act_list;

	if (!db_trace_symbols_found)
		db_find_trace_symbols();
	{
		char *cp = modif;
		char c;

		while ((c = *cp++) != 0) {
			if (c == 't')
				trace_thread = TRUE;
			if (c == 'T') {
				trace_all_threads = TRUE;
				trace_thread = TRUE;
			}
			if (c == 'u')
				kernel_only = FALSE;
		}
	}

	if (trace_all_threads) {
		if (!have_addr && !trace_thread) {
			have_addr = TRUE;
			trace_thread = TRUE;
			act_list = &(current_task()->threads);
			addr = (db_expr_t) queue_first(act_list);
		} 
		else if (trace_thread) {
			if (have_addr) {
				if (!db_check_act_address_valid((thread_act_t)addr)) {
					if (db_lookup_task((task_t)addr) == -1)
						return;
					act_list = &(((task_t)addr)->threads);
					addr = (db_expr_t) queue_first(act_list);
				} 
				else {
					act_list = &(((thread_act_t)addr)->task->threads);
					thcount = db_lookup_task_act(((thread_act_t)addr)->task,
							(thread_act_t)addr);
				}
			} 
			else {
				th = db_default_act;
				if (th == THR_ACT_NULL)
					th = current_thread();
				if (th == THR_ACT_NULL) {
					db_printf("no active thr_act\n");
					return;
				}
				have_addr = TRUE;
				act_list = &th->task->threads;
				addr = (db_expr_t) queue_first(act_list);
			}
		}
	}

	if (count == -1)
		count = 65535;

next_thread:
	top_act = THR_ACT_NULL;

	user_frame = 0;
	frame_count = count;

	if (!have_addr && !trace_thread) {
		frame = (struct db_ppc_frame *)(ddb_regs.save_r1);
		callpc = (db_addr_t)ddb_regs.save_srr0;
		linkpc = (db_addr_t)ddb_regs.save_lr;
		th = current_thread();
		task = (th != THR_ACT_NULL)? th->task: TASK_NULL;
	} 
	else if (trace_thread) {
		if (have_addr) {
			th = (thread_act_t) addr;
			if (!db_check_act_address_valid(th))
				return;
		} 
		else {
			th = db_default_act;
			if (th == THR_ACT_NULL)
				th = current_thread();
			if (th == THR_ACT_NULL) {
				db_printf("no active thread\n");
				return;
			}
		}
		if (trace_all_threads)
			db_printf("---------- Thread 0x%x (#%d of %d) ----------\n",
					addr, thcount, th->task->thread_count);

next_activation:
		user_frame = 0;

		task = th->task;
		if (th == current_thread()) {
			frame = (struct db_ppc_frame *)(ddb_regs.save_r1);
			callpc = (db_addr_t)ddb_regs.save_srr0;
			linkpc = (db_addr_t)ddb_regs.save_lr;
		} 
		else {
			if (th->machine.pcb == 0) {
				db_printf("thread has no pcb\n");
				goto thread_done;
			}
			if (th->kernel_stack == 0) {
				struct savearea *pss = th->machine.pcb;

				db_printf("Continuation ");
				db_task_printsym((db_expr_t)th->continuation,
						DB_STGY_PROC, task);
				db_printf("\n");
				frame = (struct db_ppc_frame *) (pss->save_r1);
				callpc = (db_addr_t) (pss->save_srr0);
				linkpc = (db_addr_t) (pss->save_lr);
			} 
			else {
				int cpu;

				for (cpu = 0; cpu < real_ncpus; cpu++) {
					if (cpu_to_processor(cpu)->state == PROCESSOR_RUNNING &&
							cpu_to_processor(cpu)->active_thread == th &&
							PerProcTable[cpu].ppe_vaddr->db_saved_state) {
						break;
					}
				}
				if (top_act != THR_ACT_NULL) {
					/*
					 * Trying to get the backtrace of an activation
					 * which is not the top_most one in the RPC chain:
					 * use the activation's pcb.
					 */
					struct savearea *pss;

					pss = th->machine.pcb;
					frame = (struct db_ppc_frame *) (pss->save_r1);
					callpc = (db_addr_t) (pss->save_srr0);
					linkpc = (db_addr_t) (pss->save_lr);
				} else {
					if (cpu == real_ncpus) {
						struct savearea *iks;
						int r;

						iks = th->machine.pcb;
						prev = db_recover;
						if ((r = _setjmp(db_recover = &db_jmp_buf)) == 0) {
							frame = (struct db_ppc_frame *) (iks->save_r1);
							callpc = (db_addr_t) (iks->save_lr);
							linkpc = 0;
						} else {
							/*
							 * The kernel stack has probably been
							 * paged out (swapped out activation).
							 */
							db_recover = prev;
							if (r == 2)	/* 'q' from db_more() */
								db_error(0);
							db_printf("<kernel stack (0x%x) error "
									"(probably swapped out)>\n",
									iks);
							goto next_act;
						}
						db_recover = prev;
					} else {
						db_printf(">>>>> active on cpu %d <<<<<\n",
								cpu);
						frame = (struct db_ppc_frame *)
							(PerProcTable[cpu].ppe_vaddr->db_saved_state->save_r1);
						callpc = (db_addr_t) PerProcTable[cpu].ppe_vaddr->db_saved_state->save_srr0;
						linkpc = (db_addr_t) PerProcTable[cpu].ppe_vaddr->db_saved_state->save_lr;
					}
				}
			}
		}
	} else {
		frame = (struct db_ppc_frame *)addr;
		th = (db_default_act)? db_default_act: current_thread();
		task = (th != THR_ACT_NULL)? th->task: TASK_NULL;
		if (frame->f_frame) {
			callpc = (db_addr_t)db_get_task_value
				((int)&frame->f_frame->f_retaddr,
				 4, FALSE, (user_frame) ? task : 0);
			callpc = callpc-sizeof(callpc);
		} else
			callpc =0;
		linkpc = 0;
	}

	if (!INKERNELSTACK((unsigned)frame, th)) {
		db_printf(">>>>> user space <<<<<\n");
		if (kernel_only)
			goto thread_done;
		user_frame++;
	}

	lastframe = 0;
	lastcallpc = (db_addr_t) 0;
	while (frame_count-- && frame != 0) {
		int narg = DB_NUMARGS_MAX;
		int arg;	
		char *	name;
		db_expr_t	offset;
		db_addr_t call_func = 0;
		int r;
		db_addr_t	off;

		db_symbol_values(NULL,
				db_search_task_symbol_and_line(
					callpc, DB_STGY_XTRN, &offset, &filename,
					&linenum, (user_frame) ? task : 0, &narg),
				&name, (db_expr_t *)&call_func);
		if ( name == NULL) {
			db_find_task_sym_and_offset(callpc, 
					&name, &off, (user_frame) ? task : 0);
			offset = (db_expr_t) off;
		}

		if (user_frame == 0) {
			if (call_func &&
					(call_func == db_user_trap_symbol_value ||
					 call_func == db_kernel_trap_symbol_value)) {
				frame_type = TRAP;
				narg = 1;
			} else if (call_func &&
					call_func == db_interrupt_symbol_value) {
				frame_type = INTERRUPT;
				goto next_frame;
			} else if (call_func &&
					call_func == db_syscall_symbol_value) {
				frame_type = SYSCALL;
				goto next_frame;
			} else {
				frame_type = 0;
				prev = db_recover;
				if ((r = _setjmp(db_recover = &db_jmp_buf)) 
						== 0) {
					if (narg < 0)
						narg = db_numargs(frame,
								(user_frame) ? task : 0);
					db_recover = prev;
				} else {
					db_recover = prev;
					goto next_act;
				}
			}
		} else {
			frame_type = 0;
			prev = db_recover;
			if ((r = _setjmp(db_recover = &db_jmp_buf)) == 0) {
				if (narg < 0)
					narg = db_numargs(frame,
							(user_frame) ? task : 0);
				db_recover = prev;
			} else {
				db_recover = prev;
				goto next_act;
			}
		}

		if (name == 0 || offset > db_maxoff) {
			db_printf("[%08X]0x%08X(", frame, callpc);
		} else {
			db_printf("[%08X]%s", frame, name);
			if (offset)
				db_printf("+%llx", offset);
			db_printf("(");
		};

		narg = db_numargs(frame, (user_frame) ? task : 0);

		for (arg = 0; arg < narg; arg++) {
			db_addr_t	argp;
			int value;
			boolean_t found;

			prev = db_recover;
			if ((r = _setjmp(db_recover = &db_jmp_buf)) == 0) {
				found = FALSE;
				if (lastframe) 
					found = db_find_arg(frame, lastframe->f_retaddr,
							(user_frame) ? task : 0, arg, &argp);
				if (found)
					value = db_get_task_value(argp, 4, FALSE,
							(user_frame) ? task : 0);
			} else {
				db_recover = prev;
				if (r == 2)	/* 'q' from db_more() */
					db_error(0);
				db_printf("... <stack error>)");
				db_printf("\n");
				goto next_act;
			}
			db_recover = prev;
			if (found)
				db_printf("%08X", value);
			else
				db_printf("??");	
			argp = argp + sizeof(argp);
			if (arg < narg-1)
				db_printf(",");
		}
		if (arg != narg)
			db_printf("...");
		db_printf(")");
		db_printf("\n");

next_frame:
		lastcallpc = callpc;
		prev = db_recover;
		if ((r = _setjmp(db_recover = &db_jmp_buf)) == 0) {
			db_nextframe(&lastframe, &frame, &callpc, frame_type,
					(user_frame) ? th : THR_ACT_NULL, linkpc);
			callpc = callpc-sizeof(callpc);
			db_recover = prev;
		} else {
			db_recover = prev;
			frame = 0;
		}
		linkpc = 0;

		if (frame == 0) {
next_act:
			/* end of chain */
			break;
		}
		if (!INKERNELSTACK(lastframe, th) ||
				!INKERNELSTACK((unsigned)frame, th))
			user_frame++;
		if (user_frame == 1) {
			db_printf(">>>>> user space <<<<<\n");
			if (kernel_only)
				break;
		}

		if (frame <= lastframe) {
			if ((INKERNELSTACK(lastframe, th) && !INKERNELSTACK(frame, th)))
				continue;
			db_printf("Bad frame pointer: 0x%x\n", frame);
			break;
		}
	}

thread_done:
	if (trace_all_threads) {
		if (top_act != THR_ACT_NULL)
			th = top_act;
		th = (thread_act_t) queue_next(&th->task_threads);
		if (! queue_end(act_list, (queue_entry_t) th)) {
			db_printf("\n");
			addr = (db_expr_t) th;
			thcount++;
			goto next_thread;
		}
	}
}