aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/trace/trace_ksym.c
diff options
context:
space:
mode:
authorK.Prasad <prasad@linux.vnet.ibm.com>2009-06-01 14:16:40 -0400
committerFrederic Weisbecker <fweisbec@gmail.com>2009-06-02 16:47:00 -0400
commit0722db015c246204044299eae3b02d18d3ca4faf (patch)
treecc9a6b21961d62e1788ee2b9cbd0ae23b43f11a0 /kernel/trace/trace_ksym.c
parent432039933a16b8227b7b267f46ac1c1b9b3adf14 (diff)
hw-breakpoints: ftrace plugin for kernel symbol tracing using HW Breakpoint interfaces
This patch adds an ftrace plugin to detect and profile memory access over kernel variables. It uses HW Breakpoint interfaces to 'watch memory addresses. Signed-off-by: K.Prasad <prasad@linux.vnet.ibm.com> Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Diffstat (limited to 'kernel/trace/trace_ksym.c')
-rw-r--r--kernel/trace/trace_ksym.c525
1 files changed, 525 insertions, 0 deletions
diff --git a/kernel/trace/trace_ksym.c b/kernel/trace/trace_ksym.c
new file mode 100644
index 000000000000..11c74f6404cc
--- /dev/null
+++ b/kernel/trace/trace_ksym.c
@@ -0,0 +1,525 @@
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#define KSYM_FILTER_ENTRY_LEN (KSYM_NAME_LEN + KSYM_TRACER_OP_LEN + 1)
39
40static struct trace_array *ksym_trace_array;
41
42static unsigned int ksym_filter_entry_count;
43static unsigned int ksym_tracing_enabled;
44
45static HLIST_HEAD(ksym_filter_head);
46
47#ifdef CONFIG_PROFILE_KSYM_TRACER
48
49#define MAX_UL_INT 0xffffffff
50
51static DEFINE_MUTEX(ksym_tracer_mutex);
52
53void ksym_collect_stats(unsigned long hbp_hit_addr)
54{
55 struct hlist_node *node;
56 struct trace_ksym *entry;
57
58 rcu_read_lock();
59 hlist_for_each_entry_rcu(entry, node, &ksym_filter_head, ksym_hlist) {
60 if ((entry->ksym_addr == hbp_hit_addr) &&
61 (entry->counter <= MAX_UL_INT)) {
62 entry->counter++;
63 break;
64 }
65 }
66 rcu_read_unlock();
67}
68#endif /* CONFIG_PROFILE_KSYM_TRACER */
69
70void ksym_hbp_handler(struct hw_breakpoint *hbp, struct pt_regs *regs)
71{
72 struct ring_buffer_event *event;
73 struct trace_array *tr;
74 struct trace_ksym *entry;
75 int pc;
76
77 if (!ksym_tracing_enabled)
78 return;
79
80 tr = ksym_trace_array;
81 pc = preempt_count();
82
83 event = trace_buffer_lock_reserve(tr, TRACE_KSYM,
84 sizeof(*entry), 0, pc);
85 if (!event)
86 return;
87
88 entry = ring_buffer_event_data(event);
89 strlcpy(entry->ksym_name, hbp->info.name, KSYM_SYMBOL_LEN);
90 entry->ksym_hbp = hbp;
91 entry->ip = instruction_pointer(regs);
92 strlcpy(entry->p_name, current->comm, TASK_COMM_LEN);
93#ifdef CONFIG_PROFILE_KSYM_TRACER
94 ksym_collect_stats(hbp->info.address);
95#endif /* CONFIG_PROFILE_KSYM_TRACER */
96
97 trace_buffer_unlock_commit(tr, event, 0, pc);
98}
99
100/* Valid access types are represented as
101 *
102 * rw- : Set Read/Write Access Breakpoint
103 * -w- : Set Write Access Breakpoint
104 * --- : Clear Breakpoints
105 * --x : Set Execution Break points (Not available yet)
106 *
107 */
108static int ksym_trace_get_access_type(char *access_str)
109{
110 int pos, access = 0;
111
112 for (pos = 0; pos < KSYM_TRACER_OP_LEN; pos++) {
113 switch (access_str[pos]) {
114 case 'r':
115 access += (pos == 0) ? 4 : -1;
116 break;
117 case 'w':
118 access += (pos == 1) ? 2 : -1;
119 break;
120 case '-':
121 break;
122 default:
123 return -EINVAL;
124 }
125 }
126
127 switch (access) {
128 case 6:
129 access = HW_BREAKPOINT_RW;
130 break;
131 case 2:
132 access = HW_BREAKPOINT_WRITE;
133 break;
134 case 0:
135 access = 0;
136 }
137
138 return access;
139}
140
141/*
142 * There can be several possible malformed requests and we attempt to capture
143 * all of them. We enumerate some of the rules
144 * 1. We will not allow kernel symbols with ':' since it is used as a delimiter.
145 * i.e. multiple ':' symbols disallowed. Possible uses are of the form
146 * <module>:<ksym_name>:<op>.
147 * 2. No delimiter symbol ':' in the input string
148 * 3. Spurious operator symbols or symbols not in their respective positions
149 * 4. <ksym_name>:--- i.e. clear breakpoint request when ksym_name not in file
150 * 5. Kernel symbol not a part of /proc/kallsyms
151 * 6. Duplicate requests
152 */
153static int parse_ksym_trace_str(char *input_string, char **ksymname,
154 unsigned long *addr)
155{
156 char *delimiter = ":";
157 int ret;
158
159 ret = -EINVAL;
160 *ksymname = strsep(&input_string, delimiter);
161 *addr = kallsyms_lookup_name(*ksymname);
162
163 /* Check for malformed request: (2), (1) and (5) */
164 if ((!input_string) ||
165 (strlen(input_string) != (KSYM_TRACER_OP_LEN + 1)) ||
166 (*addr == 0))
167 goto return_code;
168 ret = ksym_trace_get_access_type(input_string);
169
170return_code:
171 return ret;
172}
173
174int process_new_ksym_entry(char *ksymname, int op, unsigned long addr)
175{
176 struct trace_ksym *entry;
177 int ret;
178
179 if (ksym_filter_entry_count >= KSYM_TRACER_MAX) {
180 printk(KERN_ERR "ksym_tracer: Maximum limit:(%d) reached. No"
181 " new requests for tracing can be accepted now.\n",
182 KSYM_TRACER_MAX);
183 return -ENOSPC;
184 }
185
186 entry = kzalloc(sizeof(struct trace_ksym), GFP_KERNEL);
187 if (!entry)
188 return -ENOMEM;
189
190 entry->ksym_hbp = kzalloc(sizeof(struct hw_breakpoint), GFP_KERNEL);
191 if (!entry->ksym_hbp) {
192 kfree(entry);
193 return -ENOMEM;
194 }
195
196 entry->ksym_hbp->info.name = ksymname;
197 entry->ksym_hbp->info.type = op;
198 entry->ksym_addr = entry->ksym_hbp->info.address = addr;
199#ifdef CONFIG_X86
200 entry->ksym_hbp->info.len = HW_BREAKPOINT_LEN_4;
201#endif
202 entry->ksym_hbp->triggered = (void *)ksym_hbp_handler;
203
204 ret = register_kernel_hw_breakpoint(entry->ksym_hbp);
205 if (ret < 0) {
206 printk(KERN_INFO "ksym_tracer request failed. Try again"
207 " later!!\n");
208 kfree(entry->ksym_hbp);
209 kfree(entry);
210 return -EAGAIN;
211 }
212 hlist_add_head_rcu(&(entry->ksym_hlist), &ksym_filter_head);
213 ksym_filter_entry_count++;
214
215 return 0;
216}
217
218static ssize_t ksym_trace_filter_read(struct file *filp, char __user *ubuf,
219 size_t count, loff_t *ppos)
220{
221 struct trace_ksym *entry;
222 struct hlist_node *node;
223 char buf[KSYM_FILTER_ENTRY_LEN * KSYM_TRACER_MAX];
224 ssize_t ret, cnt = 0;
225
226 mutex_lock(&ksym_tracer_mutex);
227
228 hlist_for_each_entry(entry, node, &ksym_filter_head, ksym_hlist) {
229 cnt += snprintf(&buf[cnt], KSYM_FILTER_ENTRY_LEN - cnt, "%s:",
230 entry->ksym_hbp->info.name);
231 if (entry->ksym_hbp->info.type == HW_BREAKPOINT_WRITE)
232 cnt += snprintf(&buf[cnt], KSYM_FILTER_ENTRY_LEN - cnt,
233 "-w-\n");
234 else if (entry->ksym_hbp->info.type == HW_BREAKPOINT_RW)
235 cnt += snprintf(&buf[cnt], KSYM_FILTER_ENTRY_LEN - cnt,
236 "rw-\n");
237 }
238 ret = simple_read_from_buffer(ubuf, count, ppos, buf, strlen(buf));
239 mutex_unlock(&ksym_tracer_mutex);
240
241 return ret;
242}
243
244static ssize_t ksym_trace_filter_write(struct file *file,
245 const char __user *buffer,
246 size_t count, loff_t *ppos)
247{
248 struct trace_ksym *entry;
249 struct hlist_node *node;
250 char *input_string, *ksymname = NULL;
251 unsigned long ksym_addr = 0;
252 int ret, op, changed = 0;
253
254 /* Ignore echo "" > ksym_trace_filter */
255 if (count == 0)
256 return 0;
257
258 input_string = kzalloc(count, GFP_KERNEL);
259 if (!input_string)
260 return -ENOMEM;
261
262 if (copy_from_user(input_string, buffer, count)) {
263 kfree(input_string);
264 return -EFAULT;
265 }
266
267 ret = op = parse_ksym_trace_str(input_string, &ksymname, &ksym_addr);
268 if (ret < 0) {
269 kfree(input_string);
270 return ret;
271 }
272
273 mutex_lock(&ksym_tracer_mutex);
274
275 ret = -EINVAL;
276 hlist_for_each_entry(entry, node, &ksym_filter_head, ksym_hlist) {
277 if (entry->ksym_addr == ksym_addr) {
278 /* Check for malformed request: (6) */
279 if (entry->ksym_hbp->info.type != op)
280 changed = 1;
281 else
282 goto err_ret;
283 break;
284 }
285 }
286 if (changed) {
287 unregister_kernel_hw_breakpoint(entry->ksym_hbp);
288 entry->ksym_hbp->info.type = op;
289 if (op > 0) {
290 ret = register_kernel_hw_breakpoint(entry->ksym_hbp);
291 if (ret == 0) {
292 ret = count;
293 goto unlock_ret_path;
294 }
295 }
296 ksym_filter_entry_count--;
297 hlist_del_rcu(&(entry->ksym_hlist));
298 synchronize_rcu();
299 kfree(entry->ksym_hbp);
300 kfree(entry);
301 ret = count;
302 goto err_ret;
303 } else {
304 /* Check for malformed request: (4) */
305 if (op == 0)
306 goto err_ret;
307 ret = process_new_ksym_entry(ksymname, op, ksym_addr);
308 if (ret)
309 goto err_ret;
310 }
311 ret = count;
312 goto unlock_ret_path;
313
314err_ret:
315 kfree(input_string);
316
317unlock_ret_path:
318 mutex_unlock(&ksym_tracer_mutex);
319 return ret;
320}
321
322static const struct file_operations ksym_tracing_fops = {
323 .open = tracing_open_generic,
324 .read = ksym_trace_filter_read,
325 .write = ksym_trace_filter_write,
326};
327
328static void ksym_trace_reset(struct trace_array *tr)
329{
330 struct trace_ksym *entry;
331 struct hlist_node *node, *node1;
332
333 ksym_tracing_enabled = 0;
334
335 mutex_lock(&ksym_tracer_mutex);
336 hlist_for_each_entry_safe(entry, node, node1, &ksym_filter_head,
337 ksym_hlist) {
338 unregister_kernel_hw_breakpoint(entry->ksym_hbp);
339 ksym_filter_entry_count--;
340 hlist_del_rcu(&(entry->ksym_hlist));
341 synchronize_rcu();
342 /* Free the 'input_string' only if reset
343 * after startup self-test
344 */
345#ifdef CONFIG_FTRACE_SELFTEST
346 if (strncmp(entry->ksym_hbp->info.name, KSYM_SELFTEST_ENTRY,
347 strlen(KSYM_SELFTEST_ENTRY)) != 0)
348#endif /* CONFIG_FTRACE_SELFTEST*/
349 kfree(entry->ksym_hbp->info.name);
350 kfree(entry->ksym_hbp);
351 kfree(entry);
352 }
353 mutex_unlock(&ksym_tracer_mutex);
354}
355
356static int ksym_trace_init(struct trace_array *tr)
357{
358 int cpu, ret = 0;
359
360 for_each_online_cpu(cpu)
361 tracing_reset(tr, cpu);
362 ksym_tracing_enabled = 1;
363 ksym_trace_array = tr;
364
365 return ret;
366}
367
368static void ksym_trace_print_header(struct seq_file *m)
369{
370
371 seq_puts(m,
372 "# TASK-PID CPU# Symbol Type "
373 "Function \n");
374 seq_puts(m,
375 "# | | | | "
376 "| \n");
377}
378
379static enum print_line_t ksym_trace_output(struct trace_iterator *iter)
380{
381 struct trace_entry *entry = iter->ent;
382 struct trace_seq *s = &iter->seq;
383 struct trace_ksym *field;
384 char str[KSYM_SYMBOL_LEN];
385 int ret;
386
387 if (entry->type != TRACE_KSYM)
388 return TRACE_TYPE_UNHANDLED;
389
390 trace_assign_type(field, entry);
391
392 ret = trace_seq_printf(s, "%-15s %-5d %-3d %-20s ", field->p_name,
393 entry->pid, iter->cpu, field->ksym_name);
394 if (!ret)
395 return TRACE_TYPE_PARTIAL_LINE;
396
397 switch (field->ksym_hbp->info.type) {
398 case HW_BREAKPOINT_WRITE:
399 ret = trace_seq_printf(s, " W ");
400 break;
401 case HW_BREAKPOINT_RW:
402 ret = trace_seq_printf(s, " RW ");
403 break;
404 default:
405 return TRACE_TYPE_PARTIAL_LINE;
406 }
407
408 if (!ret)
409 return TRACE_TYPE_PARTIAL_LINE;
410
411 sprint_symbol(str, field->ip);
412 ret = trace_seq_printf(s, "%-20s\n", str);
413 if (!ret)
414 return TRACE_TYPE_PARTIAL_LINE;
415
416 return TRACE_TYPE_HANDLED;
417}
418
419struct tracer ksym_tracer __read_mostly =
420{
421 .name = "ksym_tracer",
422 .init = ksym_trace_init,
423 .reset = ksym_trace_reset,
424#ifdef CONFIG_FTRACE_SELFTEST
425 .selftest = trace_selftest_startup_ksym,
426#endif
427 .print_header = ksym_trace_print_header,
428 .print_line = ksym_trace_output
429};
430
431__init static int init_ksym_trace(void)
432{
433 struct dentry *d_tracer;
434 struct dentry *entry;
435
436 d_tracer = tracing_init_dentry();
437 ksym_filter_entry_count = 0;
438
439 entry = debugfs_create_file("ksym_trace_filter", 0644, d_tracer,
440 NULL, &ksym_tracing_fops);
441 if (!entry)
442 pr_warning("Could not create debugfs "
443 "'ksym_trace_filter' file\n");
444
445 return register_tracer(&ksym_tracer);
446}
447device_initcall(init_ksym_trace);
448
449
450#ifdef CONFIG_PROFILE_KSYM_TRACER
451static int ksym_tracer_stat_headers(struct seq_file *m)
452{
453 seq_printf(m, " Access type ");
454 seq_printf(m, " Symbol Counter \n");
455 return 0;
456}
457
458static int ksym_tracer_stat_show(struct seq_file *m, void *v)
459{
460 struct hlist_node *stat = v;
461 struct trace_ksym *entry;
462 int access_type = 0;
463 char fn_name[KSYM_NAME_LEN];
464
465 entry = hlist_entry(stat, struct trace_ksym, ksym_hlist);
466
467 if (entry->ksym_hbp)
468 access_type = entry->ksym_hbp->info.type;
469
470 switch (access_type) {
471 case HW_BREAKPOINT_WRITE:
472 seq_printf(m, " W ");
473 break;
474 case HW_BREAKPOINT_RW:
475 seq_printf(m, " RW ");
476 break;
477 default:
478 seq_printf(m, " NA ");
479 }
480
481 if (lookup_symbol_name(entry->ksym_addr, fn_name) >= 0)
482 seq_printf(m, " %s ", fn_name);
483 else
484 seq_printf(m, " <NA> ");
485
486 seq_printf(m, "%15lu\n", entry->counter);
487 return 0;
488}
489
490static void *ksym_tracer_stat_start(struct tracer_stat *trace)
491{
492 return &(ksym_filter_head.first);
493}
494
495static void *
496ksym_tracer_stat_next(void *v, int idx)
497{
498 struct hlist_node *stat = v;
499
500 return stat->next;
501}
502
503static struct tracer_stat ksym_tracer_stats = {
504 .name = "ksym_tracer",
505 .stat_start = ksym_tracer_stat_start,
506 .stat_next = ksym_tracer_stat_next,
507 .stat_headers = ksym_tracer_stat_headers,
508 .stat_show = ksym_tracer_stat_show
509};
510
511__init static int ksym_tracer_stat_init(void)
512{
513 int ret;
514
515 ret = register_stat_tracer(&ksym_tracer_stats);
516 if (ret) {
517 printk(KERN_WARNING "Warning: could not register "
518 "ksym tracer stats\n");
519 return 1;
520 }
521
522 return 0;
523}
524fs_initcall(ksym_tracer_stat_init);
525#endif /* CONFIG_PROFILE_KSYM_TRACER */