blob: c75712adacad8246955a5eb62ed0918d4c4c569b [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
Brenden Blanco42d60982017-04-24 14:31:28 -070021from bcc.utils import ArgString, printb
22import bcc.utils as utils
Brendan Greggaf18bb32016-02-07 15:28:50 -080023import argparse
Mark Drayton5b47e0f2016-06-02 10:53:20 +010024import ctypes as ct
Brendan Greggaf18bb32016-02-07 15:28:50 -080025import re
Mark Drayton5b47e0f2016-06-02 10:53:20 +010026import time
27from collections import defaultdict
Brendan Greggaf18bb32016-02-07 15:28:50 -080028
29# arguments
30examples = """examples:
31 ./execsnoop # trace all exec() syscalls
Brendan Gregg151a43a2016-02-09 00:28:09 -080032 ./execsnoop -x # include failed exec()s
Brendan Greggaf18bb32016-02-07 15:28:50 -080033 ./execsnoop -t # include timestamps
Bastian Reitemeier059ff552018-04-08 22:26:46 +020034 ./execsnoop -q # add "quotemarks" around arguments
Brendan Greggaf18bb32016-02-07 15:28:50 -080035 ./execsnoop -n main # only print command lines containing "main"
Mauricio Vasquez Bd1324ac2017-05-17 20:26:47 -050036 ./execsnoop -l tpkg # only print command where arguments contains "tpkg"
Brendan Greggaf18bb32016-02-07 15:28:50 -080037"""
38parser = argparse.ArgumentParser(
39 description="Trace exec() syscalls",
40 formatter_class=argparse.RawDescriptionHelpFormatter,
41 epilog=examples)
42parser.add_argument("-t", "--timestamp", action="store_true",
43 help="include timestamp on output")
Brendan Gregg151a43a2016-02-09 00:28:09 -080044parser.add_argument("-x", "--fails", action="store_true",
45 help="include failed exec()s")
Bastian Reitemeier79ce51c2018-04-08 21:42:41 +020046parser.add_argument("-q", "--quote", action="store_true",
47 help="Add quotemarks (\") around arguments."
48 )
Brendan Greggaf18bb32016-02-07 15:28:50 -080049parser.add_argument("-n", "--name",
Brenden Blanco42d60982017-04-24 14:31:28 -070050 type=ArgString,
Brendan Greggaf18bb32016-02-07 15:28:50 -080051 help="only print commands matching this name (regex), any arg")
Nikita V. Shirokov0a015062017-04-19 13:07:08 -070052parser.add_argument("-l", "--line",
Brenden Blanco42d60982017-04-24 14:31:28 -070053 type=ArgString,
Nikita V. Shirokov0a015062017-04-19 13:07:08 -070054 help="only print commands where arg contains this line (regex)")
Paul Chaignona0c9b482017-09-29 13:42:18 +020055parser.add_argument("--max-args", default="20",
56 help="maximum number of arguments parsed and displayed, defaults to 20")
Nathan Scottcf0792f2018-02-02 16:56:50 +110057parser.add_argument("--ebpf", action="store_true",
58 help=argparse.SUPPRESS)
Brendan Greggaf18bb32016-02-07 15:28:50 -080059args = parser.parse_args()
60
61# define BPF program
62bpf_text = """
63#include <uapi/linux/ptrace.h>
64#include <linux/sched.h>
65#include <linux/fs.h>
66
Mark Drayton5b47e0f2016-06-02 10:53:20 +010067#define ARGSIZE 128
Brendan Greggaf18bb32016-02-07 15:28:50 -080068
Mark Drayton5b47e0f2016-06-02 10:53:20 +010069enum event_type {
70 EVENT_ARG,
71 EVENT_RET,
72};
Brendan Greggaf18bb32016-02-07 15:28:50 -080073
Mark Drayton5b47e0f2016-06-02 10:53:20 +010074struct data_t {
75 u32 pid; // PID as in the userspace term (i.e. task->tgid in kernel)
76 char comm[TASK_COMM_LEN];
77 enum event_type type;
78 char argv[ARGSIZE];
79 int retval;
80};
Brendan Greggaf18bb32016-02-07 15:28:50 -080081
Mark Drayton5b47e0f2016-06-02 10:53:20 +010082BPF_PERF_OUTPUT(events);
Brendan Greggaf18bb32016-02-07 15:28:50 -080083
Mark Drayton5b47e0f2016-06-02 10:53:20 +010084static int __submit_arg(struct pt_regs *ctx, void *ptr, struct data_t *data)
85{
86 bpf_probe_read(data->argv, sizeof(data->argv), ptr);
87 events.perf_submit(ctx, data, sizeof(struct data_t));
Brendan Greggaf18bb32016-02-07 15:28:50 -080088 return 1;
89}
90
Mark Drayton5b47e0f2016-06-02 10:53:20 +010091static int submit_arg(struct pt_regs *ctx, void *ptr, struct data_t *data)
92{
93 const char *argp = NULL;
94 bpf_probe_read(&argp, sizeof(argp), ptr);
95 if (argp) {
96 return __submit_arg(ctx, (void *)(argp), data);
97 }
98 return 0;
99}
100
yonghong-song2da34262018-06-13 06:12:22 -0700101int syscall__execve(struct pt_regs *ctx,
htbegin3c399da2017-12-09 09:34:56 +0800102 const char __user *filename,
Brendan Greggaf18bb32016-02-07 15:28:50 -0800103 const char __user *const __user *__argv,
104 const char __user *const __user *__envp)
105{
Sasha Goldshteinf41ae862016-10-19 01:14:30 +0300106 // create data here and pass to submit_arg to save stack space (#555)
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100107 struct data_t data = {};
108 data.pid = bpf_get_current_pid_tgid() >> 32;
109 bpf_get_current_comm(&data.comm, sizeof(data.comm));
110 data.type = EVENT_ARG;
Brendan Greggaf18bb32016-02-07 15:28:50 -0800111
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100112 __submit_arg(ctx, (void *)filename, &data);
113
Paul Chaignona0c9b482017-09-29 13:42:18 +0200114 // skip first arg, as we submitted filename
115 #pragma unroll
116 for (int i = 1; i < MAXARG; i++) {
117 if (submit_arg(ctx, (void *)&__argv[i], &data) == 0)
118 goto out;
119 }
Brendan Greggaf18bb32016-02-07 15:28:50 -0800120
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100121 // handle truncated argument list
122 char ellipsis[] = "...";
123 __submit_arg(ctx, (void *)ellipsis, &data);
Brendan Greggaf18bb32016-02-07 15:28:50 -0800124out:
125 return 0;
126}
127
Yonghong Song64335692018-04-25 00:40:13 -0700128int do_ret_sys_execve(struct pt_regs *ctx)
Brendan Greggaf18bb32016-02-07 15:28:50 -0800129{
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100130 struct data_t data = {};
131 data.pid = bpf_get_current_pid_tgid() >> 32;
132 bpf_get_current_comm(&data.comm, sizeof(data.comm));
133 data.type = EVENT_RET;
134 data.retval = PT_REGS_RC(ctx);
135 events.perf_submit(ctx, &data, sizeof(data));
136
Brendan Greggaf18bb32016-02-07 15:28:50 -0800137 return 0;
138}
139"""
140
Nathan Scottf13107a2018-02-05 08:28:20 +1100141bpf_text = bpf_text.replace("MAXARG", args.max_args)
Nathan Scottcf0792f2018-02-02 16:56:50 +1100142if args.ebpf:
143 print(bpf_text)
144 exit()
145
Brendan Greggaf18bb32016-02-07 15:28:50 -0800146# initialize BPF
Nathan Scottcf0792f2018-02-02 16:56:50 +1100147b = BPF(text=bpf_text)
Yonghong Song64335692018-04-25 00:40:13 -0700148execve_fnname = b.get_syscall_fnname("execve")
yonghong-song2da34262018-06-13 06:12:22 -0700149b.attach_kprobe(event=execve_fnname, fn_name="syscall__execve")
Yonghong Song64335692018-04-25 00:40:13 -0700150b.attach_kretprobe(event=execve_fnname, fn_name="do_ret_sys_execve")
Brendan Greggaf18bb32016-02-07 15:28:50 -0800151
152# header
153if args.timestamp:
154 print("%-8s" % ("TIME(s)"), end="")
Mark Draytonbfdb3d42016-06-02 10:53:34 +0100155print("%-16s %-6s %-6s %3s %s" % ("PCOMM", "PID", "PPID", "RET", "ARGS"))
Brendan Greggaf18bb32016-02-07 15:28:50 -0800156
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100157TASK_COMM_LEN = 16 # linux/sched.h
158ARGSIZE = 128 # should match #define in C above
Brendan Greggaf18bb32016-02-07 15:28:50 -0800159
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100160class Data(ct.Structure):
161 _fields_ = [
162 ("pid", ct.c_uint),
163 ("comm", ct.c_char * TASK_COMM_LEN),
164 ("type", ct.c_int),
165 ("argv", ct.c_char * ARGSIZE),
166 ("retval", ct.c_int),
167 ]
168
169class EventType(object):
170 EVENT_ARG = 0
171 EVENT_RET = 1
172
173start_ts = time.time()
174argv = defaultdict(list)
175
Mark Draytonbfdb3d42016-06-02 10:53:34 +0100176# TODO: This is best-effort PPID matching. Short-lived processes may exit
Sasha Goldshteinf41ae862016-10-19 01:14:30 +0300177# before we get a chance to read the PPID. This should be replaced with
178# fetching PPID via C when available (#364).
Mark Draytonbfdb3d42016-06-02 10:53:34 +0100179def get_ppid(pid):
180 try:
181 with open("/proc/%d/status" % pid) as status:
182 for line in status:
183 if line.startswith("PPid:"):
184 return int(line.split()[1])
185 except IOError:
186 pass
187 return 0
188
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100189# process event
190def print_event(cpu, data, size):
191 event = ct.cast(data, ct.POINTER(Data)).contents
192
193 skip = False
194
195 if event.type == EventType.EVENT_ARG:
196 argv[event.pid].append(event.argv)
197 elif event.type == EventType.EVENT_RET:
Kirill Smelkovce36bb62017-09-24 21:58:19 +0300198 if event.retval != 0 and not args.fails:
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100199 skip = True
Brenden Blanco42d60982017-04-24 14:31:28 -0700200 if args.name and not re.search(bytes(args.name), event.comm):
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100201 skip = True
Bastian Reitemeier9345df12018-04-08 21:46:59 +0200202 if args.line and not re.search(bytes(args.line),
203 b' '.join(argv[event.pid])):
204 skip = True
Bastian Reitemeier79ce51c2018-04-08 21:42:41 +0200205 if args.quote:
206 argv[event.pid] = [
207 "\"" + arg.replace("\"", "\\\"") + "\""
208 for arg in argv[event.pid]
209 ]
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100210
211 if not skip:
212 if args.timestamp:
213 print("%-8.3f" % (time.time() - start_ts), end="")
Mark Draytonbfdb3d42016-06-02 10:53:34 +0100214 ppid = get_ppid(event.pid)
Brenden Blanco42d60982017-04-24 14:31:28 -0700215 ppid = b"%d" % ppid if ppid > 0 else b"?"
Javier Honduvilla Coto5340c212018-05-21 12:11:32 +0200216 argv_text = b' '.join(argv[event.pid]).replace(b'\n', b'\\n')
Brenden Blanco42d60982017-04-24 14:31:28 -0700217 printb(b"%-16s %-6d %-6s %3d %s" % (event.comm, event.pid,
Javier Honduvilla Coto5340c212018-05-21 12:11:32 +0200218 ppid, event.retval, argv_text))
Nikita V. Shirokov0a015062017-04-19 13:07:08 -0700219 try:
220 del(argv[event.pid])
221 except Exception:
222 pass
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100223
Mark Drayton5b47e0f2016-06-02 10:53:20 +0100224
225# loop with callback to print_event
226b["events"].open_perf_buffer(print_event)
Brendan Greggaf18bb32016-02-07 15:28:50 -0800227while 1:
Teng Qindbf00292018-02-28 21:47:50 -0800228 b.perf_buffer_poll()