mirror of
https://github.com/torvalds/linux.git
synced 2024-11-29 15:41:36 +00:00
1b6cced6ec
This patch adds indexes into the stack that the functions in the stack dump were found at. As an added bonus, I also added a diff to show which function is the most notorious consumer of the stack. The output now looks like this: # cat /debug/tracing/stack_trace Depth Size Location (48 entries) ----- ---- -------- 0) 2476 212 blk_recount_segments+0x39/0x59 1) 2264 12 bio_phys_segments+0x16/0x1d 2) 2252 20 blk_rq_bio_prep+0x23/0xaf 3) 2232 12 init_request_from_bio+0x74/0x77 4) 2220 56 __make_request+0x294/0x331 5) 2164 136 generic_make_request+0x34f/0x37d 6) 2028 56 submit_bio+0xe7/0xef 7) 1972 28 submit_bh+0xd1/0xf0 8) 1944 112 block_read_full_page+0x299/0x2a9 9) 1832 8 blkdev_readpage+0x14/0x16 10) 1824 28 read_cache_page_async+0x7e/0x109 11) 1796 16 read_cache_page+0x11/0x49 12) 1780 32 read_dev_sector+0x3c/0x72 13) 1748 48 read_lba+0x4d/0xaa 14) 1700 168 efi_partition+0x85/0x61b 15) 1532 72 rescan_partitions+0x10e/0x266 16) 1460 40 do_open+0x1c7/0x24e 17) 1420 292 __blkdev_get+0x79/0x84 18) 1128 12 blkdev_get+0x12/0x14 19) 1116 20 register_disk+0xd1/0x11e 20) 1096 28 add_disk+0x34/0x90 21) 1068 52 sd_probe+0x2b1/0x366 22) 1016 20 driver_probe_device+0xa5/0x120 23) 996 8 __device_attach+0xd/0xf 24) 988 32 bus_for_each_drv+0x3e/0x68 25) 956 24 device_attach+0x56/0x6c 26) 932 16 bus_attach_device+0x26/0x4d 27) 916 64 device_add+0x380/0x4b4 28) 852 28 scsi_sysfs_add_sdev+0xa1/0x1c9 29) 824 160 scsi_probe_and_add_lun+0x919/0xa2a 30) 664 36 __scsi_add_device+0x88/0xae 31) 628 44 ata_scsi_scan_host+0x9e/0x21c 32) 584 28 ata_host_register+0x1cb/0x1db 33) 556 24 ata_host_activate+0x98/0xb5 34) 532 192 ahci_init_one+0x9bd/0x9e9 35) 340 20 pci_device_probe+0x3e/0x5e 36) 320 20 driver_probe_device+0xa5/0x120 37) 300 20 __driver_attach+0x3f/0x5e 38) 280 36 bus_for_each_dev+0x40/0x62 39) 244 12 driver_attach+0x19/0x1b 40) 232 28 bus_add_driver+0x9c/0x1af 41) 204 28 driver_register+0x76/0xd2 42) 176 20 __pci_register_driver+0x44/0x71 43) 156 8 ahci_init+0x14/0x16 44) 148 100 _stext+0x42/0x122 45) 48 20 kernel_init+0x175/0x1dc 46) 28 28 kernel_thread_helper+0x7/0x10 The first column is simply an index starting from the inner most function and counting down to the outer most. The next column is the location that the function was found on the stack. The next column is the size of the stack for that function. Signed-off-by: Steven Rostedt <srostedt@redhat.com> Signed-off-by: Ingo Molnar <mingo@elte.hu>
311 lines
6.4 KiB
C
311 lines
6.4 KiB
C
/*
|
|
* Copyright (C) 2008 Steven Rostedt <srostedt@redhat.com>
|
|
*
|
|
*/
|
|
#include <linux/stacktrace.h>
|
|
#include <linux/kallsyms.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/ftrace.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/fs.h>
|
|
#include "trace.h"
|
|
|
|
#define STACK_TRACE_ENTRIES 500
|
|
|
|
static unsigned long stack_dump_trace[STACK_TRACE_ENTRIES+1] =
|
|
{ [0 ... (STACK_TRACE_ENTRIES)] = ULONG_MAX };
|
|
static unsigned stack_dump_index[STACK_TRACE_ENTRIES];
|
|
|
|
static struct stack_trace max_stack_trace = {
|
|
.max_entries = STACK_TRACE_ENTRIES,
|
|
.entries = stack_dump_trace,
|
|
};
|
|
|
|
static unsigned long max_stack_size;
|
|
static raw_spinlock_t max_stack_lock =
|
|
(raw_spinlock_t)__RAW_SPIN_LOCK_UNLOCKED;
|
|
|
|
static int stack_trace_disabled __read_mostly;
|
|
static DEFINE_PER_CPU(int, trace_active);
|
|
|
|
static inline void check_stack(void)
|
|
{
|
|
unsigned long this_size, flags;
|
|
unsigned long *p, *top, *start;
|
|
int i;
|
|
|
|
this_size = ((unsigned long)&this_size) & (THREAD_SIZE-1);
|
|
this_size = THREAD_SIZE - this_size;
|
|
|
|
if (this_size <= max_stack_size)
|
|
return;
|
|
|
|
raw_local_irq_save(flags);
|
|
__raw_spin_lock(&max_stack_lock);
|
|
|
|
/* a race could have already updated it */
|
|
if (this_size <= max_stack_size)
|
|
goto out;
|
|
|
|
max_stack_size = this_size;
|
|
|
|
max_stack_trace.nr_entries = 0;
|
|
max_stack_trace.skip = 3;
|
|
|
|
save_stack_trace(&max_stack_trace);
|
|
|
|
/*
|
|
* Now find where in the stack these are.
|
|
*/
|
|
i = 0;
|
|
start = &this_size;
|
|
top = (unsigned long *)
|
|
(((unsigned long)start & ~(THREAD_SIZE-1)) + THREAD_SIZE);
|
|
|
|
/*
|
|
* Loop through all the entries. One of the entries may
|
|
* for some reason be missed on the stack, so we may
|
|
* have to account for them. If they are all there, this
|
|
* loop will only happen once. This code only takes place
|
|
* on a new max, so it is far from a fast path.
|
|
*/
|
|
while (i < max_stack_trace.nr_entries) {
|
|
|
|
stack_dump_index[i] = this_size;
|
|
p = start;
|
|
|
|
for (; p < top && i < max_stack_trace.nr_entries; p++) {
|
|
if (*p == stack_dump_trace[i]) {
|
|
this_size = stack_dump_index[i++] =
|
|
(top - p) * sizeof(unsigned long);
|
|
/* Start the search from here */
|
|
start = p + 1;
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
out:
|
|
__raw_spin_unlock(&max_stack_lock);
|
|
raw_local_irq_restore(flags);
|
|
}
|
|
|
|
static void
|
|
stack_trace_call(unsigned long ip, unsigned long parent_ip)
|
|
{
|
|
int cpu, resched;
|
|
|
|
if (unlikely(!ftrace_enabled || stack_trace_disabled))
|
|
return;
|
|
|
|
resched = need_resched();
|
|
preempt_disable_notrace();
|
|
|
|
cpu = raw_smp_processor_id();
|
|
/* no atomic needed, we only modify this variable by this cpu */
|
|
if (per_cpu(trace_active, cpu)++ != 0)
|
|
goto out;
|
|
|
|
check_stack();
|
|
|
|
out:
|
|
per_cpu(trace_active, cpu)--;
|
|
/* prevent recursion in schedule */
|
|
if (resched)
|
|
preempt_enable_no_resched_notrace();
|
|
else
|
|
preempt_enable_notrace();
|
|
}
|
|
|
|
static struct ftrace_ops trace_ops __read_mostly =
|
|
{
|
|
.func = stack_trace_call,
|
|
};
|
|
|
|
static ssize_t
|
|
stack_max_size_read(struct file *filp, char __user *ubuf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
unsigned long *ptr = filp->private_data;
|
|
char buf[64];
|
|
int r;
|
|
|
|
r = snprintf(buf, sizeof(buf), "%ld\n", *ptr);
|
|
if (r > sizeof(buf))
|
|
r = sizeof(buf);
|
|
return simple_read_from_buffer(ubuf, count, ppos, buf, r);
|
|
}
|
|
|
|
static ssize_t
|
|
stack_max_size_write(struct file *filp, const char __user *ubuf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
long *ptr = filp->private_data;
|
|
unsigned long val, flags;
|
|
char buf[64];
|
|
int ret;
|
|
|
|
if (count >= sizeof(buf))
|
|
return -EINVAL;
|
|
|
|
if (copy_from_user(&buf, ubuf, count))
|
|
return -EFAULT;
|
|
|
|
buf[count] = 0;
|
|
|
|
ret = strict_strtoul(buf, 10, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
raw_local_irq_save(flags);
|
|
__raw_spin_lock(&max_stack_lock);
|
|
*ptr = val;
|
|
__raw_spin_unlock(&max_stack_lock);
|
|
raw_local_irq_restore(flags);
|
|
|
|
return count;
|
|
}
|
|
|
|
static struct file_operations stack_max_size_fops = {
|
|
.open = tracing_open_generic,
|
|
.read = stack_max_size_read,
|
|
.write = stack_max_size_write,
|
|
};
|
|
|
|
static void *
|
|
t_next(struct seq_file *m, void *v, loff_t *pos)
|
|
{
|
|
long i = (long)m->private;
|
|
|
|
(*pos)++;
|
|
|
|
i++;
|
|
|
|
if (i >= max_stack_trace.nr_entries ||
|
|
stack_dump_trace[i] == ULONG_MAX)
|
|
return NULL;
|
|
|
|
m->private = (void *)i;
|
|
|
|
return &m->private;
|
|
}
|
|
|
|
static void *t_start(struct seq_file *m, loff_t *pos)
|
|
{
|
|
void *t = &m->private;
|
|
loff_t l = 0;
|
|
|
|
local_irq_disable();
|
|
__raw_spin_lock(&max_stack_lock);
|
|
|
|
for (; t && l < *pos; t = t_next(m, t, &l))
|
|
;
|
|
|
|
return t;
|
|
}
|
|
|
|
static void t_stop(struct seq_file *m, void *p)
|
|
{
|
|
__raw_spin_unlock(&max_stack_lock);
|
|
local_irq_enable();
|
|
}
|
|
|
|
static int trace_lookup_stack(struct seq_file *m, long i)
|
|
{
|
|
unsigned long addr = stack_dump_trace[i];
|
|
#ifdef CONFIG_KALLSYMS
|
|
char str[KSYM_SYMBOL_LEN];
|
|
|
|
sprint_symbol(str, addr);
|
|
|
|
return seq_printf(m, "%s\n", str);
|
|
#else
|
|
return seq_printf(m, "%p\n", (void*)addr);
|
|
#endif
|
|
}
|
|
|
|
static int t_show(struct seq_file *m, void *v)
|
|
{
|
|
long i = *(long *)v;
|
|
int size;
|
|
|
|
if (i < 0) {
|
|
seq_printf(m, " Depth Size Location"
|
|
" (%d entries)\n"
|
|
" ----- ---- --------\n",
|
|
max_stack_trace.nr_entries);
|
|
return 0;
|
|
}
|
|
|
|
if (i >= max_stack_trace.nr_entries ||
|
|
stack_dump_trace[i] == ULONG_MAX)
|
|
return 0;
|
|
|
|
if (i+1 == max_stack_trace.nr_entries ||
|
|
stack_dump_trace[i+1] == ULONG_MAX)
|
|
size = stack_dump_index[i];
|
|
else
|
|
size = stack_dump_index[i] - stack_dump_index[i+1];
|
|
|
|
seq_printf(m, "%3ld) %8d %5d ", i, stack_dump_index[i], size);
|
|
|
|
trace_lookup_stack(m, i);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct seq_operations stack_trace_seq_ops = {
|
|
.start = t_start,
|
|
.next = t_next,
|
|
.stop = t_stop,
|
|
.show = t_show,
|
|
};
|
|
|
|
static int stack_trace_open(struct inode *inode, struct file *file)
|
|
{
|
|
int ret;
|
|
|
|
ret = seq_open(file, &stack_trace_seq_ops);
|
|
if (!ret) {
|
|
struct seq_file *m = file->private_data;
|
|
m->private = (void *)-1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct file_operations stack_trace_fops = {
|
|
.open = stack_trace_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
};
|
|
|
|
static __init int stack_trace_init(void)
|
|
{
|
|
struct dentry *d_tracer;
|
|
struct dentry *entry;
|
|
|
|
d_tracer = tracing_init_dentry();
|
|
|
|
entry = debugfs_create_file("stack_max_size", 0644, d_tracer,
|
|
&max_stack_size, &stack_max_size_fops);
|
|
if (!entry)
|
|
pr_warning("Could not create debugfs 'stack_max_size' entry\n");
|
|
|
|
entry = debugfs_create_file("stack_trace", 0444, d_tracer,
|
|
NULL, &stack_trace_fops);
|
|
if (!entry)
|
|
pr_warning("Could not create debugfs 'stack_trace' entry\n");
|
|
|
|
register_ftrace_function(&trace_ops);
|
|
|
|
return 0;
|
|
}
|
|
|
|
device_initcall(stack_trace_init);
|