Finally add full stack trace support by grovelling function prologues to
determine the call stack. (I was sick of doing this manually)
This commit is contained in:
parent
47e178df57
commit
910c7664b3
|
@ -1,4 +1,4 @@
|
|||
/* $NetBSD: db_trace.c,v 1.1 2002/08/26 10:16:44 scw Exp $ */
|
||||
/* $NetBSD: db_trace.c,v 1.2 2002/09/19 10:05:25 scw Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright 2002 Wasabi Systems, Inc.
|
||||
|
@ -39,6 +39,7 @@
|
|||
|
||||
#include <sys/param.h>
|
||||
#include <sys/proc.h>
|
||||
#include <sys/user.h>
|
||||
#include <sys/systm.h>
|
||||
|
||||
#include <machine/db_machdep.h>
|
||||
|
@ -52,10 +53,483 @@
|
|||
#include <ddb/db_output.h>
|
||||
#include <ddb/ddbvar.h>
|
||||
|
||||
|
||||
/*
|
||||
* Given a signed immediate value 'imm', of length 'bits', return a
|
||||
* sign-extended version of 'imm'.
|
||||
*/
|
||||
#define IMM_EXT(imm, bits) \
|
||||
((((imm) & (1 << ((bits) - 1))) != 0) ? ((imm) | (-1 << (bits))) : (imm))
|
||||
|
||||
/*
|
||||
* A stack frame is created using "addi{,.l} r15, -n, r15"
|
||||
*/
|
||||
#define OP_ADDI_R15_n_R15_M 0xfff003ff
|
||||
#define OP_ADDI_n(op) IMM_EXT(((int)((op) >> 10) & 0x3ff), 10)
|
||||
#ifndef _LP64
|
||||
#define OP_ADDI_R15_n_R15 0xd4f000f0
|
||||
#else
|
||||
#define OP_ADDI_R15_n_R15 0xd0f000f0
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The prologue is generally terminated by "add{,.l} r15, r63, r14"
|
||||
*/
|
||||
#define OP_ADD_R15_R63_R14_M 0xffffffff
|
||||
#ifndef _LP64
|
||||
#define OP_ADD_R15_R63_R14 0x00f8fce0
|
||||
#else
|
||||
#define OP_ADD_R15_R63_R14 0x00f9fce0
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Identify the opcodes which preserve r18 and r14
|
||||
*/
|
||||
#define OP_ST_R15_n_R14_M 0xfff003ff
|
||||
#define OP_ST_R15_n_R18_M 0xfff003ff
|
||||
#ifndef _LP64
|
||||
#define OP_ST_R15_n_R14 0xa8f000e0
|
||||
#define OP_ST_R15_n_R18 0xa8f00120
|
||||
#define OP_ST_n(op) (IMM_EXT((int)(((op) >> 10) & 0x3ff), 10) * 4)
|
||||
#else
|
||||
#define OP_ST_R15_n_R14 0xacf000e0
|
||||
#define OP_ST_R15_n_R18 0xacf00120
|
||||
#define OP_ST_n(op) (IMM_EXT((int)(((op) >> 10) & 0x3ff), 10) * 8)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Leaf functions will generally just stuff r18 into a branch-target register
|
||||
* using the "ptabs" instruction.
|
||||
*/
|
||||
#define OP_PTABS_R18_TRn_M 0xfffffd8f
|
||||
#define OP_PTABS_R18_TRn 0x6bf14800
|
||||
#define OP_PTABS_n(op) (((op) >> 4) & 0x7)
|
||||
|
||||
/*
|
||||
* Depending on how many callee-saved registers the prologue pushes on the
|
||||
* stack, we could have to search a fair few instructions to find what we're
|
||||
* looking for. The following figure seems to be sufficient for the kernel.
|
||||
*/
|
||||
#define PROLOGUE_LEN 56
|
||||
|
||||
static db_addr_t find_prologue(db_addr_t);
|
||||
static int prev_frame(db_addr_t, db_addr_t, db_addr_t *, db_addr_t *);
|
||||
|
||||
static struct intrframe *cur_intrframe;
|
||||
|
||||
void
|
||||
db_stack_trace_print(db_expr_t addr, int have_addr, db_expr_t count,
|
||||
char *modif, void (*pr)(const char *, ...))
|
||||
{
|
||||
struct proc *p;
|
||||
db_addr_t pc, fp;
|
||||
db_addr_t nextpc, nextfp;
|
||||
db_sym_t sym;
|
||||
db_expr_t diff;
|
||||
char *symp;
|
||||
int trace_thread;
|
||||
|
||||
(*pr)("sh5 stack tracing not yet implemented\n");
|
||||
/* trace_thread is non-zero if tracing a specific process */
|
||||
trace_thread = (strchr(modif, 't') != NULL);
|
||||
|
||||
if (have_addr == 0) {
|
||||
/*
|
||||
* The usual case. Trace back from the current trapframe
|
||||
*/
|
||||
pc = (db_addr_t) ddb_regs.tf_state.sf_spc & ~1;
|
||||
fp = (db_addr_t) ddb_regs.tf_caller.r14;
|
||||
cur_intrframe = &ddb_regs.tf_ifr;
|
||||
} else {
|
||||
/*
|
||||
* Otherwise, the user specified an "address".
|
||||
*/
|
||||
if (trace_thread) {
|
||||
/*
|
||||
* Trace a specific process.
|
||||
*/
|
||||
(*pr)("trace: pid %d ", (int)addr);
|
||||
p = pfind(addr);
|
||||
if (p == NULL) {
|
||||
(*pr)("not found\n");
|
||||
return;
|
||||
}
|
||||
if ((p->p_flag & P_INMEM) == 0) {
|
||||
(*pr)("swapped out\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* When the process is NOT current, its PC and FP
|
||||
* are stashed in the process' PCB. Otherwise, just
|
||||
* use the current stack frame.
|
||||
*/
|
||||
if (p != curproc) {
|
||||
pc = (db_addr_t) p->p_addr->u_pcb.pcb_ctx.sf_pc;
|
||||
pc &= ~1;
|
||||
fp = (db_addr_t) p->p_addr->u_pcb.pcb_ctx.sf_fp;
|
||||
cur_intrframe = NULL;
|
||||
} else {
|
||||
pc = (db_addr_t) ddb_regs.tf_state.sf_spc & ~1;
|
||||
fp = (db_addr_t) ddb_regs.tf_caller.r14;
|
||||
cur_intrframe = &ddb_regs.tf_ifr;
|
||||
}
|
||||
(*pr)("at 0x%lx\n", fp);
|
||||
} else {
|
||||
(*pr)("sh5 trace requires known PC =eject=\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Walk the call stack until the PC or FP are not valid
|
||||
*/
|
||||
while (pc >= SH5_KSEG0_BASE && fp >= SH5_KSEG0_BASE &&
|
||||
(pc & 3) == 0 && (fp & 7) == 0) {
|
||||
/*
|
||||
* Lookup the name of the current function
|
||||
*/
|
||||
symp = NULL;
|
||||
if ((sym = db_search_symbol(pc, DB_STGY_PROC, &diff)) == NULL) {
|
||||
(*pr)("0x%lx: Symbol not found\n");
|
||||
break;
|
||||
}
|
||||
symp = NULL;
|
||||
db_symbol_values(sym, &symp, NULL);
|
||||
if (symp == NULL) {
|
||||
(*pr)("0x%lx: No symbol string found\n");
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compensate for the <handy breakpoint location after
|
||||
* process "wakes"> symbol in ltsleep(), which matches
|
||||
* before the real function name.
|
||||
*/
|
||||
if (strcmp(symp, "bpendtsleep") == 0) {
|
||||
symp = NULL;
|
||||
sym = db_search_symbol(pc - 4, DB_STGY_PROC, &diff);
|
||||
if (sym == NULL) {
|
||||
(*pr)("0x%lx: Symbol not found\n");
|
||||
break;
|
||||
}
|
||||
symp = NULL;
|
||||
db_symbol_values(sym, &symp, NULL);
|
||||
if (symp == NULL) {
|
||||
(*pr)("0x%lx: No symbol string found\n");
|
||||
break;
|
||||
}
|
||||
diff += 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* There's no point even trying to grovel for function
|
||||
* parameters. It's just Too Much Trouble.
|
||||
* A determined hacker should be able to use the Frame
|
||||
* Pointer value and a disassembly of the prologue to
|
||||
* figure out what's what anyway.
|
||||
*/
|
||||
(*pr)("%s(fp=0x%lx) at ", symp, fp);
|
||||
db_printsym(pc, DB_STGY_PROC, pr);
|
||||
(*pr)("\n");
|
||||
|
||||
/*
|
||||
* There really is no point trying to trace back
|
||||
* through "idle". It has no valid "context" anyway.
|
||||
*/
|
||||
if (strcmp(symp, "idle") == 0)
|
||||
break;
|
||||
|
||||
if (strcmp(symp, "Lsh5_event_sync") == 0) {
|
||||
/*
|
||||
* We're tracing back through a syncronous exception.
|
||||
* The previous PC and FP are available from the
|
||||
* 'struct trapframe' saved on the stack.
|
||||
*/
|
||||
struct trapframe *tf = (struct trapframe *)fp;
|
||||
pc = (db_addr_t) tf->tf_state.sf_spc & ~1;
|
||||
fp = (db_addr_t) tf->tf_caller.r14;
|
||||
cur_intrframe = &tf->tf_ifr;
|
||||
} else
|
||||
if (strcmp(symp, "Lsh5_event_interrupt") == 0) {
|
||||
/*
|
||||
* We're tracing back through an asyncronous
|
||||
* exception (hardware interrupt). The previous PC
|
||||
* and FP are available from the 'struct intrframe'
|
||||
* saved on the stack.
|
||||
*/
|
||||
struct intrframe *tf = (struct intrframe *)fp;
|
||||
pc = (db_addr_t) tf->if_state.sf_spc & ~1;
|
||||
fp = (db_addr_t) tf->if_caller.r14;
|
||||
cur_intrframe = tf;
|
||||
} else
|
||||
if (prev_frame(fp, pc, &nextfp, &nextpc) == 0) {
|
||||
(*pr)("Can't find caller's stack frame.\n");
|
||||
break;
|
||||
} else {
|
||||
/*
|
||||
* Otherwise, we were able to determine the caller's
|
||||
* details by grovelling the current function's
|
||||
* stack frame.
|
||||
*/
|
||||
fp = nextfp;
|
||||
pc = nextpc & ~1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If the loop terminated due to a bad PC or FP value, print
|
||||
* the reason why.
|
||||
*/
|
||||
if (pc < SH5_KSEG0_BASE) {
|
||||
(*pr)("Program counter is in user-space: 0x%lx(fp=0x%lx)\n",
|
||||
pc, fp);
|
||||
} else
|
||||
if (fp < SH5_KSEG0_BASE) {
|
||||
(*pr)("Frame pointer is in user-space: 0x%lx(fp=0x%lx)\n",
|
||||
pc, fp);
|
||||
} else
|
||||
if ((pc & 3) != 0)
|
||||
(*pr)("Program counter is invalid: 0x%lx(fp=0x%lx)\n", pc, fp);
|
||||
else
|
||||
if ((fp & 7) != 0)
|
||||
(*pr)("Frame pointer is invalid: 0x%lx(fp=0x%lx)\n", pc, fp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a Program Counter address, find the address of the start of
|
||||
* the function (the prologue) which contains the PC's address.
|
||||
*/
|
||||
static db_addr_t
|
||||
find_prologue(db_addr_t addr)
|
||||
{
|
||||
db_sym_t sym;
|
||||
db_expr_t diff;
|
||||
char *symp;
|
||||
|
||||
addr &= ~3;
|
||||
|
||||
again:
|
||||
symp = NULL;
|
||||
sym = db_search_symbol(addr, DB_STGY_PROC, &diff);
|
||||
if (sym == NULL)
|
||||
return (0);
|
||||
|
||||
symp = NULL;
|
||||
db_symbol_values(sym, &symp, NULL);
|
||||
|
||||
if (symp == NULL)
|
||||
return (0);
|
||||
|
||||
/*
|
||||
* Compensate for the <handy breakpoint location after process "wakes">
|
||||
* symbol in ltsleep(), which screws up our search for the function's
|
||||
* prologue.
|
||||
*/
|
||||
if (strcmp(symp, "bpendtsleep") == 0) {
|
||||
addr -= 4;
|
||||
goto again;
|
||||
}
|
||||
|
||||
return (addr - diff);
|
||||
}
|
||||
|
||||
/*
|
||||
* Given the current frame-pointer and program counter, figure out the
|
||||
* caller's frame-pointer and program counter.
|
||||
*
|
||||
* This involves much grovelling around in the current function's prologue
|
||||
* to determine where it stashed the caller's details in the stack frame.
|
||||
*/
|
||||
static int
|
||||
prev_frame(db_addr_t curfp, db_addr_t curpc,
|
||||
db_addr_t *pprevfp, db_addr_t *pprevpc)
|
||||
{
|
||||
db_addr_t prologue;
|
||||
db_addr_t prevfp;
|
||||
opcode_t op;
|
||||
long r14off, r18off;
|
||||
long lastfpoff, v;
|
||||
int i, found_frame_setup;
|
||||
|
||||
/*
|
||||
* We need to grovel the current function's prologue to figure out
|
||||
* where the saved r18/r14 pair are stored in the function's stack.
|
||||
* If we can't locate the prologue, we're stuffed.
|
||||
*/
|
||||
if ((prologue = find_prologue(curpc)) == 0)
|
||||
return (0);
|
||||
|
||||
lastfpoff = r14off = r18off = 0;
|
||||
prevfp = 0;
|
||||
found_frame_setup = 0;
|
||||
|
||||
/*
|
||||
* Search at most PROLOGUE_LEN instructions.
|
||||
*/
|
||||
for (i = 0; i < PROLOGUE_LEN; i++, prologue += sizeof(opcode_t)) {
|
||||
/*
|
||||
* Short-circuit, for the case where we're stopped right in the
|
||||
* middle of the prologue (e.g. breakpoint at top of func.)
|
||||
* We can just pull the required values straight out the frame.
|
||||
*/
|
||||
if (prologue >= curpc && cur_intrframe) {
|
||||
*pprevfp = cur_intrframe->if_caller.r14;
|
||||
*pprevpc = cur_intrframe->if_caller.r18 & ~1;
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch an opcode from the prologue.
|
||||
*/
|
||||
op = *((opcode_t *)prologue);
|
||||
|
||||
if ((op & OP_ADDI_R15_n_R15_M) == OP_ADDI_R15_n_R15) {
|
||||
/*
|
||||
* Found "addi r15, -n, r15"
|
||||
*
|
||||
* By accumulating the offsets '-n', we can determine
|
||||
* the size of stack frame the current function
|
||||
* created. This is then used to 'pop' the stack to
|
||||
* give us the base of the caller's frame.
|
||||
*/
|
||||
lastfpoff += OP_ADDI_n(op);
|
||||
prevfp = curfp - lastfpoff;
|
||||
} else
|
||||
if ((op & OP_ST_R15_n_R14_M) == OP_ST_R15_n_R14) {
|
||||
/*
|
||||
* Found "st r15, n, r14"
|
||||
*
|
||||
* The prologue is saving the address of caller's
|
||||
* stack frame. We will use this later to verify
|
||||
* our idea of the size of the current function's
|
||||
* frame.
|
||||
*/
|
||||
if (r14off || lastfpoff == 0)
|
||||
return (0); /* Found a duplicate! */
|
||||
|
||||
/*
|
||||
* Record the offset (from the CALLER'S frame) at
|
||||
* which the current function stored r14.
|
||||
*/
|
||||
r14off = OP_ST_n(op) + lastfpoff;
|
||||
} else
|
||||
if ((op & OP_ST_R15_n_R18_M) == OP_ST_R15_n_R18) {
|
||||
/*
|
||||
* Found "st r15, n, r18"
|
||||
*
|
||||
* The prologue is saving the caller's return address
|
||||
* in the stack frame.
|
||||
*/
|
||||
if (r18off || lastfpoff == 0)
|
||||
return (0); /* Found a duplicate! */
|
||||
|
||||
/*
|
||||
* Record the offset (from the CALLER'S frame) at
|
||||
* which the current function stored r18.
|
||||
*/
|
||||
r18off = OP_ST_n(op) + lastfpoff;
|
||||
} else
|
||||
if ((op & OP_PTABS_R18_TRn_M) == OP_PTABS_R18_TRn) {
|
||||
/*
|
||||
* Found "ptabs r18, trn"
|
||||
*
|
||||
* This is for the benefit of Leaf functions, which
|
||||
* generally don't need to save the return address in
|
||||
* the frame. They tend to stuff the address straight
|
||||
* into a branch-target register.
|
||||
*/
|
||||
if (r18off)
|
||||
return (0); /* Found a duplicate */
|
||||
|
||||
/*
|
||||
* Fix up an "invalid" offset, which will be noticed
|
||||
* later, so as not to grovel the frame.
|
||||
*/
|
||||
r18off = -1;
|
||||
|
||||
/*
|
||||
* Pick the value out of the specified branch-target
|
||||
* register.
|
||||
*/
|
||||
switch (OP_PTABS_n(op)) {
|
||||
case 0: *pprevpc = (db_addr_t)ddb_regs.tf_caller.tr0;
|
||||
break;
|
||||
case 1: *pprevpc = (db_addr_t)ddb_regs.tf_caller.tr1;
|
||||
break;
|
||||
case 2: *pprevpc = (db_addr_t)ddb_regs.tf_caller.tr2;
|
||||
break;
|
||||
case 3: *pprevpc = (db_addr_t)ddb_regs.tf_caller.tr3;
|
||||
break;
|
||||
case 4: *pprevpc = (db_addr_t)ddb_regs.tf_caller.tr4;
|
||||
break;
|
||||
case 5: *pprevpc = (db_addr_t)ddb_regs.tf_callee.tr5;
|
||||
break;
|
||||
case 6: *pprevpc = (db_addr_t)ddb_regs.tf_callee.tr6;
|
||||
break;
|
||||
case 7: *pprevpc = (db_addr_t)ddb_regs.tf_callee.tr7;
|
||||
break;
|
||||
}
|
||||
} else
|
||||
if ((op & OP_ADD_R15_R63_R14_M) == OP_ADD_R15_R63_R14) {
|
||||
/*
|
||||
* Found "add r15, r63, r14"
|
||||
*
|
||||
* Some prologues contain TWO "addi r15, -n, r15"
|
||||
* instructions. We need to find both of them if
|
||||
* we are to guess correctly the size of the frame.
|
||||
* Fortunately, if we see this instruction, we can
|
||||
* be sure the prologue has finished creating space
|
||||
* for the frame. All that's left now is to continue
|
||||
* searching for any remaining r14/r18 stores.
|
||||
*/
|
||||
found_frame_setup = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Terminate the search if we've got all the required details.
|
||||
*/
|
||||
if (found_frame_setup && r14off && r18off && prevfp)
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we searched PROLOGUE_LEN opcodes but still didn't find anything
|
||||
* useful, punt.
|
||||
*/
|
||||
if (r14off == 0 || r18off == 0 || prevfp == 0)
|
||||
return (0);
|
||||
|
||||
/*
|
||||
* Simply add r14off or r18off to prevfp to get the address of
|
||||
* where the function stashed them.
|
||||
*
|
||||
* XXX: Should check for valid address
|
||||
*/
|
||||
|
||||
/* Fetch the saved r14 (caller's frame pointer) */
|
||||
db_read_bytes(r14off + prevfp, sizeof(long), (char *)&v);
|
||||
|
||||
/*
|
||||
* If all is well, the value we read will be identical to the
|
||||
* caller's frame pointer we calculated in the loop above.
|
||||
*/
|
||||
if (v != prevfp)
|
||||
return (0);
|
||||
|
||||
*pprevfp = (db_addr_t)v;
|
||||
|
||||
/*
|
||||
* If we need to fetch the saved r18 ...
|
||||
*/
|
||||
if (r18off != -1) {
|
||||
db_read_bytes(r18off + prevfp, sizeof(long), (char *)&v);
|
||||
*pprevpc = (db_addr_t)v;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure to clear the SHmedia flag in the PC
|
||||
*/
|
||||
*pprevpc &= ~1;
|
||||
|
||||
return (1);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue