blob: 93cf838484efb80d37aa19a9544c08d75a1db9d6 [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
22import re
23
24# arguments
25examples = """examples:
26 ./execsnoop # trace all exec() syscalls
Brendan Gregg151a43a2016-02-09 00:28:09 -080027 ./execsnoop -x # include failed exec()s
Brendan Greggaf18bb32016-02-07 15:28:50 -080028 ./execsnoop -t # include timestamps
29 ./execsnoop -n main # only print command lines containing "main"
30"""
31parser = argparse.ArgumentParser(
32 description="Trace exec() syscalls",
33 formatter_class=argparse.RawDescriptionHelpFormatter,
34 epilog=examples)
35parser.add_argument("-t", "--timestamp", action="store_true",
36 help="include timestamp on output")
Brendan Gregg151a43a2016-02-09 00:28:09 -080037parser.add_argument("-x", "--fails", action="store_true",
38 help="include failed exec()s")
Brendan Greggaf18bb32016-02-07 15:28:50 -080039parser.add_argument("-n", "--name",
40 help="only print commands matching this name (regex), any arg")
41args = parser.parse_args()
42
43# define BPF program
44bpf_text = """
45#include <uapi/linux/ptrace.h>
46#include <linux/sched.h>
47#include <linux/fs.h>
48
49#define MAXARG 20
50#define ARGSIZE 64
51
52static int print_arg(void *ptr) {
53 // Fetch an argument, and print using bpf_trace_printk(). This is a work
54 // around until we have a binary trace interface for passing event data to
55 // bcc. Since exec()s should be low frequency, the additional overhead in
56 // this case should not be a problem.
57 const char *argp = NULL;
58 char buf[ARGSIZE] = {};
59
60 bpf_probe_read(&argp, sizeof(argp), ptr);
61 if (argp == NULL) return 0;
62
63 bpf_probe_read(&buf, sizeof(buf), (void *)(argp));
64 bpf_trace_printk("ARG %s\\n", buf);
65
66 return 1;
67}
68
69int kprobe__sys_execve(struct pt_regs *ctx, struct filename *filename,
70 const char __user *const __user *__argv,
71 const char __user *const __user *__envp)
72{
73 char fname[ARGSIZE] = {};
74 bpf_probe_read(&fname, sizeof(fname), (void *)(filename));
75 bpf_trace_printk("ARG %s\\n", fname);
76
77 int i = 1; // skip first arg, as we printed fname
78
79 // unrolled loop to walk argv[] (MAXARG)
80 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
81 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
82 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
83 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
84 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
85 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
86 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
87 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
88 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
89 if (print_arg((void *)&__argv[i]) == 0) goto out; i++; // X
90 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
91 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
92 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
93 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
94 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
95 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
96 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
97 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
98 if (print_arg((void *)&__argv[i]) == 0) goto out; i++;
99 if (print_arg((void *)&__argv[i]) == 0) goto out; i++; // XX
100 bpf_trace_printk("ARG ...\\n"); // truncated
101
102out:
103 return 0;
104}
105
106int kretprobe__sys_execve(struct pt_regs *ctx)
107{
108 bpf_trace_printk("RET %d\\n", ctx->ax);
109 return 0;
110}
111"""
112
113# initialize BPF
114b = BPF(text=bpf_text)
115
116# header
117if args.timestamp:
118 print("%-8s" % ("TIME(s)"), end="")
119print("%-16s %-6s %3s %s" % ("PCOMM", "PID", "RET", "ARGS"))
120
121start_ts = 0
122cmd = {}
123pcomm = {}
124
125# format output
126while 1:
127 (task, pid, cpu, flags, ts, msg) = b.trace_fields()
Brendan Gregg151a43a2016-02-09 00:28:09 -0800128 try:
129 (type, arg) = msg.split(" ", 1)
130 except ValueError:
131 continue
Brendan Greggaf18bb32016-02-07 15:28:50 -0800132
133 if start_ts == 0:
134 start_ts = ts
135
136 if type == "RET":
Brendan Gregg151a43a2016-02-09 00:28:09 -0800137 if pid not in cmd:
138 # zero args
139 cmd[pid] = ""
140 pcomm[pid] = ""
141
Brendan Greggaf18bb32016-02-07 15:28:50 -0800142 skip = 0
143 if args.name:
144 if not re.search(args.name, cmd[pid]):
145 skip = 1
Brendan Gregg151a43a2016-02-09 00:28:09 -0800146 if not args.fails and int(arg) < 0:
Brendan Greggaf18bb32016-02-07 15:28:50 -0800147 skip = 1
148 if skip:
149 del cmd[pid]
150 del pcomm[pid]
151 continue
152
153 # output
154 if args.timestamp:
155 print("%-8.3f" % (ts - start_ts), end="")
156 print("%-16s %-6s %3s %s" % (pcomm[pid], pid, arg, cmd[pid]))
157 del cmd[pid]
158 del pcomm[pid]
159 else:
160 # build command line string
161 if pid in cmd:
162 cmd[pid] = cmd[pid] + " " + arg
163 else:
164 cmd[pid] = arg
165 if pid not in pcomm:
166 pcomm[pid] = task