[ARM] 5382/1: unwind: Reorganise the stacktrace support
This patch changes the walk_stacktrace and its callers for easier integration of stack unwinding. The arch/arm/kernel/stacktrace.h file is also moved to arch/arm/include/asm/stacktrace.h. Signed-off-by: Catalin Marinas <catalin.marinas@arm.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
This commit is contained in:
parent
67a94c23bb
commit
2d7c11bfc9
7 changed files with 109 additions and 59 deletions
15
arch/arm/include/asm/stacktrace.h
Normal file
15
arch/arm/include/asm/stacktrace.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
#ifndef __ASM_STACKTRACE_H
|
||||
#define __ASM_STACKTRACE_H
|
||||
|
||||
struct stackframe {
|
||||
unsigned long fp;
|
||||
unsigned long sp;
|
||||
unsigned long lr;
|
||||
unsigned long pc;
|
||||
};
|
||||
|
||||
extern int unwind_frame(struct stackframe *frame);
|
||||
extern void walk_stackframe(struct stackframe *frame,
|
||||
int (*fn)(struct stackframe *, void *), void *data);
|
||||
|
||||
#endif /* __ASM_STACKTRACE_H */
|
|
@ -99,6 +99,8 @@ static inline struct thread_info *current_thread_info(void)
|
|||
|
||||
#define thread_saved_pc(tsk) \
|
||||
((unsigned long)(task_thread_info(tsk)->cpu_context.pc))
|
||||
#define thread_saved_sp(tsk) \
|
||||
((unsigned long)(task_thread_info(tsk)->cpu_context.sp))
|
||||
#define thread_saved_fp(tsk) \
|
||||
((unsigned long)(task_thread_info(tsk)->cpu_context.fp))
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include <asm/processor.h>
|
||||
#include <asm/system.h>
|
||||
#include <asm/thread_notify.h>
|
||||
#include <asm/stacktrace.h>
|
||||
#include <asm/mach/time.h>
|
||||
|
||||
static const char *processor_modes[] = {
|
||||
|
@ -372,23 +373,21 @@ EXPORT_SYMBOL(kernel_thread);
|
|||
|
||||
unsigned long get_wchan(struct task_struct *p)
|
||||
{
|
||||
unsigned long fp, lr;
|
||||
unsigned long stack_start, stack_end;
|
||||
struct stackframe frame;
|
||||
int count = 0;
|
||||
if (!p || p == current || p->state == TASK_RUNNING)
|
||||
return 0;
|
||||
|
||||
stack_start = (unsigned long)end_of_stack(p);
|
||||
stack_end = (unsigned long)task_stack_page(p) + THREAD_SIZE;
|
||||
|
||||
fp = thread_saved_fp(p);
|
||||
frame.fp = thread_saved_fp(p);
|
||||
frame.sp = thread_saved_sp(p);
|
||||
frame.lr = 0; /* recovered from the stack */
|
||||
frame.pc = thread_saved_pc(p);
|
||||
do {
|
||||
if (fp < stack_start || fp > stack_end)
|
||||
int ret = unwind_frame(&frame);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
lr = ((unsigned long *)fp)[-1];
|
||||
if (!in_sched_functions(lr))
|
||||
return lr;
|
||||
fp = *(unsigned long *) (fp - 12);
|
||||
if (!in_sched_functions(frame.pc))
|
||||
return frame.pc;
|
||||
} while (count ++ < 16);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -2,34 +2,59 @@
|
|||
#include <linux/sched.h>
|
||||
#include <linux/stacktrace.h>
|
||||
|
||||
#include "stacktrace.h"
|
||||
#include <asm/stacktrace.h>
|
||||
|
||||
int walk_stackframe(unsigned long fp, unsigned long low, unsigned long high,
|
||||
int (*fn)(struct stackframe *, void *), void *data)
|
||||
#if defined(CONFIG_FRAME_POINTER) && !defined(CONFIG_ARM_UNWIND)
|
||||
/*
|
||||
* Unwind the current stack frame and store the new register values in the
|
||||
* structure passed as argument. Unwinding is equivalent to a function return,
|
||||
* hence the new PC value rather than LR should be used for backtrace.
|
||||
*
|
||||
* With framepointer enabled, a simple function prologue looks like this:
|
||||
* mov ip, sp
|
||||
* stmdb sp!, {fp, ip, lr, pc}
|
||||
* sub fp, ip, #4
|
||||
*
|
||||
* A simple function epilogue looks like this:
|
||||
* ldm sp, {fp, sp, pc}
|
||||
*
|
||||
* Note that with framepointer enabled, even the leaf functions have the same
|
||||
* prologue and epilogue, therefore we can ignore the LR value in this case.
|
||||
*/
|
||||
int unwind_frame(struct stackframe *frame)
|
||||
{
|
||||
struct stackframe *frame;
|
||||
unsigned long high, low;
|
||||
unsigned long fp = frame->fp;
|
||||
|
||||
do {
|
||||
/*
|
||||
* Check current frame pointer is within bounds
|
||||
*/
|
||||
if (fp < (low + 12) || fp + 4 >= high)
|
||||
break;
|
||||
/* only go to a higher address on the stack */
|
||||
low = frame->sp;
|
||||
high = ALIGN(low, THREAD_SIZE) + THREAD_SIZE;
|
||||
|
||||
frame = (struct stackframe *)(fp - 12);
|
||||
/* check current frame pointer is within bounds */
|
||||
if (fp < (low + 12) || fp + 4 >= high)
|
||||
return -EINVAL;
|
||||
|
||||
/* restore the registers from the stack frame */
|
||||
frame->fp = *(unsigned long *)(fp - 12);
|
||||
frame->sp = *(unsigned long *)(fp - 8);
|
||||
frame->pc = *(unsigned long *)(fp - 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
void walk_stackframe(struct stackframe *frame,
|
||||
int (*fn)(struct stackframe *, void *), void *data)
|
||||
{
|
||||
while (1) {
|
||||
int ret;
|
||||
|
||||
if (fn(frame, data))
|
||||
break;
|
||||
|
||||
/*
|
||||
* Update the low bound - the next frame must always
|
||||
* be at a higher address than the current frame.
|
||||
*/
|
||||
low = fp + 4;
|
||||
fp = frame->fp;
|
||||
} while (fp);
|
||||
|
||||
return 0;
|
||||
ret = unwind_frame(frame);
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(walk_stackframe);
|
||||
|
||||
|
@ -44,7 +69,7 @@ static int save_trace(struct stackframe *frame, void *d)
|
|||
{
|
||||
struct stack_trace_data *data = d;
|
||||
struct stack_trace *trace = data->trace;
|
||||
unsigned long addr = frame->lr;
|
||||
unsigned long addr = frame->pc;
|
||||
|
||||
if (data->no_sched_functions && in_sched_functions(addr))
|
||||
return 0;
|
||||
|
@ -61,11 +86,10 @@ static int save_trace(struct stackframe *frame, void *d)
|
|||
void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
|
||||
{
|
||||
struct stack_trace_data data;
|
||||
unsigned long fp, base;
|
||||
struct stackframe frame;
|
||||
|
||||
data.trace = trace;
|
||||
data.skip = trace->skip;
|
||||
base = (unsigned long)task_stack_page(tsk);
|
||||
|
||||
if (tsk != current) {
|
||||
#ifdef CONFIG_SMP
|
||||
|
@ -76,14 +100,22 @@ void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
|
|||
BUG();
|
||||
#else
|
||||
data.no_sched_functions = 1;
|
||||
fp = thread_saved_fp(tsk);
|
||||
frame.fp = thread_saved_fp(tsk);
|
||||
frame.sp = thread_saved_sp(tsk);
|
||||
frame.lr = 0; /* recovered from the stack */
|
||||
frame.pc = thread_saved_pc(tsk);
|
||||
#endif
|
||||
} else {
|
||||
register unsigned long current_sp asm ("sp");
|
||||
|
||||
data.no_sched_functions = 0;
|
||||
asm("mov %0, fp" : "=r" (fp));
|
||||
frame.fp = (unsigned long)__builtin_frame_address(0);
|
||||
frame.sp = current_sp;
|
||||
frame.lr = (unsigned long)__builtin_return_address(0);
|
||||
frame.pc = (unsigned long)save_stack_trace_tsk;
|
||||
}
|
||||
|
||||
walk_stackframe(fp, base, base + THREAD_SIZE, save_trace, &data);
|
||||
walk_stackframe(&frame, save_trace, &data);
|
||||
if (trace->nr_entries < trace->max_entries)
|
||||
trace->entries[trace->nr_entries++] = ULONG_MAX;
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
struct stackframe {
|
||||
unsigned long fp;
|
||||
unsigned long sp;
|
||||
unsigned long lr;
|
||||
unsigned long pc;
|
||||
};
|
||||
|
||||
int walk_stackframe(unsigned long fp, unsigned long low, unsigned long high,
|
||||
int (*fn)(struct stackframe *, void *), void *data);
|
|
@ -33,6 +33,7 @@
|
|||
|
||||
#include <asm/leds.h>
|
||||
#include <asm/thread_info.h>
|
||||
#include <asm/stacktrace.h>
|
||||
#include <asm/mach/time.h>
|
||||
|
||||
/*
|
||||
|
@ -55,14 +56,22 @@ EXPORT_SYMBOL(rtc_lock);
|
|||
#ifdef CONFIG_SMP
|
||||
unsigned long profile_pc(struct pt_regs *regs)
|
||||
{
|
||||
unsigned long fp, pc = instruction_pointer(regs);
|
||||
struct stackframe frame;
|
||||
|
||||
if (in_lock_functions(pc)) {
|
||||
fp = regs->ARM_fp;
|
||||
pc = ((unsigned long *)fp)[-1];
|
||||
}
|
||||
if (!in_lock_functions(regs->ARM_pc))
|
||||
return regs->ARM_pc;
|
||||
|
||||
return pc;
|
||||
frame.fp = regs->ARM_fp;
|
||||
frame.sp = regs->ARM_sp;
|
||||
frame.lr = regs->ARM_lr;
|
||||
frame.pc = regs->ARM_pc;
|
||||
do {
|
||||
int ret = unwind_frame(&frame);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
} while (in_lock_functions(frame.pc));
|
||||
|
||||
return frame.pc;
|
||||
}
|
||||
EXPORT_SYMBOL(profile_pc);
|
||||
#endif
|
||||
|
|
|
@ -18,15 +18,14 @@
|
|||
#include <linux/mm.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <asm/ptrace.h>
|
||||
|
||||
#include "../kernel/stacktrace.h"
|
||||
#include <asm/stacktrace.h>
|
||||
|
||||
static int report_trace(struct stackframe *frame, void *d)
|
||||
{
|
||||
unsigned int *depth = d;
|
||||
|
||||
if (*depth) {
|
||||
oprofile_add_trace(frame->lr);
|
||||
oprofile_add_trace(frame->pc);
|
||||
(*depth)--;
|
||||
}
|
||||
|
||||
|
@ -70,9 +69,12 @@ void arm_backtrace(struct pt_regs * const regs, unsigned int depth)
|
|||
struct frame_tail *tail = ((struct frame_tail *) regs->ARM_fp) - 1;
|
||||
|
||||
if (!user_mode(regs)) {
|
||||
unsigned long base = ((unsigned long)regs) & ~(THREAD_SIZE - 1);
|
||||
walk_stackframe(regs->ARM_fp, base, base + THREAD_SIZE,
|
||||
report_trace, &depth);
|
||||
struct stackframe frame;
|
||||
frame.fp = regs->ARM_fp;
|
||||
frame.sp = regs->ARM_sp;
|
||||
frame.lr = regs->ARM_lr;
|
||||
frame.pc = regs->ARM_pc;
|
||||
walk_stackframe(&frame, report_trace, &depth);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue