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