blob: 22cb54d9b527cf3e6631cf96322ec9ac11ae9c0a [file] [log] [blame]
Brendan Greggaf18bb32016-02-07 15:28:50 -08001#!/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 Gregg151a43a2016-02-09 00:28:09 -08007# USAGE: execsnoop [-h] [-t] [-x] [-n NAME]
Brendan Greggaf18bb32016-02-07 15:28:50 -08008#
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
19from __future__ import print_function
20from bcc import BPF
21import argparse
Mark Drayton5b47e0f2016-06-02 10:53:20 +010022import ctypes as ct
Brendan Greggaf18bb32016-02-07 15:28:50 -080023import re
Mark Drayton5b47e0f2016-06-02 10:53:20 +010024import time
25from collections import defaultdict
Brendan Greggaf18bb32016-02-07 15:28:50 -080026
27# arguments
28examples = """examples:
29 ./execsnoop # trace all exec() syscalls
Brendan Gregg151a43a2016-02-09 00:28:09 -080030 ./execsnoop -x # include failed exec()s
Brendan Greggaf18bb32016-02-07 15:28:50 -080031 ./execsnoop -t # include timestamps
32 ./execsnoop -n main # only print command lines containing "main"
33"""
34parser = argparse.ArgumentParser(
35 description="Trace exec() syscalls",
36 formatter_class=argparse.RawDescriptionHelpFormatter,
37 epilog=examples)
38parser.add_argument("-t", "--timestamp", action="store_true",
39 help="include timestamp on output")
Brendan Gregg151a43a2016-02-09 00:28:09 -080040parser.add_argument("-x", "--fails", action="store_true",
41 help="include failed exec()s")
Brendan Greggaf18bb32016-02-07 15:28:50 -080042parser.add_argument("-n", "--name",
43 help="only print commands matching this name (regex), any arg")
44args = parser.parse_args()
45
46# define BPF program
47bpf_text = """
48#include <uapi/linux/ptrace.h>
49#include <linux/sched.h>
50#include <linux/fs.h>
51
52#define MAXARG 20
Mark Drayton5b47e0f2016-06-02 10:53:20 +010053#define ARGSIZE 128
Brendan Greggaf18bb32016-02-07 15:28:50 -080054
Mark Drayton5b47e0f2016-06-02 10:53:20 +010055enum event_type {
56 EVENT_ARG,
57 EVENT_RET,
58};
Brendan Greggaf18bb32016-02-07 15:28:50 -080059
Mark Drayton5b47e0f2016-06-02 10:53:20 +010060struct data_t {
61 u32 pid; // PID as in the userspace term (i.e. task->tgid in kernel)
62 char comm[TASK_COMM_LEN];
63 enum event_type type;
64 char argv[ARGSIZE];
65 int retval;
66};
Brendan Greggaf18bb32016-02-07 15:28:50 -080067
Mark Drayton5b47e0f2016-06-02 10:53:20 +010068BPF_PERF_OUTPUT(events);
Brendan Greggaf18bb32016-02-07 15:28:50 -080069
Mark Drayton5b47e0f2016-06-02 10:53:20 +010070static int __submit_arg(struct pt_regs *ctx, void *ptr, struct data_t *data)
71{
72 bpf_probe_read(data->argv, sizeof(data->argv), ptr);
73 events.perf_submit(ctx, data, sizeof(struct data_t));
Brendan Greggaf18bb32016-02-07 15:28:50 -080074 return 1;
75}
76
Mark Drayton5b47e0f2016-06-02 10:53:20 +010077static int submit_arg(struct pt_regs *ctx, void *ptr, struct data_t *data)
78{
79 const char *argp = NULL;
80 bpf_probe_read(&argp, sizeof(argp), ptr);
81 if (argp) {
82 return __submit_arg(ctx, (void *)(argp), data);
83 }
84 return 0;
85}
86
Brendan Greggaf18bb32016-02-07 15:28:50 -080087int kprobe__sys_execve(struct pt_regs *ctx, struct filename *filename,
88 const char __user *const __user *__argv,
89 const char __user *const __user *__envp)
90{
Sasha Goldshteinf41ae862016-10-19 01:14:30 +030091 // create data here and pass to submit_arg to save stack space (#555)
Mark Drayton5b47e0f2016-06-02 10:53:20 +010092 struct data_t data = {};
93 data.pid = bpf_get_current_pid_tgid() >> 32;
94 bpf_get_current_comm(&data.comm, sizeof(data.comm));
95 data.type = EVENT_ARG;
Brendan Greggaf18bb32016-02-07 15:28:50 -080096
Mark Drayton5b47e0f2016-06-02 10:53:20 +010097 __submit_arg(ctx, (void *)filename, &data);
98
99 int i = 1; // skip first arg, as we submitted filename
Brendan Greggaf18bb32016-02-07 15:28:50 -0800100
101 // unrolled loop to walk argv[] (MAXARG)
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100102 if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++;
103 if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++;
104 if (submit_arg(ctx, (void *)&__argv[i], &data) == 0) goto out; i++;
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++; // X
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++;
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++; // XX
Brendan Greggaf18bb32016-02-07 15:28:50 -0800121
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100122 // handle truncated argument list
123 char ellipsis[] = "...";
124 __submit_arg(ctx, (void *)ellipsis, &data);
Brendan Greggaf18bb32016-02-07 15:28:50 -0800125out:
126 return 0;
127}
128
129int kretprobe__sys_execve(struct pt_regs *ctx)
130{
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100131 struct data_t data = {};
132 data.pid = bpf_get_current_pid_tgid() >> 32;
133 bpf_get_current_comm(&data.comm, sizeof(data.comm));
134 data.type = EVENT_RET;
135 data.retval = PT_REGS_RC(ctx);
136 events.perf_submit(ctx, &data, sizeof(data));
137
Brendan Greggaf18bb32016-02-07 15:28:50 -0800138 return 0;
139}
140"""
141
142# initialize BPF
143b = BPF(text=bpf_text)
144
145# header
146if args.timestamp:
147 print("%-8s" % ("TIME(s)"), end="")
Mark Draytonbfdb3d42016-06-02 10:53:34 +0100148print("%-16s %-6s %-6s %3s %s" % ("PCOMM", "PID", "PPID", "RET", "ARGS"))
Brendan Greggaf18bb32016-02-07 15:28:50 -0800149
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100150TASK_COMM_LEN = 16 # linux/sched.h
151ARGSIZE = 128 # should match #define in C above
Brendan Greggaf18bb32016-02-07 15:28:50 -0800152
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100153class Data(ct.Structure):
154 _fields_ = [
155 ("pid", ct.c_uint),
156 ("comm", ct.c_char * TASK_COMM_LEN),
157 ("type", ct.c_int),
158 ("argv", ct.c_char * ARGSIZE),
159 ("retval", ct.c_int),
160 ]
161
162class EventType(object):
163 EVENT_ARG = 0
164 EVENT_RET = 1
165
166start_ts = time.time()
167argv = defaultdict(list)
168
Mark Draytonbfdb3d42016-06-02 10:53:34 +0100169# TODO: This is best-effort PPID matching. Short-lived processes may exit
Sasha Goldshteinf41ae862016-10-19 01:14:30 +0300170# before we get a chance to read the PPID. This should be replaced with
171# fetching PPID via C when available (#364).
Mark Draytonbfdb3d42016-06-02 10:53:34 +0100172def get_ppid(pid):
173 try:
174 with open("/proc/%d/status" % pid) as status:
175 for line in status:
176 if line.startswith("PPid:"):
177 return int(line.split()[1])
178 except IOError:
179 pass
180 return 0
181
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100182# process event
183def print_event(cpu, data, size):
184 event = ct.cast(data, ct.POINTER(Data)).contents
185
186 skip = False
187
188 if event.type == EventType.EVENT_ARG:
189 argv[event.pid].append(event.argv)
190 elif event.type == EventType.EVENT_RET:
191 if args.fails and event.retval == 0:
192 skip = True
193 if args.name and not re.search(args.name, event.comm):
194 skip = True
195
196 if not skip:
197 if args.timestamp:
198 print("%-8.3f" % (time.time() - start_ts), end="")
Mark Draytonbfdb3d42016-06-02 10:53:34 +0100199 ppid = get_ppid(event.pid)
Brenden Blanco2d862042017-01-05 11:13:12 -0800200 print("%-16s %-6s %-6s %3s %s" % (event.comm.decode(), event.pid,
Mark Draytonbfdb3d42016-06-02 10:53:34 +0100201 ppid if ppid > 0 else "?", event.retval,
Brenden Blanco2d862042017-01-05 11:13:12 -0800202 b' '.join(argv[event.pid]).decode()))
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100203
204 del(argv[event.pid])
205
206# loop with callback to print_event
207b["events"].open_perf_buffer(print_event)
Brendan Greggaf18bb32016-02-07 15:28:50 -0800208while 1:
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100209 b.kprobe_poll()