hw-breakpoint: Move asm-generic/hw_breakpoint.h to linux/hw_breakpoint.h
[linux-2.6-block.git] / kernel / trace / trace_ksym.c
CommitLineData
0722db01
P
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- */
0722db01 38
db59504d
LZ
39struct 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
0722db01
P
48static struct trace_array *ksym_trace_array;
49
50static unsigned int ksym_filter_entry_count;
51static unsigned int ksym_tracing_enabled;
52
53static HLIST_HEAD(ksym_filter_head);
54
73874005
FW
55static DEFINE_MUTEX(ksym_tracer_mutex);
56
0722db01
P
57#ifdef CONFIG_PROFILE_KSYM_TRACER
58
59#define MAX_UL_INT 0xffffffff
60
0722db01
P
61void 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
78void ksym_hbp_handler(struct hw_breakpoint *hbp, struct pt_regs *regs)
79{
80 struct ring_buffer_event *event;
db59504d 81 struct ksym_trace_entry *entry;
d6a65dff 82 struct ring_buffer *buffer;
0722db01
P
83 int pc;
84
85 if (!ksym_tracing_enabled)
86 return;
87
d6a65dff
FW
88 buffer = ksym_trace_array->buffer;
89
0722db01
P
90 pc = preempt_count();
91
d6a65dff 92 event = trace_buffer_lock_reserve(buffer, TRACE_KSYM,
0722db01
P
93 sizeof(*entry), 0, pc);
94 if (!event)
95 return;
96
db59504d
LZ
97 entry = ring_buffer_event_data(event);
98 entry->ip = instruction_pointer(regs);
99 entry->type = hbp->info.type;
0722db01 100 strlcpy(entry->ksym_name, hbp->info.name, KSYM_SYMBOL_LEN);
db59504d
LZ
101 strlcpy(entry->cmd, current->comm, TASK_COMM_LEN);
102
0722db01
P
103#ifdef CONFIG_PROFILE_KSYM_TRACER
104 ksym_collect_stats(hbp->info.address);
105#endif /* CONFIG_PROFILE_KSYM_TRACER */
106
d6a65dff 107 trace_buffer_unlock_commit(buffer, event, 0, pc);
0722db01
P
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 */
f088e547 118static int ksym_trace_get_access_type(char *str)
0722db01 119{
f088e547 120 int access = 0;
0722db01 121
f088e547
LZ
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;
0722db01
P
134
135 switch (access) {
136 case 6:
137 access = HW_BREAKPOINT_RW;
138 break;
8e068542
XG
139 case 4:
140 access = -EINVAL;
141 break;
0722db01
P
142 case 2:
143 access = HW_BREAKPOINT_WRITE;
144 break;
0722db01
P
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 */
162static int parse_ksym_trace_str(char *input_string, char **ksymname,
163 unsigned long *addr)
164{
0722db01
P
165 int ret;
166
92cf9f8f 167 *ksymname = strsep(&input_string, ":");
0722db01
P
168 *addr = kallsyms_lookup_name(*ksymname);
169
170 /* Check for malformed request: (2), (1) and (5) */
171 if ((!input_string) ||
92cf9f8f
LZ
172 (strlen(input_string) != KSYM_TRACER_OP_LEN) ||
173 (*addr == 0))
174 return -EINVAL;;
175
0722db01
P
176 ret = ksym_trace_get_access_type(input_string);
177
0722db01
P
178 return ret;
179}
180
181int process_new_ksym_entry(char *ksymname, int op, unsigned long addr)
182{
183 struct trace_ksym *entry;
558df6c8 184 int ret = -ENOMEM;
0722db01
P
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);
558df6c8
LZ
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;
0722db01 204
0722db01
P
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");
558df6c8
LZ
216 ret = -EAGAIN;
217 goto err;
0722db01
P
218 }
219 hlist_add_head_rcu(&(entry->ksym_hlist), &ksym_filter_head);
220 ksym_filter_entry_count++;
0722db01 221 return 0;
558df6c8
LZ
222err:
223 if (entry->ksym_hbp)
224 kfree(entry->ksym_hbp->info.name);
225 kfree(entry->ksym_hbp);
226 kfree(entry);
227 return ret;
0722db01
P
228}
229
230static 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;
be9742e6
LZ
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);
0722db01
P
243
244 mutex_lock(&ksym_tracer_mutex);
245
246 hlist_for_each_entry(entry, node, &ksym_filter_head, ksym_hlist) {
be9742e6 247 ret = trace_seq_printf(s, "%s:", entry->ksym_hbp->info.name);
0722db01 248 if (entry->ksym_hbp->info.type == HW_BREAKPOINT_WRITE)
be9742e6 249 ret = trace_seq_puts(s, "-w-\n");
0722db01 250 else if (entry->ksym_hbp->info.type == HW_BREAKPOINT_RW)
be9742e6
LZ
251 ret = trace_seq_puts(s, "rw-\n");
252 WARN_ON_ONCE(!ret);
0722db01 253 }
be9742e6
LZ
254
255 cnt = simple_read_from_buffer(ubuf, count, ppos, s->buffer, s->len);
256
0722db01
P
257 mutex_unlock(&ksym_tracer_mutex);
258
be9742e6
LZ
259 kfree(s);
260
261 return cnt;
0722db01
P
262}
263
75e33751
XG
264static 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
0722db01
P
283static 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
011ed568 293 input_string = kzalloc(count + 1, GFP_KERNEL);
0722db01
P
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 }
011ed568 301 input_string[count] = '\0';
0722db01 302
75e33751
XG
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
0722db01
P
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
558df6c8 333 goto out;
0722db01
P
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);
558df6c8
LZ
342 if (ret == 0)
343 goto out;
344 }
0722db01
P
345 ksym_filter_entry_count--;
346 hlist_del_rcu(&(entry->ksym_hlist));
347 synchronize_rcu();
558df6c8 348 kfree(entry->ksym_hbp->info.name);
0722db01
P
349 kfree(entry->ksym_hbp);
350 kfree(entry);
8e068542 351 ret = 0;
558df6c8 352 goto out;
0722db01
P
353 } else {
354 /* Check for malformed request: (4) */
355 if (op == 0)
558df6c8 356 goto out;
0722db01 357 ret = process_new_ksym_entry(ksymname, op, ksym_addr);
0722db01 358 }
558df6c8
LZ
359out:
360 mutex_unlock(&ksym_tracer_mutex);
0722db01 361
0722db01
P
362 kfree(input_string);
363
558df6c8
LZ
364 if (!ret)
365 ret = count;
0722db01
P
366 return ret;
367}
368
369static 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
375static void ksym_trace_reset(struct trace_array *tr)
376{
0722db01 377 ksym_tracing_enabled = 0;
75e33751 378 __ksym_trace_reset();
0722db01
P
379}
380
381static 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
393static void ksym_trace_print_header(struct seq_file *m)
394{
0722db01 395 seq_puts(m,
d857ace1
XG
396 "# TASK-PID CPU# Symbol "
397 "Type Function\n");
0722db01 398 seq_puts(m,
d857ace1
XG
399 "# | | | "
400 " | |\n");
0722db01
P
401}
402
403static 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;
db59504d 407 struct ksym_trace_entry *field;
0722db01
P
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
d857ace1 416 ret = trace_seq_printf(s, "%11s-%-5d [%03d] %-30s ", field->cmd,
0722db01
P
417 entry->pid, iter->cpu, field->ksym_name);
418 if (!ret)
419 return TRACE_TYPE_PARTIAL_LINE;
420
db59504d 421 switch (field->type) {
0722db01
P
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);
d857ace1 436 ret = trace_seq_printf(s, "%s\n", str);
0722db01
P
437 if (!ret)
438 return TRACE_TYPE_PARTIAL_LINE;
439
440 return TRACE_TYPE_HANDLED;
441}
442
443struct 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}
471device_initcall(init_ksym_trace);
472
473
474#ifdef CONFIG_PROFILE_KSYM_TRACER
475static int ksym_tracer_stat_headers(struct seq_file *m)
476{
9d7e9344
LZ
477 seq_puts(m, " Access Type ");
478 seq_puts(m, " Symbol Counter\n");
479 seq_puts(m, " ----------- ");
480 seq_puts(m, " ------ -------\n");
0722db01
P
481 return 0;
482}
483
484static 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:
9d7e9344 498 seq_puts(m, " W ");
0722db01
P
499 break;
500 case HW_BREAKPOINT_RW:
9d7e9344 501 seq_puts(m, " RW ");
0722db01
P
502 break;
503 default:
9d7e9344 504 seq_puts(m, " NA ");
0722db01
P
505 }
506
507 if (lookup_symbol_name(entry->ksym_addr, fn_name) >= 0)
9d7e9344 508 seq_printf(m, " %-36s", fn_name);
0722db01 509 else
9d7e9344
LZ
510 seq_printf(m, " %-36s", "<NA>");
511 seq_printf(m, " %15lu\n", entry->counter);
0722db01 512
0722db01
P
513 return 0;
514}
515
516static void *ksym_tracer_stat_start(struct tracer_stat *trace)
517{
9d7e9344 518 return ksym_filter_head.first;
0722db01
P
519}
520
521static void *
522ksym_tracer_stat_next(void *v, int idx)
523{
524 struct hlist_node *stat = v;
525
526 return stat->next;
527}
528
529static 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}
550fs_initcall(ksym_tracer_stat_init);
551#endif /* CONFIG_PROFILE_KSYM_TRACER */