| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 1 | #!/usr/bin/python | 
|  | 2 | # @lint-avoid-python-3-compatibility-imports | 
|  | 3 | # | 
|  | 4 | # execsnoop Trace new processes via exec() syscalls. | 
|  | 5 | #           For Linux, uses BCC, eBPF. Embedded C. | 
|  | 6 | # | 
| Brendan Gregg | 151a43a | 2016-02-09 00:28:09 -0800 | [diff] [blame] | 7 | # USAGE: execsnoop [-h] [-t] [-x] [-n NAME] | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 8 | # | 
|  | 9 | # This currently will print up to a maximum of 19 arguments, plus the process | 
|  | 10 | # name, so 20 fields in total (MAXARG). | 
|  | 11 | # | 
|  | 12 | # This won't catch all new processes: an application may fork() but not exec(). | 
|  | 13 | # | 
|  | 14 | # Copyright 2016 Netflix, Inc. | 
|  | 15 | # Licensed under the Apache License, Version 2.0 (the "License") | 
|  | 16 | # | 
|  | 17 | # 07-Feb-2016   Brendan Gregg   Created this. | 
|  | 18 |  | 
|  | 19 | from __future__ import print_function | 
|  | 20 | from bcc import BPF | 
|  | 21 | import argparse | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 22 | import ctypes as ct | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 23 | import re | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 24 | import time | 
|  | 25 | from collections import defaultdict | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 26 |  | 
|  | 27 | # arguments | 
|  | 28 | examples = """examples: | 
|  | 29 | ./execsnoop           # trace all exec() syscalls | 
| Brendan Gregg | 151a43a | 2016-02-09 00:28:09 -0800 | [diff] [blame] | 30 | ./execsnoop -x        # include failed exec()s | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 31 | ./execsnoop -t        # include timestamps | 
|  | 32 | ./execsnoop -n main   # only print command lines containing "main" | 
| Mauricio Vasquez B | d1324ac | 2017-05-17 20:26:47 -0500 | [diff] [blame] | 33 | ./execsnoop -l tpkg   # only print command where arguments contains "tpkg" | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 34 | """ | 
|  | 35 | parser = argparse.ArgumentParser( | 
|  | 36 | description="Trace exec() syscalls", | 
|  | 37 | formatter_class=argparse.RawDescriptionHelpFormatter, | 
|  | 38 | epilog=examples) | 
|  | 39 | parser.add_argument("-t", "--timestamp", action="store_true", | 
|  | 40 | help="include timestamp on output") | 
| Brendan Gregg | 151a43a | 2016-02-09 00:28:09 -0800 | [diff] [blame] | 41 | parser.add_argument("-x", "--fails", action="store_true", | 
|  | 42 | help="include failed exec()s") | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 43 | parser.add_argument("-n", "--name", | 
|  | 44 | help="only print commands matching this name (regex), any arg") | 
| Nikita V. Shirokov | 0a01506 | 2017-04-19 13:07:08 -0700 | [diff] [blame] | 45 | parser.add_argument("-l", "--line", | 
|  | 46 | help="only print commands where arg contains this line (regex)") | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 47 | args = parser.parse_args() | 
|  | 48 |  | 
|  | 49 | # define BPF program | 
|  | 50 | bpf_text = """ | 
|  | 51 | #include <uapi/linux/ptrace.h> | 
|  | 52 | #include <linux/sched.h> | 
|  | 53 | #include <linux/fs.h> | 
|  | 54 |  | 
|  | 55 | #define MAXARG   20 | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 56 | #define ARGSIZE  128 | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 57 |  | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 58 | enum event_type { | 
|  | 59 | EVENT_ARG, | 
|  | 60 | EVENT_RET, | 
|  | 61 | }; | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 62 |  | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 63 | struct data_t { | 
|  | 64 | u32 pid;  // PID as in the userspace term (i.e. task->tgid in kernel) | 
|  | 65 | char comm[TASK_COMM_LEN]; | 
|  | 66 | enum event_type type; | 
|  | 67 | char argv[ARGSIZE]; | 
|  | 68 | int retval; | 
|  | 69 | }; | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 70 |  | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 71 | BPF_PERF_OUTPUT(events); | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 72 |  | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 73 | static int __submit_arg(struct pt_regs *ctx, void *ptr, struct data_t *data) | 
|  | 74 | { | 
|  | 75 | bpf_probe_read(data->argv, sizeof(data->argv), ptr); | 
|  | 76 | events.perf_submit(ctx, data, sizeof(struct data_t)); | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 77 | return 1; | 
|  | 78 | } | 
|  | 79 |  | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 80 | static int submit_arg(struct pt_regs *ctx, void *ptr, struct data_t *data) | 
|  | 81 | { | 
|  | 82 | const char *argp = NULL; | 
|  | 83 | bpf_probe_read(&argp, sizeof(argp), ptr); | 
|  | 84 | if (argp) { | 
|  | 85 | return __submit_arg(ctx, (void *)(argp), data); | 
|  | 86 | } | 
|  | 87 | return 0; | 
|  | 88 | } | 
|  | 89 |  | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 90 | int kprobe__sys_execve(struct pt_regs *ctx, struct filename *filename, | 
|  | 91 | const char __user *const __user *__argv, | 
|  | 92 | const char __user *const __user *__envp) | 
|  | 93 | { | 
| Sasha Goldshtein | f41ae86 | 2016-10-19 01:14:30 +0300 | [diff] [blame] | 94 | // create data here and pass to submit_arg to save stack space (#555) | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 95 | struct data_t data = {}; | 
|  | 96 | data.pid = bpf_get_current_pid_tgid() >> 32; | 
|  | 97 | bpf_get_current_comm(&data.comm, sizeof(data.comm)); | 
|  | 98 | data.type = EVENT_ARG; | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 99 |  | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 100 | __submit_arg(ctx, (void *)filename, &data); | 
|  | 101 |  | 
|  | 102 | int i = 1;  // skip first arg, as we submitted filename | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 103 |  | 
|  | 104 | // unrolled loop to walk argv[] (MAXARG) | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 105 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 106 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 107 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 108 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 109 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 110 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 111 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 112 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 113 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; // X | 
|  | 114 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 115 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 116 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 117 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 118 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 119 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 120 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 121 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 122 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; | 
|  | 123 | if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++; // XX | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 124 |  | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 125 | // handle truncated argument list | 
|  | 126 | char ellipsis[] = "..."; | 
|  | 127 | __submit_arg(ctx, (void *)ellipsis, &data); | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 128 | out: | 
|  | 129 | return 0; | 
|  | 130 | } | 
|  | 131 |  | 
|  | 132 | int kretprobe__sys_execve(struct pt_regs *ctx) | 
|  | 133 | { | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 134 | struct data_t data = {}; | 
|  | 135 | data.pid = bpf_get_current_pid_tgid() >> 32; | 
|  | 136 | bpf_get_current_comm(&data.comm, sizeof(data.comm)); | 
|  | 137 | data.type = EVENT_RET; | 
|  | 138 | data.retval = PT_REGS_RC(ctx); | 
|  | 139 | events.perf_submit(ctx, &data, sizeof(data)); | 
|  | 140 |  | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 141 | return 0; | 
|  | 142 | } | 
|  | 143 | """ | 
|  | 144 |  | 
|  | 145 | # initialize BPF | 
|  | 146 | b = BPF(text=bpf_text) | 
|  | 147 |  | 
|  | 148 | # header | 
|  | 149 | if args.timestamp: | 
|  | 150 | print("%-8s" % ("TIME(s)"), end="") | 
| Mark Drayton | bfdb3d4 | 2016-06-02 10:53:34 +0100 | [diff] [blame] | 151 | print("%-16s %-6s %-6s %3s %s" % ("PCOMM", "PID", "PPID", "RET", "ARGS")) | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 152 |  | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 153 | TASK_COMM_LEN = 16      # linux/sched.h | 
|  | 154 | ARGSIZE = 128           # should match #define in C above | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 155 |  | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 156 | class Data(ct.Structure): | 
|  | 157 | _fields_ = [ | 
|  | 158 | ("pid", ct.c_uint), | 
|  | 159 | ("comm", ct.c_char * TASK_COMM_LEN), | 
|  | 160 | ("type", ct.c_int), | 
|  | 161 | ("argv", ct.c_char * ARGSIZE), | 
|  | 162 | ("retval", ct.c_int), | 
|  | 163 | ] | 
|  | 164 |  | 
|  | 165 | class EventType(object): | 
|  | 166 | EVENT_ARG = 0 | 
|  | 167 | EVENT_RET = 1 | 
|  | 168 |  | 
|  | 169 | start_ts = time.time() | 
|  | 170 | argv = defaultdict(list) | 
|  | 171 |  | 
| Mark Drayton | bfdb3d4 | 2016-06-02 10:53:34 +0100 | [diff] [blame] | 172 | # TODO: This is best-effort PPID matching. Short-lived processes may exit | 
| Sasha Goldshtein | f41ae86 | 2016-10-19 01:14:30 +0300 | [diff] [blame] | 173 | # before we get a chance to read the PPID. This should be replaced with | 
|  | 174 | # fetching PPID via C when available (#364). | 
| Mark Drayton | bfdb3d4 | 2016-06-02 10:53:34 +0100 | [diff] [blame] | 175 | def get_ppid(pid): | 
|  | 176 | try: | 
|  | 177 | with open("/proc/%d/status" % pid) as status: | 
|  | 178 | for line in status: | 
|  | 179 | if line.startswith("PPid:"): | 
|  | 180 | return int(line.split()[1]) | 
|  | 181 | except IOError: | 
|  | 182 | pass | 
|  | 183 | return 0 | 
|  | 184 |  | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 185 | # process event | 
|  | 186 | def print_event(cpu, data, size): | 
|  | 187 | event = ct.cast(data, ct.POINTER(Data)).contents | 
|  | 188 |  | 
|  | 189 | skip = False | 
|  | 190 |  | 
|  | 191 | if event.type == EventType.EVENT_ARG: | 
|  | 192 | argv[event.pid].append(event.argv) | 
|  | 193 | elif event.type == EventType.EVENT_RET: | 
|  | 194 | if args.fails and event.retval == 0: | 
|  | 195 | skip = True | 
|  | 196 | if args.name and not re.search(args.name, event.comm): | 
|  | 197 | skip = True | 
| Nikita V. Shirokov | 0a01506 | 2017-04-19 13:07:08 -0700 | [diff] [blame] | 198 | if args.line and not re.search(args.line, | 
|  | 199 | b' '.join(argv[event.pid]).decode()): | 
|  | 200 | skip = True | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 201 |  | 
|  | 202 | if not skip: | 
|  | 203 | if args.timestamp: | 
|  | 204 | print("%-8.3f" % (time.time() - start_ts), end="") | 
| Mark Drayton | bfdb3d4 | 2016-06-02 10:53:34 +0100 | [diff] [blame] | 205 | ppid = get_ppid(event.pid) | 
| Brenden Blanco | 2d86204 | 2017-01-05 11:13:12 -0800 | [diff] [blame] | 206 | print("%-16s %-6s %-6s %3s %s" % (event.comm.decode(), event.pid, | 
| Mark Drayton | bfdb3d4 | 2016-06-02 10:53:34 +0100 | [diff] [blame] | 207 | ppid if ppid > 0 else "?", event.retval, | 
| Brenden Blanco | 2d86204 | 2017-01-05 11:13:12 -0800 | [diff] [blame] | 208 | b' '.join(argv[event.pid]).decode())) | 
| Nikita V. Shirokov | 0a01506 | 2017-04-19 13:07:08 -0700 | [diff] [blame] | 209 | try: | 
|  | 210 | del(argv[event.pid]) | 
|  | 211 | except Exception: | 
|  | 212 | pass | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 213 |  | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 214 |  | 
|  | 215 | # loop with callback to print_event | 
|  | 216 | b["events"].open_perf_buffer(print_event) | 
| Brendan Gregg | af18bb3 | 2016-02-07 15:28:50 -0800 | [diff] [blame] | 217 | while 1: | 
| Mark Drayton | 5b47e0f | 2016-06-02 10:53:20 +0100 | [diff] [blame] | 218 | b.kprobe_poll() |