| /* Copyright (c) 2011 The Regents of the University of California |
| * Barret Rhoden <brho@cs.berkeley.edu> |
| * See LICENSE for details. |
| * |
| * Arch-independent kernel debugging */ |
| |
| #include <kdebug.h> |
| #include <kmalloc.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <smp.h> |
| |
| struct symtab_entry gbl_symtab[1] __attribute__((weak)) = {{0, 0}}; |
| |
| /* Returns a null-terminated string from the reflected symbol table with the |
| * function name for a given PC / instruction pointer. Returns NULL on |
| * failure. */ |
| const char *get_fn_name(uintptr_t pc) |
| { |
| struct symtab_entry *i, *prev = 0, *found = 0; |
| |
| /* Table is in ascending order. As soon as we get to an entry greater |
| * than us, we were in the previous one. This is only true if we were |
| * given a good PC. Random addresses will just find the previous |
| * symbol. */ |
| for (i = &gbl_symtab[0]; i->name; i++) { |
| if (i->addr > pc) { |
| found = prev; |
| break; |
| } |
| prev = i; |
| } |
| if (!found) |
| return NULL; |
| assert(found->name); |
| return found->name; |
| } |
| |
| uintptr_t get_symbol_addr(char *sym) |
| { |
| struct symtab_entry *i; |
| |
| for (i = &gbl_symtab[0]; i->name; i++) { |
| if (strcmp(i->name, sym) == 0) |
| return i->addr; |
| } |
| return 0; |
| } |
| |
| static const char *blacklist[] = { |
| "addnode", |
| "addqueue", |
| "allocroute", |
| "balancetree", |
| "calcd", |
| "freeroute", |
| "genrandom", /* not noisy, just never returns */ |
| "limborexmit", |
| "rangecompare", |
| "walkadd", |
| "bnx2x_alloc_rx_data", |
| "bnx2x_frag_alloc", |
| "__dma_map_single", |
| "__dma_mapping_error", |
| "__dma_zalloc_coherent", |
| "__dma_alloc_coherent", |
| "bnx2x_ilt_line_mem_op", |
| "bnx2x_ilt_line_init_op", |
| "bnx2x_ilt_line_wr", |
| "bnx2x_wr_64", |
| "pci_write_config_dword", |
| "bnx2x_init_str_wr", |
| "bnx2x_init_fill", |
| "bnx2x_init_block", |
| "bnx2x_write_big_buf", |
| "bnx2x_init_wr_wb", |
| "bnx2x_write_big_buf_wb", |
| "bnx2x_cl45_read", |
| "bnx2x_cl45_write", |
| "bnx2x_set_mdio_clk", |
| }; |
| |
| static bool is_blacklisted(const char *s) |
| { |
| for (int i = 0; i < ARRAY_SIZE(blacklist); i++) { |
| if (!strcmp(blacklist[i], s)) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| static int tab_depth = 0; |
| |
| /* Call this via kfunc */ |
| void reset_print_func_depth(void) |
| { |
| tab_depth = 0; |
| } |
| |
| static void __print_hdr(void) |
| { |
| struct per_cpu_info *pcpui = &per_cpu_info[core_id()]; |
| printd("Core %2d ", core_id()); /* may help with multicore output */ |
| if (in_irq_ctx(pcpui)) { |
| printk("IRQ :"); |
| } else { |
| assert(pcpui->cur_kthread); |
| if (is_ktask(pcpui->cur_kthread)) { |
| printk("%10s:", pcpui->cur_kthread->name); |
| } else { |
| printk("PID %3d :", pcpui->cur_proc ? |
| pcpui->cur_proc->pid : 0); |
| } |
| } |
| } |
| |
| void __print_func_entry(const char *func, const char *file) |
| { |
| char tentabs[] = "\t\t\t\t\t\t\t\t\t\t"; // ten tabs and a \0 |
| char *ourtabs = &tentabs[10 - MIN(tab_depth, 10)]; |
| |
| if (!printx_on) |
| return; |
| if (is_blacklisted(func)) |
| return; |
| print_lock(); |
| __print_hdr(); |
| printk("%s%s() in %s\n", ourtabs, func, file); |
| print_unlock(); |
| tab_depth++; |
| } |
| |
| void __print_func_exit(const char *func, const char *file) |
| { |
| char tentabs[] = "\t\t\t\t\t\t\t\t\t\t"; // ten tabs and a \0 |
| char *ourtabs; |
| |
| if (!printx_on) |
| return; |
| if (is_blacklisted(func)) |
| return; |
| tab_depth--; |
| ourtabs = &tentabs[10 - MIN(tab_depth, 10)]; |
| print_lock(); |
| __print_hdr(); |
| printk("%s---- %s()\n", ourtabs, func); |
| print_unlock(); |
| } |
| |
| bool printx_on = FALSE; |
| |
| void set_printx(int mode) |
| { |
| switch (mode) { |
| case 0: |
| printx_on = FALSE; |
| break; |
| case 1: |
| printx_on = TRUE; |
| break; |
| case 2: |
| printx_on = !printx_on; |
| break; |
| } |
| } |
| |
| void debug_addr_proc(struct proc *p, unsigned long addr) |
| { |
| struct vm_region *vmr; |
| |
| spin_lock(&p->vmr_lock); |
| TAILQ_FOREACH(vmr, &p->vm_regions, vm_link) { |
| if ((vmr->vm_base <= addr) && (addr < vmr->vm_end)) |
| break; |
| } |
| if (!vmr) { |
| spin_unlock(&p->vmr_lock); |
| printk("Addr %p has no VMR\n", addr); |
| return; |
| } |
| if (!vmr_has_file(vmr)) { |
| spin_unlock(&p->vmr_lock); |
| printk("Addr %p's VMR has no file\n", addr); |
| return; |
| } |
| printk("Addr %p is in %s at offset %p\n", addr, vmr_to_filename(vmr), |
| addr - vmr->vm_base + vmr->vm_foff); |
| spin_unlock(&p->vmr_lock); |
| } |
| |
| void debug_addr_pid(int pid, unsigned long addr) |
| { |
| struct proc *p; |
| p = pid2proc(pid); |
| if (!p) { |
| printk("No such proc for pid %d\n", pid); |
| return; |
| } |
| debug_addr_proc(p, addr); |
| proc_decref(p); |
| } |
| |
| #define BT_FMT "#%02d [<%p>] in %s\n" |
| |
| void print_backtrace_list(uintptr_t *pcs, size_t nr_pcs, void (*pfunc)(void *, |
| const |
| char *), |
| void *opaque) |
| { |
| char bt_line[128]; |
| |
| for (size_t i = 0; i < nr_pcs; i++) { |
| snprintf(bt_line, sizeof(bt_line), BT_FMT, i + 1, pcs[i], |
| get_fn_name(pcs[i])); |
| pfunc(opaque, bt_line); |
| } |
| } |
| |
| void sza_print_backtrace_list(struct sized_alloc *sza, uintptr_t *pcs, |
| size_t nr_pcs) |
| { |
| for (size_t i = 0; i < nr_pcs; i++) |
| sza_printf(sza, BT_FMT, i + 1, pcs[i], get_fn_name(pcs[i])); |
| } |
| |
| static void printk_func(void *opaque, const char *str) |
| { |
| printk("%s", str); |
| } |
| |
| void backtrace(void) |
| { |
| print_lock(); |
| printk("Stack Backtrace on Core %d:\n", core_id()); |
| gen_backtrace(&printk_func, NULL); |
| print_unlock(); |
| } |
| |
| static void trace_printk_func(void *opaque, const char *str) |
| { |
| trace_printk("%s", str); |
| } |
| |
| void backtrace_trace(void) |
| { |
| /* Don't need this strictly, but it helps serialize to the trace buf */ |
| print_lock(); |
| trace_printk("Stack Backtrace on Core %d:\n", core_id()); |
| gen_backtrace(&trace_printk_func, NULL); |
| print_unlock(); |
| } |
| |
| static void trace_printx_func(void *opaque, const char *str) |
| { |
| trace_printx("%s", str); |
| } |
| |
| void backtrace_trace_printx(void) |
| { |
| /* Don't need this strictly, but it helps serialize to the trace buf */ |
| print_lock(); |
| trace_printx("Stack Backtrace on Core %d:\n", core_id()); |
| gen_backtrace(&trace_printk_func, NULL); |
| print_unlock(); |
| } |
| |
| void backtrace_frame(uintptr_t eip, uintptr_t ebp) |
| { |
| uintptr_t pcs[MAX_BT_DEPTH]; |
| size_t nr_pcs = backtrace_list(eip, ebp, pcs, MAX_BT_DEPTH); |
| |
| print_lock(); |
| printk("\nBacktrace of kernel context on Core %d:\n", core_id()); |
| print_backtrace_list(pcs, nr_pcs, &printk_func, NULL); |
| print_unlock(); |
| } |
| |
| /* TODO: change debug_addr_proc() to allow print redirection like |
| * print_backtrace_list(). */ |
| void backtrace_user_frame(uintptr_t eip, uintptr_t ebp) |
| { |
| uintptr_t pcs[MAX_BT_DEPTH]; |
| /* TODO: this assumes we have the user's address space loaded (current). |
| */ |
| size_t nr_pcs = backtrace_user_list(eip, ebp, pcs, MAX_BT_DEPTH); |
| |
| print_lock(); |
| printk("\nBacktrace of user context on Core %d:\n", core_id()); |
| printk("\tOffsets only matter for shared libraries\n"); |
| /* This formatting is consumed by scripts/bt-akaros.sh. */ |
| for (int i = 0; i < nr_pcs; i++) { |
| printk("#%02d ", i + 1); |
| /* TODO: user backtraces all assume we're working on 'current'*/ |
| debug_addr_proc(current, pcs[i]); |
| } |
| print_unlock(); |
| } |
| |
| void backtrace_hwtf(struct hw_trapframe *hw_tf) |
| { |
| if (in_kernel(hw_tf)) |
| backtrace_frame(get_hwtf_pc(hw_tf), get_hwtf_fp(hw_tf)); |
| else |
| backtrace_user_frame(get_hwtf_pc(hw_tf), get_hwtf_fp(hw_tf)); |
| } |
| |
| void backtrace_user_ctx(struct proc *p, struct user_context *ctx) |
| { |
| uintptr_t st_save; |
| |
| if (!ctx) { |
| printk("Null user context!\n"); |
| return; |
| } |
| st_save = switch_to(p); |
| backtrace_user_frame(get_user_ctx_pc(ctx), get_user_ctx_fp(ctx)); |
| switch_back(p, st_save); |
| } |
| |
| void backtrace_current_ctx(void) |
| { |
| if (current) |
| backtrace_user_ctx(current, current_ctx); |
| } |
| |
| void backtrace_kthread(struct kthread *kth) |
| { |
| backtrace_frame(jmpbuf_get_pc(&kth->context), |
| jmpbuf_get_fp(&kth->context)); |
| } |