1#!/usr/bin/env python 2# @lint-avoid-python-3-compatibility-imports 3# 4# opensnoop Trace open() syscalls. 5# For Linux, uses BCC, eBPF. Embedded C. 6# 7# USAGE: opensnoop [-h] [-T] [-x] [-p PID] [-d DURATION] [-t TID] [-n NAME] 8# 9# Copyright (c) 2015 Brendan Gregg. 10# Licensed under the Apache License, Version 2.0 (the "License") 11# 12# 17-Sep-2015 Brendan Gregg Created this. 13# 29-Apr-2016 Allan McAleavy Updated for BPF_PERF_OUTPUT. 14# 08-Oct-2016 Dina Goldshtein Support filtering by PID and TID. 15# 28-Dec-2018 Tim Douglas Print flags argument, enable filtering 16# 06-Jan-2019 Takuma Kume Support filtering by UID 17 18from __future__ import print_function 19from bcc import ArgString, BPF 20from bcc.utils import printb 21import argparse 22import ctypes as ct 23from datetime import datetime, timedelta 24import os 25 26# arguments 27examples = """examples: 28 ./opensnoop # trace all open() syscalls 29 ./opensnoop -T # include timestamps 30 ./opensnoop -U # include UID 31 ./opensnoop -x # only show failed opens 32 ./opensnoop -p 181 # only trace PID 181 33 ./opensnoop -t 123 # only trace TID 123 34 ./opensnoop -u 1000 # only trace UID 1000 35 ./opensnoop -d 10 # trace for 10 seconds only 36 ./opensnoop -n main # only print process names containing "main" 37 ./opensnoop -e # show extended fields 38 ./opensnoop -f O_WRONLY -f O_RDWR # only print calls for writing 39""" 40parser = argparse.ArgumentParser( 41 description="Trace open() syscalls", 42 formatter_class=argparse.RawDescriptionHelpFormatter, 43 epilog=examples) 44parser.add_argument("-T", "--timestamp", action="store_true", 45 help="include timestamp on output") 46parser.add_argument("-U", "--print-uid", action="store_true", 47 help="print UID column") 48parser.add_argument("-x", "--failed", action="store_true", 49 help="only show failed opens") 50parser.add_argument("-p", "--pid", 51 help="trace this PID only") 52parser.add_argument("-t", "--tid", 53 help="trace this TID only") 54parser.add_argument("-u", "--uid", 55 help="trace this UID only") 56parser.add_argument("-d", "--duration", 57 help="total duration of trace in seconds") 58parser.add_argument("-n", "--name", 59 type=ArgString, 60 help="only print process names containing this name") 61parser.add_argument("--ebpf", action="store_true", 62 help=argparse.SUPPRESS) 63parser.add_argument("-e", "--extended_fields", action="store_true", 64 help="show extended fields") 65parser.add_argument("-f", "--flag_filter", action="append", 66 help="filter on flags argument (e.g., O_WRONLY)") 67args = parser.parse_args() 68debug = 0 69if args.duration: 70 args.duration = timedelta(seconds=int(args.duration)) 71flag_filter_mask = 0 72for flag in args.flag_filter or []: 73 if not flag.startswith('O_'): 74 exit("Bad flag: %s" % flag) 75 try: 76 flag_filter_mask |= getattr(os, flag) 77 except AttributeError: 78 exit("Bad flag: %s" % flag) 79 80# define BPF program 81bpf_text = """ 82#include <uapi/linux/ptrace.h> 83#include <uapi/linux/limits.h> 84#include <linux/sched.h> 85 86struct val_t { 87 u64 id; 88 char comm[TASK_COMM_LEN]; 89 const char *fname; 90 int flags; // EXTENDED_STRUCT_MEMBER 91}; 92 93struct data_t { 94 u64 id; 95 u64 ts; 96 u32 uid; 97 int ret; 98 char comm[TASK_COMM_LEN]; 99 char fname[NAME_MAX]; 100 int flags; // EXTENDED_STRUCT_MEMBER 101}; 102 103BPF_HASH(infotmp, u64, struct val_t); 104BPF_PERF_OUTPUT(events); 105 106int trace_entry(struct pt_regs *ctx, int dfd, const char __user *filename, int flags) 107{ 108 struct val_t val = {}; 109 u64 id = bpf_get_current_pid_tgid(); 110 u32 pid = id >> 32; // PID is higher part 111 u32 tid = id; // Cast and get the lower part 112 u32 uid = bpf_get_current_uid_gid(); 113 114 PID_TID_FILTER 115 UID_FILTER 116 FLAGS_FILTER 117 if (bpf_get_current_comm(&val.comm, sizeof(val.comm)) == 0) { 118 val.id = id; 119 val.fname = filename; 120 val.flags = flags; // EXTENDED_STRUCT_MEMBER 121 infotmp.update(&id, &val); 122 } 123 124 return 0; 125}; 126 127int trace_return(struct pt_regs *ctx) 128{ 129 u64 id = bpf_get_current_pid_tgid(); 130 struct val_t *valp; 131 struct data_t data = {}; 132 133 u64 tsp = bpf_ktime_get_ns(); 134 135 valp = infotmp.lookup(&id); 136 if (valp == 0) { 137 // missed entry 138 return 0; 139 } 140 bpf_probe_read(&data.comm, sizeof(data.comm), valp->comm); 141 bpf_probe_read(&data.fname, sizeof(data.fname), (void *)valp->fname); 142 data.id = valp->id; 143 data.ts = tsp / 1000; 144 data.uid = bpf_get_current_uid_gid(); 145 data.flags = valp->flags; // EXTENDED_STRUCT_MEMBER 146 data.ret = PT_REGS_RC(ctx); 147 148 events.perf_submit(ctx, &data, sizeof(data)); 149 infotmp.delete(&id); 150 151 return 0; 152} 153""" 154if args.tid: # TID trumps PID 155 bpf_text = bpf_text.replace('PID_TID_FILTER', 156 'if (tid != %s) { return 0; }' % args.tid) 157elif args.pid: 158 bpf_text = bpf_text.replace('PID_TID_FILTER', 159 'if (pid != %s) { return 0; }' % args.pid) 160else: 161 bpf_text = bpf_text.replace('PID_TID_FILTER', '') 162if args.uid: 163 bpf_text = bpf_text.replace('UID_FILTER', 164 'if (uid != %s) { return 0; }' % args.uid) 165else: 166 bpf_text = bpf_text.replace('UID_FILTER', '') 167if args.flag_filter: 168 bpf_text = bpf_text.replace('FLAGS_FILTER', 169 'if (!(flags & %d)) { return 0; }' % flag_filter_mask) 170else: 171 bpf_text = bpf_text.replace('FLAGS_FILTER', '') 172if not (args.extended_fields or args.flag_filter): 173 bpf_text = '\n'.join(x for x in bpf_text.split('\n') 174 if 'EXTENDED_STRUCT_MEMBER' not in x) 175if debug or args.ebpf: 176 print(bpf_text) 177 if args.ebpf: 178 exit() 179 180# initialize BPF 181b = BPF(text=bpf_text) 182b.attach_kprobe(event="do_sys_open", fn_name="trace_entry") 183b.attach_kretprobe(event="do_sys_open", fn_name="trace_return") 184 185TASK_COMM_LEN = 16 # linux/sched.h 186NAME_MAX = 255 # linux/limits.h 187 188class Data(ct.Structure): 189 _fields_ = [ 190 ("id", ct.c_ulonglong), 191 ("ts", ct.c_ulonglong), 192 ("uid", ct.c_uint32), 193 ("ret", ct.c_int), 194 ("comm", ct.c_char * TASK_COMM_LEN), 195 ("fname", ct.c_char * NAME_MAX), 196 ("flags", ct.c_int), 197 ] 198 199initial_ts = 0 200 201# header 202if args.timestamp: 203 print("%-14s" % ("TIME(s)"), end="") 204if args.print_uid: 205 print("%-6s" % ("UID"), end="") 206print("%-6s %-16s %4s %3s " % 207 ("TID" if args.tid else "PID", "COMM", "FD", "ERR"), end="") 208if args.extended_fields: 209 print("%-9s" % ("FLAGS"), end="") 210print("PATH") 211 212# process event 213def print_event(cpu, data, size): 214 event = ct.cast(data, ct.POINTER(Data)).contents 215 global initial_ts 216 217 # split return value into FD and errno columns 218 if event.ret >= 0: 219 fd_s = event.ret 220 err = 0 221 else: 222 fd_s = -1 223 err = - event.ret 224 225 if not initial_ts: 226 initial_ts = event.ts 227 228 if args.failed and (event.ret >= 0): 229 return 230 231 if args.name and bytes(args.name) not in event.comm: 232 return 233 234 if args.timestamp: 235 delta = event.ts - initial_ts 236 print("%-14.9f" % (float(delta) / 1000000), end="") 237 238 if args.print_uid: 239 print("%-6d" % event.uid, end="") 240 241 print("%-6d %-16s %4d %3d " % 242 (event.id & 0xffffffff if args.tid else event.id >> 32, 243 event.comm.decode('utf-8', 'replace'), fd_s, err), end="") 244 245 if args.extended_fields: 246 print("%08o " % event.flags, end="") 247 248 printb(b'%s' % event.fname) 249 250# loop with callback to print_event 251b["events"].open_perf_buffer(print_event, page_cnt=64) 252start_time = datetime.now() 253while not args.duration or datetime.now() - start_time < args.duration: 254 try: 255 b.perf_buffer_poll() 256 except KeyboardInterrupt: 257 exit() 258