ftrace: add filter select functions to trace
authorSteven Rostedt <srostedt@redhat.com>
Mon, 12 May 2008 19:20:43 +0000 (21:20 +0200)
committerThomas Gleixner <tglx@linutronix.de>
Fri, 23 May 2008 18:38:41 +0000 (20:38 +0200)
This patch adds two files to the debugfs system:

 /debugfs/tracing/available_filter_functions

and

 /debugfs/tracing/set_ftrace_filter

The available_filter_functions lists all functions that has been
recorded by the ftraced that has called the ftrace_record_ip function.
This is to allow users to see what functions have been converted
to nops and can be enabled for tracing.

To enable functions, simply echo the names (whitespace delimited)
into set_ftrace_filter. Simple wildcards are also allowed.

echo 'scheduler' > /debugfs/tracing/set_ftrace_filter

Will have only the scheduler be activated when tracing is enabled.

echo 'sched_*' > /debugfs/tracing/set_ftrace_filter

Will have only the functions starting with 'sched_' be activated.

echo '*lock' > /debugfs/tracing/set_ftrace_filter

Will have only functions ending with 'lock' be activated.

echo '*lock*' > /debugfs/tracing/set_ftrace_filter

Will have only functions with 'lock' in its name be activated.

Note: 'sched*lock' will not work. The only wildcards that are
allowed is an asterisk and the beginning and or end of the string
passed in.

Multiple names can be passed in with whitespace delimited:

echo 'scheduler *lock *acpi*' > /debugfs/tracing/set_ftrace_filter

is also the same as:

echo 'scheduler' > /debugfs/tracing/set_ftrace_filter
echo '*lock' >> /debugfs/tracing/set_ftrace_filter
echo '*acpi*' >> /debugfs/tracing/set_ftrace_filter

Appending does just that. It appends to the list.

To disable all filters simply echo an empty line in:

echo > /debugfs/tracing/set_ftrace_filter

Signed-off-by: Steven Rostedt <srostedt@redhat.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
include/linux/ftrace.h
kernel/trace/ftrace.c

index b0dd009..f5911d2 100644 (file)
@@ -43,7 +43,9 @@ extern void mcount(void);
 # define FTRACE_HASHSIZE       (1<<FTRACE_HASHBITS)
 
 enum {
-       FTRACE_FL_FAILED        = (1<<0),
+       FTRACE_FL_FAILED        = (1 << 0),
+       FTRACE_FL_FILTER        = (1 << 1),
+       FTRACE_FL_ENABLED       = (1 << 2),
 };
 
 struct dyn_ftrace {
index 88544f9..97d5cb7 100644 (file)
 #include <linux/stop_machine.h>
 #include <linux/clocksource.h>
 #include <linux/kallsyms.h>
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
 #include <linux/kthread.h>
 #include <linux/hardirq.h>
 #include <linux/ftrace.h>
-#include <linux/module.h>
+#include <linux/uaccess.h>
 #include <linux/sysctl.h>
 #include <linux/hash.h>
+#include <linux/ctype.h>
 #include <linux/list.h>
 
 #include "trace.h"
@@ -151,12 +154,15 @@ enum {
        FTRACE_DISABLE_MCOUNT           = (1 << 4),
 };
 
+static int ftrace_filtered;
+
 static struct hlist_head ftrace_hash[FTRACE_HASHSIZE];
 
 static DEFINE_PER_CPU(int, ftrace_shutdown_disable_cpu);
 
 static DEFINE_SPINLOCK(ftrace_shutdown_lock);
 static DEFINE_MUTEX(ftraced_lock);
+static DEFINE_MUTEX(ftrace_filter_lock);
 
 struct ftrace_page {
        struct ftrace_page      *next;
@@ -282,16 +288,82 @@ ftrace_record_ip(unsigned long ip)
 #define FTRACE_ADDR ((long)(&ftrace_caller))
 #define MCOUNT_ADDR ((long)(&mcount))
 
-static void notrace ftrace_replace_code(int saved)
+static void notrace
+__ftrace_replace_code(struct dyn_ftrace *rec,
+                     unsigned char *old, unsigned char *new, int enable)
+{
+       unsigned long ip;
+       int failed;
+
+       ip = rec->ip;
+
+       if (ftrace_filtered && enable) {
+               unsigned long fl;
+               /*
+                * If filtering is on:
+                *
+                * If this record is set to be filtered and
+                * is enabled then do nothing.
+                *
+                * If this record is set to be filtered and
+                * it is not enabled, enable it.
+                *
+                * If this record is not set to be filtered
+                * and it is not enabled do nothing.
+                *
+                * If this record is not set to be filtered and
+                * it is enabled, disable it.
+                */
+               fl = rec->flags & (FTRACE_FL_FILTER | FTRACE_FL_ENABLED);
+
+               if ((fl ==  (FTRACE_FL_FILTER | FTRACE_FL_ENABLED)) ||
+                   (fl == 0))
+                       return;
+
+               /*
+                * If it is enabled disable it,
+                * otherwise enable it!
+                */
+               if (fl == FTRACE_FL_ENABLED) {
+                       /* swap new and old */
+                       new = old;
+                       old = ftrace_call_replace(ip, FTRACE_ADDR);
+                       rec->flags &= ~FTRACE_FL_ENABLED;
+               } else {
+                       new = ftrace_call_replace(ip, FTRACE_ADDR);
+                       rec->flags |= FTRACE_FL_ENABLED;
+               }
+       } else {
+
+               if (enable)
+                       new = ftrace_call_replace(ip, FTRACE_ADDR);
+               else
+                       old = ftrace_call_replace(ip, FTRACE_ADDR);
+
+               if (enable) {
+                       if (rec->flags & FTRACE_FL_ENABLED)
+                               return;
+                       rec->flags |= FTRACE_FL_ENABLED;
+               } else {
+                       if (!(rec->flags & FTRACE_FL_ENABLED))
+                               return;
+                       rec->flags &= ~FTRACE_FL_ENABLED;
+               }
+       }
+
+       failed = ftrace_modify_code(ip, old, new);
+       if (failed)
+               rec->flags |= FTRACE_FL_FAILED;
+}
+
+static void notrace ftrace_replace_code(int enable)
 {
        unsigned char *new = NULL, *old = NULL;
        struct dyn_ftrace *rec;
        struct ftrace_page *pg;
-       unsigned long ip;
-       int failed;
        int i;
 
-       if (saved)
+       if (enable)
                old = ftrace_nop_replace();
        else
                new = ftrace_nop_replace();
@@ -304,16 +376,7 @@ static void notrace ftrace_replace_code(int saved)
                        if (rec->flags & FTRACE_FL_FAILED)
                                continue;
 
-                       ip = rec->ip;
-
-                       if (saved)
-                               new = ftrace_call_replace(ip, FTRACE_ADDR);
-                       else
-                               old = ftrace_call_replace(ip, FTRACE_ADDR);
-
-                       failed = ftrace_modify_code(ip, old, new);
-                       if (failed)
-                               rec->flags |= FTRACE_FL_FAILED;
+                       __ftrace_replace_code(rec, old, new, enable);
                }
        }
 }
@@ -580,6 +643,436 @@ static int __init ftrace_dyn_table_alloc(void)
        return 0;
 }
 
+enum {
+       FTRACE_ITER_FILTER      = (1 << 0),
+       FTRACE_ITER_CONT        = (1 << 1),
+};
+
+#define FTRACE_BUFF_MAX (KSYM_SYMBOL_LEN+4) /* room for wildcards */
+
+struct ftrace_iterator {
+       loff_t                  pos;
+       struct ftrace_page      *pg;
+       unsigned                idx;
+       unsigned                flags;
+       unsigned char           buffer[FTRACE_BUFF_MAX+1];
+       unsigned                buffer_idx;
+       unsigned                filtered;
+};
+
+static void notrace *
+t_next(struct seq_file *m, void *v, loff_t *pos)
+{
+       struct ftrace_iterator *iter = m->private;
+       struct dyn_ftrace *rec = NULL;
+
+       (*pos)++;
+
+ retry:
+       if (iter->idx >= iter->pg->index) {
+               if (iter->pg->next) {
+                       iter->pg = iter->pg->next;
+                       iter->idx = 0;
+                       goto retry;
+               }
+       } else {
+               rec = &iter->pg->records[iter->idx++];
+               if ((rec->flags & FTRACE_FL_FAILED) ||
+                   ((iter->flags & FTRACE_ITER_FILTER) &&
+                    !(rec->flags & FTRACE_FL_FILTER))) {
+                       rec = NULL;
+                       goto retry;
+               }
+       }
+
+       iter->pos = *pos;
+
+       return rec;
+}
+
+static void *t_start(struct seq_file *m, loff_t *pos)
+{
+       struct ftrace_iterator *iter = m->private;
+       void *p = NULL;
+       loff_t l = -1;
+
+       if (*pos != iter->pos) {
+               for (p = t_next(m, p, &l); p && l < *pos; p = t_next(m, p, &l))
+                       ;
+       } else {
+               l = *pos;
+               p = t_next(m, p, &l);
+       }
+
+       return p;
+}
+
+static void t_stop(struct seq_file *m, void *p)
+{
+}
+
+static int t_show(struct seq_file *m, void *v)
+{
+       struct dyn_ftrace *rec = v;
+       char str[KSYM_SYMBOL_LEN];
+
+       if (!rec)
+               return 0;
+
+       kallsyms_lookup(rec->ip, NULL, NULL, NULL, str);
+
+       seq_printf(m, "%s\n", str);
+
+       return 0;
+}
+
+static struct seq_operations show_ftrace_seq_ops = {
+       .start = t_start,
+       .next = t_next,
+       .stop = t_stop,
+       .show = t_show,
+};
+
+static int notrace
+ftrace_avail_open(struct inode *inode, struct file *file)
+{
+       struct ftrace_iterator *iter;
+       int ret;
+
+       iter = kzalloc(sizeof(*iter), GFP_KERNEL);
+       if (!iter)
+               return -ENOMEM;
+
+       iter->pg = ftrace_pages_start;
+       iter->pos = -1;
+
+       ret = seq_open(file, &show_ftrace_seq_ops);
+       if (!ret) {
+               struct seq_file *m = file->private_data;
+               m->private = iter;
+       } else
+               kfree(iter);
+
+       return ret;
+}
+
+int ftrace_avail_release(struct inode *inode, struct file *file)
+{
+       struct seq_file *m = (struct seq_file *)file->private_data;
+       struct ftrace_iterator *iter = m->private;
+
+       seq_release(inode, file);
+       kfree(iter);
+       return 0;
+}
+
+static void notrace ftrace_filter_reset(void)
+{
+       struct ftrace_page *pg;
+       struct dyn_ftrace *rec;
+       unsigned i;
+
+       /* keep kstop machine from running */
+       preempt_disable();
+       ftrace_filtered = 0;
+       pg = ftrace_pages_start;
+       while (pg) {
+               for (i = 0; i < pg->index; i++) {
+                       rec = &pg->records[i];
+                       if (rec->flags & FTRACE_FL_FAILED)
+                               continue;
+                       rec->flags &= ~FTRACE_FL_FILTER;
+               }
+               pg = pg->next;
+       }
+       preempt_enable();
+}
+
+static int notrace
+ftrace_filter_open(struct inode *inode, struct file *file)
+{
+       struct ftrace_iterator *iter;
+       int ret = 0;
+
+       iter = kzalloc(sizeof(*iter), GFP_KERNEL);
+       if (!iter)
+               return -ENOMEM;
+
+       mutex_lock(&ftrace_filter_lock);
+       if ((file->f_mode & FMODE_WRITE) &&
+           !(file->f_flags & O_APPEND))
+               ftrace_filter_reset();
+
+       if (file->f_mode & FMODE_READ) {
+               iter->pg = ftrace_pages_start;
+               iter->pos = -1;
+               iter->flags = FTRACE_ITER_FILTER;
+
+               ret = seq_open(file, &show_ftrace_seq_ops);
+               if (!ret) {
+                       struct seq_file *m = file->private_data;
+                       m->private = iter;
+               } else
+                       kfree(iter);
+       } else
+               file->private_data = iter;
+       mutex_unlock(&ftrace_filter_lock);
+
+       return ret;
+}
+
+static ssize_t notrace
+ftrace_filter_read(struct file *file, char __user *ubuf,
+                      size_t cnt, loff_t *ppos)
+{
+       if (file->f_mode & FMODE_READ)
+               return seq_read(file, ubuf, cnt, ppos);
+       else
+               return -EPERM;
+}
+
+static loff_t notrace
+ftrace_filter_lseek(struct file *file, loff_t offset, int origin)
+{
+       loff_t ret;
+
+       if (file->f_mode & FMODE_READ)
+               ret = seq_lseek(file, offset, origin);
+       else
+               file->f_pos = ret = 1;
+
+       return ret;
+}
+
+enum {
+       MATCH_FULL,
+       MATCH_FRONT_ONLY,
+       MATCH_MIDDLE_ONLY,
+       MATCH_END_ONLY,
+};
+
+static void notrace
+ftrace_match(unsigned char *buff, int len)
+{
+       char str[KSYM_SYMBOL_LEN];
+       char *search = NULL;
+       struct ftrace_page *pg;
+       struct dyn_ftrace *rec;
+       int type = MATCH_FULL;
+       unsigned i, match = 0, search_len = 0;
+
+       for (i = 0; i < len; i++) {
+               if (buff[i] == '*') {
+                       if (!i) {
+                               search = buff + i + 1;
+                               type = MATCH_END_ONLY;
+                               search_len = len - (i + 1);
+                       } else {
+                               if (type == MATCH_END_ONLY) {
+                                       type = MATCH_MIDDLE_ONLY;
+                               } else {
+                                       match = i;
+                                       type = MATCH_FRONT_ONLY;
+                               }
+                               buff[i] = 0;
+                               break;
+                       }
+               }
+       }
+
+       /* keep kstop machine from running */
+       preempt_disable();
+       ftrace_filtered = 1;
+       pg = ftrace_pages_start;
+       while (pg) {
+               for (i = 0; i < pg->index; i++) {
+                       int matched = 0;
+                       char *ptr;
+
+                       rec = &pg->records[i];
+                       if (rec->flags & FTRACE_FL_FAILED)
+                               continue;
+                       kallsyms_lookup(rec->ip, NULL, NULL, NULL, str);
+                       switch (type) {
+                       case MATCH_FULL:
+                               if (strcmp(str, buff) == 0)
+                                       matched = 1;
+                               break;
+                       case MATCH_FRONT_ONLY:
+                               if (memcmp(str, buff, match) == 0)
+                                       matched = 1;
+                               break;
+                       case MATCH_MIDDLE_ONLY:
+                               if (strstr(str, search))
+                                       matched = 1;
+                               break;
+                       case MATCH_END_ONLY:
+                               ptr = strstr(str, search);
+                               if (ptr && (ptr[search_len] == 0))
+                                       matched = 1;
+                               break;
+                       }
+                       if (matched)
+                               rec->flags |= FTRACE_FL_FILTER;
+               }
+               pg = pg->next;
+       }
+       preempt_enable();
+}
+
+static ssize_t notrace
+ftrace_filter_write(struct file *file, const char __user *ubuf,
+                   size_t cnt, loff_t *ppos)
+{
+       struct ftrace_iterator *iter;
+       char ch;
+       size_t read = 0;
+       ssize_t ret;
+
+       if (!cnt || cnt < 0)
+               return 0;
+
+       mutex_lock(&ftrace_filter_lock);
+
+       if (file->f_mode & FMODE_READ) {
+               struct seq_file *m = file->private_data;
+               iter = m->private;
+       } else
+               iter = file->private_data;
+
+       if (!*ppos) {
+               iter->flags &= ~FTRACE_ITER_CONT;
+               iter->buffer_idx = 0;
+       }
+
+       ret = get_user(ch, ubuf++);
+       if (ret)
+               goto out;
+       read++;
+       cnt--;
+
+       if (!(iter->flags & ~FTRACE_ITER_CONT)) {
+               /* skip white space */
+               while (cnt && isspace(ch)) {
+                       ret = get_user(ch, ubuf++);
+                       if (ret)
+                               goto out;
+                       read++;
+                       cnt--;
+               }
+
+
+               if (isspace(ch)) {
+                       file->f_pos += read;
+                       ret = read;
+                       goto out;
+               }
+
+               iter->buffer_idx = 0;
+       }
+
+       while (cnt && !isspace(ch)) {
+               if (iter->buffer_idx < FTRACE_BUFF_MAX)
+                       iter->buffer[iter->buffer_idx++] = ch;
+               else {
+                       ret = -EINVAL;
+                       goto out;
+               }
+               ret = get_user(ch, ubuf++);
+               if (ret)
+                       goto out;
+               read++;
+               cnt--;
+       }
+
+       if (isspace(ch)) {
+               iter->filtered++;
+               iter->buffer[iter->buffer_idx] = 0;
+               ftrace_match(iter->buffer, iter->buffer_idx);
+               iter->buffer_idx = 0;
+       } else
+               iter->flags |= FTRACE_ITER_CONT;
+
+
+       file->f_pos += read;
+
+       ret = read;
+ out:
+       mutex_unlock(&ftrace_filter_lock);
+
+       return ret;
+}
+
+static int notrace
+ftrace_filter_release(struct inode *inode, struct file *file)
+{
+       struct seq_file *m = (struct seq_file *)file->private_data;
+       struct ftrace_iterator *iter;
+
+       mutex_lock(&ftrace_filter_lock);
+       if (file->f_mode & FMODE_READ) {
+               iter = m->private;
+
+               seq_release(inode, file);
+       } else
+               iter = file->private_data;
+
+       if (iter->buffer_idx) {
+               iter->filtered++;
+               iter->buffer[iter->buffer_idx] = 0;
+               ftrace_match(iter->buffer, iter->buffer_idx);
+       }
+
+       mutex_lock(&ftrace_sysctl_lock);
+       mutex_lock(&ftraced_lock);
+       if (iter->filtered && ftraced_suspend && ftrace_enabled)
+               ftrace_run_update_code(FTRACE_ENABLE_CALLS);
+       mutex_unlock(&ftraced_lock);
+       mutex_unlock(&ftrace_sysctl_lock);
+
+       kfree(iter);
+       mutex_unlock(&ftrace_filter_lock);
+       return 0;
+}
+
+static struct file_operations ftrace_avail_fops = {
+       .open = ftrace_avail_open,
+       .read = seq_read,
+       .llseek = seq_lseek,
+       .release = ftrace_avail_release,
+};
+
+static struct file_operations ftrace_filter_fops = {
+       .open = ftrace_filter_open,
+       .read = ftrace_filter_read,
+       .write = ftrace_filter_write,
+       .llseek = ftrace_filter_lseek,
+       .release = ftrace_filter_release,
+};
+
+static __init int ftrace_init_debugfs(void)
+{
+       struct dentry *d_tracer;
+       struct dentry *entry;
+
+       d_tracer = tracing_init_dentry();
+
+       entry = debugfs_create_file("available_filter_functions", 0444,
+                                   d_tracer, NULL, &ftrace_avail_fops);
+       if (!entry)
+               pr_warning("Could not create debugfs "
+                          "'available_filter_functions' entry\n");
+
+       entry = debugfs_create_file("set_ftrace_filter", 0644, d_tracer,
+                                   NULL, &ftrace_filter_fops);
+       if (!entry)
+               pr_warning("Could not create debugfs "
+                          "'set_ftrace_filter' entry\n");
+       return 0;
+}
+
+fs_initcall(ftrace_init_debugfs);
+
 static int __init notrace ftrace_dynamic_init(void)
 {
        struct task_struct *p;
@@ -657,14 +1150,14 @@ int unregister_ftrace_function(struct ftrace_ops *ops)
 
 notrace int
 ftrace_enable_sysctl(struct ctl_table *table, int write,
-                    struct file *filp, void __user *buffer, size_t *lenp,
+                    struct file *file, void __user *buffer, size_t *lenp,
                     loff_t *ppos)
 {
        int ret;
 
        mutex_lock(&ftrace_sysctl_lock);
 
-       ret  = proc_dointvec(table, write, filp, buffer, lenp, ppos);
+       ret  = proc_dointvec(table, write, file, buffer, lenp, ppos);
 
        if (ret || !write || (last_ftrace_enabled == ftrace_enabled))
                goto out;