Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f16fb1ec authored by Russell King's avatar Russell King Committed by Russell King
Browse files

[ARM] Add stacktrace support and make oprofile use it



Add support for stacktrace.  Use the new stacktrace code with
oprofile instead of it's version; there's no point having
multiple versions of stacktracing in the kernel.

Signed-off-by: default avatarRussell King <rmk+kernel@arm.linux.org.uk>
parent ed519ded
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -67,6 +67,14 @@ config GENERIC_HARDIRQS
	bool
	default y

config STACKTRACE_SUPPORT
	bool
	default y

config LOCKDEP_SUPPORT
	bool
	default y

config TRACE_IRQFLAGS_SUPPORT
	bool
	default y
+2 −2
Original line number Diff line number Diff line
@@ -7,8 +7,8 @@ AFLAGS_head.o := -DTEXT_OFFSET=$(TEXT_OFFSET)
# Object file lists.

obj-y		:= compat.o entry-armv.o entry-common.o irq.o \
		   process.o ptrace.o semaphore.o setup.o signal.o sys_arm.o \
		   time.o traps.o
		   process.o ptrace.o semaphore.o setup.o signal.o \
		   sys_arm.o stacktrace.o time.o traps.o

obj-$(CONFIG_ISA_DMA_API)	+= dma.o
obj-$(CONFIG_ARCH_ACORN)	+= ecard.o 
+73 −0
Original line number Diff line number Diff line
#include <linux/sched.h>
#include <linux/stacktrace.h>

#include "stacktrace.h"

int walk_stackframe(unsigned long fp, unsigned long low, unsigned long high,
		    int (*fn)(struct stackframe *, void *), void *data)
{
	struct stackframe *frame;

	do {
		/*
		 * Check current frame pointer is within bounds
		 */
		if ((fp - 12) < low || fp + 4 >= high)
			break;

		frame = (struct stackframe *)(fp - 12);

		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;
}

#ifdef CONFIG_STACKTRACE
struct stack_trace_data {
	struct stack_trace *trace;
	unsigned int skip;
};

static int save_trace(struct stackframe *frame, void *d)
{
	struct stack_trace_data *data = d;
	struct stack_trace *trace = data->trace;

	if (data->skip) {
		data->skip--;
		return 0;
	}

	trace->entries[trace->nr_entries++] = frame->lr;

	return trace->nr_entries >= trace->max_entries;
}

void save_stack_trace(struct stack_trace *trace, struct task_struct *task)
{
	struct stack_trace_data data;
	unsigned long fp, base;

	data.trace = trace;
	data.skip = trace->skip;

	if (task) {
		base = (unsigned long)task_stack_page(task);
		fp = 0; /* FIXME */
	} else {
		base = (unsigned long)task_stack_page(current);
		asm("mov %0, fp" : "=r" (fp));
	}

	walk_stackframe(fp, base, base + THREAD_SIZE, save_trace, &data);
}
#endif
+9 −0
Original line number Diff line number Diff line
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);
+17 −52
Original line number Diff line number Diff line
@@ -19,6 +19,19 @@
#include <asm/ptrace.h>
#include <asm/uaccess.h>

#include "../kernel/stacktrace.h"

static int report_trace(struct stackframe *frame, void *d)
{
	unsigned int *depth = d;

	if (*depth) {
		oprofile_add_trace(frame->lr);
		(*depth)--;
	}

	return *depth == 0;
}

/*
 * The registers we're interested in are at the end of the variable
@@ -32,21 +45,6 @@ struct frame_tail {
	unsigned long lr;
} __attribute__((packed));


#ifdef CONFIG_FRAME_POINTER
static struct frame_tail* kernel_backtrace(struct frame_tail *tail)
{
	oprofile_add_trace(tail->lr);

	/* frame pointers should strictly progress back up the stack
	 * (towards higher addresses) */
	if (tail >= tail->fp)
		return NULL;

	return tail->fp-1;
}
#endif

static struct frame_tail* user_backtrace(struct frame_tail *tail)
{
	struct frame_tail buftail[2];
@@ -67,47 +65,14 @@ static struct frame_tail* user_backtrace(struct frame_tail *tail)
	return buftail[0].fp-1;
}

/*
 * |             | /\ Higher addresses
 * |             |
 * --------------- stack base (address of current_thread_info)
 * | thread info |
 * .             .
 * |    stack    |
 * --------------- saved regs->ARM_fp value if valid (frame_tail address)
 * .             .
 * --------------- struct pt_regs stored on stack (struct pt_regs *)
 * |             |
 * .             .
 * |             |
 * --------------- %esp
 * |             |
 * |             | \/ Lower addresses
 *
 * Thus, &pt_regs <-> stack base restricts the valid(ish) fp values
 */
static int valid_kernel_stack(struct frame_tail *tail, struct pt_regs *regs)
{
	unsigned long tailaddr = (unsigned long)tail;
	unsigned long stack = (unsigned long)regs;
	unsigned long stack_base = (stack & ~(THREAD_SIZE - 1)) + THREAD_SIZE;

	return (tailaddr > stack) && (tailaddr < stack_base);
}

void arm_backtrace(struct pt_regs * const regs, unsigned int depth)
{
	struct frame_tail *tail;

	tail = ((struct frame_tail *) regs->ARM_fp) - 1;
	struct frame_tail *tail = ((struct frame_tail *) regs->ARM_fp) - 1;

	if (!user_mode(regs)) {

#ifdef CONFIG_FRAME_POINTER
		while (depth-- && tail && valid_kernel_stack(tail, regs)) {
			tail = kernel_backtrace(tail);
		}
#endif
		unsigned long base = ((unsigned long)regs) & ~(THREAD_SIZE - 1);
		walk_stackframe(regs->ARM_fp, base, base + THREAD_SIZE,
				report_trace, &depth);
		return;
	}