blob: 770b5f188873ce1c820c004dac844ef17efdb8aa [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"
Mauricio Vasquez Bd1324ac2017-05-17 20:26:47 -050033 ./execsnoop -l tpkg # only print command where arguments contains "tpkg"
Brendan Greggaf18bb32016-02-07 15:28:50 -080034"""
35parser = argparse.ArgumentParser(
36 description="Trace exec() syscalls",
37 formatter_class=argparse.RawDescriptionHelpFormatter,
38 epilog=examples)
39parser.add_argument("-t", "--timestamp", action="store_true",
40 help="include timestamp on output")
Brendan Gregg151a43a2016-02-09 00:28:09 -080041parser.add_argument("-x", "--fails", action="store_true",
42 help="include failed exec()s")
Brendan Greggaf18bb32016-02-07 15:28:50 -080043parser.add_argument("-n", "--name",
44 help="only print commands matching this name (regex), any arg")
Nikita V. Shirokov0a015062017-04-19 13:07:08 -070045parser.add_argument("-l", "--line",
46 help="only print commands where arg contains this line (regex)")
Brendan Greggaf18bb32016-02-07 15:28:50 -080047args = parser.parse_args()
48
49# define BPF program
50bpf_text = """
51#include <uapi/linux/ptrace.h>
52#include <linux/sched.h>
53#include <linux/fs.h>
54
55#define MAXARG 20
Mark Drayton5b47e0f2016-06-02 10:53:20 +010056#define ARGSIZE 128
Brendan Greggaf18bb32016-02-07 15:28:50 -080057
Mark Drayton5b47e0f2016-06-02 10:53:20 +010058enum event_type {
59 EVENT_ARG,
60 EVENT_RET,
61};
Brendan Greggaf18bb32016-02-07 15:28:50 -080062
Mark Drayton5b47e0f2016-06-02 10:53:20 +010063struct 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 Greggaf18bb32016-02-07 15:28:50 -080070
Mark Drayton5b47e0f2016-06-02 10:53:20 +010071BPF_PERF_OUTPUT(events);
Brendan Greggaf18bb32016-02-07 15:28:50 -080072
Mark Drayton5b47e0f2016-06-02 10:53:20 +010073static 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 Greggaf18bb32016-02-07 15:28:50 -080077 return 1;
78}
79
Mark Drayton5b47e0f2016-06-02 10:53:20 +010080static 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 Greggaf18bb32016-02-07 15:28:50 -080090int 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 Goldshteinf41ae862016-10-19 01:14:30 +030094 // create data here and pass to submit_arg to save stack space (#555)
Mark Drayton5b47e0f2016-06-02 10:53:20 +010095 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 Greggaf18bb32016-02-07 15:28:50 -080099
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100100 __submit_arg(ctx, (void *)filename, &data);
101
102 int i = 1; // skip first arg, as we submitted filename
Brendan Greggaf18bb32016-02-07 15:28:50 -0800103
104 // unrolled loop to walk argv[] (MAXARG)
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100105 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 Greggaf18bb32016-02-07 15:28:50 -0800124
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100125 // handle truncated argument list
126 char ellipsis[] = "...";
127 __submit_arg(ctx, (void *)ellipsis, &data);
Brendan Greggaf18bb32016-02-07 15:28:50 -0800128out:
129 return 0;
130}
131
132int kretprobe__sys_execve(struct pt_regs *ctx)
133{
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100134 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 Greggaf18bb32016-02-07 15:28:50 -0800141 return 0;
142}
143"""
144
145# initialize BPF
146b = BPF(text=bpf_text)
147
148# header
149if args.timestamp:
150 print("%-8s" % ("TIME(s)"), end="")
Mark Draytonbfdb3d42016-06-02 10:53:34 +0100151print("%-16s %-6s %-6s %3s %s" % ("PCOMM", "PID", "PPID", "RET", "ARGS"))
Brendan Greggaf18bb32016-02-07 15:28:50 -0800152
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100153TASK_COMM_LEN = 16 # linux/sched.h
154ARGSIZE = 128 # should match #define in C above
Brendan Greggaf18bb32016-02-07 15:28:50 -0800155
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100156class 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
165class EventType(object):
166 EVENT_ARG = 0
167 EVENT_RET = 1
168
169start_ts = time.time()
170argv = defaultdict(list)
171
Mark Draytonbfdb3d42016-06-02 10:53:34 +0100172# TODO: This is best-effort PPID matching. Short-lived processes may exit
Sasha Goldshteinf41ae862016-10-19 01:14:30 +0300173# before we get a chance to read the PPID. This should be replaced with
174# fetching PPID via C when available (#364).
Mark Draytonbfdb3d42016-06-02 10:53:34 +0100175def 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 Drayton5b47e0f2016-06-02 10:53:20 +0100185# process event
186def 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. Shirokov0a015062017-04-19 13:07:08 -0700198 if args.line and not re.search(args.line,
199 b' '.join(argv[event.pid]).decode()):
200 skip = True
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100201
202 if not skip:
203 if args.timestamp:
204 print("%-8.3f" % (time.time() - start_ts), end="")
Mark Draytonbfdb3d42016-06-02 10:53:34 +0100205 ppid = get_ppid(event.pid)
Brenden Blanco2d862042017-01-05 11:13:12 -0800206 print("%-16s %-6s %-6s %3s %s" % (event.comm.decode(), event.pid,
Mark Draytonbfdb3d42016-06-02 10:53:34 +0100207 ppid if ppid > 0 else "?", event.retval,
Brenden Blanco2d862042017-01-05 11:13:12 -0800208 b' '.join(argv[event.pid]).decode()))
Nikita V. Shirokov0a015062017-04-19 13:07:08 -0700209 try:
210 del(argv[event.pid])
211 except Exception:
212 pass
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100213
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100214
215# loop with callback to print_event
216b["events"].open_perf_buffer(print_event)
Brendan Greggaf18bb32016-02-07 15:28:50 -0800217while 1:
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100218 b.kprobe_poll()