| 1 | /* $NetBSD: db_trace.c,v 1.3 2011/04/21 00:24:07 enami Exp $ */ |
| 2 | |
| 3 | /* |
| 4 | * Mach Operating System |
| 5 | * Copyright (c) 1991,1990 Carnegie Mellon University |
| 6 | * All Rights Reserved. |
| 7 | * |
| 8 | * Permission to use, copy, modify and distribute this software and its |
| 9 | * documentation is hereby granted, provided that both the copyright |
| 10 | * notice and this permission notice appear in all copies of the |
| 11 | * software, derivative works or modified versions, and any portions |
| 12 | * thereof, and that both notices appear in supporting documentation. |
| 13 | * |
| 14 | * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" |
| 15 | * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR |
| 16 | * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. |
| 17 | * |
| 18 | * Carnegie Mellon requests users of this software to return to |
| 19 | * |
| 20 | * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU |
| 21 | * School of Computer Science |
| 22 | * Carnegie Mellon University |
| 23 | * Pittsburgh PA 15213-3890 |
| 24 | * |
| 25 | * any improvements or extensions that they make and grant Carnegie the |
| 26 | * rights to redistribute these changes. |
| 27 | */ |
| 28 | |
| 29 | #include <sys/cdefs.h> |
| 30 | __KERNEL_RCSID(0, "$NetBSD: db_trace.c,v 1.3 2011/04/21 00:24:07 enami Exp $" ); |
| 31 | |
| 32 | #include <sys/param.h> |
| 33 | #include <sys/systm.h> |
| 34 | #include <sys/proc.h> |
| 35 | |
| 36 | #include <uvm/uvm_prot.h> |
| 37 | #include <uvm/uvm_pmap.h> |
| 38 | |
| 39 | #include <machine/frame.h> |
| 40 | #include <machine/trap.h> |
| 41 | #include <machine/intrdefs.h> |
| 42 | #include <machine/pmap.h> |
| 43 | |
| 44 | #include <machine/db_machdep.h> |
| 45 | #include <ddb/db_sym.h> |
| 46 | #include <ddb/db_access.h> |
| 47 | #include <ddb/db_variables.h> |
| 48 | #include <ddb/db_output.h> |
| 49 | #include <ddb/db_interface.h> |
| 50 | #include <ddb/db_user.h> |
| 51 | #include <ddb/db_proc.h> |
| 52 | #include <ddb/db_command.h> |
| 53 | #include <x86/db_machdep.h> |
| 54 | |
| 55 | int |
| 56 | db_x86_regop(const struct db_variable *vp, db_expr_t *val, int opcode) |
| 57 | { |
| 58 | db_expr_t *regaddr = |
| 59 | (db_expr_t *)(((uint8_t *)DDB_REGS) + ((size_t)vp->valuep)); |
| 60 | |
| 61 | switch (opcode) { |
| 62 | case DB_VAR_GET: |
| 63 | *val = *regaddr; |
| 64 | break; |
| 65 | case DB_VAR_SET: |
| 66 | *regaddr = *val; |
| 67 | break; |
| 68 | default: |
| 69 | db_printf("db_x86_regop: unknown op %d" , opcode); |
| 70 | db_error(NULL); |
| 71 | } |
| 72 | return 0; |
| 73 | } |
| 74 | |
| 75 | /* |
| 76 | * Stack trace. |
| 77 | */ |
| 78 | |
| 79 | #if 0 |
| 80 | db_addr_t db_trap_symbol_value = 0; |
| 81 | db_addr_t db_syscall_symbol_value = 0; |
| 82 | db_addr_t db_kdintr_symbol_value = 0; |
| 83 | bool db_trace_symbols_found = false; |
| 84 | |
| 85 | void db_find_trace_symbols(void); |
| 86 | |
| 87 | void |
| 88 | db_find_trace_symbols(void) |
| 89 | { |
| 90 | db_expr_t value; |
| 91 | |
| 92 | if (db_value_of_name("_trap" , &value)) |
| 93 | db_trap_symbol_value = (db_addr_t) value; |
| 94 | if (db_value_of_name("_kdintr" , &value)) |
| 95 | db_kdintr_symbol_value = (db_addr_t) value; |
| 96 | if (db_value_of_name("_syscall" , &value)) |
| 97 | db_syscall_symbol_value = (db_addr_t) value; |
| 98 | db_trace_symbols_found = true; |
| 99 | } |
| 100 | #endif |
| 101 | |
| 102 | void |
| 103 | db_stack_trace_print(db_expr_t addr, bool have_addr, db_expr_t count, |
| 104 | const char *modif, void (*pr)(const char *, ...)) |
| 105 | { |
| 106 | long *frame, *lastframe; |
| 107 | long *retaddr, *arg0; |
| 108 | long *argp; |
| 109 | db_addr_t callpc; |
| 110 | int is_trap; |
| 111 | bool kernel_only = true; |
| 112 | bool trace_thread = false; |
| 113 | bool lwpaddr = false; |
| 114 | |
| 115 | #if 0 |
| 116 | if (!db_trace_symbols_found) |
| 117 | db_find_trace_symbols(); |
| 118 | #endif |
| 119 | |
| 120 | { |
| 121 | const char *cp = modif; |
| 122 | char c; |
| 123 | |
| 124 | while ((c = *cp++) != 0) { |
| 125 | if (c == 'a') { |
| 126 | lwpaddr = true; |
| 127 | trace_thread = true; |
| 128 | } |
| 129 | if (c == 't') |
| 130 | trace_thread = true; |
| 131 | if (c == 'u') |
| 132 | kernel_only = false; |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | #define set_frame_callpc() do { \ |
| 137 | frame = (long *)ddb_regs.tf_bp; \ |
| 138 | callpc = (db_addr_t)ddb_regs.tf_ip; \ |
| 139 | } while (/*CONSTCCOND*/0) |
| 140 | |
| 141 | if (have_addr && trace_thread) { |
| 142 | struct pcb *pcb; |
| 143 | proc_t p; |
| 144 | lwp_t l; |
| 145 | |
| 146 | if (lwpaddr) { |
| 147 | db_read_bytes(addr, sizeof(l), |
| 148 | (char *)&l); |
| 149 | db_read_bytes((db_addr_t)l.l_proc, |
| 150 | sizeof(p), (char *)&p); |
| 151 | (*pr)("trace: pid %d " , p.p_pid); |
| 152 | } else { |
| 153 | proc_t *pp; |
| 154 | |
| 155 | (*pr)("trace: pid %d " , (int)addr); |
| 156 | if ((pp = db_proc_find((pid_t)addr)) == 0) { |
| 157 | (*pr)("not found\n" ); |
| 158 | return; |
| 159 | } |
| 160 | db_read_bytes((db_addr_t)pp, sizeof(p), (char *)&p); |
| 161 | addr = (db_addr_t)p.p_lwps.lh_first; |
| 162 | db_read_bytes(addr, sizeof(l), (char *)&l); |
| 163 | } |
| 164 | (*pr)("lid %d " , l.l_lid); |
| 165 | pcb = lwp_getpcb(&l); |
| 166 | #ifdef _KERNEL |
| 167 | if (l.l_proc == curproc && (lwp_t *)addr == curlwp) |
| 168 | set_frame_callpc(); |
| 169 | else |
| 170 | #endif |
| 171 | { |
| 172 | db_read_bytes((db_addr_t)&pcb->pcb_bp, |
| 173 | sizeof(frame), (char *)&frame); |
| 174 | db_read_bytes((db_addr_t)(frame + 1), |
| 175 | sizeof(callpc), (char *)&callpc); |
| 176 | db_read_bytes((db_addr_t)frame, |
| 177 | sizeof(frame), (char *)&frame); |
| 178 | } |
| 179 | (*pr)("at %p\n" , frame); |
| 180 | } else if (have_addr) { |
| 181 | frame = (long *)addr; |
| 182 | db_read_bytes((db_addr_t)(frame + 1), |
| 183 | sizeof(callpc), (char *)&callpc); |
| 184 | db_read_bytes((db_addr_t)frame, |
| 185 | sizeof(frame), (char *)&frame); |
| 186 | } else |
| 187 | set_frame_callpc(); |
| 188 | retaddr = frame + 1; |
| 189 | arg0 = frame + 2; |
| 190 | |
| 191 | lastframe = 0; |
| 192 | while (count && frame != 0) { |
| 193 | int narg; |
| 194 | const char * name; |
| 195 | db_expr_t offset; |
| 196 | db_sym_t sym; |
| 197 | char *argnames[MAXNARG], **argnp = NULL; |
| 198 | db_addr_t lastcallpc; |
| 199 | |
| 200 | name = "?" ; |
| 201 | is_trap = NONE; |
| 202 | offset = 0; |
| 203 | sym = db_frame_info(frame, callpc, &name, &offset, &is_trap, |
| 204 | &narg); |
| 205 | |
| 206 | if (lastframe == 0 && sym == (db_sym_t)0 && callpc != 0) { |
| 207 | /* Symbol not found, peek at code */ |
| 208 | u_long instr = db_get_value(callpc, 4, false); |
| 209 | |
| 210 | offset = 1; |
| 211 | if ( |
| 212 | #ifdef __x86_64__ |
| 213 | instr == 0xe5894855 || |
| 214 | /* enter: pushq %rbp, movq %rsp, %rbp */ |
| 215 | (instr & 0x00ffffff) == 0x0048e589 |
| 216 | /* enter+1: movq %rsp, %rbp */) |
| 217 | #else |
| 218 | (instr & 0x00ffffff) == 0x00e58955 || |
| 219 | /* enter: pushl %ebp, movl %esp, %ebp */ |
| 220 | (instr & 0x0000ffff) == 0x0000e589 |
| 221 | /* enter+1: movl %esp, %ebp */) |
| 222 | #endif |
| 223 | { |
| 224 | offset = 0; |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | if (is_trap == NONE) { |
| 229 | if (db_sym_numargs(sym, &narg, argnames)) |
| 230 | argnp = argnames; |
| 231 | else |
| 232 | narg = db_numargs(frame); |
| 233 | } |
| 234 | |
| 235 | (*pr)("%s(" , name); |
| 236 | |
| 237 | if (lastframe == 0 && offset == 0 && !have_addr) { |
| 238 | /* |
| 239 | * We have a breakpoint before the frame is set up |
| 240 | * Use %[er]sp instead |
| 241 | */ |
| 242 | argp = (long *)&((struct x86_frame *) |
| 243 | (ddb_regs.tf_sp-sizeof(long)))->f_arg0; |
| 244 | } else { |
| 245 | argp = frame + 2; |
| 246 | } |
| 247 | |
| 248 | while (narg) { |
| 249 | if (argnp) |
| 250 | (*pr)("%s=" , *argnp++); |
| 251 | (*pr)("%lx" , db_get_value((long)argp, sizeof(long), |
| 252 | false)); |
| 253 | argp++; |
| 254 | if (--narg != 0) |
| 255 | (*pr)("," ); |
| 256 | } |
| 257 | (*pr)(") at " ); |
| 258 | db_printsym(callpc, DB_STGY_PROC, pr); |
| 259 | (*pr)("\n" ); |
| 260 | |
| 261 | if (lastframe == 0 && offset == 0 && !have_addr) { |
| 262 | /* Frame really belongs to next callpc */ |
| 263 | struct x86_frame *fp = (void *) |
| 264 | (ddb_regs.tf_sp-sizeof(long)); |
| 265 | lastframe = (long *)fp; |
| 266 | callpc = (db_addr_t) |
| 267 | db_get_value((db_addr_t)&fp->f_retaddr, |
| 268 | sizeof(long), false); |
| 269 | |
| 270 | continue; |
| 271 | } |
| 272 | |
| 273 | lastframe = frame; |
| 274 | lastcallpc = callpc; |
| 275 | if (!db_nextframe(&frame, &retaddr, &arg0, |
| 276 | &callpc, frame + 2, is_trap, pr)) |
| 277 | break; |
| 278 | |
| 279 | if (INKERNEL((long)frame)) { |
| 280 | /* staying in kernel */ |
| 281 | #ifdef __i386__ |
| 282 | if (!db_intrstack_p(frame) |
| 283 | && db_intrstack_p(lastframe)) { |
| 284 | (*pr)("--- switch to interrupt stack ---\n" ); |
| 285 | } else |
| 286 | #endif |
| 287 | if (frame < lastframe || |
| 288 | (frame == lastframe && callpc == lastcallpc)) { |
| 289 | (*pr)("Bad frame pointer: %p\n" , frame); |
| 290 | break; |
| 291 | } |
| 292 | } else if (INKERNEL((long)lastframe)) { |
| 293 | /* switch from user to kernel */ |
| 294 | if (kernel_only) |
| 295 | break; /* kernel stack only */ |
| 296 | } else { |
| 297 | /* in user */ |
| 298 | if (frame <= lastframe) { |
| 299 | (*pr)("Bad user frame pointer: %p\n" , frame); |
| 300 | break; |
| 301 | } |
| 302 | } |
| 303 | --count; |
| 304 | } |
| 305 | |
| 306 | if (count && is_trap != NONE) { |
| 307 | db_printsym(callpc, DB_STGY_XTRN, pr); |
| 308 | (*pr)(":\n" ); |
| 309 | } |
| 310 | #undef set_frame_callpc |
| 311 | } |
| 312 | |