Something went wrong. Try again.
Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274/* * Kernel and userspace stack tracing. * * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. * * Copyright (C) 2001 - 2013 Tensilica Inc. * Copyright (C) 2015 Cadence Design Systems Inc. */#include <linux/export.h>#include <linux/sched.h>#include <linux/stacktrace.h>
#include <asm/ftrace.h>#include <asm/sections.h>#include <asm/stacktrace.h>#include <asm/traps.h>#include <linux/uaccess.h>
#if IS_ENABLED(CONFIG_PERF_EVENTS)
/* Address of common_exception_return, used to check the * transition from kernel to user space. */extern int common_exception_return;
void xtensa_backtrace_user(struct pt_regs *regs, unsigned int depth, int (*ufn)(struct stackframe *frame, void *data), void *data){ unsigned long windowstart = regs->windowstart; unsigned long windowbase = regs->windowbase; unsigned long a0 = regs->areg[0]; unsigned long a1 = regs->areg[1]; unsigned long pc = regs->pc; struct stackframe frame; int index;
if (!depth--) return;
frame.pc = pc; frame.sp = a1;
if (pc == 0 || pc >= TASK_SIZE || ufn(&frame, data)) return;
if (IS_ENABLED(CONFIG_USER_ABI_CALL0_ONLY) || (IS_ENABLED(CONFIG_USER_ABI_CALL0_PROBE) && !(regs->ps & PS_WOE_MASK))) return;
/* Two steps: * * 1. Look through the register window for the * previous PCs in the call trace. * * 2. Look on the stack. */
/* Step 1. */ /* Rotate WINDOWSTART to move the bit corresponding to * the current window to the bit #0. */ windowstart = (windowstart << WSBITS | windowstart) >> windowbase;
/* Look for bits that are set, they correspond to * valid windows. */ for (index = WSBITS - 1; (index > 0) && depth; depth--, index--) if (windowstart & (1 << index)) { /* Get the PC from a0 and a1. */ pc = MAKE_PC_FROM_RA(a0, pc); /* Read a0 and a1 from the * corresponding position in AREGs. */ a0 = regs->areg[index * 4]; a1 = regs->areg[index * 4 + 1];
frame.pc = pc; frame.sp = a1;
if (pc == 0 || pc >= TASK_SIZE || ufn(&frame, data)) return; }
/* Step 2. */ /* We are done with the register window, we need to * look through the stack. */ if (!depth) return;
/* Start from the a1 register. */ /* a1 = regs->areg[1]; */ while (a0 != 0 && depth--) { pc = MAKE_PC_FROM_RA(a0, pc);
/* Check if the region is OK to access. */ if (!access_ok(&SPILL_SLOT(a1, 0), 8)) return; /* Copy a1, a0 from user space stack frame. */ if (__get_user(a0, &SPILL_SLOT(a1, 0)) || __get_user(a1, &SPILL_SLOT(a1, 1))) return;
frame.pc = pc; frame.sp = a1;
if (pc == 0 || pc >= TASK_SIZE || ufn(&frame, data)) return; }}EXPORT_SYMBOL(xtensa_backtrace_user);
void xtensa_backtrace_kernel(struct pt_regs *regs, unsigned int depth, int (*kfn)(struct stackframe *frame, void *data), int (*ufn)(struct stackframe *frame, void *data), void *data){ unsigned long pc = regs->depc > VALID_DOUBLE_EXCEPTION_ADDRESS ? regs->depc : regs->pc; unsigned long sp_start, sp_end; unsigned long a0 = regs->areg[0]; unsigned long a1 = regs->areg[1];
sp_start = a1 & ~(THREAD_SIZE - 1); sp_end = sp_start + THREAD_SIZE;
/* Spill the register window to the stack first. */ spill_registers();
/* Read the stack frames one by one and create the PC * from the a0 and a1 registers saved there. */ while (a1 > sp_start && a1 < sp_end && depth--) { struct stackframe frame;
frame.pc = pc; frame.sp = a1;
if (kernel_text_address(pc) && kfn(&frame, data)) return;
if (pc == (unsigned long)&common_exception_return) { regs = (struct pt_regs *)a1; if (user_mode(regs)) { if (ufn == NULL) return; xtensa_backtrace_user(regs, depth, ufn, data); return; } a0 = regs->areg[0]; a1 = regs->areg[1]; continue; }
sp_start = a1;
pc = MAKE_PC_FROM_RA(a0, pc); a0 = SPILL_SLOT(a1, 0); a1 = SPILL_SLOT(a1, 1); }}EXPORT_SYMBOL(xtensa_backtrace_kernel);
#endif
void walk_stackframe(unsigned long *sp, int (*fn)(struct stackframe *frame, void *data), void *data){ unsigned long a0, a1; unsigned long sp_end;
a1 = (unsigned long)sp; sp_end = ALIGN(a1, THREAD_SIZE);
spill_registers();
while (a1 < sp_end) { struct stackframe frame;
sp = (unsigned long *)a1;
a0 = SPILL_SLOT(a1, 0); a1 = SPILL_SLOT(a1, 1);
if (a1 <= (unsigned long)sp) break;
frame.pc = MAKE_PC_FROM_RA(a0, _text); frame.sp = a1;
if (fn(&frame, data)) return; }}
#ifdef CONFIG_STACKTRACE
struct stack_trace_data { struct stack_trace *trace; unsigned skip;};
static int stack_trace_cb(struct stackframe *frame, void *data){ struct stack_trace_data *trace_data = data; struct stack_trace *trace = trace_data->trace;
if (trace_data->skip) { --trace_data->skip; return 0; } if (!kernel_text_address(frame->pc)) return 0;
trace->entries[trace->nr_entries++] = frame->pc; return trace->nr_entries >= trace->max_entries;}
void save_stack_trace_tsk(struct task_struct *task, struct stack_trace *trace){ struct stack_trace_data trace_data = { .trace = trace, .skip = trace->skip, }; walk_stackframe(stack_pointer(task), stack_trace_cb, &trace_data);}EXPORT_SYMBOL_GPL(save_stack_trace_tsk);
void save_stack_trace(struct stack_trace *trace){ save_stack_trace_tsk(current, trace);}EXPORT_SYMBOL_GPL(save_stack_trace);
#endif
struct return_addr_data { unsigned long addr; unsigned skip;};
static int return_address_cb(struct stackframe *frame, void *data){ struct return_addr_data *r = data;
if (r->skip) { --r->skip; return 0; } if (!kernel_text_address(frame->pc)) return 0; r->addr = frame->pc; return 1;}
/* * level == 0 is for the return address from the caller of this function, * not from this function itself. */unsigned long return_address(unsigned level){ struct return_addr_data r = { .skip = level, }; walk_stackframe(stack_pointer(NULL), return_address_cb, &r); return r.addr;}EXPORT_SYMBOL(return_address);