diff options
Diffstat (limited to 'arch/mips/kernel/stacktrace.c')
-rw-r--r-- | arch/mips/kernel/stacktrace.c | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/arch/mips/kernel/stacktrace.c b/arch/mips/kernel/stacktrace.c new file mode 100644 index 000000000000..f851d0cc245f --- /dev/null +++ b/arch/mips/kernel/stacktrace.c @@ -0,0 +1,111 @@ +/* + * arch/mips/kernel/stacktrace.c + * + * Stack trace management functions + * + * Copyright (C) 2006 Atsushi Nemoto <anemo@mba.ocn.ne.jp> + */ +#include <linux/sched.h> +#include <linux/stacktrace.h> +#include <asm/stacktrace.h> + +/* + * Save stack-backtrace addresses into a stack_trace buffer: + */ +static void save_raw_context_stack(struct stack_trace *trace, + unsigned int skip, unsigned long reg29) +{ + unsigned long *sp = (unsigned long *)reg29; + unsigned long addr; + + while (!kstack_end(sp)) { + addr = *sp++; + if (__kernel_text_address(addr)) { + if (!skip) + trace->entries[trace->nr_entries++] = addr; + else + skip--; + if (trace->nr_entries >= trace->max_entries) + break; + } + } +} + +static struct pt_regs * save_context_stack(struct stack_trace *trace, + unsigned int skip, struct task_struct *task, struct pt_regs *regs) +{ + unsigned long sp = regs->regs[29]; +#ifdef CONFIG_KALLSYMS + unsigned long ra = regs->regs[31]; + unsigned long pc = regs->cp0_epc; + extern void ret_from_irq(void); + + if (raw_show_trace || !__kernel_text_address(pc)) { + save_raw_context_stack(trace, skip, sp); + return NULL; + } + do { + if (!skip) + trace->entries[trace->nr_entries++] = pc; + else + skip--; + if (trace->nr_entries >= trace->max_entries) + break; + /* + * If we reached the bottom of interrupt context, + * return saved pt_regs. + */ + if (pc == (unsigned long)ret_from_irq) { + unsigned long stack_page = + (unsigned long)task_stack_page(task); + if (!stack_page || + sp < stack_page || + sp > stack_page + THREAD_SIZE - 32) + break; + return (struct pt_regs *)sp; + } + pc = unwind_stack(task, &sp, pc, ra); + ra = 0; + } while (pc); +#else + save_raw_context_stack(sp); +#endif + + return NULL; +} + +/* + * Save stack-backtrace addresses into a stack_trace buffer. + * If all_contexts is set, all contexts (hardirq, softirq and process) + * are saved. If not set then only the current context is saved. + */ +void save_stack_trace(struct stack_trace *trace, + struct task_struct *task, int all_contexts, + unsigned int skip) +{ + struct pt_regs dummyregs; + struct pt_regs *regs = &dummyregs; + + WARN_ON(trace->nr_entries || !trace->max_entries); + + if (task && task != current) { + regs->regs[29] = task->thread.reg29; + regs->regs[31] = 0; + regs->cp0_epc = task->thread.reg31; + } else { + if (!task) + task = current; + prepare_frametrace(regs); + } + + while (1) { + regs = save_context_stack(trace, skip, task, regs); + if (!all_contexts || !regs || + trace->nr_entries >= trace->max_entries) + break; + trace->entries[trace->nr_entries++] = ULONG_MAX; + if (trace->nr_entries >= trace->max_entries) + break; + skip = 0; + } +} |