1 // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2 // Copyright (c) 2019 Facebook
3 // Copyright (c) 2020 Netflix
4 //
5 // Based on opensnoop(8) from BCC by Brendan Gregg and others.
6 // 14-Feb-2020 Brendan Gregg Created this.
7 #include <argp.h>
8 #include <signal.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <sys/time.h>
13 #include <time.h>
14 #include <unistd.h>
15 #include <bpf/libbpf.h>
16 #include <bpf/bpf.h>
17 #include "opensnoop.h"
18 #include "opensnoop.skel.h"
19 #include "btf_helpers.h"
20 #include "trace_helpers.h"
21 #ifdef USE_BLAZESYM
22 #include "blazesym.h"
23 #endif
24
25 /* Tune the buffer size and wakeup rate. These settings cope with roughly
26 * 50k opens/sec.
27 */
28 #define PERF_BUFFER_PAGES 64
29 #define PERF_BUFFER_TIME_MS 10
30
31 /* Set the poll timeout when no events occur. This can affect -d accuracy. */
32 #define PERF_POLL_TIMEOUT_MS 100
33
34 #define NSEC_PER_SEC 1000000000ULL
35
36 static volatile sig_atomic_t exiting = 0;
37
38 #ifdef USE_BLAZESYM
39 static blazesym *symbolizer;
40 #endif
41
42 static struct env {
43 pid_t pid;
44 pid_t tid;
45 uid_t uid;
46 int duration;
47 bool verbose;
48 bool timestamp;
49 bool print_uid;
50 bool extended;
51 bool failed;
52 char *name;
53 #ifdef USE_BLAZESYM
54 bool callers;
55 #endif
56 } env = {
57 .uid = INVALID_UID
58 };
59
60 const char *argp_program_version = "opensnoop 0.1";
61 const char *argp_program_bug_address =
62 "https://github.com/iovisor/bcc/tree/master/libbpf-tools";
63 const char argp_program_doc[] =
64 "Trace open family syscalls\n"
65 "\n"
66 "USAGE: opensnoop [-h] [-T] [-U] [-x] [-p PID] [-t TID] [-u UID] [-d DURATION]\n"
67 #ifdef USE_BLAZESYM
68 " [-n NAME] [-e] [-c]\n"
69 #else
70 " [-n NAME] [-e]\n"
71 #endif
72 "\n"
73 "EXAMPLES:\n"
74 " ./opensnoop # trace all open() syscalls\n"
75 " ./opensnoop -T # include timestamps\n"
76 " ./opensnoop -U # include UID\n"
77 " ./opensnoop -x # only show failed opens\n"
78 " ./opensnoop -p 181 # only trace PID 181\n"
79 " ./opensnoop -t 123 # only trace TID 123\n"
80 " ./opensnoop -u 1000 # only trace UID 1000\n"
81 " ./opensnoop -d 10 # trace for 10 seconds only\n"
82 " ./opensnoop -n main # only print process names containing \"main\"\n"
83 " ./opensnoop -e # show extended fields\n"
84 #ifdef USE_BLAZESYM
85 " ./opensnoop -c # show calling functions\n"
86 #endif
87 "";
88
89 static const struct argp_option opts[] = {
90 { "duration", 'd', "DURATION", 0, "Duration to trace"},
91 { "extended-fields", 'e', NULL, 0, "Print extended fields"},
92 { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help"},
93 { "name", 'n', "NAME", 0, "Trace process names containing this"},
94 { "pid", 'p', "PID", 0, "Process ID to trace"},
95 { "tid", 't', "TID", 0, "Thread ID to trace"},
96 { "timestamp", 'T', NULL, 0, "Print timestamp"},
97 { "uid", 'u', "UID", 0, "User ID to trace"},
98 { "print-uid", 'U', NULL, 0, "Print UID"},
99 { "verbose", 'v', NULL, 0, "Verbose debug output" },
100 { "failed", 'x', NULL, 0, "Failed opens only"},
101 #ifdef USE_BLAZESYM
102 { "callers", 'c', NULL, 0, "Show calling functions"},
103 #endif
104 {},
105 };
106
parse_arg(int key,char * arg,struct argp_state * state)107 static error_t parse_arg(int key, char *arg, struct argp_state *state)
108 {
109 static int pos_args;
110 long int pid, uid, duration;
111
112 switch (key) {
113 case 'e':
114 env.extended = true;
115 break;
116 case 'h':
117 argp_usage(state);
118 break;
119 case 'T':
120 env.timestamp = true;
121 break;
122 case 'U':
123 env.print_uid = true;
124 break;
125 case 'v':
126 env.verbose = true;
127 break;
128 case 'x':
129 env.failed = true;
130 break;
131 case 'd':
132 errno = 0;
133 duration = strtol(arg, NULL, 10);
134 if (errno || duration <= 0) {
135 fprintf(stderr, "Invalid duration: %s\n", arg);
136 argp_usage(state);
137 }
138 env.duration = duration;
139 break;
140 case 'n':
141 errno = 0;
142 env.name = arg;
143 break;
144 case 'p':
145 errno = 0;
146 pid = strtol(arg, NULL, 10);
147 if (errno || pid <= 0) {
148 fprintf(stderr, "Invalid PID: %s\n", arg);
149 argp_usage(state);
150 }
151 env.pid = pid;
152 break;
153 case 't':
154 errno = 0;
155 pid = strtol(arg, NULL, 10);
156 if (errno || pid <= 0) {
157 fprintf(stderr, "Invalid TID: %s\n", arg);
158 argp_usage(state);
159 }
160 env.tid = pid;
161 break;
162 case 'u':
163 errno = 0;
164 uid = strtol(arg, NULL, 10);
165 if (errno || uid < 0 || uid >= INVALID_UID) {
166 fprintf(stderr, "Invalid UID %s\n", arg);
167 argp_usage(state);
168 }
169 env.uid = uid;
170 break;
171 #ifdef USE_BLAZESYM
172 case 'c':
173 env.callers = true;
174 break;
175 #endif
176 case ARGP_KEY_ARG:
177 if (pos_args++) {
178 fprintf(stderr,
179 "Unrecognized positional argument: %s\n", arg);
180 argp_usage(state);
181 }
182 errno = 0;
183 break;
184 default:
185 return ARGP_ERR_UNKNOWN;
186 }
187 return 0;
188 }
189
libbpf_print_fn(enum libbpf_print_level level,const char * format,va_list args)190 static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
191 {
192 if (level == LIBBPF_DEBUG && !env.verbose)
193 return 0;
194 return vfprintf(stderr, format, args);
195 }
196
sig_int(int signo)197 static void sig_int(int signo)
198 {
199 exiting = 1;
200 }
201
handle_event(void * ctx,int cpu,void * data,__u32 data_sz)202 void handle_event(void *ctx, int cpu, void *data, __u32 data_sz)
203 {
204 const struct event *e = data;
205 struct tm *tm;
206 #ifdef USE_BLAZESYM
207 sym_src_cfg cfgs[] = {
208 { .src_type = SRC_T_PROCESS, .params = { .process = { .pid = e->pid }}},
209 };
210 const blazesym_result *result = NULL;
211 const blazesym_csym *sym;
212 int i, j;
213 #endif
214 int sps_cnt;
215 char ts[32];
216 time_t t;
217 int fd, err;
218
219 /* name filtering is currently done in user space */
220 if (env.name && strstr(e->comm, env.name) == NULL)
221 return;
222
223 /* prepare fields */
224 time(&t);
225 tm = localtime(&t);
226 strftime(ts, sizeof(ts), "%H:%M:%S", tm);
227 if (e->ret >= 0) {
228 fd = e->ret;
229 err = 0;
230 } else {
231 fd = -1;
232 err = - e->ret;
233 }
234
235 #ifdef USE_BLAZESYM
236 if (env.callers)
237 result = blazesym_symbolize(symbolizer, cfgs, 1, (const uint64_t *)&e->callers, 2);
238 #endif
239
240 /* print output */
241 sps_cnt = 0;
242 if (env.timestamp) {
243 printf("%-8s ", ts);
244 sps_cnt += 9;
245 }
246 if (env.print_uid) {
247 printf("%-7d ", e->uid);
248 sps_cnt += 8;
249 }
250 printf("%-6d %-16s %3d %3d ", e->pid, e->comm, fd, err);
251 sps_cnt += 7 + 17 + 4 + 4;
252 if (env.extended) {
253 printf("%08o ", e->flags);
254 sps_cnt += 9;
255 }
256 printf("%s\n", e->fname);
257
258 #ifdef USE_BLAZESYM
259 for (i = 0; result && i < result->size; i++) {
260 if (result->entries[i].size == 0)
261 continue;
262 sym = &result->entries[i].syms[0];
263
264 for (j = 0; j < sps_cnt; j++)
265 printf(" ");
266 if (sym->line_no)
267 printf("%s:%ld\n", sym->symbol, sym->line_no);
268 else
269 printf("%s\n", sym->symbol);
270 }
271
272 blazesym_result_free(result);
273 #endif
274 }
275
handle_lost_events(void * ctx,int cpu,__u64 lost_cnt)276 void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt)
277 {
278 fprintf(stderr, "Lost %llu events on CPU #%d!\n", lost_cnt, cpu);
279 }
280
main(int argc,char ** argv)281 int main(int argc, char **argv)
282 {
283 LIBBPF_OPTS(bpf_object_open_opts, open_opts);
284 static const struct argp argp = {
285 .options = opts,
286 .parser = parse_arg,
287 .doc = argp_program_doc,
288 };
289 struct perf_buffer *pb = NULL;
290 struct opensnoop_bpf *obj;
291 __u64 time_end = 0;
292 int err;
293
294 err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
295 if (err)
296 return err;
297
298 libbpf_set_print(libbpf_print_fn);
299
300 err = ensure_core_btf(&open_opts);
301 if (err) {
302 fprintf(stderr, "failed to fetch necessary BTF for CO-RE: %s\n", strerror(-err));
303 return 1;
304 }
305
306 obj = opensnoop_bpf__open_opts(&open_opts);
307 if (!obj) {
308 fprintf(stderr, "failed to open BPF object\n");
309 return 1;
310 }
311
312 /* initialize global data (filtering options) */
313 obj->rodata->targ_tgid = env.pid;
314 obj->rodata->targ_pid = env.tid;
315 obj->rodata->targ_uid = env.uid;
316 obj->rodata->targ_failed = env.failed;
317
318 /* aarch64 and riscv64 don't have open syscall */
319 if (!tracepoint_exists("syscalls", "sys_enter_open")) {
320 bpf_program__set_autoload(obj->progs.tracepoint__syscalls__sys_enter_open, false);
321 bpf_program__set_autoload(obj->progs.tracepoint__syscalls__sys_exit_open, false);
322 }
323
324 err = opensnoop_bpf__load(obj);
325 if (err) {
326 fprintf(stderr, "failed to load BPF object: %d\n", err);
327 goto cleanup;
328 }
329
330 err = opensnoop_bpf__attach(obj);
331 if (err) {
332 fprintf(stderr, "failed to attach BPF programs\n");
333 goto cleanup;
334 }
335
336 #ifdef USE_BLAZESYM
337 if (env.callers)
338 symbolizer = blazesym_new();
339 #endif
340
341 /* print headers */
342 if (env.timestamp)
343 printf("%-8s ", "TIME");
344 if (env.print_uid)
345 printf("%-7s ", "UID");
346 printf("%-6s %-16s %3s %3s ", "PID", "COMM", "FD", "ERR");
347 if (env.extended)
348 printf("%-8s ", "FLAGS");
349 printf("%s", "PATH");
350 #ifdef USE_BLAZESYM
351 if (env.callers)
352 printf("/CALLER");
353 #endif
354 printf("\n");
355
356 /* setup event callbacks */
357 pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES,
358 handle_event, handle_lost_events, NULL, NULL);
359 if (!pb) {
360 err = -errno;
361 fprintf(stderr, "failed to open perf buffer: %d\n", err);
362 goto cleanup;
363 }
364
365 /* setup duration */
366 if (env.duration)
367 time_end = get_ktime_ns() + env.duration * NSEC_PER_SEC;
368
369 if (signal(SIGINT, sig_int) == SIG_ERR) {
370 fprintf(stderr, "can't set signal handler: %s\n", strerror(errno));
371 err = 1;
372 goto cleanup;
373 }
374
375 /* main: poll */
376 while (!exiting) {
377 err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
378 if (err < 0 && err != -EINTR) {
379 fprintf(stderr, "error polling perf buffer: %s\n", strerror(-err));
380 goto cleanup;
381 }
382 if (env.duration && get_ktime_ns() > time_end)
383 goto cleanup;
384 /* reset err to return 0 if exiting */
385 err = 0;
386 }
387
388 cleanup:
389 perf_buffer__free(pb);
390 opensnoop_bpf__destroy(obj);
391 cleanup_core_btf(&open_opts);
392 #ifdef USE_BLAZESYM
393 blazesym_free(symbolizer);
394 #endif
395
396 return err != 0;
397 }
398