1#!/usr/bin/env python 2# @lint-avoid-python-3-compatibility-imports 3# 4# statsnoop Trace stat() syscalls. 5# For Linux, uses BCC, eBPF. Embedded C. 6# 7# USAGE: statsnoop [-h] [-t] [-x] [-p PID] 8# 9# Copyright 2016 Netflix, Inc. 10# Licensed under the Apache License, Version 2.0 (the "License") 11# 12# 08-Feb-2016 Brendan Gregg Created this. 13# 17-Feb-2016 Allan McAleavy updated for BPF_PERF_OUTPUT 14# 29-Nov-2022 Rocky Xing Added stat() variants. 15 16from __future__ import print_function 17from bcc import BPF 18import argparse 19 20# arguments 21examples = """examples: 22 ./statsnoop # trace all stat() syscalls 23 ./statsnoop -t # include timestamps 24 ./statsnoop -x # only show failed stats 25 ./statsnoop -p 181 # only trace PID 181 26""" 27parser = argparse.ArgumentParser( 28 description="Trace stat() syscalls", 29 formatter_class=argparse.RawDescriptionHelpFormatter, 30 epilog=examples) 31parser.add_argument("-t", "--timestamp", action="store_true", 32 help="include timestamp on output") 33parser.add_argument("-x", "--failed", action="store_true", 34 help="only show failed stats") 35parser.add_argument("-p", "--pid", 36 help="trace this PID only") 37parser.add_argument("--ebpf", action="store_true", 38 help=argparse.SUPPRESS) 39args = parser.parse_args() 40debug = 0 41 42# define BPF program 43bpf_text = """ 44#include <uapi/linux/ptrace.h> 45#include <uapi/linux/limits.h> 46#include <linux/sched.h> 47 48struct val_t { 49 const char *fname; 50}; 51 52struct data_t { 53 u32 pid; 54 u64 ts_ns; 55 int ret; 56 char comm[TASK_COMM_LEN]; 57 char fname[NAME_MAX]; 58}; 59 60BPF_HASH(infotmp, u32, struct val_t); 61BPF_PERF_OUTPUT(events); 62 63static int trace_entry(struct pt_regs *ctx, const char __user *filename) 64{ 65 struct val_t val = {}; 66 u64 pid_tgid = bpf_get_current_pid_tgid(); 67 u32 pid = pid_tgid >> 32; 68 u32 tid = (u32)pid_tgid; 69 70 FILTER 71 val.fname = filename; 72 infotmp.update(&tid, &val); 73 74 return 0; 75}; 76 77int syscall__stat_entry(struct pt_regs *ctx, const char __user *filename) 78{ 79 return trace_entry(ctx, filename); 80} 81 82int syscall__statx_entry(struct pt_regs *ctx, int dfd, const char __user *filename) 83{ 84 return trace_entry(ctx, filename); 85} 86 87int trace_return(struct pt_regs *ctx) 88{ 89 u64 pid_tgid = bpf_get_current_pid_tgid(); 90 u32 tid = (u32)pid_tgid; 91 struct val_t *valp; 92 93 valp = infotmp.lookup(&tid); 94 if (valp == 0) { 95 // missed entry 96 return 0; 97 } 98 99 struct data_t data = {.pid = pid_tgid >> 32}; 100 bpf_probe_read_user(&data.fname, sizeof(data.fname), (void *)valp->fname); 101 bpf_get_current_comm(&data.comm, sizeof(data.comm)); 102 data.ts_ns = bpf_ktime_get_ns(); 103 data.ret = PT_REGS_RC(ctx); 104 105 events.perf_submit(ctx, &data, sizeof(data)); 106 infotmp.delete(&tid); 107 108 return 0; 109} 110""" 111if args.pid: 112 bpf_text = bpf_text.replace('FILTER', 113 'if (pid != %s) { return 0; }' % args.pid) 114else: 115 bpf_text = bpf_text.replace('FILTER', '') 116if debug or args.ebpf: 117 print(bpf_text) 118 if args.ebpf: 119 exit() 120 121# initialize BPF 122b = BPF(text=bpf_text) 123 124# for POSIX compliance, all architectures implement these 125# system calls but the name of the actual entry point may 126# be different for which we must check if the entry points 127# actually exist before attaching the probes 128def try_attach_syscall_probes(syscall): 129 syscall_fnname = b.get_syscall_fnname(syscall) 130 if BPF.ksymname(syscall_fnname) != -1: 131 if syscall in ["statx", "fstatat64", "newfstatat"]: 132 b.attach_kprobe(event=syscall_fnname, fn_name="syscall__statx_entry") 133 else: 134 b.attach_kprobe(event=syscall_fnname, fn_name="syscall__stat_entry") 135 b.attach_kretprobe(event=syscall_fnname, fn_name="trace_return") 136 137try_attach_syscall_probes("stat") 138try_attach_syscall_probes("statx") 139try_attach_syscall_probes("statfs") 140try_attach_syscall_probes("newstat") 141try_attach_syscall_probes("newlstat") 142try_attach_syscall_probes("fstatat64") 143try_attach_syscall_probes("newfstatat") 144 145start_ts = 0 146prev_ts = 0 147delta = 0 148 149# header 150if args.timestamp: 151 print("%-14s" % ("TIME(s)"), end="") 152print("%-7s %-16s %4s %3s %s" % ("PID", "COMM", "FD", "ERR", "PATH")) 153 154# process event 155def print_event(cpu, data, size): 156 event = b["events"].event(data) 157 global start_ts 158 global prev_ts 159 global delta 160 global cont 161 162 # split return value into FD and errno columns 163 if event.ret >= 0: 164 if args.failed: 165 return 166 fd_s = event.ret 167 err = 0 168 else: 169 fd_s = -1 170 err = - event.ret 171 172 if start_ts == 0: 173 start_ts = event.ts_ns 174 175 if args.timestamp: 176 print("%-14.9f" % (float(event.ts_ns - start_ts) / 1000000000), end="") 177 178 print("%-7d %-16s %4d %3d %s" % (event.pid, 179 event.comm.decode('utf-8', 'replace'), fd_s, err, 180 event.fname.decode('utf-8', 'replace'))) 181 182# loop with callback to print_event 183b["events"].open_perf_buffer(print_event, page_cnt=64) 184while 1: 185 try: 186 b.perf_buffer_poll() 187 except KeyboardInterrupt: 188 exit() 189