diff options
-rw-r--r-- | kernel/trace/Kconfig | 14 | ||||
-rw-r--r-- | kernel/trace/Makefile | 1 | ||||
-rw-r--r-- | kernel/trace/ftrace.c | 16 | ||||
-rw-r--r-- | kernel/trace/trace.c | 65 | ||||
-rw-r--r-- | kernel/trace/trace.h | 35 | ||||
-rw-r--r-- | kernel/trace/trace_functions_return.c | 82 |
6 files changed, 205 insertions, 8 deletions
diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig index fc4febc3334a..d986216c8327 100644 --- a/kernel/trace/Kconfig +++ b/kernel/trace/Kconfig | |||
@@ -9,6 +9,9 @@ config NOP_TRACER | |||
9 | config HAVE_FUNCTION_TRACER | 9 | config HAVE_FUNCTION_TRACER |
10 | bool | 10 | bool |
11 | 11 | ||
12 | config HAVE_FUNCTION_RET_TRACER | ||
13 | bool | ||
14 | |||
12 | config HAVE_FUNCTION_TRACE_MCOUNT_TEST | 15 | config HAVE_FUNCTION_TRACE_MCOUNT_TEST |
13 | bool | 16 | bool |
14 | help | 17 | help |
@@ -54,6 +57,17 @@ config FUNCTION_TRACER | |||
54 | (the bootup default), then the overhead of the instructions is very | 57 | (the bootup default), then the overhead of the instructions is very |
55 | small and not measurable even in micro-benchmarks. | 58 | small and not measurable even in micro-benchmarks. |
56 | 59 | ||
60 | config FUNCTION_RET_TRACER | ||
61 | bool "Kernel Function return Tracer" | ||
62 | depends on !DYNAMIC_FTRACE | ||
63 | depends on HAVE_FUNCTION_RET_TRACER | ||
64 | depends on FUNCTION_TRACER | ||
65 | help | ||
66 | Enable the kernel to trace a function at its return. | ||
67 | It's first purpose is to trace the duration of functions. | ||
68 | This is done by setting the current return address on the thread | ||
69 | info structure of the current task. | ||
70 | |||
57 | config IRQSOFF_TRACER | 71 | config IRQSOFF_TRACER |
58 | bool "Interrupts-off Latency Tracer" | 72 | bool "Interrupts-off Latency Tracer" |
59 | default n | 73 | default n |
diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile index c8228b1a49e9..3e1f361bbc17 100644 --- a/kernel/trace/Makefile +++ b/kernel/trace/Makefile | |||
@@ -24,5 +24,6 @@ obj-$(CONFIG_NOP_TRACER) += trace_nop.o | |||
24 | obj-$(CONFIG_STACK_TRACER) += trace_stack.o | 24 | obj-$(CONFIG_STACK_TRACER) += trace_stack.o |
25 | obj-$(CONFIG_MMIOTRACE) += trace_mmiotrace.o | 25 | obj-$(CONFIG_MMIOTRACE) += trace_mmiotrace.o |
26 | obj-$(CONFIG_BOOT_TRACER) += trace_boot.o | 26 | obj-$(CONFIG_BOOT_TRACER) += trace_boot.o |
27 | obj-$(CONFIG_FUNCTION_RET_TRACER) += trace_functions_return.o | ||
27 | 28 | ||
28 | libftrace-y := ftrace.o | 29 | libftrace-y := ftrace.o |
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index 4d2e751bfb11..f03fe74ecd67 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c | |||
@@ -1484,3 +1484,19 @@ ftrace_enable_sysctl(struct ctl_table *table, int write, | |||
1484 | return ret; | 1484 | return ret; |
1485 | } | 1485 | } |
1486 | 1486 | ||
1487 | #ifdef CONFIG_FUNCTION_RET_TRACER | ||
1488 | trace_function_return_t ftrace_function_return = | ||
1489 | (trace_function_return_t)ftrace_stub; | ||
1490 | void register_ftrace_return(trace_function_return_t func) | ||
1491 | { | ||
1492 | ftrace_function_return = func; | ||
1493 | } | ||
1494 | |||
1495 | void unregister_ftrace_return(void) | ||
1496 | { | ||
1497 | ftrace_function_return = (trace_function_return_t)ftrace_stub; | ||
1498 | } | ||
1499 | #endif | ||
1500 | |||
1501 | |||
1502 | |||
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 216bbe7547a4..a3f7ae9cd8e1 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c | |||
@@ -244,13 +244,6 @@ unsigned long nsecs_to_usecs(unsigned long nsecs) | |||
244 | return nsecs / 1000; | 244 | return nsecs / 1000; |
245 | } | 245 | } |
246 | 246 | ||
247 | /* | ||
248 | * TRACE_ITER_SYM_MASK masks the options in trace_flags that | ||
249 | * control the output of kernel symbols. | ||
250 | */ | ||
251 | #define TRACE_ITER_SYM_MASK \ | ||
252 | (TRACE_ITER_PRINT_PARENT|TRACE_ITER_SYM_OFFSET|TRACE_ITER_SYM_ADDR) | ||
253 | |||
254 | /* These must match the bit postions in trace_iterator_flags */ | 247 | /* These must match the bit postions in trace_iterator_flags */ |
255 | static const char *trace_options[] = { | 248 | static const char *trace_options[] = { |
256 | "print-parent", | 249 | "print-parent", |
@@ -810,6 +803,35 @@ trace_function(struct trace_array *tr, struct trace_array_cpu *data, | |||
810 | ring_buffer_unlock_commit(tr->buffer, event, irq_flags); | 803 | ring_buffer_unlock_commit(tr->buffer, event, irq_flags); |
811 | } | 804 | } |
812 | 805 | ||
806 | #ifdef CONFIG_FUNCTION_RET_TRACER | ||
807 | static void __trace_function_return(struct trace_array *tr, | ||
808 | struct trace_array_cpu *data, | ||
809 | struct ftrace_retfunc *trace, | ||
810 | unsigned long flags, | ||
811 | int pc) | ||
812 | { | ||
813 | struct ring_buffer_event *event; | ||
814 | struct ftrace_ret_entry *entry; | ||
815 | unsigned long irq_flags; | ||
816 | |||
817 | if (unlikely(local_read(&__get_cpu_var(ftrace_cpu_disabled)))) | ||
818 | return; | ||
819 | |||
820 | event = ring_buffer_lock_reserve(global_trace.buffer, sizeof(*entry), | ||
821 | &irq_flags); | ||
822 | if (!event) | ||
823 | return; | ||
824 | entry = ring_buffer_event_data(event); | ||
825 | tracing_generic_entry_update(&entry->ent, flags, pc); | ||
826 | entry->ent.type = TRACE_FN_RET; | ||
827 | entry->ip = trace->func; | ||
828 | entry->parent_ip = trace->ret; | ||
829 | entry->rettime = trace->rettime; | ||
830 | entry->calltime = trace->calltime; | ||
831 | ring_buffer_unlock_commit(global_trace.buffer, event, irq_flags); | ||
832 | } | ||
833 | #endif | ||
834 | |||
813 | void | 835 | void |
814 | ftrace(struct trace_array *tr, struct trace_array_cpu *data, | 836 | ftrace(struct trace_array *tr, struct trace_array_cpu *data, |
815 | unsigned long ip, unsigned long parent_ip, unsigned long flags, | 837 | unsigned long ip, unsigned long parent_ip, unsigned long flags, |
@@ -1038,6 +1060,29 @@ function_trace_call(unsigned long ip, unsigned long parent_ip) | |||
1038 | raw_local_irq_restore(flags); | 1060 | raw_local_irq_restore(flags); |
1039 | } | 1061 | } |
1040 | 1062 | ||
1063 | #ifdef CONFIG_FUNCTION_RET_TRACER | ||
1064 | void trace_function_return(struct ftrace_retfunc *trace) | ||
1065 | { | ||
1066 | struct trace_array *tr = &global_trace; | ||
1067 | struct trace_array_cpu *data; | ||
1068 | unsigned long flags; | ||
1069 | long disabled; | ||
1070 | int cpu; | ||
1071 | int pc; | ||
1072 | |||
1073 | raw_local_irq_save(flags); | ||
1074 | cpu = raw_smp_processor_id(); | ||
1075 | data = tr->data[cpu]; | ||
1076 | disabled = atomic_inc_return(&data->disabled); | ||
1077 | if (likely(disabled == 1)) { | ||
1078 | pc = preempt_count(); | ||
1079 | __trace_function_return(tr, data, trace, flags, pc); | ||
1080 | } | ||
1081 | atomic_dec(&data->disabled); | ||
1082 | raw_local_irq_restore(flags); | ||
1083 | } | ||
1084 | #endif /* CONFIG_FUNCTION_RET_TRACER */ | ||
1085 | |||
1041 | static struct ftrace_ops trace_ops __read_mostly = | 1086 | static struct ftrace_ops trace_ops __read_mostly = |
1042 | { | 1087 | { |
1043 | .func = function_trace_call, | 1088 | .func = function_trace_call, |
@@ -1285,7 +1330,7 @@ seq_print_sym_offset(struct trace_seq *s, const char *fmt, | |||
1285 | # define IP_FMT "%016lx" | 1330 | # define IP_FMT "%016lx" |
1286 | #endif | 1331 | #endif |
1287 | 1332 | ||
1288 | static int | 1333 | int |
1289 | seq_print_ip_sym(struct trace_seq *s, unsigned long ip, unsigned long sym_flags) | 1334 | seq_print_ip_sym(struct trace_seq *s, unsigned long ip, unsigned long sym_flags) |
1290 | { | 1335 | { |
1291 | int ret; | 1336 | int ret; |
@@ -1738,6 +1783,10 @@ static enum print_line_t print_trace_fmt(struct trace_iterator *iter) | |||
1738 | trace_seq_print_cont(s, iter); | 1783 | trace_seq_print_cont(s, iter); |
1739 | break; | 1784 | break; |
1740 | } | 1785 | } |
1786 | case TRACE_FN_RET: { | ||
1787 | return print_return_function(iter); | ||
1788 | break; | ||
1789 | } | ||
1741 | } | 1790 | } |
1742 | return TRACE_TYPE_HANDLED; | 1791 | return TRACE_TYPE_HANDLED; |
1743 | } | 1792 | } |
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 978145088fb8..e40ce0c14690 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h | |||
@@ -22,6 +22,7 @@ enum trace_type { | |||
22 | TRACE_MMIO_RW, | 22 | TRACE_MMIO_RW, |
23 | TRACE_MMIO_MAP, | 23 | TRACE_MMIO_MAP, |
24 | TRACE_BOOT, | 24 | TRACE_BOOT, |
25 | TRACE_FN_RET, | ||
25 | 26 | ||
26 | __TRACE_LAST_TYPE | 27 | __TRACE_LAST_TYPE |
27 | }; | 28 | }; |
@@ -48,6 +49,15 @@ struct ftrace_entry { | |||
48 | unsigned long ip; | 49 | unsigned long ip; |
49 | unsigned long parent_ip; | 50 | unsigned long parent_ip; |
50 | }; | 51 | }; |
52 | |||
53 | /* Function return entry */ | ||
54 | struct ftrace_ret_entry { | ||
55 | struct trace_entry ent; | ||
56 | unsigned long ip; | ||
57 | unsigned long parent_ip; | ||
58 | unsigned long long calltime; | ||
59 | unsigned long long rettime; | ||
60 | }; | ||
51 | extern struct tracer boot_tracer; | 61 | extern struct tracer boot_tracer; |
52 | 62 | ||
53 | /* | 63 | /* |
@@ -218,6 +228,7 @@ extern void __ftrace_bad_type(void); | |||
218 | IF_ASSIGN(var, ent, struct trace_mmiotrace_map, \ | 228 | IF_ASSIGN(var, ent, struct trace_mmiotrace_map, \ |
219 | TRACE_MMIO_MAP); \ | 229 | TRACE_MMIO_MAP); \ |
220 | IF_ASSIGN(var, ent, struct trace_boot, TRACE_BOOT); \ | 230 | IF_ASSIGN(var, ent, struct trace_boot, TRACE_BOOT); \ |
231 | IF_ASSIGN(var, ent, struct ftrace_ret_entry, TRACE_FN_RET); \ | ||
221 | __ftrace_bad_type(); \ | 232 | __ftrace_bad_type(); \ |
222 | } while (0) | 233 | } while (0) |
223 | 234 | ||
@@ -321,6 +332,8 @@ void trace_function(struct trace_array *tr, | |||
321 | unsigned long ip, | 332 | unsigned long ip, |
322 | unsigned long parent_ip, | 333 | unsigned long parent_ip, |
323 | unsigned long flags, int pc); | 334 | unsigned long flags, int pc); |
335 | void | ||
336 | trace_function_return(struct ftrace_retfunc *trace); | ||
324 | 337 | ||
325 | void tracing_start_cmdline_record(void); | 338 | void tracing_start_cmdline_record(void); |
326 | void tracing_stop_cmdline_record(void); | 339 | void tracing_stop_cmdline_record(void); |
@@ -393,6 +406,10 @@ extern void *head_page(struct trace_array_cpu *data); | |||
393 | extern int trace_seq_printf(struct trace_seq *s, const char *fmt, ...); | 406 | extern int trace_seq_printf(struct trace_seq *s, const char *fmt, ...); |
394 | extern void trace_seq_print_cont(struct trace_seq *s, | 407 | extern void trace_seq_print_cont(struct trace_seq *s, |
395 | struct trace_iterator *iter); | 408 | struct trace_iterator *iter); |
409 | |||
410 | extern int | ||
411 | seq_print_ip_sym(struct trace_seq *s, unsigned long ip, | ||
412 | unsigned long sym_flags); | ||
396 | extern ssize_t trace_seq_to_user(struct trace_seq *s, char __user *ubuf, | 413 | extern ssize_t trace_seq_to_user(struct trace_seq *s, char __user *ubuf, |
397 | size_t cnt); | 414 | size_t cnt); |
398 | extern long ns2usecs(cycle_t nsec); | 415 | extern long ns2usecs(cycle_t nsec); |
@@ -400,6 +417,17 @@ extern int trace_vprintk(unsigned long ip, const char *fmt, va_list args); | |||
400 | 417 | ||
401 | extern unsigned long trace_flags; | 418 | extern unsigned long trace_flags; |
402 | 419 | ||
420 | /* Standard output formatting function used for function return traces */ | ||
421 | #ifdef CONFIG_FUNCTION_RET_TRACER | ||
422 | extern enum print_line_t print_return_function(struct trace_iterator *iter); | ||
423 | #else | ||
424 | static inline enum print_line_t | ||
425 | print_return_function(struct trace_iterator *iter) | ||
426 | { | ||
427 | return TRACE_TYPE_UNHANDLED; | ||
428 | } | ||
429 | #endif | ||
430 | |||
403 | /* | 431 | /* |
404 | * trace_iterator_flags is an enumeration that defines bit | 432 | * trace_iterator_flags is an enumeration that defines bit |
405 | * positions into trace_flags that controls the output. | 433 | * positions into trace_flags that controls the output. |
@@ -422,6 +450,13 @@ enum trace_iterator_flags { | |||
422 | TRACE_ITER_PREEMPTONLY = 0x800, | 450 | TRACE_ITER_PREEMPTONLY = 0x800, |
423 | }; | 451 | }; |
424 | 452 | ||
453 | /* | ||
454 | * TRACE_ITER_SYM_MASK masks the options in trace_flags that | ||
455 | * control the output of kernel symbols. | ||
456 | */ | ||
457 | #define TRACE_ITER_SYM_MASK \ | ||
458 | (TRACE_ITER_PRINT_PARENT|TRACE_ITER_SYM_OFFSET|TRACE_ITER_SYM_ADDR) | ||
459 | |||
425 | extern struct tracer nop_trace; | 460 | extern struct tracer nop_trace; |
426 | 461 | ||
427 | /** | 462 | /** |
diff --git a/kernel/trace/trace_functions_return.c b/kernel/trace/trace_functions_return.c new file mode 100644 index 000000000000..7680b21537dd --- /dev/null +++ b/kernel/trace/trace_functions_return.c | |||
@@ -0,0 +1,82 @@ | |||
1 | /* | ||
2 | * | ||
3 | * Function return tracer. | ||
4 | * Copyright (c) 2008 Frederic Weisbecker <fweisbec@gmail.com> | ||
5 | * Mostly borrowed from function tracer which | ||
6 | * is Copyright (c) Steven Rostedt <srostedt@redhat.com> | ||
7 | * | ||
8 | */ | ||
9 | #include <linux/debugfs.h> | ||
10 | #include <linux/uaccess.h> | ||
11 | #include <linux/ftrace.h> | ||
12 | #include <linux/fs.h> | ||
13 | |||
14 | #include "trace.h" | ||
15 | |||
16 | |||
17 | static void start_return_trace(struct trace_array *tr) | ||
18 | { | ||
19 | register_ftrace_return(&trace_function_return); | ||
20 | } | ||
21 | |||
22 | static void stop_return_trace(struct trace_array *tr) | ||
23 | { | ||
24 | unregister_ftrace_return(); | ||
25 | } | ||
26 | |||
27 | static void return_trace_init(struct trace_array *tr) | ||
28 | { | ||
29 | int cpu; | ||
30 | for_each_online_cpu(cpu) | ||
31 | tracing_reset(tr, cpu); | ||
32 | |||
33 | start_return_trace(tr); | ||
34 | } | ||
35 | |||
36 | static void return_trace_reset(struct trace_array *tr) | ||
37 | { | ||
38 | stop_return_trace(tr); | ||
39 | } | ||
40 | |||
41 | |||
42 | enum print_line_t | ||
43 | print_return_function(struct trace_iterator *iter) | ||
44 | { | ||
45 | struct trace_seq *s = &iter->seq; | ||
46 | struct trace_entry *entry = iter->ent; | ||
47 | struct ftrace_ret_entry *field; | ||
48 | int ret; | ||
49 | |||
50 | if (entry->type == TRACE_FN_RET) { | ||
51 | trace_assign_type(field, entry); | ||
52 | ret = trace_seq_printf(s, "%pF -> ", (void *)field->parent_ip); | ||
53 | if (!ret) | ||
54 | return TRACE_TYPE_PARTIAL_LINE; | ||
55 | ret = seq_print_ip_sym(s, field->ip, | ||
56 | trace_flags & TRACE_ITER_SYM_MASK); | ||
57 | if (!ret) | ||
58 | return TRACE_TYPE_PARTIAL_LINE; | ||
59 | ret = trace_seq_printf(s, " (%llu ns)\n", | ||
60 | field->rettime - field->calltime); | ||
61 | if (!ret) | ||
62 | return TRACE_TYPE_PARTIAL_LINE; | ||
63 | else | ||
64 | return TRACE_TYPE_HANDLED; | ||
65 | } | ||
66 | return TRACE_TYPE_UNHANDLED; | ||
67 | } | ||
68 | |||
69 | static struct tracer return_trace __read_mostly = | ||
70 | { | ||
71 | .name = "return", | ||
72 | .init = return_trace_init, | ||
73 | .reset = return_trace_reset, | ||
74 | .print_line = print_return_function | ||
75 | }; | ||
76 | |||
77 | static __init int init_return_trace(void) | ||
78 | { | ||
79 | return register_tracer(&return_trace); | ||
80 | } | ||
81 | |||
82 | device_initcall(init_return_trace); | ||