execsnoop.py 6.05 KB
Newer Older
Brendan Gregg's avatar
Brendan Gregg committed
1 2 3 4 5 6
#!/usr/bin/python
# @lint-avoid-python-3-compatibility-imports
#
# execsnoop Trace new processes via exec() syscalls.
#           For Linux, uses BCC, eBPF. Embedded C.
#
7
# USAGE: execsnoop [-h] [-t] [-x] [-n NAME]
Brendan Gregg's avatar
Brendan Gregg committed
8 9 10 11 12 13 14 15 16 17 18 19 20
#
# This currently will print up to a maximum of 19 arguments, plus the process
# name, so 20 fields in total (MAXARG).
#
# This won't catch all new processes: an application may fork() but not exec().
#
# Copyright 2016 Netflix, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
#
# 07-Feb-2016   Brendan Gregg   Created this.

from __future__ import print_function
from bcc import BPF
21 22
from bcc.utils import ArgString, printb
import bcc.utils as utils
Brendan Gregg's avatar
Brendan Gregg committed
23
import argparse
24
import ctypes as ct
Brendan Gregg's avatar
Brendan Gregg committed
25
import re
26 27
import time
from collections import defaultdict
Brendan Gregg's avatar
Brendan Gregg committed
28 29 30 31

# arguments
examples = """examples:
    ./execsnoop           # trace all exec() syscalls
32
    ./execsnoop -x        # include failed exec()s
Brendan Gregg's avatar
Brendan Gregg committed
33 34
    ./execsnoop -t        # include timestamps
    ./execsnoop -n main   # only print command lines containing "main"
35
    ./execsnoop -l tpkg   # only print command where arguments contains "tpkg"
Brendan Gregg's avatar
Brendan Gregg committed
36 37 38 39 40 41 42
"""
parser = argparse.ArgumentParser(
    description="Trace exec() syscalls",
    formatter_class=argparse.RawDescriptionHelpFormatter,
    epilog=examples)
parser.add_argument("-t", "--timestamp", action="store_true",
    help="include timestamp on output")
43 44
parser.add_argument("-x", "--fails", action="store_true",
    help="include failed exec()s")
Brendan Gregg's avatar
Brendan Gregg committed
45
parser.add_argument("-n", "--name",
46
    type=ArgString,
Brendan Gregg's avatar
Brendan Gregg committed
47
    help="only print commands matching this name (regex), any arg")
48
parser.add_argument("-l", "--line",
49
    type=ArgString,
50
    help="only print commands where arg contains this line (regex)")
51 52
parser.add_argument("--max-args", default="20",
    help="maximum number of arguments parsed and displayed, defaults to 20")
53 54
parser.add_argument("--ebpf", action="store_true",
    help=argparse.SUPPRESS)
Brendan Gregg's avatar
Brendan Gregg committed
55 56 57 58 59 60 61 62
args = parser.parse_args()

# define BPF program
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
#include <linux/fs.h>

63
#define ARGSIZE  128
Brendan Gregg's avatar
Brendan Gregg committed
64

65 66 67 68
enum event_type {
    EVENT_ARG,
    EVENT_RET,
};
Brendan Gregg's avatar
Brendan Gregg committed
69

70 71 72 73 74 75 76
struct data_t {
    u32 pid;  // PID as in the userspace term (i.e. task->tgid in kernel)
    char comm[TASK_COMM_LEN];
    enum event_type type;
    char argv[ARGSIZE];
    int retval;
};
Brendan Gregg's avatar
Brendan Gregg committed
77

78
BPF_PERF_OUTPUT(events);
Brendan Gregg's avatar
Brendan Gregg committed
79

80 81 82 83
static int __submit_arg(struct pt_regs *ctx, void *ptr, struct data_t *data)
{
    bpf_probe_read(data->argv, sizeof(data->argv), ptr);
    events.perf_submit(ctx, data, sizeof(struct data_t));
Brendan Gregg's avatar
Brendan Gregg committed
84 85 86
    return 1;
}

87 88 89 90 91 92 93 94 95 96
static int submit_arg(struct pt_regs *ctx, void *ptr, struct data_t *data)
{
    const char *argp = NULL;
    bpf_probe_read(&argp, sizeof(argp), ptr);
    if (argp) {
        return __submit_arg(ctx, (void *)(argp), data);
    }
    return 0;
}

97 98
int kprobe__sys_execve(struct pt_regs *ctx,
    const char __user *filename,
Brendan Gregg's avatar
Brendan Gregg committed
99 100 101
    const char __user *const __user *__argv,
    const char __user *const __user *__envp)
{
102
    // create data here and pass to submit_arg to save stack space (#555)
103 104 105 106
    struct data_t data = {};
    data.pid = bpf_get_current_pid_tgid() >> 32;
    bpf_get_current_comm(&data.comm, sizeof(data.comm));
    data.type = EVENT_ARG;
Brendan Gregg's avatar
Brendan Gregg committed
107

108
    __submit_arg(ctx, (void *)filename, &data);
Brendan Gregg's avatar
Brendan Gregg committed
109

110 111 112 113 114 115
    // skip first arg, as we submitted filename
    #pragma unroll
    for (int i = 1; i < MAXARG; i++) {
        if (submit_arg(ctx, (void *)&__argv[i], &data) == 0)
             goto out;
    }
116 117 118 119

    // handle truncated argument list
    char ellipsis[] = "...";
    __submit_arg(ctx, (void *)ellipsis, &data);
Brendan Gregg's avatar
Brendan Gregg committed
120 121 122 123 124 125
out:
    return 0;
}

int kretprobe__sys_execve(struct pt_regs *ctx)
{
126 127 128 129 130 131 132
    struct data_t data = {};
    data.pid = bpf_get_current_pid_tgid() >> 32;
    bpf_get_current_comm(&data.comm, sizeof(data.comm));
    data.type = EVENT_RET;
    data.retval = PT_REGS_RC(ctx);
    events.perf_submit(ctx, &data, sizeof(data));

Brendan Gregg's avatar
Brendan Gregg committed
133 134 135 136
    return 0;
}
"""

137
bpf_text = bpf_text.replace("MAXARG", args.max_args)
138 139 140 141
if args.ebpf:
    print(bpf_text)
    exit()

Brendan Gregg's avatar
Brendan Gregg committed
142
# initialize BPF
143
b = BPF(text=bpf_text)
Brendan Gregg's avatar
Brendan Gregg committed
144 145 146 147

# header
if args.timestamp:
    print("%-8s" % ("TIME(s)"), end="")
148
print("%-16s %-6s %-6s %3s %s" % ("PCOMM", "PID", "PPID", "RET", "ARGS"))
Brendan Gregg's avatar
Brendan Gregg committed
149

150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
TASK_COMM_LEN = 16      # linux/sched.h
ARGSIZE = 128           # should match #define in C above

class Data(ct.Structure):
    _fields_ = [
        ("pid", ct.c_uint),
        ("comm", ct.c_char * TASK_COMM_LEN),
        ("type", ct.c_int),
        ("argv", ct.c_char * ARGSIZE),
        ("retval", ct.c_int),
    ]

class EventType(object):
    EVENT_ARG = 0
    EVENT_RET = 1

start_ts = time.time()
argv = defaultdict(list)

169
# TODO: This is best-effort PPID matching. Short-lived processes may exit
170 171
# before we get a chance to read the PPID. This should be replaced with
# fetching PPID via C when available (#364).
172 173 174 175 176 177 178 179 180 181
def get_ppid(pid):
    try:
        with open("/proc/%d/status" % pid) as status:
            for line in status:
                if line.startswith("PPid:"):
                    return int(line.split()[1])
    except IOError:
        pass
    return 0

182 183 184 185 186 187 188 189 190
# process event
def print_event(cpu, data, size):
    event = ct.cast(data, ct.POINTER(Data)).contents

    skip = False

    if event.type == EventType.EVENT_ARG:
        argv[event.pid].append(event.argv)
    elif event.type == EventType.EVENT_RET:
191
        if event.retval != 0 and not args.fails:
192
            skip = True
193
        if args.name and not re.search(bytes(args.name), event.comm):
194
            skip = True
195 196
        if args.line and not re.search(bytes(args.line),
                                       b' '.join(argv[event.pid])):
197
            skip = True
198 199 200 201

        if not skip:
            if args.timestamp:
                print("%-8.3f" % (time.time() - start_ts), end="")
202
            ppid = get_ppid(event.pid)
203 204 205
            ppid = b"%d" % ppid if ppid > 0 else b"?"
            printb(b"%-16s %-6d %-6s %3d %s" % (event.comm, event.pid,
                   ppid, event.retval, b' '.join(argv[event.pid])))
206 207 208 209
        try:
            del(argv[event.pid])
        except Exception:
            pass
210 211 212 213


# loop with callback to print_event
b["events"].open_perf_buffer(print_event)
Brendan Gregg's avatar
Brendan Gregg committed
214
while 1:
215
    b.kprobe_poll()