diff options
Diffstat (limited to 'kernel/trace/trace_syscalls.c')
-rw-r--r-- | kernel/trace/trace_syscalls.c | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/kernel/trace/trace_syscalls.c b/kernel/trace/trace_syscalls.c new file mode 100644 index 000000000000..a2a3af29c943 --- /dev/null +++ b/kernel/trace/trace_syscalls.c | |||
@@ -0,0 +1,250 @@ | |||
1 | #include <linux/kernel.h> | ||
2 | #include <linux/ftrace.h> | ||
3 | #include <asm/syscall.h> | ||
4 | |||
5 | #include "trace_output.h" | ||
6 | #include "trace.h" | ||
7 | |||
8 | /* Keep a counter of the syscall tracing users */ | ||
9 | static int refcount; | ||
10 | |||
11 | /* Prevent from races on thread flags toggling */ | ||
12 | static DEFINE_MUTEX(syscall_trace_lock); | ||
13 | |||
14 | /* Option to display the parameters types */ | ||
15 | enum { | ||
16 | TRACE_SYSCALLS_OPT_TYPES = 0x1, | ||
17 | }; | ||
18 | |||
19 | static struct tracer_opt syscalls_opts[] = { | ||
20 | { TRACER_OPT(syscall_arg_type, TRACE_SYSCALLS_OPT_TYPES) }, | ||
21 | { } | ||
22 | }; | ||
23 | |||
24 | static struct tracer_flags syscalls_flags = { | ||
25 | .val = 0, /* By default: no parameters types */ | ||
26 | .opts = syscalls_opts | ||
27 | }; | ||
28 | |||
29 | enum print_line_t | ||
30 | print_syscall_enter(struct trace_iterator *iter, int flags) | ||
31 | { | ||
32 | struct trace_seq *s = &iter->seq; | ||
33 | struct trace_entry *ent = iter->ent; | ||
34 | struct syscall_trace_enter *trace; | ||
35 | struct syscall_metadata *entry; | ||
36 | int i, ret, syscall; | ||
37 | |||
38 | trace_assign_type(trace, ent); | ||
39 | |||
40 | syscall = trace->nr; | ||
41 | |||
42 | entry = syscall_nr_to_meta(syscall); | ||
43 | if (!entry) | ||
44 | goto end; | ||
45 | |||
46 | ret = trace_seq_printf(s, "%s(", entry->name); | ||
47 | if (!ret) | ||
48 | return TRACE_TYPE_PARTIAL_LINE; | ||
49 | |||
50 | for (i = 0; i < entry->nb_args; i++) { | ||
51 | /* parameter types */ | ||
52 | if (syscalls_flags.val & TRACE_SYSCALLS_OPT_TYPES) { | ||
53 | ret = trace_seq_printf(s, "%s ", entry->types[i]); | ||
54 | if (!ret) | ||
55 | return TRACE_TYPE_PARTIAL_LINE; | ||
56 | } | ||
57 | /* parameter values */ | ||
58 | ret = trace_seq_printf(s, "%s: %lx%s ", entry->args[i], | ||
59 | trace->args[i], | ||
60 | i == entry->nb_args - 1 ? ")" : ","); | ||
61 | if (!ret) | ||
62 | return TRACE_TYPE_PARTIAL_LINE; | ||
63 | } | ||
64 | |||
65 | end: | ||
66 | trace_seq_printf(s, "\n"); | ||
67 | return TRACE_TYPE_HANDLED; | ||
68 | } | ||
69 | |||
70 | enum print_line_t | ||
71 | print_syscall_exit(struct trace_iterator *iter, int flags) | ||
72 | { | ||
73 | struct trace_seq *s = &iter->seq; | ||
74 | struct trace_entry *ent = iter->ent; | ||
75 | struct syscall_trace_exit *trace; | ||
76 | int syscall; | ||
77 | struct syscall_metadata *entry; | ||
78 | int ret; | ||
79 | |||
80 | trace_assign_type(trace, ent); | ||
81 | |||
82 | syscall = trace->nr; | ||
83 | |||
84 | entry = syscall_nr_to_meta(syscall); | ||
85 | if (!entry) { | ||
86 | trace_seq_printf(s, "\n"); | ||
87 | return TRACE_TYPE_HANDLED; | ||
88 | } | ||
89 | |||
90 | ret = trace_seq_printf(s, "%s -> 0x%lx\n", entry->name, | ||
91 | trace->ret); | ||
92 | if (!ret) | ||
93 | return TRACE_TYPE_PARTIAL_LINE; | ||
94 | |||
95 | return TRACE_TYPE_HANDLED; | ||
96 | } | ||
97 | |||
98 | void start_ftrace_syscalls(void) | ||
99 | { | ||
100 | unsigned long flags; | ||
101 | struct task_struct *g, *t; | ||
102 | |||
103 | mutex_lock(&syscall_trace_lock); | ||
104 | |||
105 | /* Don't enable the flag on the tasks twice */ | ||
106 | if (++refcount != 1) | ||
107 | goto unlock; | ||
108 | |||
109 | arch_init_ftrace_syscalls(); | ||
110 | read_lock_irqsave(&tasklist_lock, flags); | ||
111 | |||
112 | do_each_thread(g, t) { | ||
113 | set_tsk_thread_flag(t, TIF_SYSCALL_FTRACE); | ||
114 | } while_each_thread(g, t); | ||
115 | |||
116 | read_unlock_irqrestore(&tasklist_lock, flags); | ||
117 | |||
118 | unlock: | ||
119 | mutex_unlock(&syscall_trace_lock); | ||
120 | } | ||
121 | |||
122 | void stop_ftrace_syscalls(void) | ||
123 | { | ||
124 | unsigned long flags; | ||
125 | struct task_struct *g, *t; | ||
126 | |||
127 | mutex_lock(&syscall_trace_lock); | ||
128 | |||
129 | /* There are perhaps still some users */ | ||
130 | if (--refcount) | ||
131 | goto unlock; | ||
132 | |||
133 | read_lock_irqsave(&tasklist_lock, flags); | ||
134 | |||
135 | do_each_thread(g, t) { | ||
136 | clear_tsk_thread_flag(t, TIF_SYSCALL_FTRACE); | ||
137 | } while_each_thread(g, t); | ||
138 | |||
139 | read_unlock_irqrestore(&tasklist_lock, flags); | ||
140 | |||
141 | unlock: | ||
142 | mutex_unlock(&syscall_trace_lock); | ||
143 | } | ||
144 | |||
145 | void ftrace_syscall_enter(struct pt_regs *regs) | ||
146 | { | ||
147 | struct syscall_trace_enter *entry; | ||
148 | struct syscall_metadata *sys_data; | ||
149 | struct ring_buffer_event *event; | ||
150 | int size; | ||
151 | int syscall_nr; | ||
152 | |||
153 | syscall_nr = syscall_get_nr(current, regs); | ||
154 | |||
155 | sys_data = syscall_nr_to_meta(syscall_nr); | ||
156 | if (!sys_data) | ||
157 | return; | ||
158 | |||
159 | size = sizeof(*entry) + sizeof(unsigned long) * sys_data->nb_args; | ||
160 | |||
161 | event = trace_current_buffer_lock_reserve(TRACE_SYSCALL_ENTER, size, | ||
162 | 0, 0); | ||
163 | if (!event) | ||
164 | return; | ||
165 | |||
166 | entry = ring_buffer_event_data(event); | ||
167 | entry->nr = syscall_nr; | ||
168 | syscall_get_arguments(current, regs, 0, sys_data->nb_args, entry->args); | ||
169 | |||
170 | trace_current_buffer_unlock_commit(event, 0, 0); | ||
171 | trace_wake_up(); | ||
172 | } | ||
173 | |||
174 | void ftrace_syscall_exit(struct pt_regs *regs) | ||
175 | { | ||
176 | struct syscall_trace_exit *entry; | ||
177 | struct syscall_metadata *sys_data; | ||
178 | struct ring_buffer_event *event; | ||
179 | int syscall_nr; | ||
180 | |||
181 | syscall_nr = syscall_get_nr(current, regs); | ||
182 | |||
183 | sys_data = syscall_nr_to_meta(syscall_nr); | ||
184 | if (!sys_data) | ||
185 | return; | ||
186 | |||
187 | event = trace_current_buffer_lock_reserve(TRACE_SYSCALL_EXIT, | ||
188 | sizeof(*entry), 0, 0); | ||
189 | if (!event) | ||
190 | return; | ||
191 | |||
192 | entry = ring_buffer_event_data(event); | ||
193 | entry->nr = syscall_nr; | ||
194 | entry->ret = syscall_get_return_value(current, regs); | ||
195 | |||
196 | trace_current_buffer_unlock_commit(event, 0, 0); | ||
197 | trace_wake_up(); | ||
198 | } | ||
199 | |||
200 | static int init_syscall_tracer(struct trace_array *tr) | ||
201 | { | ||
202 | start_ftrace_syscalls(); | ||
203 | |||
204 | return 0; | ||
205 | } | ||
206 | |||
207 | static void reset_syscall_tracer(struct trace_array *tr) | ||
208 | { | ||
209 | stop_ftrace_syscalls(); | ||
210 | tracing_reset_online_cpus(tr); | ||
211 | } | ||
212 | |||
213 | static struct trace_event syscall_enter_event = { | ||
214 | .type = TRACE_SYSCALL_ENTER, | ||
215 | .trace = print_syscall_enter, | ||
216 | }; | ||
217 | |||
218 | static struct trace_event syscall_exit_event = { | ||
219 | .type = TRACE_SYSCALL_EXIT, | ||
220 | .trace = print_syscall_exit, | ||
221 | }; | ||
222 | |||
223 | static struct tracer syscall_tracer __read_mostly = { | ||
224 | .name = "syscall", | ||
225 | .init = init_syscall_tracer, | ||
226 | .reset = reset_syscall_tracer, | ||
227 | .flags = &syscalls_flags, | ||
228 | }; | ||
229 | |||
230 | __init int register_ftrace_syscalls(void) | ||
231 | { | ||
232 | int ret; | ||
233 | |||
234 | ret = register_ftrace_event(&syscall_enter_event); | ||
235 | if (!ret) { | ||
236 | printk(KERN_WARNING "event %d failed to register\n", | ||
237 | syscall_enter_event.type); | ||
238 | WARN_ON_ONCE(1); | ||
239 | } | ||
240 | |||
241 | ret = register_ftrace_event(&syscall_exit_event); | ||
242 | if (!ret) { | ||
243 | printk(KERN_WARNING "event %d failed to register\n", | ||
244 | syscall_exit_event.type); | ||
245 | WARN_ON_ONCE(1); | ||
246 | } | ||
247 | |||
248 | return register_tracer(&syscall_tracer); | ||
249 | } | ||
250 | device_initcall(register_ftrace_syscalls); | ||