blob: e31d762a9d3da4265417865c7e5526d5288c8cd4 [file] [log] [blame]
Sasha Goldshtein38847f02016-02-22 02:19:24 -08001#!/usr/bin/env python
2#
3# trace Trace a function and print a trace message based on its
4# parameters, with an optional filter.
5#
6# USAGE: trace [-h] [-p PID] [-v] [-Z STRING_SIZE] [-S] [-M MAX_EVENTS] [-o]
7# probe [probe ...]
8# Licensed under the Apache License, Version 2.0 (the "License")
9# Copyright (C) 2016 Sasha Goldshtein.
10
11from bcc import BPF
12from time import sleep, strftime
13import argparse
14import re
15import ctypes as ct
16import os
17import traceback
18import sys
19
20class Time(object):
21 # BPF timestamps come from the monotonic clock. To be able to filter
22 # and compare them from Python, we need to invoke clock_gettime.
23 # Adapted from http://stackoverflow.com/a/1205762
24 CLOCK_MONOTONIC_RAW = 4 # see <linux/time.h>
25
26 class timespec(ct.Structure):
27 _fields_ = [
28 ('tv_sec', ct.c_long),
29 ('tv_nsec', ct.c_long)
30 ]
31
32 librt = ct.CDLL('librt.so.1', use_errno=True)
33 clock_gettime = librt.clock_gettime
34 clock_gettime.argtypes = [ct.c_int, ct.POINTER(timespec)]
35
36 @staticmethod
37 def monotonic_time():
38 t = Time.timespec()
39 if Time.clock_gettime(
40 Time.CLOCK_MONOTONIC_RAW, ct.pointer(t)) != 0:
41 errno_ = ct.get_errno()
42 raise OSError(errno_, os.strerror(errno_))
43 return t.tv_sec * 1e9 + t.tv_nsec
44
45class Probe(object):
46 probe_count = 0
47 max_events = None
48 event_count = 0
49 first_ts = 0
50 use_localtime = True
51
52 @classmethod
53 def configure(cls, args):
54 cls.max_events = args.max_events
55 cls.use_localtime = not args.offset
56 cls.first_ts = Time.monotonic_time()
57
58 def __init__(self, probe, string_size):
59 self.raw_probe = probe
60 self.string_size = string_size
61 Probe.probe_count += 1
62 self._parse_probe()
63 self.probe_num = Probe.probe_count
64 self.probe_name = "probe_%s_%d" % \
65 (self.function, self.probe_num)
66
67 def __str__(self):
68 return "%s:%s`%s FLT=%s ACT=%s/%s" % (self.probe_type,
69 self.library, self.function, self.filter,
70 self.types, self.values)
71
72 def is_default_action(self):
73 return self.python_format == ""
74
75 def _bail(self, error):
76 raise ValueError("error parsing probe '%s': %s" %
77 (self.raw_probe, error))
78
79 def _parse_probe(self):
80 text = self.raw_probe
81
82 # Everything until the first space is the probe specifier
83 first_space = text.find(' ')
84 spec = text[:first_space] if first_space >= 0 else text
85 self._parse_spec(spec)
86 if first_space >= 0:
87 text = text[first_space:].lstrip()
88 else:
89 text = ""
90
91 # If we now have a (, wait for the balanced closing ) and that
92 # will be the predicate
93 self.filter = None
94 if len(text) > 0 and text[0] == "(":
95 balance = 1
96 for i in range(1, len(text)):
97 if text[i] == "(":
98 balance += 1
99 if text[i] == ")":
100 balance -= 1
101 if balance == 0:
102 self._parse_filter(text[:i+1])
103 text = text[i+1:]
104 break
105 if self.filter is None:
106 self._bail("unmatched end of predicate")
107
108 if self.filter is None:
109 self.filter = "1"
110
111 # The remainder of the text is the printf action
112 self._parse_action(text.lstrip())
113
114 def _parse_spec(self, spec):
115 parts = spec.split(":")
116 # Two special cases: 'func' means 'p::func', 'lib:func' means
117 # 'p:lib:func'. Other combinations need to provide an empty
118 # value between delimiters, e.g. 'r::func' for a kretprobe on
119 # the function func.
120 if len(parts) == 1:
121 parts = ["p", "", parts[0]]
122 elif len(parts) == 2:
123 parts = ["p", parts[0], parts[1]]
124 if len(parts[0]) == 0:
125 self.probe_type = "p"
126 elif parts[0] in ["p", "r"]:
127 self.probe_type = parts[0]
128 else:
129 self._bail("expected '', 'p', or 'r', got '%s'" %
130 parts[0])
131 self.library = parts[1]
132 self.function = parts[2]
133
134 def _parse_filter(self, filt):
135 self.filter = self._replace_args(filt)
136
137 def _parse_types(self, fmt):
138 for match in re.finditer(
139 r'[^%]%(s|u|d|llu|lld|hu|hd|x|llx|c)', fmt):
140 self.types.append(match.group(1))
141 fmt = re.sub(r'([^%]%)(u|d|llu|lld|hu|hd)', r'\1d', fmt)
142 fmt = re.sub(r'([^%]%)(x|llx)', r'\1x', fmt)
143 self.python_format = fmt.strip('"')
144
145 def _parse_action(self, action):
146 self.values = []
147 self.types = []
148 self.python_format = ""
149 if len(action) == 0:
150 return
151
152 parts = action.split(',')
153 self.raw_format = parts[0]
154 self._parse_types(self.raw_format)
155 for part in parts[1:]:
156 part = self._replace_args(part)
157 self.values.append(part)
158
159 aliases = {
160 "retval": "ctx->ax",
161 "arg1": "ctx->di",
162 "arg2": "ctx->si",
163 "arg3": "ctx->dx",
164 "arg4": "ctx->cx",
165 "arg5": "ctx->r8",
166 "arg6": "ctx->r9",
167 "$uid": "(unsigned)(bpf_get_current_uid_gid() & 0xffffffff)",
168 "$gid": "(unsigned)(bpf_get_current_uid_gid() >> 32)",
169 "$pid": "(unsigned)(bpf_get_current_pid_tgid() & 0xffffffff)",
170 "$tgid": "(unsigned)(bpf_get_current_pid_tgid() >> 32)",
171 "$cpu": "bpf_get_smp_processor_id()"
172 }
173
174 def _replace_args(self, expr):
175 for alias, replacement in Probe.aliases.items():
176 expr = expr.replace(alias, replacement)
177 return expr
178
179 p_type = { "u": ct.c_uint, "d": ct.c_int,
180 "llu": ct.c_ulonglong, "lld": ct.c_longlong,
181 "hu": ct.c_ushort, "hd": ct.c_short,
182 "x": ct.c_uint, "llx": ct.c_ulonglong,
183 "c": ct.c_ubyte }
184
185 def _generate_python_field_decl(self, idx, fields):
186 field_type = self.types[idx]
187 if field_type == "s":
188 ptype = ct.c_char * self.string_size
189 else:
190 ptype = Probe.p_type[field_type]
191 fields.append(("v%d" % idx, ptype))
192
193 def _generate_python_data_decl(self):
194 self.python_struct_name = "%s_%d_Data" % \
195 (self.function, self.probe_num)
196 fields = [
197 ("timestamp_ns", ct.c_ulonglong),
198 ("pid", ct.c_uint),
199 ("comm", ct.c_char * 16) # TASK_COMM_LEN
200 ]
201 for i in range(0, len(self.types)):
202 self._generate_python_field_decl(i, fields)
203 return type(self.python_struct_name, (ct.Structure,),
204 dict(_fields_=fields))
205
206 c_type = { "u": "unsigned int", "d": "int",
207 "llu": "unsigned long long", "lld": "long long",
208 "hu": "unsigned short", "hd": "short",
209 "x": "unsigned int", "llx": "unsigned long long",
210 "c": "char" }
211 fmt_types = c_type.keys()
212
213 def _generate_field_decl(self, idx):
214 field_type = self.types[idx]
215 if field_type == "s":
216 return "char v%d[%d];\n" % (idx, self.string_size)
217 if field_type in Probe.fmt_types:
218 return "%s v%d;\n" % (Probe.c_type[field_type], idx)
219 self._bail("unrecognized format specifier %s" % field_type)
220
221 def _generate_data_decl(self):
222 # The BPF program will populate values into the struct
223 # according to the format string, and the Python program will
224 # construct the final display string.
225 self.events_name = "%s_events" % self.probe_name
226 self.struct_name = "%s_data_t" % self.probe_name
227
228 data_fields = ""
229 for i, field_type in enumerate(self.types):
230 data_fields += " " + \
231 self._generate_field_decl(i)
232
233 text = """
234struct %s
235{
236 u64 timestamp_ns;
237 u32 pid;
238 char comm[TASK_COMM_LEN];
239%s
240};
241
242BPF_PERF_OUTPUT(%s);
243"""
244 return text % (self.struct_name, data_fields, self.events_name)
245
246 def _generate_field_assign(self, idx):
247 field_type = self.types[idx]
248 expr = self.values[idx]
249 if field_type == "s":
250 return """
251 if (%s != 0) {
252 bpf_probe_read(&__data.v%d, sizeof(__data.v%d), (void *)%s);
253 }
254""" % (expr, idx, idx, expr)
255 # return ("bpf_probe_read(&__data.v%d, " + \
256 # "sizeof(__data.v%d), (char*)%s);\n") % (idx, idx, expr)
257 # return ("__builtin_memcpy(&__data.v%d, (void *)%s, " + \
258 # "sizeof(__data.v%d));\n") % (idx, expr, idx)
259 if field_type in Probe.fmt_types:
260 return " __data.v%d = (%s)%s;\n" % \
261 (idx, Probe.c_type[field_type], expr)
262 self._bail("unrecognized field type %s" % field_type)
263
264 def generate_program(self, pid, include_self):
265 data_decl = self._generate_data_decl()
266 self.pid = pid
267 # kprobes don't have built-in pid filters, so we have to add
268 # it to the function body:
269 if len(self.library) == 0 and pid != -1:
270 pid_filter = """
271 u32 __pid = bpf_get_current_pid_tgid();
272 if (__pid != %d) { return 0; }
273""" % pid
274 elif not include_self:
275 pid_filter = """
276 u32 __pid = bpf_get_current_pid_tgid();
277 if (__pid == %d) { return 0; }
278""" % os.getpid()
279 else:
280 pid_filter = ""
281
282 data_fields = ""
283 for i, expr in enumerate(self.values):
284 data_fields += self._generate_field_assign(i)
285
286 text = """
287int %s(struct pt_regs *ctx)
288{
289 %s
290 if (!(%s)) return 0;
291
292 struct %s __data = {0};
293 __data.timestamp_ns = bpf_ktime_get_ns();
294 __data.pid = bpf_get_current_pid_tgid();
295 bpf_get_current_comm(&__data.comm, sizeof(__data.comm));
296%s
297 %s.perf_submit(ctx, &__data, sizeof(__data));
298 return 0;
299}
300"""
301 text = text % (self.probe_name, pid_filter,
302 self.filter, self.struct_name,
303 data_fields, self.events_name)
304
305 return data_decl + "\n" + text
306
307 @classmethod
308 def _time_off_str(cls, timestamp_ns):
309 return "%.6f" % (1e-9 * (timestamp_ns - cls.first_ts))
310
311 def print_event(self, cpu, data, size):
312 # Cast as the generated structure type and display
313 # according to the format string in the probe.
314 event = ct.cast(data, ct.POINTER(self.python_struct)).contents
315 values = map(lambda i: getattr(event, "v%d" % i),
316 range(0, len(self.values)))
317 msg = self.python_format % tuple(values)
318 time = strftime("%H:%M:%S") if Probe.use_localtime else \
319 Probe._time_off_str(event.timestamp_ns)
320 print("%-8s %-6d %-12s %-16s %s" % \
321 (time[:8], event.pid, event.comm[:12], self.function, msg))
322
323 Probe.event_count += 1
324 if Probe.max_events is not None and \
325 Probe.event_count >= Probe.max_events:
326 exit()
327
328 def attach(self, bpf, verbose):
329 if len(self.library) == 0:
330 self._attach_k(bpf)
331 else:
332 self._attach_u(bpf)
333 self.python_struct = self._generate_python_data_decl()
334 bpf[self.events_name].open_perf_buffer(self.print_event)
335
336 def _attach_k(self, bpf):
337 if self.probe_type == "r":
338 bpf.attach_kretprobe(event=self.function,
339 fn_name=self.probe_name)
340 elif self.probe_type == "p":
341 bpf.attach_kprobe(event=self.function,
342 fn_name=self.probe_name)
343
344 def _attach_u(self, bpf):
345 libpath = BPF.find_library(self.library)
346 if libpath is None:
347 # This might be an executable (e.g. 'bash')
348 with os.popen("/usr/bin/which %s 2>/dev/null" %
349 self.library) as w:
350 libpath = w.read().strip()
351 if libpath is None or len(libpath) == 0:
352 self._bail("unable to find library %s" % self.library)
353
354 if self.probe_type == "r":
355 bpf.attach_uretprobe(name=libpath,
356 sym=self.function,
357 fn_name=self.probe_name,
358 pid=self.pid)
359 else:
360 bpf.attach_uprobe(name=libpath,
361 sym=self.function,
362 fn_name=self.probe_name,
363 pid=self.pid)
364
365class Tool(object):
366 examples = """
367EXAMPLES:
368
369trace ::do_sys_open
370 Trace the open syscall and print a default trace message when entered
371trace '::do_sys_open "%s", arg2'
372 Trace the open syscall and print the filename being opened
373trace '::sys_read (arg3 > 20000) "read %d bytes", arg3'
374 Trace the read syscall and print a message for reads >20000 bytes
375trace 'r::do_sys_return "%llx", retval'
376 Trace the return from the open syscall and print the return value
377trace ':c:open (arg2 == 42) "%s %d", arg1, arg2'
378 Trace the open() call from libc only if the flags (arg2) argument is 42
379trace ':c:malloc "size = %d", arg1'
380 Trace malloc calls and print the size being allocated
381trace 'r:c:malloc (retval) "allocated = %p", retval
382 Trace returns from malloc and print non-NULL allocated buffers
383"""
384
385 def __init__(self):
386 parser = argparse.ArgumentParser(description=
387 "Attach to functions and print trace messages.",
388 formatter_class=argparse.RawDescriptionHelpFormatter,
389 epilog=Tool.examples)
390 parser.add_argument("-p", "--pid", type=int,
391 help="id of the process to trace (optional)")
392 parser.add_argument("-v", "--verbose", action="store_true",
393 help="print resulting BPF program code before executing")
394 parser.add_argument("-Z", "--string-size", type=int,
395 default=80, help="maximum size to read from strings")
396 parser.add_argument("-S", "--include-self", action="store_true",
397 help="do not filter trace's own pid from the trace")
398 parser.add_argument("-M", "--max-events", type=int,
399 help="number of events to print before quitting")
400 parser.add_argument("-o", "--offset", action="store_true",
401 help="use relative time from first traced message")
402 parser.add_argument(metavar="probe", dest="probes", nargs="+",
403 help="probe specifier (see examples)")
404 self.args = parser.parse_args()
405
406 def _create_probes(self):
407 Probe.configure(self.args)
408 self.probes = []
409 for probe_spec in self.args.probes:
410 self.probes.append(Probe(
411 probe_spec, self.args.string_size))
412
413 def _generate_program(self):
414 self.program = """
415#include <linux/ptrace.h>
416#include <linux/sched.h> /* For TASK_COMM_LEN */
417
418"""
419 for probe in self.probes:
420 self.program += probe.generate_program(
421 self.args.pid or -1, self.args.include_self)
422
423 if self.args.verbose:
424 print(self.program)
425
426 def _attach_probes(self):
427 self.bpf = BPF(text=self.program)
428 for probe in self.probes:
429 if self.args.verbose:
430 print(probe)
431 probe.attach(self.bpf, self.args.verbose)
432
433 def _main_loop(self):
434 all_probes_trivial = all(map(Probe.is_default_action,
435 self.probes))
436
437 # Print header
438 print("%-8s %-6s %-12s %-16s %s" % \
439 ("TIME", "PID", "COMM", "FUNC",
440 "-" if not all_probes_trivial else ""))
441
442 while True:
443 self.bpf.kprobe_poll()
444
445 def run(self):
446 try:
447 self._create_probes()
448 self._generate_program()
449 self._attach_probes()
450 self._main_loop()
451 except:
452 if self.args.verbose:
453 traceback.print_exc()
454 else:
455 print(sys.exc_value)
456
457if __name__ == "__main__":
458 Tool().run()