]> Pileus Git - ~andy/linux/blob - kernel/trace/trace_ksym.c
Merge commit 'perf/core' into perf/hw-breakpoint
[~andy/linux] / kernel / trace / trace_ksym.c
1 /*
2  * trace_ksym.c - Kernel Symbol Tracer
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  *
18  * Copyright (C) IBM Corporation, 2009
19  */
20
21 #include <linux/kallsyms.h>
22 #include <linux/uaccess.h>
23 #include <linux/debugfs.h>
24 #include <linux/ftrace.h>
25 #include <linux/module.h>
26 #include <linux/fs.h>
27
28 #include "trace_output.h"
29 #include "trace_stat.h"
30 #include "trace.h"
31
32 /* For now, let us restrict the no. of symbols traced simultaneously to number
33  * of available hardware breakpoint registers.
34  */
35 #define KSYM_TRACER_MAX HBP_NUM
36
37 #define KSYM_TRACER_OP_LEN 3 /* rw- */
38
39 struct trace_ksym {
40         struct hw_breakpoint    *ksym_hbp;
41         unsigned long           ksym_addr;
42 #ifdef CONFIG_PROFILE_KSYM_TRACER
43         unsigned long           counter;
44 #endif
45         struct hlist_node       ksym_hlist;
46 };
47
48 static struct trace_array *ksym_trace_array;
49
50 static unsigned int ksym_filter_entry_count;
51 static unsigned int ksym_tracing_enabled;
52
53 static HLIST_HEAD(ksym_filter_head);
54
55 static DEFINE_MUTEX(ksym_tracer_mutex);
56
57 #ifdef CONFIG_PROFILE_KSYM_TRACER
58
59 #define MAX_UL_INT 0xffffffff
60
61 void ksym_collect_stats(unsigned long hbp_hit_addr)
62 {
63         struct hlist_node *node;
64         struct trace_ksym *entry;
65
66         rcu_read_lock();
67         hlist_for_each_entry_rcu(entry, node, &ksym_filter_head, ksym_hlist) {
68                 if ((entry->ksym_addr == hbp_hit_addr) &&
69                     (entry->counter <= MAX_UL_INT)) {
70                         entry->counter++;
71                         break;
72                 }
73         }
74         rcu_read_unlock();
75 }
76 #endif /* CONFIG_PROFILE_KSYM_TRACER */
77
78 void ksym_hbp_handler(struct hw_breakpoint *hbp, struct pt_regs *regs)
79 {
80         struct ring_buffer_event *event;
81         struct ksym_trace_entry *entry;
82         struct ring_buffer *buffer;
83         int pc;
84
85         if (!ksym_tracing_enabled)
86                 return;
87
88         buffer = ksym_trace_array->buffer;
89
90         pc = preempt_count();
91
92         event = trace_buffer_lock_reserve(buffer, TRACE_KSYM,
93                                                         sizeof(*entry), 0, pc);
94         if (!event)
95                 return;
96
97         entry           = ring_buffer_event_data(event);
98         entry->ip       = instruction_pointer(regs);
99         entry->type     = hbp->info.type;
100         strlcpy(entry->ksym_name, hbp->info.name, KSYM_SYMBOL_LEN);
101         strlcpy(entry->cmd, current->comm, TASK_COMM_LEN);
102
103 #ifdef CONFIG_PROFILE_KSYM_TRACER
104         ksym_collect_stats(hbp->info.address);
105 #endif /* CONFIG_PROFILE_KSYM_TRACER */
106
107         trace_buffer_unlock_commit(buffer, event, 0, pc);
108 }
109
110 /* Valid access types are represented as
111  *
112  * rw- : Set Read/Write Access Breakpoint
113  * -w- : Set Write Access Breakpoint
114  * --- : Clear Breakpoints
115  * --x : Set Execution Break points (Not available yet)
116  *
117  */
118 static int ksym_trace_get_access_type(char *str)
119 {
120         int access = 0;
121
122         if (str[0] == 'r')
123                 access += 4;
124         else if (str[0] != '-')
125                 return -EINVAL;
126
127         if (str[1] == 'w')
128                 access += 2;
129         else if (str[1] != '-')
130                 return -EINVAL;
131
132         if (str[2] != '-')
133                 return -EINVAL;
134
135         switch (access) {
136         case 6:
137                 access = HW_BREAKPOINT_RW;
138                 break;
139         case 4:
140                 access = -EINVAL;
141                 break;
142         case 2:
143                 access = HW_BREAKPOINT_WRITE;
144                 break;
145         }
146
147         return access;
148 }
149
150 /*
151  * There can be several possible malformed requests and we attempt to capture
152  * all of them. We enumerate some of the rules
153  * 1. We will not allow kernel symbols with ':' since it is used as a delimiter.
154  *    i.e. multiple ':' symbols disallowed. Possible uses are of the form
155  *    <module>:<ksym_name>:<op>.
156  * 2. No delimiter symbol ':' in the input string
157  * 3. Spurious operator symbols or symbols not in their respective positions
158  * 4. <ksym_name>:--- i.e. clear breakpoint request when ksym_name not in file
159  * 5. Kernel symbol not a part of /proc/kallsyms
160  * 6. Duplicate requests
161  */
162 static int parse_ksym_trace_str(char *input_string, char **ksymname,
163                                                         unsigned long *addr)
164 {
165         int ret;
166
167         *ksymname = strsep(&input_string, ":");
168         *addr = kallsyms_lookup_name(*ksymname);
169
170         /* Check for malformed request: (2), (1) and (5) */
171         if ((!input_string) ||
172             (strlen(input_string) != KSYM_TRACER_OP_LEN) ||
173             (*addr == 0))
174                 return -EINVAL;;
175
176         ret = ksym_trace_get_access_type(input_string);
177
178         return ret;
179 }
180
181 int process_new_ksym_entry(char *ksymname, int op, unsigned long addr)
182 {
183         struct trace_ksym *entry;
184         int ret = -ENOMEM;
185
186         if (ksym_filter_entry_count >= KSYM_TRACER_MAX) {
187                 printk(KERN_ERR "ksym_tracer: Maximum limit:(%d) reached. No"
188                 " new requests for tracing can be accepted now.\n",
189                         KSYM_TRACER_MAX);
190                 return -ENOSPC;
191         }
192
193         entry = kzalloc(sizeof(struct trace_ksym), GFP_KERNEL);
194         if (!entry)
195                 return -ENOMEM;
196
197         entry->ksym_hbp = kzalloc(sizeof(struct hw_breakpoint), GFP_KERNEL);
198         if (!entry->ksym_hbp)
199                 goto err;
200
201         entry->ksym_hbp->info.name = kstrdup(ksymname, GFP_KERNEL);
202         if (!entry->ksym_hbp->info.name)
203                 goto err;
204
205         entry->ksym_hbp->info.type = op;
206         entry->ksym_addr = entry->ksym_hbp->info.address = addr;
207 #ifdef CONFIG_X86
208         entry->ksym_hbp->info.len = HW_BREAKPOINT_LEN_4;
209 #endif
210         entry->ksym_hbp->triggered = (void *)ksym_hbp_handler;
211
212         ret = register_kernel_hw_breakpoint(entry->ksym_hbp);
213         if (ret < 0) {
214                 printk(KERN_INFO "ksym_tracer request failed. Try again"
215                                         " later!!\n");
216                 ret = -EAGAIN;
217                 goto err;
218         }
219         hlist_add_head_rcu(&(entry->ksym_hlist), &ksym_filter_head);
220         ksym_filter_entry_count++;
221         return 0;
222 err:
223         if (entry->ksym_hbp)
224                 kfree(entry->ksym_hbp->info.name);
225         kfree(entry->ksym_hbp);
226         kfree(entry);
227         return ret;
228 }
229
230 static ssize_t ksym_trace_filter_read(struct file *filp, char __user *ubuf,
231                                                 size_t count, loff_t *ppos)
232 {
233         struct trace_ksym *entry;
234         struct hlist_node *node;
235         struct trace_seq *s;
236         ssize_t cnt = 0;
237         int ret;
238
239         s = kmalloc(sizeof(*s), GFP_KERNEL);
240         if (!s)
241                 return -ENOMEM;
242         trace_seq_init(s);
243
244         mutex_lock(&ksym_tracer_mutex);
245
246         hlist_for_each_entry(entry, node, &ksym_filter_head, ksym_hlist) {
247                 ret = trace_seq_printf(s, "%s:", entry->ksym_hbp->info.name);
248                 if (entry->ksym_hbp->info.type == HW_BREAKPOINT_WRITE)
249                         ret = trace_seq_puts(s, "-w-\n");
250                 else if (entry->ksym_hbp->info.type == HW_BREAKPOINT_RW)
251                         ret = trace_seq_puts(s, "rw-\n");
252                 WARN_ON_ONCE(!ret);
253         }
254
255         cnt = simple_read_from_buffer(ubuf, count, ppos, s->buffer, s->len);
256
257         mutex_unlock(&ksym_tracer_mutex);
258
259         kfree(s);
260
261         return cnt;
262 }
263
264 static void __ksym_trace_reset(void)
265 {
266         struct trace_ksym *entry;
267         struct hlist_node *node, *node1;
268
269         mutex_lock(&ksym_tracer_mutex);
270         hlist_for_each_entry_safe(entry, node, node1, &ksym_filter_head,
271                                                                 ksym_hlist) {
272                 unregister_kernel_hw_breakpoint(entry->ksym_hbp);
273                 ksym_filter_entry_count--;
274                 hlist_del_rcu(&(entry->ksym_hlist));
275                 synchronize_rcu();
276                 kfree(entry->ksym_hbp->info.name);
277                 kfree(entry->ksym_hbp);
278                 kfree(entry);
279         }
280         mutex_unlock(&ksym_tracer_mutex);
281 }
282
283 static ssize_t ksym_trace_filter_write(struct file *file,
284                                         const char __user *buffer,
285                                                 size_t count, loff_t *ppos)
286 {
287         struct trace_ksym *entry;
288         struct hlist_node *node;
289         char *input_string, *ksymname = NULL;
290         unsigned long ksym_addr = 0;
291         int ret, op, changed = 0;
292
293         input_string = kzalloc(count + 1, GFP_KERNEL);
294         if (!input_string)
295                 return -ENOMEM;
296
297         if (copy_from_user(input_string, buffer, count)) {
298                 kfree(input_string);
299                 return -EFAULT;
300         }
301         input_string[count] = '\0';
302
303         strstrip(input_string);
304
305         /*
306          * Clear all breakpoints if:
307          * 1: echo > ksym_trace_filter
308          * 2: echo 0 > ksym_trace_filter
309          * 3: echo "*:---" > ksym_trace_filter
310          */
311         if (!input_string[0] || !strcmp(input_string, "0") ||
312             !strcmp(input_string, "*:---")) {
313                 __ksym_trace_reset();
314                 kfree(input_string);
315                 return count;
316         }
317
318         ret = op = parse_ksym_trace_str(input_string, &ksymname, &ksym_addr);
319         if (ret < 0) {
320                 kfree(input_string);
321                 return ret;
322         }
323
324         mutex_lock(&ksym_tracer_mutex);
325
326         ret = -EINVAL;
327         hlist_for_each_entry(entry, node, &ksym_filter_head, ksym_hlist) {
328                 if (entry->ksym_addr == ksym_addr) {
329                         /* Check for malformed request: (6) */
330                         if (entry->ksym_hbp->info.type != op)
331                                 changed = 1;
332                         else
333                                 goto out;
334                         break;
335                 }
336         }
337         if (changed) {
338                 unregister_kernel_hw_breakpoint(entry->ksym_hbp);
339                 entry->ksym_hbp->info.type = op;
340                 if (op > 0) {
341                         ret = register_kernel_hw_breakpoint(entry->ksym_hbp);
342                         if (ret == 0)
343                                 goto out;
344                 }
345                 ksym_filter_entry_count--;
346                 hlist_del_rcu(&(entry->ksym_hlist));
347                 synchronize_rcu();
348                 kfree(entry->ksym_hbp->info.name);
349                 kfree(entry->ksym_hbp);
350                 kfree(entry);
351                 ret = 0;
352                 goto out;
353         } else {
354                 /* Check for malformed request: (4) */
355                 if (op == 0)
356                         goto out;
357                 ret = process_new_ksym_entry(ksymname, op, ksym_addr);
358         }
359 out:
360         mutex_unlock(&ksym_tracer_mutex);
361
362         kfree(input_string);
363
364         if (!ret)
365                 ret = count;
366         return ret;
367 }
368
369 static const struct file_operations ksym_tracing_fops = {
370         .open           = tracing_open_generic,
371         .read           = ksym_trace_filter_read,
372         .write          = ksym_trace_filter_write,
373 };
374
375 static void ksym_trace_reset(struct trace_array *tr)
376 {
377         ksym_tracing_enabled = 0;
378         __ksym_trace_reset();
379 }
380
381 static int ksym_trace_init(struct trace_array *tr)
382 {
383         int cpu, ret = 0;
384
385         for_each_online_cpu(cpu)
386                 tracing_reset(tr, cpu);
387         ksym_tracing_enabled = 1;
388         ksym_trace_array = tr;
389
390         return ret;
391 }
392
393 static void ksym_trace_print_header(struct seq_file *m)
394 {
395         seq_puts(m,
396                  "#       TASK-PID   CPU#      Symbol                    "
397                  "Type    Function\n");
398         seq_puts(m,
399                  "#          |        |          |                       "
400                  " |         |\n");
401 }
402
403 static enum print_line_t ksym_trace_output(struct trace_iterator *iter)
404 {
405         struct trace_entry *entry = iter->ent;
406         struct trace_seq *s = &iter->seq;
407         struct ksym_trace_entry *field;
408         char str[KSYM_SYMBOL_LEN];
409         int ret;
410
411         if (entry->type != TRACE_KSYM)
412                 return TRACE_TYPE_UNHANDLED;
413
414         trace_assign_type(field, entry);
415
416         ret = trace_seq_printf(s, "%11s-%-5d [%03d] %-30s ", field->cmd,
417                                 entry->pid, iter->cpu, field->ksym_name);
418         if (!ret)
419                 return TRACE_TYPE_PARTIAL_LINE;
420
421         switch (field->type) {
422         case HW_BREAKPOINT_WRITE:
423                 ret = trace_seq_printf(s, " W  ");
424                 break;
425         case HW_BREAKPOINT_RW:
426                 ret = trace_seq_printf(s, " RW ");
427                 break;
428         default:
429                 return TRACE_TYPE_PARTIAL_LINE;
430         }
431
432         if (!ret)
433                 return TRACE_TYPE_PARTIAL_LINE;
434
435         sprint_symbol(str, field->ip);
436         ret = trace_seq_printf(s, "%s\n", str);
437         if (!ret)
438                 return TRACE_TYPE_PARTIAL_LINE;
439
440         return TRACE_TYPE_HANDLED;
441 }
442
443 struct tracer ksym_tracer __read_mostly =
444 {
445         .name           = "ksym_tracer",
446         .init           = ksym_trace_init,
447         .reset          = ksym_trace_reset,
448 #ifdef CONFIG_FTRACE_SELFTEST
449         .selftest       = trace_selftest_startup_ksym,
450 #endif
451         .print_header   = ksym_trace_print_header,
452         .print_line     = ksym_trace_output
453 };
454
455 __init static int init_ksym_trace(void)
456 {
457         struct dentry *d_tracer;
458         struct dentry *entry;
459
460         d_tracer = tracing_init_dentry();
461         ksym_filter_entry_count = 0;
462
463         entry = debugfs_create_file("ksym_trace_filter", 0644, d_tracer,
464                                     NULL, &ksym_tracing_fops);
465         if (!entry)
466                 pr_warning("Could not create debugfs "
467                            "'ksym_trace_filter' file\n");
468
469         return register_tracer(&ksym_tracer);
470 }
471 device_initcall(init_ksym_trace);
472
473
474 #ifdef CONFIG_PROFILE_KSYM_TRACER
475 static int ksym_tracer_stat_headers(struct seq_file *m)
476 {
477         seq_puts(m, "  Access Type ");
478         seq_puts(m, "  Symbol                                       Counter\n");
479         seq_puts(m, "  ----------- ");
480         seq_puts(m, "  ------                                       -------\n");
481         return 0;
482 }
483
484 static int ksym_tracer_stat_show(struct seq_file *m, void *v)
485 {
486         struct hlist_node *stat = v;
487         struct trace_ksym *entry;
488         int access_type = 0;
489         char fn_name[KSYM_NAME_LEN];
490
491         entry = hlist_entry(stat, struct trace_ksym, ksym_hlist);
492
493         if (entry->ksym_hbp)
494                 access_type = entry->ksym_hbp->info.type;
495
496         switch (access_type) {
497         case HW_BREAKPOINT_WRITE:
498                 seq_puts(m, "  W           ");
499                 break;
500         case HW_BREAKPOINT_RW:
501                 seq_puts(m, "  RW          ");
502                 break;
503         default:
504                 seq_puts(m, "  NA          ");
505         }
506
507         if (lookup_symbol_name(entry->ksym_addr, fn_name) >= 0)
508                 seq_printf(m, "  %-36s", fn_name);
509         else
510                 seq_printf(m, "  %-36s", "<NA>");
511         seq_printf(m, " %15lu\n", entry->counter);
512
513         return 0;
514 }
515
516 static void *ksym_tracer_stat_start(struct tracer_stat *trace)
517 {
518         return ksym_filter_head.first;
519 }
520
521 static void *
522 ksym_tracer_stat_next(void *v, int idx)
523 {
524         struct hlist_node *stat = v;
525
526         return stat->next;
527 }
528
529 static struct tracer_stat ksym_tracer_stats = {
530         .name = "ksym_tracer",
531         .stat_start = ksym_tracer_stat_start,
532         .stat_next = ksym_tracer_stat_next,
533         .stat_headers = ksym_tracer_stat_headers,
534         .stat_show = ksym_tracer_stat_show
535 };
536
537 __init static int ksym_tracer_stat_init(void)
538 {
539         int ret;
540
541         ret = register_stat_tracer(&ksym_tracer_stats);
542         if (ret) {
543                 printk(KERN_WARNING "Warning: could not register "
544                                     "ksym tracer stats\n");
545                 return 1;
546         }
547
548         return 0;
549 }
550 fs_initcall(ksym_tracer_stat_init);
551 #endif /* CONFIG_PROFILE_KSYM_TRACER */