![Frederic Weisbecker](/assets/img/avatar_default.png)
The current print_context_stack helper that does the stack walking job is good for usual stacktraces as it walks through all the stack and reports even addresses that look unreliable, which is nice when we don't have frame pointers for example. But we have users like perf that only require reliable stacktraces, and those may want a more adapted stack walker, so lets make this function a callback in stacktrace_ops that users can tune for their needs. Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Arnaldo Carvalho de Melo <acme@redhat.com> Cc: Paul Mackerras <paulus@samba.org> LKML-Reference: <1261024834-5336-1-git-send-regression-fweisbec@gmail.com> Signed-off-by: Ingo Molnar <mingo@elte.hu>
330 lines
6.5 KiB
C
330 lines
6.5 KiB
C
/*
|
|
* trace stack traces
|
|
*
|
|
* Copyright (C) 2004-2008, Soeren Sandmann
|
|
* Copyright (C) 2007 Steven Rostedt <srostedt@redhat.com>
|
|
* Copyright (C) 2008 Ingo Molnar <mingo@redhat.com>
|
|
*/
|
|
#include <linux/kallsyms.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/hrtimer.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/ftrace.h>
|
|
#include <linux/module.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/fs.h>
|
|
|
|
#include <asm/stacktrace.h>
|
|
|
|
#include "trace.h"
|
|
|
|
static struct trace_array *sysprof_trace;
|
|
static int __read_mostly tracer_enabled;
|
|
|
|
/*
|
|
* 1 msec sample interval by default:
|
|
*/
|
|
static unsigned long sample_period = 1000000;
|
|
static const unsigned int sample_max_depth = 512;
|
|
|
|
static DEFINE_MUTEX(sample_timer_lock);
|
|
/*
|
|
* Per CPU hrtimers that do the profiling:
|
|
*/
|
|
static DEFINE_PER_CPU(struct hrtimer, stack_trace_hrtimer);
|
|
|
|
struct stack_frame {
|
|
const void __user *next_fp;
|
|
unsigned long return_address;
|
|
};
|
|
|
|
static int copy_stack_frame(const void __user *fp, struct stack_frame *frame)
|
|
{
|
|
int ret;
|
|
|
|
if (!access_ok(VERIFY_READ, fp, sizeof(*frame)))
|
|
return 0;
|
|
|
|
ret = 1;
|
|
pagefault_disable();
|
|
if (__copy_from_user_inatomic(frame, fp, sizeof(*frame)))
|
|
ret = 0;
|
|
pagefault_enable();
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct backtrace_info {
|
|
struct trace_array_cpu *data;
|
|
struct trace_array *tr;
|
|
int pos;
|
|
};
|
|
|
|
static void
|
|
backtrace_warning_symbol(void *data, char *msg, unsigned long symbol)
|
|
{
|
|
/* Ignore warnings */
|
|
}
|
|
|
|
static void backtrace_warning(void *data, char *msg)
|
|
{
|
|
/* Ignore warnings */
|
|
}
|
|
|
|
static int backtrace_stack(void *data, char *name)
|
|
{
|
|
/* Don't bother with IRQ stacks for now */
|
|
return -1;
|
|
}
|
|
|
|
static void backtrace_address(void *data, unsigned long addr, int reliable)
|
|
{
|
|
struct backtrace_info *info = data;
|
|
|
|
if (info->pos < sample_max_depth && reliable) {
|
|
__trace_special(info->tr, info->data, 1, addr, 0);
|
|
|
|
info->pos++;
|
|
}
|
|
}
|
|
|
|
static const struct stacktrace_ops backtrace_ops = {
|
|
.warning = backtrace_warning,
|
|
.warning_symbol = backtrace_warning_symbol,
|
|
.stack = backtrace_stack,
|
|
.address = backtrace_address,
|
|
.walk_stack = print_context_stack,
|
|
};
|
|
|
|
static int
|
|
trace_kernel(struct pt_regs *regs, struct trace_array *tr,
|
|
struct trace_array_cpu *data)
|
|
{
|
|
struct backtrace_info info;
|
|
unsigned long bp;
|
|
char *stack;
|
|
|
|
info.tr = tr;
|
|
info.data = data;
|
|
info.pos = 1;
|
|
|
|
__trace_special(info.tr, info.data, 1, regs->ip, 0);
|
|
|
|
stack = ((char *)regs + sizeof(struct pt_regs));
|
|
#ifdef CONFIG_FRAME_POINTER
|
|
bp = regs->bp;
|
|
#else
|
|
bp = 0;
|
|
#endif
|
|
|
|
dump_trace(NULL, regs, (void *)stack, bp, &backtrace_ops, &info);
|
|
|
|
return info.pos;
|
|
}
|
|
|
|
static void timer_notify(struct pt_regs *regs, int cpu)
|
|
{
|
|
struct trace_array_cpu *data;
|
|
struct stack_frame frame;
|
|
struct trace_array *tr;
|
|
const void __user *fp;
|
|
int is_user;
|
|
int i;
|
|
|
|
if (!regs)
|
|
return;
|
|
|
|
tr = sysprof_trace;
|
|
data = tr->data[cpu];
|
|
is_user = user_mode(regs);
|
|
|
|
if (!current || current->pid == 0)
|
|
return;
|
|
|
|
if (is_user && current->state != TASK_RUNNING)
|
|
return;
|
|
|
|
__trace_special(tr, data, 0, 0, current->pid);
|
|
|
|
if (!is_user)
|
|
i = trace_kernel(regs, tr, data);
|
|
else
|
|
i = 0;
|
|
|
|
/*
|
|
* Trace user stack if we are not a kernel thread
|
|
*/
|
|
if (current->mm && i < sample_max_depth) {
|
|
regs = (struct pt_regs *)current->thread.sp0 - 1;
|
|
|
|
fp = (void __user *)regs->bp;
|
|
|
|
__trace_special(tr, data, 2, regs->ip, 0);
|
|
|
|
while (i < sample_max_depth) {
|
|
frame.next_fp = NULL;
|
|
frame.return_address = 0;
|
|
if (!copy_stack_frame(fp, &frame))
|
|
break;
|
|
if ((unsigned long)fp < regs->sp)
|
|
break;
|
|
|
|
__trace_special(tr, data, 2, frame.return_address,
|
|
(unsigned long)fp);
|
|
fp = frame.next_fp;
|
|
|
|
i++;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* Special trace entry if we overflow the max depth:
|
|
*/
|
|
if (i == sample_max_depth)
|
|
__trace_special(tr, data, -1, -1, -1);
|
|
|
|
__trace_special(tr, data, 3, current->pid, i);
|
|
}
|
|
|
|
static enum hrtimer_restart stack_trace_timer_fn(struct hrtimer *hrtimer)
|
|
{
|
|
/* trace here */
|
|
timer_notify(get_irq_regs(), smp_processor_id());
|
|
|
|
hrtimer_forward_now(hrtimer, ns_to_ktime(sample_period));
|
|
|
|
return HRTIMER_RESTART;
|
|
}
|
|
|
|
static void start_stack_timer(void *unused)
|
|
{
|
|
struct hrtimer *hrtimer = &__get_cpu_var(stack_trace_hrtimer);
|
|
|
|
hrtimer_init(hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
hrtimer->function = stack_trace_timer_fn;
|
|
|
|
hrtimer_start(hrtimer, ns_to_ktime(sample_period),
|
|
HRTIMER_MODE_REL_PINNED);
|
|
}
|
|
|
|
static void start_stack_timers(void)
|
|
{
|
|
on_each_cpu(start_stack_timer, NULL, 1);
|
|
}
|
|
|
|
static void stop_stack_timer(int cpu)
|
|
{
|
|
struct hrtimer *hrtimer = &per_cpu(stack_trace_hrtimer, cpu);
|
|
|
|
hrtimer_cancel(hrtimer);
|
|
}
|
|
|
|
static void stop_stack_timers(void)
|
|
{
|
|
int cpu;
|
|
|
|
for_each_online_cpu(cpu)
|
|
stop_stack_timer(cpu);
|
|
}
|
|
|
|
static void stop_stack_trace(struct trace_array *tr)
|
|
{
|
|
mutex_lock(&sample_timer_lock);
|
|
stop_stack_timers();
|
|
tracer_enabled = 0;
|
|
mutex_unlock(&sample_timer_lock);
|
|
}
|
|
|
|
static int stack_trace_init(struct trace_array *tr)
|
|
{
|
|
sysprof_trace = tr;
|
|
|
|
tracing_start_cmdline_record();
|
|
|
|
mutex_lock(&sample_timer_lock);
|
|
start_stack_timers();
|
|
tracer_enabled = 1;
|
|
mutex_unlock(&sample_timer_lock);
|
|
return 0;
|
|
}
|
|
|
|
static void stack_trace_reset(struct trace_array *tr)
|
|
{
|
|
tracing_stop_cmdline_record();
|
|
stop_stack_trace(tr);
|
|
}
|
|
|
|
static struct tracer stack_trace __read_mostly =
|
|
{
|
|
.name = "sysprof",
|
|
.init = stack_trace_init,
|
|
.reset = stack_trace_reset,
|
|
#ifdef CONFIG_FTRACE_SELFTEST
|
|
.selftest = trace_selftest_startup_sysprof,
|
|
#endif
|
|
};
|
|
|
|
__init static int init_stack_trace(void)
|
|
{
|
|
return register_tracer(&stack_trace);
|
|
}
|
|
device_initcall(init_stack_trace);
|
|
|
|
#define MAX_LONG_DIGITS 22
|
|
|
|
static ssize_t
|
|
sysprof_sample_read(struct file *filp, char __user *ubuf,
|
|
size_t cnt, loff_t *ppos)
|
|
{
|
|
char buf[MAX_LONG_DIGITS];
|
|
int r;
|
|
|
|
r = sprintf(buf, "%ld\n", nsecs_to_usecs(sample_period));
|
|
|
|
return simple_read_from_buffer(ubuf, cnt, ppos, buf, r);
|
|
}
|
|
|
|
static ssize_t
|
|
sysprof_sample_write(struct file *filp, const char __user *ubuf,
|
|
size_t cnt, loff_t *ppos)
|
|
{
|
|
char buf[MAX_LONG_DIGITS];
|
|
unsigned long val;
|
|
|
|
if (cnt > MAX_LONG_DIGITS-1)
|
|
cnt = MAX_LONG_DIGITS-1;
|
|
|
|
if (copy_from_user(&buf, ubuf, cnt))
|
|
return -EFAULT;
|
|
|
|
buf[cnt] = 0;
|
|
|
|
val = simple_strtoul(buf, NULL, 10);
|
|
/*
|
|
* Enforce a minimum sample period of 100 usecs:
|
|
*/
|
|
if (val < 100)
|
|
val = 100;
|
|
|
|
mutex_lock(&sample_timer_lock);
|
|
stop_stack_timers();
|
|
sample_period = val * 1000;
|
|
start_stack_timers();
|
|
mutex_unlock(&sample_timer_lock);
|
|
|
|
return cnt;
|
|
}
|
|
|
|
static const struct file_operations sysprof_sample_fops = {
|
|
.read = sysprof_sample_read,
|
|
.write = sysprof_sample_write,
|
|
};
|
|
|
|
void init_tracer_sysprof_debugfs(struct dentry *d_tracer)
|
|
{
|
|
|
|
trace_create_file("sysprof_sample_period", 0644,
|
|
d_tracer, NULL, &sysprof_sample_fops);
|
|
}
|