/* $NetBSD: db_trace.c,v 1.60 2008/07/02 19:49:58 rmind Exp $ */ /* * Mach Operating System * Copyright (c) 1991,1990 Carnegie Mellon University * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * * Carnegie Mellon requests users of this software to return to * * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU * School of Computer Science * Carnegie Mellon University * Pittsburgh PA 15213-3890 * * any improvements or extensions that they make and grant Carnegie the * rights to redistribute these changes. */ #include <sys/cdefs.h> __KERNEL_RCSID(0, "$NetBSD: db_trace.c,v 1.60 2008/07/02 19:49:58 rmind Exp $"); #include <sys/param.h> #include <sys/systm.h> #include <sys/proc.h> #include <sys/user.h> #include <machine/db_machdep.h> #include <machine/frame.h> #include <machine/trap.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> /* * Machine set. */ #define dbreg(xx) (long *)offsetof(db_regs_t, tf_ ## xx) static int db_i386_regop(const struct db_variable *, db_expr_t *, int); const struct db_variable db_regs[] = { { "ds", dbreg(ds), db_i386_regop, NULL }, { "es", dbreg(es), db_i386_regop, NULL }, { "fs", dbreg(fs), db_i386_regop, NULL }, { "gs", dbreg(gs), db_i386_regop, NULL }, { "edi", dbreg(edi), db_i386_regop, NULL }, { "esi", dbreg(esi), db_i386_regop, NULL }, { "ebp", dbreg(ebp), db_i386_regop, NULL }, { "ebx", dbreg(ebx), db_i386_regop, NULL }, { "edx", dbreg(edx), db_i386_regop, NULL }, { "ecx", dbreg(ecx), db_i386_regop, NULL }, { "eax", dbreg(eax), db_i386_regop, NULL }, { "eip", dbreg(eip), db_i386_regop, NULL }, { "cs", dbreg(cs), db_i386_regop, NULL }, { "eflags", dbreg(eflags), db_i386_regop, NULL }, { "esp", dbreg(esp), db_i386_regop, NULL }, { "ss", dbreg(ss), db_i386_regop, NULL }, }; const struct db_variable * const db_eregs = db_regs + sizeof(db_regs)/sizeof(db_regs[0]); static int db_i386_regop (const struct db_variable *vp, db_expr_t *val, int opcode) { db_expr_t *regaddr = (db_expr_t *)(((uint8_t *)DDB_REGS) + ((size_t)vp->valuep)); switch (opcode) { case DB_VAR_GET: *val = *regaddr; break; case DB_VAR_SET: *regaddr = *val; break; default: panic("db_i386_regop: unknown op %d", opcode); } return 0; } /* * Stack trace. */ #define INKERNEL(va) (((vaddr_t)(va)) >= VM_MIN_KERNEL_ADDRESS) struct i386_frame { struct i386_frame *f_frame; int f_retaddr; int f_arg0; }; #define NONE 0 #define TRAP 1 #define SYSCALL 2 #define INTERRUPT 3 #define INTERRUPT_TSS 4 #define TRAP_TSS 5 #define MAXNARG 16 db_addr_t db_trap_symbol_value = 0; db_addr_t db_syscall_symbol_value = 0; db_addr_t db_kdintr_symbol_value = 0; bool db_trace_symbols_found = false; #if 0 static void db_find_trace_symbols(void) { db_expr_t value; if (db_value_of_name("_trap", &value)) db_trap_symbol_value = (db_addr_t) value; if (db_value_of_name("_kdintr", &value)) db_kdintr_symbol_value = (db_addr_t) value; if (db_value_of_name("_syscall", &value)) db_syscall_symbol_value = (db_addr_t) value; db_trace_symbols_found = true; } #endif /* * Figure out how many arguments were passed into the frame at "fp". */ static int db_numargs(int *retaddrp) { int *argp; int inst; int args; extern char etext[]; argp = (int *)db_get_value((int)retaddrp, 4, false); if (argp < (int *)VM_MIN_KERNEL_ADDRESS || argp > (int *)etext) { args = 10; } else { inst = db_get_value((int)argp, 4, false); if ((inst & 0xff) == 0x59) /* popl %ecx */ args = 1; else if ((inst & 0xffff) == 0xc483) /* addl %n, %esp */ args = ((inst >> 16) & 0xff) / 4; else args = 10; } return (args); } static db_sym_t db_frame_info(int *frame, db_addr_t callpc, const char **namep, db_expr_t *offp, int *is_trap, int *nargp) { db_expr_t offset; db_sym_t sym; int narg; const char *name; sym = db_search_symbol(callpc, DB_STGY_ANY, &offset); db_symbol_values(sym, &name, NULL); if (sym == (db_sym_t)0) return (db_sym_t)0; *is_trap = NONE; narg = MAXNARG; if (INKERNEL((int)frame) && name) { /* * XXX traps should be based off of the Xtrap* * locations rather than on trap, since some traps * (e.g., npxdna) don't go through trap() */ #ifdef __ELF__ if (!strcmp(name, "trap_tss")) { *is_trap = TRAP_TSS; narg = 0; } else if (!strcmp(name, "trap")) { *is_trap = TRAP; narg = 0; } else if (!strcmp(name, "syscall_plain") || !strcmp(name, "syscall_fancy")) { *is_trap = SYSCALL; narg = 0; } else if (name[0] == 'X') { if (!strncmp(name, "Xintr", 5) || !strncmp(name, "Xresume", 7) || !strncmp(name, "Xstray", 6) || !strncmp(name, "Xhold", 5) || !strncmp(name, "Xrecurse", 8) || !strcmp(name, "Xdoreti") || !strncmp(name, "Xsoft", 5)) { *is_trap = INTERRUPT; narg = 0; } else if (!strncmp(name, "Xtss_", 5)) { *is_trap = INTERRUPT_TSS; narg = 0; } } #else if (!strcmp(name, "_trap_tss")) { *is_trap = TRAP_TSS; narg = 0; } else if (!strcmp(name, "_trap")) { *is_trap = TRAP; narg = 0; } else if (!strcmp(name, "_syscall")) { *is_trap = SYSCALL; narg = 0; } else if (name[0] == '_' && name[1] == 'X') { if (!strncmp(name, "_Xintr", 6) || !strncmp(name, "_Xresume", 8) || !strncmp(name, "_Xstray", 7) || !strncmp(name, "_Xhold", 6) || !strncmp(name, "_Xrecurse", 9) || !strcmp(name, "_Xdoreti") || !strncmp(name, "_Xsoft", 6)) { *is_trap = INTERRUPT; narg = 0; } else if (!strncmp(name, "_Xtss_", 6)) { *is_trap = INTERRUPT_TSS; narg = 0; } } #endif /* __ELF__ */ } if (offp != NULL) *offp = offset; if (nargp != NULL) *nargp = narg; if (namep != NULL) *namep = name; return sym; } /* * 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. */ static int db_nextframe( int **nextframe, /* IN/OUT */ int **retaddr, /* IN/OUT */ int **arg0, /* OUT */ db_addr_t *ip, /* OUT */ int *argp, /* IN */ int is_trap, void (*pr)(const char *, ...)) { struct trapframe *tf; struct i386tss *tss; struct i386_frame *fp; struct intrframe *ifp; int traptype, trapno, err, i; switch (is_trap) { case NONE: *ip = (db_addr_t) db_get_value((int)*retaddr, 4, false); fp = (struct i386_frame *) db_get_value((int)*nextframe, 4, false); if (fp == NULL) return 0; *nextframe = (int *)&fp->f_frame; *retaddr = (int *)&fp->f_retaddr; *arg0 = (int *)&fp->f_arg0; break; case TRAP_TSS: case INTERRUPT_TSS: tss = (struct i386tss *)*argp; *ip = tss->__tss_eip; fp = (struct i386_frame *)tss->tss_ebp; if (fp == NULL) return 0; *nextframe = (int *)&fp->f_frame; *retaddr = (int *)&fp->f_retaddr; *arg0 = (int *)&fp->f_arg0; if (is_trap == INTERRUPT_TSS) (*pr)("--- interrupt via task gate ---\n"); else (*pr)("--- trap via task gate ---\n"); break; case TRAP: case SYSCALL: case INTERRUPT: default: /* The only argument to trap() or syscall() is the trapframe. */ tf = *(struct trapframe **)argp; switch (is_trap) { case TRAP: (*pr)("--- trap (number %d) ---\n", tf->tf_trapno); break; case SYSCALL: (*pr)("--- syscall (number %d) ---\n", tf->tf_eax); break; case INTERRUPT: (*pr)("--- interrupt ---\n"); /* * Get intrframe address as saved when switching * to interrupt stack, and convert to trapframe * (add 4). See frame.h. */ tf = (struct trapframe *)(*(argp - 1) + 4); break; } *ip = (db_addr_t)tf->tf_eip; fp = (struct i386_frame *)tf->tf_ebp; if (fp == NULL) return 0; *nextframe = (int *)&fp->f_frame; *retaddr = (int *)&fp->f_retaddr; *arg0 = (int *)&fp->f_arg0; break; } /* * A bit of a hack. Since %ebp may be used in the stub code, * walk the stack looking for a valid interrupt frame. Such * a frame can be recognized by always having * err 0 or IREENT_MAGIC and trapno T_ASTFLT. */ if (db_frame_info(*nextframe, (db_addr_t)*ip, NULL, NULL, &traptype, NULL) != (db_sym_t)0 && traptype == INTERRUPT) { for (i = 0; i < 4; i++) { ifp = (struct intrframe *)(argp + i); err = db_get_value((int)&ifp->__if_err, 4, false); trapno = db_get_value((int)&ifp->__if_trapno, 4, false); if ((err == 0 || err == IREENT_MAGIC) && trapno == T_ASTFLT) { *nextframe = (int *)ifp - 1; break; } } if (i == 4) { (*pr)("DDB lost frame for "); db_printsym(*ip, DB_STGY_ANY, pr); (*pr)(", trying %p\n",argp); *nextframe = argp; } } return 1; } static bool db_intrstack_p(const void *vp) { const struct cpu_info *ci; CPU_INFO_ITERATOR cii; for (CPU_INFO_FOREACH(cii, ci)) { const char *cp = ci->ci_intrstack; if (cp == NULL) { continue; } if ((cp - INTRSTACKSIZE + 4) <= (const char *)vp && (const char *)vp <= cp) { return true; } } return false; } void db_stack_trace_print(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif, void (*pr)(const char *, ...)) { int *frame, *lastframe; int *retaddr, *arg0; int *argp; db_addr_t callpc; int is_trap = NONE; bool kernel_only = true; bool trace_thread = false; bool lwpaddr = false; #if 0 if (!db_trace_symbols_found) db_find_trace_symbols(); #endif { const char *cp = modif; char c; while ((c = *cp++) != 0) { if (c == 'a') { lwpaddr = true; trace_thread = true; } if (c == 't') trace_thread = true; if (c == 'u') kernel_only = false; } } if (!have_addr) { frame = (int *)ddb_regs.tf_ebp; callpc = (db_addr_t)ddb_regs.tf_eip; } else { if (trace_thread) { struct proc *p; struct user *u; struct lwp *l; if (lwpaddr) { l = (struct lwp *)addr; p = l->l_proc; (*pr)("trace: pid %d ", p->p_pid); } else { (*pr)("trace: pid %d ", (int)addr); p = p_find(addr, PFIND_LOCKED); if (p == NULL) { (*pr)("not found\n"); return; } l = LIST_FIRST(&p->p_lwps); KASSERT(l != NULL); } (*pr)("lid %d ", l->l_lid); if (!(l->l_flag & LW_INMEM)) { (*pr)("swapped out\n"); return; } u = l->l_addr; if (p == curproc && l == curlwp) { frame = (int *)ddb_regs.tf_ebp; callpc = (db_addr_t)ddb_regs.tf_eip; (*pr)("at %p\n", frame); } else { frame = (int *)u->u_pcb.pcb_ebp; callpc = (db_addr_t) db_get_value((int)(frame + 1), 4, false); (*pr)("at %p\n", frame); frame = (int *)*frame; /* XXXfvdl db_get_value? */ } } else { frame = (int *)addr; callpc = (db_addr_t) db_get_value((int)(frame + 1), 4, false); frame = (int *)*frame; /* XXXfvdl db_get_value? */ } } retaddr = frame + 1; arg0 = frame + 2; lastframe = 0; while (count && frame != 0) { int narg; const char * name; db_expr_t offset; db_sym_t sym; char *argnames[MAXNARG], **argnp = NULL; db_addr_t lastcallpc; name = "?"; is_trap = NONE; offset = 0; sym = db_frame_info(frame, callpc, &name, &offset, &is_trap, &narg); if (lastframe == 0 && sym == (db_sym_t)0) { /* Symbol not found, peek at code */ int instr = db_get_value(callpc, 4, false); offset = 1; if ((instr & 0x00ffffff) == 0x00e58955 || /* enter: pushl %ebp, movl %esp, %ebp */ (instr & 0x0000ffff) == 0x0000e589 /* enter+1: movl %esp, %ebp */) { offset = 0; } } if (is_trap == NONE) { if (db_sym_numargs(sym, &narg, argnames)) argnp = argnames; else narg = db_numargs(frame); } (*pr)("%s(", name); if (lastframe == 0 && offset == 0 && !have_addr) { /* * We have a breakpoint before the frame is set up * Use %esp instead */ argp = &((struct i386_frame *)(ddb_regs.tf_esp-4))->f_arg0; } else { argp = frame + 2; } while (narg) { if (argnp) (*pr)("%s=", *argnp++); (*pr)("%lx", db_get_value((int)argp, 4, false)); argp++; if (--narg != 0) (*pr)(","); } (*pr)(") at "); db_printsym(callpc, DB_STGY_PROC, pr); (*pr)("\n"); if (lastframe == 0 && offset == 0 && !have_addr) { /* Frame really belongs to next callpc */ struct i386_frame *fp = (void *)(ddb_regs.tf_esp-4); lastframe = (int *)fp; callpc = (db_addr_t) db_get_value((db_addr_t)&fp->f_retaddr, 4, false); continue; } lastframe = frame; lastcallpc = callpc; if (!db_nextframe(&frame, &retaddr, &arg0, &callpc, frame + 2, is_trap, pr)) break; if (INKERNEL((int)frame)) { /* staying in kernel */ if (!db_intrstack_p(frame) && db_intrstack_p(lastframe)) { (*pr)("--- switch to interrupt stack ---\n"); } else if (frame < lastframe || (frame == lastframe && callpc == lastcallpc)) { (*pr)("Bad frame pointer: %p\n", frame); break; } } else if (INKERNEL((int)lastframe)) { /* switch from user to kernel */ if (kernel_only) break; /* kernel stack only */ } else { /* in user */ if (frame <= lastframe) { (*pr)("Bad user frame pointer: %p\n", frame); break; } } --count; } if (count && is_trap != NONE) { db_printsym(callpc, DB_STGY_XTRN, pr); (*pr)(":\n"); } }