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