Subject: Changes to pmax/trap.c that add interrupt counters, better stack traceback
To: None <port-pmax@NetBSD.ORG>
From: Jonathan Stone <jonathan@DSG.Stanford.EDU>
List: port-pmax
Date: 01/28/1995 13:50:26
The following patch includes some changes to trap.c I've been meaning
to get merged for some time.

The patch changes the preprocessor macros that enable debugging of
Ultrix system-call emulation, since sometimes I need to debug the
Ultrix emulation syscall vector itself, without debugging native
syscalls or Ultrix emulation per se.

The patch adds a redundant sanity check that the new-style argument
counting mechanism is right for Ultrix syscalls -- that the new-style
(sy_argsize /sizeof(int)) gives the same result as the old-style
sy_nargs..  There's no real reason to keep this.

The patch adds interrupt counters for 5k/200s.  Modifying other interrupt
handlers to count interrupts is simple enough.  I think using an enum
to index the array in locore.S is better than the current scheme, but
I don't understand entirely how the XINE interrupts work, so I haven't
tried to generate a complete set of interrupts.  If someone wants
to modify the 3min, xine, and 3max+ handlers accordingly, that'd be
great. I can guess at the appropriate code, but I can't test it.


The patch adds some really hairy-looking stack traceback code, which
I've used to do tracebacks from interrupt handlers.  This may or may
not be redundant if there's a kernel debugger.  I find that, when
debugging device drivers, getting a stack is often all I want.
There's some extra hairiness to allow an "addlog()" rather than a
"printf()", since at the time I did this, I really wanted samples of
stack tracebacks from within an interrupt handler. I think that
functionality may be useful again at some point.



--Jonathan

*** trap.c.DIST	Wed Jan 18 04:02:09 1995
--- trap.c.PUBLIC	Sat Jan 28 13:35:11 1995
***************
*** 82,87 ****
--- 82,90 ----
  #include <le.h>
  #include <dc.h>
  
+ #include <sys/cdefs.h>
+ #include <sys/syslog.h>
+ 
  struct	proc *machFPCurProcPtr;		/* pointer to last proc to use FP */
  
  extern void MachKernGenException();
***************
*** 91,96 ****
--- 94,100 ----
  extern void MachTLBModException();
  extern void MachTLBMissException();
  extern unsigned MachEmulateBranch();
+ extern void MachUTLBMiss();
  
  void (*machExceptionTable[])() = {
  /*
***************
*** 162,167 ****
--- 166,175 ----
  	u_int	ra;
  	u_int	code;
  } trapdebug[TRAPSIZE], *trp = trapdebug;
+ 
+ extern void idle(),  cpu_switch(), splx(), MachEmptyWriteBuffer();
+ extern void stacktrace();
+ extern void logstacktrace();
  #endif
  
  static void pmax_errintr();
***************
*** 178,183 ****
--- 186,192 ----
  #endif
  int (*pmax_hardware_intr)() = (int (*)())0;
  extern volatile struct chiptime *Mach_clock_addr;
+ extern unsigned long intrcnt[];
  
  #ifdef COMPAT_ULTRIX
  extern struct sysent ultrix_sysent[];
***************
*** 185,191 ****
  extern char *ultrix_syscallnames[];
  #endif
  
! #if defined(COMPAT_ULTRIX) && defined(SYSCALL_DEBUG)
  int ultrix_scdebug = 1;
  
  void
--- 194,205 ----
  extern char *ultrix_syscallnames[];
  #endif
  
! #if (defined(COMPAT_ULTRIX) && defined(SYSCALL_DEBUG))
! #define DEBUG_ULTRIX_SYSCALL 1
! #endif
! 
! 
! #if defined(DEBUG_ULTRIX_SYSCALL)
  int ultrix_scdebug = 1;
  
  void
***************
*** 195,203 ****
  {
  	int i;
  
! 	if (!ultrix_scdebug)
  		return;
! 
  	printf("proc %d: ultrix syscall ", p->p_pid);
  	if (code < 0 || code >= nultrix_sysent) {
  		printf("OUT OF RANGE (%d)", code);
--- 209,222 ----
  {
  	int i;
  
! 	if (0 < code  && code <= nultrix_sysent &&
! 	    ultrix_sysent[code].sy_call == nosys)
! 		printf("Ultrix call %d mapped to bad syscall\n", code);
! 	if (!ultrix_scdebug) {
! 		if (code < 0 || code >= nultrix_sysent)
! 			code = 0;
  		return;
! 	}
  	printf("proc %d: ultrix syscall ", p->p_pid);
  	if (code < 0 || code >= nultrix_sysent) {
  		printf("OUT OF RANGE (%d)", code);
***************
*** 493,499 ****
  				if (i) {
  					locr0[V0] = i;
  					locr0[A3] = 1;
! #if defined(SYSCALL_DEBUG) && defined(COMPAT_ULTRIX)
  					if (p->p_emul == EMUL_ULTRIX)
  						ultrix_scdebug_call(p,
  						        code, callp->sy_narg,
--- 512,518 ----
  				if (i) {
  					locr0[V0] = i;
  					locr0[A3] = 1;
! #if defined(DEBUG_ULTRIX_SYSCALL)
  					if (p->p_emul == EMUL_ULTRIX)
  						ultrix_scdebug_call(p,
  						        code, callp->sy_narg,
***************
*** 538,544 ****
  				if (i) {
  					locr0[V0] = i;
  					locr0[A3] = 1;
! #if defined(SYSCALL_DEBUG) && defined(COMPAT_ULTRIX)
  					if (p->p_emul == EMUL_ULTRIX)
  						ultrix_scdebug_call(p,
  						        code, callp->sy_narg,
--- 557,563 ----
  				if (i) {
  					locr0[V0] = i;
  					locr0[A3] = 1;
! #if defined(DEBUG_ULTRIX_SYSCALL)
  					if (p->p_emul == EMUL_ULTRIX)
  						ultrix_scdebug_call(p,
  						        code, callp->sy_narg,
***************
*** 567,573 ****
  				callp = &systab[SYS_syscall]; /* (illegal) */
  			else
  				callp = &systab[code];
! 			i = callp->sy_narg;
  			args.i[0] = locr0[A0];
  			args.i[1] = locr0[A1];
  			args.i[2] = locr0[A2];
--- 586,596 ----
  				callp = &systab[SYS_syscall]; /* (illegal) */
  			else
  				callp = &systab[code];
! 			/* i = callp->sy_narg*/
! 			i = callp->sy_argsize / sizeof(int);
! 			if (i != callp->sy_narg)
! 				printf("syscall %d: args %d or %d?\n",
! 				       code,i, callp->sy_narg);
  			args.i[0] = locr0[A0];
  			args.i[1] = locr0[A1];
  			args.i[2] = locr0[A2];
***************
*** 580,586 ****
  				if (i) {
  					locr0[V0] = i;
  					locr0[A3] = 1;
! #if defined(SYSCALL_DEBUG) && defined(COMPAT_ULTRIX)
  					if (p->p_emul == EMUL_ULTRIX)
  						ultrix_scdebug_call(p,
  						        code, callp->sy_narg,
--- 603,609 ----
  				if (i) {
  					locr0[V0] = i;
  					locr0[A3] = 1;
! #if defined(DEBUG_ULTRIX_SYSCALL)
  					if (p->p_emul == EMUL_ULTRIX)
  						ultrix_scdebug_call(p,
  						        code, callp->sy_narg,
***************
*** 601,607 ****
  				}
  			}
  		}
! #if defined(SYSCALL_DEBUG) && defined(COMPAT_ULTRIX)
  		if (p->p_emul == EMUL_ULTRIX)
  		        ultrix_scdebug_call(p, code, callp->sy_narg, args.i);
  #endif
--- 624,630 ----
  				}
  			}
  		}
! #if defined(DEBUG_ULTRIX_SYSCALL)
  		if (p->p_emul == EMUL_ULTRIX)
  		        ultrix_scdebug_call(p, code, callp->sy_narg, args.i);
  #endif
***************
*** 661,667 ****
  			locr0[A3] = 1;
  		}
  	done:
! #if defined(COMPAT_ULTRIX) && defined(SYSCALL_DEBUG)
  		if (p->p_emul == EMUL_ULTRIX)
  	                ultrix_scdebug_ret(p, code, i, rval[0]);
  #endif
--- 684,690 ----
  			locr0[A3] = 1;
  		}
  	done:
! #if defined(DEBUG_ULTRIX_SYSCALL)
  		if (p->p_emul == EMUL_ULTRIX)
  	                ultrix_scdebug_ret(p, code, i, rval[0]);
  #endif
***************
*** 873,878 ****
--- 896,902 ----
  	if (pmax_hardware_intr)
  		splx((*pmax_hardware_intr)(mask, pc, statusReg, causeReg));
  	if (mask & MACH_INT_MASK_5) {
+ 		intrcnt[7]++;
  		if (!USERMODE(statusReg)) {
  #ifdef DEBUG
  			trapDump("fpintr");
***************
*** 885,898 ****
  	}
  	if (mask & MACH_SOFT_INT_MASK_0) {
  		clearsoftclock();
  		cnt.v_soft++;
  		softclock();
  	}
  	/* process network interrupt if we trapped or will very soon */
  	if ((mask & MACH_SOFT_INT_MASK_1) ||
  	    netisr && (statusReg & MACH_SOFT_INT_MASK_1)) {
- 		clearsoftnet();
  		cnt.v_soft++;
  #ifdef INET
  		if (netisr & (1 << NETISR_ARP)) {
  			netisr &= ~(1 << NETISR_ARP);
--- 909,925 ----
  	}
  	if (mask & MACH_SOFT_INT_MASK_0) {
  		clearsoftclock();
+ 		intrcnt[0]++;
  		cnt.v_soft++;
  		softclock();
  	}
+ 
  	/* process network interrupt if we trapped or will very soon */
  	if ((mask & MACH_SOFT_INT_MASK_1) ||
  	    netisr && (statusReg & MACH_SOFT_INT_MASK_1)) {
  		cnt.v_soft++;
+ 		intrcnt[1]++;
+ 		clearsoftnet();
  #ifdef INET
  		if (netisr & (1 << NETISR_ARP)) {
  			netisr &= ~(1 << NETISR_ARP);
***************
*** 933,942 ****
--- 960,971 ----
  
  	/* handle clock interrupts ASAP */
  	if (mask & MACH_INT_MASK_3) {
+ 		intrcnt[6]++;
  		temp = c->regc;	/* XXX clear interrupt bits */
  		cf.pc = pc;
  		cf.sr = statusReg;
  		hardclock(&cf);
+ 
  		/* keep clock interrupts enabled */
  		causeReg &= ~MACH_INT_MASK_3;
  	}
***************
*** 943,961 ****
  	/* Re-enable clock interrupts */
  	splx(MACH_INT_MASK_3 | MACH_SR_INT_ENA_CUR);
  #if NSII > 0
! 	if (mask & MACH_INT_MASK_0)
  		siiintr(0);
  #endif
  #if NLE > 0
! 	if (mask & MACH_INT_MASK_1)
  		leintr(0);
  #endif
  #if NDC > 0
! 	if (mask & MACH_INT_MASK_2)
  		dcintr(0);
  #endif
! 	if (mask & MACH_INT_MASK_4)
  		pmax_errintr();
  	return ((statusReg & ~causeReg & MACH_HARD_INT_MASK) |
  		MACH_SR_INT_ENA_CUR);
  }
--- 972,998 ----
  	/* Re-enable clock interrupts */
  	splx(MACH_INT_MASK_3 | MACH_SR_INT_ENA_CUR);
  #if NSII > 0
! 	if (mask & MACH_INT_MASK_0) {
! 		intrcnt[2]++;
  		siiintr(0);
+ 	}
  #endif
  #if NLE > 0
! 	if (mask & MACH_INT_MASK_1) {
! 		intrcnt[3]++;
  		leintr(0);
+ 	}
  #endif
  #if NDC > 0
! 	if (mask & MACH_INT_MASK_2) {
! 		intrcnt[4];
  		dcintr(0);
+ 	}
  #endif
! 	if (mask & MACH_INT_MASK_4) {
! 		intrcnt[5];
  		pmax_errintr();
+ 	}
  	return ((statusReg & ~causeReg & MACH_HARD_INT_MASK) |
  		MACH_SR_INT_ENA_CUR);
  }
***************
*** 987,992 ****
--- 1024,1030 ----
  			warned = 0;
  			printf("WARNING: power supply is OK again\n");
  		}
+ 		intrcnt[6]++;
  
  		temp = c->regc;	/* XXX clear interrupt bits */
  		cf.pc = pc;
***************
*** 999,1009 ****
  	/* Re-enable clock interrupts */
  	splx(MACH_INT_MASK_1 | MACH_SR_INT_ENA_CUR);
  	if (mask & MACH_INT_MASK_0) {
  
  		csr = *(unsigned *)MACH_PHYS_TO_UNCACHED(KN02_SYS_CSR);
  		m = csr & (csr >> KN02_CSR_IOINTEN_SHIFT) & KN02_CSR_IOINT;
  #if 0
! 		*(unsigned *)MACHPHYS_TO_UNCACHED(KN02_SYS_CSR) =
  			(csr & ~(KN02_CSR_WRESERVED | 0xFF)) |
  			(m << KN02_CSR_IOINTEN_SHIFT);
  #endif
--- 1037,1048 ----
  	/* Re-enable clock interrupts */
  	splx(MACH_INT_MASK_1 | MACH_SR_INT_ENA_CUR);
  	if (mask & MACH_INT_MASK_0) {
+ 		static int intr_map[8] = { 8, 9, 10, 11, 12, 4, 3, 2 };
  
  		csr = *(unsigned *)MACH_PHYS_TO_UNCACHED(KN02_SYS_CSR);
  		m = csr & (csr >> KN02_CSR_IOINTEN_SHIFT) & KN02_CSR_IOINT;
  #if 0
! 		*(unsigned *)MACH_PHYS_TO_UNCACHED(KN02_SYS_CSR) =
  			(csr & ~(KN02_CSR_WRESERVED | 0xFF)) |
  			(m << KN02_CSR_IOINTEN_SHIFT);
  #endif
***************
*** 1010,1015 ****
--- 1049,1055 ----
  		for (i = 0; m; i++, m >>= 1) {
  			if (!(m & 1))
  				continue;
+ 			intrcnt[intr_map[i]]++;
  			if (tc_slot_info[i].intr)
  				(*tc_slot_info[i].intr)(tc_slot_info[i].unit);
  			else
***************
*** 1020,1027 ****
  			csr & ~(KN02_CSR_WRESERVED | 0xFF);
  #endif
  	}
! 	if (mask & MACH_INT_MASK_3)
  		kn02_errintr();
  	return ((statusReg & ~causeReg & MACH_HARD_INT_MASK) |
  		MACH_SR_INT_ENA_CUR);
  }
--- 1060,1074 ----
  			csr & ~(KN02_CSR_WRESERVED | 0xFF);
  #endif
  	}
! 	if (mask & MACH_INT_MASK_3) {
! 		intrcnt[5]++;
  		kn02_errintr();
+ 	}
+ 
+ 	/* Reset the interrupts we just handled.  We don't handle software
+ 	 * interrupts here so don't clear them from the function result,  which
+ 	 * will be written into the interrupt-enable status register.
+ 	 */
  	return ((statusReg & ~causeReg & MACH_HARD_INT_MASK) |
  		MACH_SR_INT_ENA_CUR);
  }
***************
*** 1715,1726 ****
  
  #define MIPS_JR_RA	0x03e00008	/* instruction code for jr ra */
  
  /*
   * Print a stack backtrace.
   */
  void
! stacktrace(a0, a1, a2, a3)
  	int a0, a1, a2, a3;
  {
  	unsigned pc, sp, fp, ra, va, subr;
  	unsigned instr, mask;
--- 1762,1790 ----
  
  #define MIPS_JR_RA	0x03e00008	/* instruction code for jr ra */
  
+ /* forward */
+ char *fn_name(unsigned addr);
+ void stacktrace_subr __P((int, int, int, int, void (*)(const char*, ...)));
+ 
  /*
   * Print a stack backtrace.
   */
+ void stacktrace(a0, a1, a2, a3)
+ 	int a0, a1, a2, a3;
+ {
+ 	stacktrace_subr(a0, a1, a2, a3, printf);
+ }
+ 
+ void logstacktrace(a0, a1, a2, a3)
+ 	int a0, a1, a2, a3;
+ {
+ 	stacktrace_subr(a0, a1, a2, a3, addlog);
+ }
+ 
  void
! stacktrace_subr(a0, a1, a2, a3, printfn)
  	int a0, a1, a2, a3;
+ 	void (*printfn) __P((const char*, ...));
  {
  	unsigned pc, sp, fp, ra, va, subr;
  	unsigned instr, mask;
***************
*** 1729,1734 ****
--- 1793,1799 ----
  	int regs[3];
  	extern setsoftclock();
  	extern char start[], edata[];
+ 	unsigned int frames =  0;
  
  	cpu_getregs(regs);
  
***************
*** 1738,1770 ****
  	ra = 0;
  	fp = regs[2];
  
  loop:
! 	/* check for current PC in the kernel interrupt handler code */
  	if (pc >= (unsigned)MachKernIntr && pc < (unsigned)MachUserIntr) {
  		/* NOTE: the offsets depend on the code in locore.s */
! 		printf("interrupt\n");
  		a0 = kdbpeek(sp + 36);
  		a1 = kdbpeek(sp + 40);
  		a2 = kdbpeek(sp + 44);
  		a3 = kdbpeek(sp + 48);
! 		pc = kdbpeek(sp + 20);
! 		ra = kdbpeek(sp + 92);
! 		sp = kdbpeek(sp + 100);
! 		fp = kdbpeek(sp + 104);
  	}
  
! 	/* check for current PC in the exception handler code */
! 	if (pc >= 0x80000000 && pc < (unsigned)setsoftclock) {
  		ra = 0;
! 		subr = 0;
  		goto done;
  	}
  
  	/* check for bad PC */
  	if (pc & 3 || pc < 0x80000000 || pc >= (unsigned)edata) {
! 		printf("PC 0x%x: not in kernel\n", pc);
  		ra = 0;
- 		subr = 0;
  		goto done;
  	}
  
--- 1803,1882 ----
  	ra = 0;
  	fp = regs[2];
  
+ /* Jump here when done with a frame, to start a new one */
  loop:
! 	ra = 0;
! 
! /* Jump here after a nonstandard (interrupt handler) frame */
! specialframe:
! 	stksize = 0;
! 	subr = 0;
! 	if	(frames++ > 100) {
! 		(*printfn)("\nstackframe count exceeded\n");
! 		return;	/*XXX*/
! 	}
! 
! 	/* check for bad SP: could foul up next frame */
! 	if (sp & 3 || sp < 0x80000000) {
! 		(*printfn)("SP 0x%x: not in kernel\n", sp);
! 		ra = 0;
! 		subr = 0;
! 		goto done;
! 	}
! 
! 	/* Backtraces should contine through interrupts from kernel mode */
  	if (pc >= (unsigned)MachKernIntr && pc < (unsigned)MachUserIntr) {
  		/* NOTE: the offsets depend on the code in locore.s */
! 		(*printfn)("MachKernIntr+%x: (%x, %x ,%x) -------\n",
! 		       pc-(unsigned)MachKernIntr, a0, a1, a2);
  		a0 = kdbpeek(sp + 36);
  		a1 = kdbpeek(sp + 40);
  		a2 = kdbpeek(sp + 44);
  		a3 = kdbpeek(sp + 48);
! 
! 		pc = kdbpeek(sp + 20);	/* exc_pc - pc at time of exception */
! 		ra = kdbpeek(sp + 92);	/* ra at time of exception */
! 		sp = sp + 108;
! 		goto specialframe;
  	}
  
! 
! # define Between(x, y, z) \
! 		( ((x) <= (y)) && ((y) < (z)) )
! # define pcBetween(a,b) \
! 		Between((unsigned)a, pc, (unsigned)b)
! 
! 	/*
! 	 * Check for current PC in  exception handler code that don't
! 	 * have a preceding "j ra" at the tail of the preceding function. 
! 	 * Depends on relative ordering of functions in locore.
! 	 */
! 	if (pcBetween(MachKernGenException, MachUserGenException))
! 		subr = (unsigned) MachKernGenException;
! 	else if (pcBetween(MachUserGenException,MachKernIntr))
! 		subr = (unsigned) MachUserGenException;
! 	else if (pcBetween(MachKernIntr, MachUserIntr))
! 		subr = (unsigned) MachKernIntr;
! 	else if (pcBetween(MachUserIntr, MachTLBMissException))
! 		subr = (unsigned) MachUserIntr;
! 	else if (pcBetween(splx, MachEmptyWriteBuffer))
! 		subr = (unsigned) splx;
! 	else if (pcBetween(cpu_switch, fuword))
! 		subr = (unsigned) cpu_switch;
! 	else if (pcBetween(idle, cpu_switch))	{
! 		subr = (unsigned) idle;
  		ra = 0;
! 		goto done;
! 	}
! 	else if (pc >= (unsigned)MachUTLBMiss && pc < (unsigned)setsoftclock) {
! 		(*printfn)("<<locore>>");
  		goto done;
  	}
  
  	/* check for bad PC */
  	if (pc & 3 || pc < 0x80000000 || pc >= (unsigned)edata) {
! 		(*printfn)("PC 0x%x: not in kernel\n", pc);
  		ra = 0;
  		goto done;
  	}
  
***************
*** 1772,1791 ****
  	 * Find the beginning of the current subroutine by scanning backwards
  	 * from the current PC for the end of the previous subroutine.
  	 */
! 	va = pc - sizeof(int);
! 	while ((instr = kdbpeek(va)) != MIPS_JR_RA)
  		va -= sizeof(int);
! 	va += 2 * sizeof(int);	/* skip back over branch & delay slot */
! 	/* skip over nulls which might separate .o files */
! 	while ((instr = kdbpeek(va)) == 0)
! 		va += sizeof(int);
! 	subr = va;
  
  	/* scan forwards to find stack size and any saved registers */
  	stksize = 0;
  	more = 3;
  	mask = 0;
! 	for (; more; va += sizeof(int), more = (more == 3) ? 3 : more - 1) {
  		/* stop if hit our current position */
  		if (va >= pc)
  			break;
--- 1884,1911 ----
  	 * Find the beginning of the current subroutine by scanning backwards
  	 * from the current PC for the end of the previous subroutine.
  	 */
! 	if (!subr) {
! 		va = pc - sizeof(int);
! 		while ((instr = kdbpeek(va)) != MIPS_JR_RA)
  		va -= sizeof(int);
! 		va += 2 * sizeof(int);	/* skip back over branch & delay slot */
! 		/* skip over nulls which might separate .o files */
! 		while ((instr = kdbpeek(va)) == 0)
! 			va += sizeof(int);
! 		subr = va;
! 	}
  
+ 	/*
+ 	 * Jump here for locore entry pointsn for which the preceding
+ 	 * function doesn't end in "j ra"
+ 	 */
+ stackscan:
  	/* scan forwards to find stack size and any saved registers */
  	stksize = 0;
  	more = 3;
  	mask = 0;
! 	for (va = subr; more; va += sizeof(int),
! 	     		      more = (more == 3) ? 3 : more - 1) {
  		/* stop if hit our current position */
  		if (va >= pc)
  			break;
***************
*** 1833,1839 ****
  			/* only restore the first one */
  			if (mask & (1 << i.IType.rt))
  				break;
! 			mask |= 1 << i.IType.rt;
  			switch (i.IType.rt) {
  			case 4: /* a0 */
  				a0 = kdbpeek(sp + (short)i.IType.imm);
--- 1953,1959 ----
  			/* only restore the first one */
  			if (mask & (1 << i.IType.rt))
  				break;
! 			mask |= (1 << i.IType.rt);
  			switch (i.IType.rt) {
  			case 4: /* a0 */
  				a0 = kdbpeek(sp + (short)i.IType.imm);
***************
*** 1865,1886 ****
  			/* look for stack pointer adjustment */
  			if (i.IType.rs != 29 || i.IType.rt != 29)
  				break;
! 			stksize = (short)i.IType.imm;
  		}
  	}
  
  done:
! 	printf("%x+%x (%x,%x,%x,%x) ra %x sz %d\n",
! 		subr, pc - subr, a0, a1, a2, a3, ra, stksize);
  
  	if (ra) {
  		if (pc == ra && stksize == 0)
! 			printf("stacktrace: loop!\n");
  		else {
  			pc = ra;
! 			sp -= stksize;
  			goto loop;
  		}
  	}
  }
  #endif /* DEBUG */
--- 1985,2050 ----
  			/* look for stack pointer adjustment */
  			if (i.IType.rs != 29 || i.IType.rt != 29)
  				break;
! 			stksize = - ((short)i.IType.imm);
  		}
  	}
  
  done:
! 	(*printfn)("%s+%x (%x,%x,%x,%x) ra %x sz %d\n",
! 		fn_name(subr), pc - subr, a0, a1, a2, a3, ra, stksize);
  
  	if (ra) {
  		if (pc == ra && stksize == 0)
! 			(*printfn)("stacktrace: loop!\n");
  		else {
  			pc = ra;
! 			sp += stksize;
! 			ra = 0;
  			goto loop;
  		}
+ 	} else {
+ 		if (curproc)
+ 			(*printfn)("User-level: pid %d\n", curproc->p_pid);
+ 		else
+ 			(*printfn)("User-level: curproc NULL\n");
  	}
  }
+ 
+ /*
+  * Functions ``special'' enough to print by name
+  */
+ #ifdef __STDC__
+ #define Name(_fn)  { (void*)_fn, # _fn }
+ #else
+ #define Name(_fn) { _fn, "_fn"}
+ #endif
+ static struct { void *addr; char *name;} names[] = {
+ 	Name(interrupt),
+ 	Name(trap),
+ 	Name(MachKernGenException),
+ 	Name(MachUserGenException),
+ 	Name(MachKernIntr),
+ 	Name(MachUserIntr),
+ 	Name(splx),
+ 	Name(idle),
+ 	Name(cpu_switch),
+ 	{0, 0}
+ };
+ 
+ /*
+  * Map a function address to a string name, if known; or a hex string.
+  */
+ char *
+ fn_name(unsigned addr)
+ {
+ 	static char buf[17];
+ 	int i = 0;
+ 
+ 	for (i = 0; names[i].name; i++)
+ 		if (names[i].addr == (void*)addr)
+ 			return (names[i].name);
+ 	sprintf(buf, "%x", addr);
+ 	return (buf);
+ }
+ 
  #endif /* DEBUG */