xref: /aosp_15_r20/external/bcc/libbpf-tools/execsnoop.c (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
1 // Based on execsnoop(8) from BCC by Brendan Gregg and others.
2 //
3 #include <argp.h>
4 #include <signal.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <sys/time.h>
9 #include <time.h>
10 #include <unistd.h>
11 #include <fcntl.h>
12 #include <bpf/libbpf.h>
13 #include <bpf/bpf.h>
14 #include "execsnoop.h"
15 #include "execsnoop.skel.h"
16 #include "btf_helpers.h"
17 #include "trace_helpers.h"
18 
19 #define PERF_BUFFER_PAGES   64
20 #define PERF_POLL_TIMEOUT_MS	100
21 #define MAX_ARGS_KEY 259
22 
23 static volatile sig_atomic_t exiting = 0;
24 
25 static struct env {
26 	bool time;
27 	bool timestamp;
28 	bool fails;
29 	uid_t uid;
30 	bool quote;
31 	const char *name;
32 	const char *line;
33 	bool print_uid;
34 	bool verbose;
35 	int max_args;
36 	char *cgroupspath;
37 	bool cg;
38 } env = {
39 	.max_args = DEFAULT_MAXARGS,
40 	.uid = INVALID_UID
41 };
42 
43 static struct timespec start_time;
44 
45 const char *argp_program_version = "execsnoop 0.1";
46 const char *argp_program_bug_address =
47 	"https://github.com/iovisor/bcc/tree/master/libbpf-tools";
48 const char argp_program_doc[] =
49 "Trace exec syscalls\n"
50 "\n"
51 "USAGE: execsnoop [-h] [-T] [-t] [-x] [-u UID] [-q] [-n NAME] [-l LINE] [-U] [-c CG]\n"
52 "                 [--max-args MAX_ARGS]\n"
53 "\n"
54 "EXAMPLES:\n"
55 "   ./execsnoop           # trace all exec() syscalls\n"
56 "   ./execsnoop -x        # include failed exec()s\n"
57 "   ./execsnoop -T        # include time (HH:MM:SS)\n"
58 "   ./execsnoop -U        # include UID\n"
59 "   ./execsnoop -u 1000   # only trace UID 1000\n"
60 "   ./execsnoop -t        # include timestamps\n"
61 "   ./execsnoop -q        # add \"quotemarks\" around arguments\n"
62 "   ./execsnoop -n main   # only print command lines containing \"main\"\n"
63 "   ./execsnoop -l tpkg   # only print command where arguments contains \"tpkg\""
64 "   ./execsnoop -c CG     # Trace process under cgroupsPath CG\n";
65 
66 static const struct argp_option opts[] = {
67 	{ "time", 'T', NULL, 0, "include time column on output (HH:MM:SS)" },
68 	{ "timestamp", 't', NULL, 0, "include timestamp on output" },
69 	{ "fails", 'x', NULL, 0, "include failed exec()s" },
70 	{ "uid", 'u', "UID", 0, "trace this UID only" },
71 	{ "quote", 'q', NULL, 0, "Add quotemarks (\") around arguments" },
72 	{ "name", 'n', "NAME", 0, "only print commands matching this name, any arg" },
73 	{ "line", 'l', "LINE", 0, "only print commands where arg contains this line" },
74 	{ "print-uid", 'U', NULL, 0, "print UID column" },
75 	{ "max-args", MAX_ARGS_KEY, "MAX_ARGS", 0,
76 		"maximum number of arguments parsed and displayed, defaults to 20" },
77 	{ "verbose", 'v', NULL, 0, "Verbose debug output" },
78 	{ "cgroup", 'c', "/sys/fs/cgroup/unified", 0, "Trace process in cgroup path"},
79 	{ NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" },
80 	{},
81 };
82 
parse_arg(int key,char * arg,struct argp_state * state)83 static error_t parse_arg(int key, char *arg, struct argp_state *state)
84 {
85 	long int uid, max_args;
86 
87 	switch (key) {
88 	case 'h':
89 		argp_state_help(state, stderr, ARGP_HELP_STD_HELP);
90 		break;
91 	case 'T':
92 		env.time = true;
93 		break;
94 	case 't':
95 		env.timestamp = true;
96 		break;
97 	case 'x':
98 		env.fails = true;
99 		break;
100 	case 'c':
101 		env.cgroupspath = arg;
102 		env.cg = true;
103 		break;
104 	case 'u':
105 		errno = 0;
106 		uid = strtol(arg, NULL, 10);
107 		if (errno || uid < 0 || uid >= INVALID_UID) {
108 			fprintf(stderr, "Invalid UID %s\n", arg);
109 			argp_usage(state);
110 		}
111 		env.uid = uid;
112 		break;
113 	case 'q':
114 		env.quote = true;
115 		break;
116 	case 'n':
117 		env.name = arg;
118 		break;
119 	case 'l':
120 		env.line = arg;
121 		break;
122 	case 'U':
123 		env.print_uid = true;
124 		break;
125 	case 'v':
126 		env.verbose = true;
127 		break;
128 	case MAX_ARGS_KEY:
129 		errno = 0;
130 		max_args = strtol(arg, NULL, 10);
131 		if (errno || max_args < 1 || max_args > TOTAL_MAX_ARGS) {
132 			fprintf(stderr, "Invalid MAX_ARGS %s, should be in [1, %d] range\n",
133 					arg, TOTAL_MAX_ARGS);
134 
135 			argp_usage(state);
136 		}
137 		env.max_args = max_args;
138 		break;
139 	default:
140 		return ARGP_ERR_UNKNOWN;
141 	}
142 	return 0;
143 }
144 
libbpf_print_fn(enum libbpf_print_level level,const char * format,va_list args)145 static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
146 {
147 	if (level == LIBBPF_DEBUG && !env.verbose)
148 		return 0;
149 	return vfprintf(stderr, format, args);
150 }
151 
sig_int(int signo)152 static void sig_int(int signo)
153 {
154 	exiting = 1;
155 }
156 
time_since_start()157 static void time_since_start()
158 {
159 	long nsec, sec;
160 	static struct timespec cur_time;
161 	double time_diff;
162 
163 	clock_gettime(CLOCK_MONOTONIC, &cur_time);
164 	nsec = cur_time.tv_nsec - start_time.tv_nsec;
165 	sec = cur_time.tv_sec - start_time.tv_sec;
166 	if (nsec < 0) {
167 		nsec += NSEC_PER_SEC;
168 		sec--;
169 	}
170 	time_diff = sec + (double)nsec / NSEC_PER_SEC;
171 	printf("%-8.3f", time_diff);
172 }
173 
quoted_symbol(char c)174 static void inline quoted_symbol(char c) {
175 	switch(c) {
176 		case '"':
177 			putchar('\\');
178 			putchar('"');
179 			break;
180 		case '\t':
181 			putchar('\\');
182 			putchar('t');
183 			break;
184 		case '\n':
185 			putchar('\\');
186 			putchar('n');
187 			break;
188 		default:
189 			putchar(c);
190 			break;
191 	}
192 }
193 
print_args(const struct event * e,bool quote)194 static void print_args(const struct event *e, bool quote)
195 {
196 	int i, args_counter = 0;
197 
198 	if (env.quote)
199 		putchar('"');
200 
201 	for (i = 0; i < e->args_size && args_counter < e->args_count; i++) {
202 		char c = e->args[i];
203 
204 		if (env.quote) {
205 			if (c == '\0') {
206 				args_counter++;
207 				putchar('"');
208 				putchar(' ');
209 				if (args_counter < e->args_count) {
210 					putchar('"');
211 				}
212 			} else {
213 				quoted_symbol(c);
214 			}
215 		} else {
216 			if (c == '\0') {
217 				args_counter++;
218 				putchar(' ');
219 			} else {
220 				putchar(c);
221 			}
222 		}
223 	}
224 	if (e->args_count == env.max_args + 1) {
225 		fputs(" ...", stdout);
226 	}
227 }
228 
handle_event(void * ctx,int cpu,void * data,__u32 data_sz)229 static void handle_event(void *ctx, int cpu, void *data, __u32 data_sz)
230 {
231 	const struct event *e = data;
232 	time_t t;
233 	struct tm *tm;
234 	char ts[32];
235 
236 	/* TODO: use pcre lib */
237 	if (env.name && strstr(e->comm, env.name) == NULL)
238 		return;
239 
240 	/* TODO: use pcre lib */
241 	if (env.line && strstr(e->comm, env.line) == NULL)
242 		return;
243 
244 	time(&t);
245 	tm = localtime(&t);
246 	strftime(ts, sizeof(ts), "%H:%M:%S", tm);
247 
248 	if (env.time) {
249 		printf("%-8s ", ts);
250 	}
251 	if (env.timestamp) {
252 		time_since_start();
253 	}
254 
255 	if (env.print_uid)
256 		printf("%-6d", e->uid);
257 
258 	printf("%-16s %-6d %-6d %3d ", e->comm, e->pid, e->ppid, e->retval);
259 	print_args(e, env.quote);
260 	putchar('\n');
261 }
262 
handle_lost_events(void * ctx,int cpu,__u64 lost_cnt)263 static void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt)
264 {
265 	fprintf(stderr, "Lost %llu events on CPU #%d!\n", lost_cnt, cpu);
266 }
267 
main(int argc,char ** argv)268 int main(int argc, char **argv)
269 {
270 	LIBBPF_OPTS(bpf_object_open_opts, open_opts);
271 	static const struct argp argp = {
272 		.options = opts,
273 		.parser = parse_arg,
274 		.doc = argp_program_doc,
275 	};
276 	struct perf_buffer *pb = NULL;
277 	struct execsnoop_bpf *obj;
278 	int err;
279 	int idx, cg_map_fd;
280 	int cgfd = -1;
281 
282 	err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
283 	if (err)
284 		return err;
285 
286 	libbpf_set_print(libbpf_print_fn);
287 
288 	err = ensure_core_btf(&open_opts);
289 	if (err) {
290 		fprintf(stderr, "failed to fetch necessary BTF for CO-RE: %s\n", strerror(-err));
291 		return 1;
292 	}
293 
294 	obj = execsnoop_bpf__open_opts(&open_opts);
295 	if (!obj) {
296 		fprintf(stderr, "failed to open BPF object\n");
297 		return 1;
298 	}
299 
300 	/* initialize global data (filtering options) */
301 	obj->rodata->ignore_failed = !env.fails;
302 	obj->rodata->targ_uid = env.uid;
303 	obj->rodata->max_args = env.max_args;
304 	obj->rodata->filter_cg = env.cg;
305 
306 	err = execsnoop_bpf__load(obj);
307 	if (err) {
308 		fprintf(stderr, "failed to load BPF object: %d\n", err);
309 		goto cleanup;
310 	}
311 
312 	/* update cgroup path fd to map */
313 	if (env.cg) {
314 		idx = 0;
315 		cg_map_fd = bpf_map__fd(obj->maps.cgroup_map);
316 		cgfd = open(env.cgroupspath, O_RDONLY);
317 		if (cgfd < 0) {
318 			fprintf(stderr, "Failed opening Cgroup path: %s", env.cgroupspath);
319 			goto cleanup;
320 		}
321 		if (bpf_map_update_elem(cg_map_fd, &idx, &cgfd, BPF_ANY)) {
322 			fprintf(stderr, "Failed adding target cgroup to map");
323 			goto cleanup;
324 		}
325 	}
326 
327 	clock_gettime(CLOCK_MONOTONIC, &start_time);
328 	err = execsnoop_bpf__attach(obj);
329 	if (err) {
330 		fprintf(stderr, "failed to attach BPF programs\n");
331 		goto cleanup;
332 	}
333 	/* print headers */
334 	if (env.time) {
335 		printf("%-9s", "TIME");
336 	}
337 	if (env.timestamp) {
338 		printf("%-8s ", "TIME(s)");
339 	}
340 	if (env.print_uid) {
341 		printf("%-6s ", "UID");
342 	}
343 
344 	printf("%-16s %-6s %-6s %3s %s\n", "PCOMM", "PID", "PPID", "RET", "ARGS");
345 
346 	/* setup event callbacks */
347 	pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES,
348 			      handle_event, handle_lost_events, NULL, NULL);
349 	if (!pb) {
350 		err = -errno;
351 		fprintf(stderr, "failed to open perf buffer: %d\n", err);
352 		goto cleanup;
353 	}
354 
355 	if (signal(SIGINT, sig_int) == SIG_ERR) {
356 		fprintf(stderr, "can't set signal handler: %s\n", strerror(errno));
357 		err = 1;
358 		goto cleanup;
359 	}
360 
361 	/* main: poll */
362 	while (!exiting) {
363 		err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
364 		if (err < 0 && err != -EINTR) {
365 			fprintf(stderr, "error polling perf buffer: %s\n", strerror(-err));
366 			goto cleanup;
367 		}
368 		/* reset err to return 0 if exiting */
369 		err = 0;
370 	}
371 
372 cleanup:
373 	perf_buffer__free(pb);
374 	execsnoop_bpf__destroy(obj);
375 	cleanup_core_btf(&open_opts);
376 	if (cgfd > 0)
377 		close(cgfd);
378 
379 	return err != 0;
380 }
381