forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
275 lines
5.9 KiB
275 lines
5.9 KiB
/* |
|
* 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/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, a1); |
|
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 |
|
|
|
#ifdef CONFIG_FRAME_POINTER |
|
|
|
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); |
|
|
|
#endif
|
|
|