|
18 | 18 | # 17-Feb-2016 Allan McAleavy updated for BPF_PERF_OUTPUT |
19 | 19 | # 13-Nov-2022 Rong Tao Check btf struct field for CO-RE and add vfs_open() |
20 | 20 | # 05-Nov-2023 Rong Tao Support unlink failed |
| 21 | +# 01-Jul-2025 Rong Tao Support full-path |
21 | 22 |
|
22 | 23 | from __future__ import print_function |
23 | 24 | from bcc import BPF |
| 25 | +from bcc.utils import printb |
24 | 26 | import argparse |
25 | 27 | from time import strftime |
26 | 28 |
|
27 | 29 | # arguments |
28 | 30 | examples = """examples: |
29 | 31 | ./filelife # trace lifecycle of file(create->remove) |
| 32 | + ./filelife -F # show full path of file |
30 | 33 | ./filelife -p 181 # only trace PID 181 |
31 | 34 | """ |
32 | 35 | parser = argparse.ArgumentParser( |
|
35 | 38 | epilog=examples) |
36 | 39 | parser.add_argument("-p", "--pid", |
37 | 40 | help="trace this PID only") |
| 41 | +parser.add_argument("-F", "--full-path", action="store_true", |
| 42 | + help="show full path") |
38 | 43 | parser.add_argument("--ebpf", action="store_true", |
39 | 44 | help=argparse.SUPPRESS) |
40 | 45 | args = parser.parse_args() |
|
45 | 50 | #include <uapi/linux/ptrace.h> |
46 | 51 | #include <linux/fs.h> |
47 | 52 | #include <linux/sched.h> |
| 53 | +#include <linux/fs_struct.h> |
| 54 | +#ifdef FULLPATH |
| 55 | +INCLUDE_FULL_PATH_H |
| 56 | +INCLUDE_PATH_HELPERS_BPF_H |
| 57 | +#endif |
48 | 58 |
|
49 | 59 | struct data_t { |
50 | 60 | u32 pid; |
51 | 61 | u64 delta; |
52 | 62 | char comm[TASK_COMM_LEN]; |
| 63 | + u32 path_depth; |
| 64 | +#ifdef FULLPATH |
| 65 | + FULL_PATH_FIELD(fname); |
| 66 | +#else |
53 | 67 | char fname[DNAME_INLINE_LEN]; |
54 | | - /* private */ |
55 | | - void *dentry; |
| 68 | +#endif |
56 | 69 | }; |
57 | 70 |
|
58 | | -BPF_HASH(birth, struct dentry *); |
59 | | -BPF_HASH(unlink_data, u32, struct data_t); |
60 | | -BPF_PERF_OUTPUT(events); |
| 71 | +struct create_arg { |
| 72 | + u64 ts; |
| 73 | + struct vfsmount *cwd_vfsmnt; |
| 74 | +}; |
| 75 | +
|
| 76 | +struct unlink_event { |
| 77 | + u32 tid; |
| 78 | + u64 delta; |
| 79 | + struct dentry *dentry; |
| 80 | + struct vfsmount *cwd_vfsmnt; |
| 81 | +}; |
| 82 | +
|
| 83 | +BPF_HASH(birth, struct dentry *, struct create_arg); |
| 84 | +BPF_HASH(unlink_data, u32, struct unlink_event); |
| 85 | +BPF_RINGBUF_OUTPUT(events, 64); |
61 | 86 |
|
62 | 87 | static int probe_dentry(struct pt_regs *ctx, struct dentry *dentry) |
63 | 88 | { |
| 89 | + struct task_struct *task; |
| 90 | + struct fs_struct *fs; |
| 91 | + struct create_arg arg = {}; |
64 | 92 | u32 pid = bpf_get_current_pid_tgid() >> 32; |
65 | 93 | FILTER |
66 | 94 |
|
67 | 95 | u64 ts = bpf_ktime_get_ns(); |
68 | | - birth.update(&dentry, &ts); |
| 96 | + task = (struct task_struct *)bpf_get_current_task_btf(); |
| 97 | +
|
| 98 | + arg.ts = ts; |
| 99 | + bpf_probe_read_kernel(&fs, sizeof(fs), &task->fs); |
| 100 | + bpf_probe_read_kernel(&arg.cwd_vfsmnt, sizeof(arg.cwd_vfsmnt), &fs->pwd.mnt); |
| 101 | +
|
| 102 | + birth.update(&dentry, &arg); |
69 | 103 |
|
70 | 104 | return 0; |
71 | 105 | } |
|
98 | 132 | // trace file deletion and output details |
99 | 133 | TRACE_UNLINK_FUNC |
100 | 134 | { |
101 | | - struct data_t data = {}; |
| 135 | + struct create_arg *arg; |
| 136 | + struct unlink_event event = {}; |
102 | 137 | u64 pid_tgid = bpf_get_current_pid_tgid(); |
103 | 138 | u32 pid = pid_tgid >> 32; |
104 | 139 | u32 tid = (u32)pid_tgid; |
105 | 140 |
|
106 | 141 | FILTER |
107 | 142 |
|
108 | | - u64 *tsp, delta; |
109 | | - tsp = birth.lookup(&dentry); |
110 | | - if (tsp == 0) { |
| 143 | + u64 delta; |
| 144 | + arg = birth.lookup(&dentry); |
| 145 | + if (arg == 0) { |
111 | 146 | return 0; // missed create |
112 | 147 | } |
113 | 148 |
|
114 | | - delta = (bpf_ktime_get_ns() - *tsp) / 1000000; |
115 | | -
|
116 | | - struct qstr d_name = dentry->d_name; |
117 | | - if (d_name.len == 0) |
118 | | - return 0; |
119 | | -
|
120 | | - if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0) { |
121 | | - data.pid = pid; |
122 | | - data.delta = delta; |
123 | | - bpf_probe_read_kernel(&data.fname, sizeof(data.fname), d_name.name); |
124 | | - } |
| 149 | + delta = (bpf_ktime_get_ns() - arg->ts) / 1000000; |
125 | 150 |
|
126 | 151 | /* record dentry, only delete from birth if unlink successful */ |
127 | | - data.dentry = dentry; |
| 152 | + event.delta = delta; |
| 153 | + event.tid = tid; |
| 154 | + event.dentry = dentry; |
| 155 | + event.cwd_vfsmnt = arg->cwd_vfsmnt; |
128 | 156 |
|
129 | | - unlink_data.update(&tid, &data); |
| 157 | + unlink_data.update(&tid, &event); |
130 | 158 | return 0; |
131 | 159 | } |
132 | 160 |
|
133 | 161 | int trace_unlink_ret(struct pt_regs *ctx) |
134 | 162 | { |
135 | 163 | int ret = PT_REGS_RC(ctx); |
| 164 | + struct unlink_event *unlink_event; |
136 | 165 | struct data_t *data; |
137 | 166 | u32 tid = (u32)bpf_get_current_pid_tgid(); |
138 | 167 |
|
139 | | - data = unlink_data.lookup(&tid); |
140 | | - if (!data) |
| 168 | + unlink_event = unlink_data.lookup(&tid); |
| 169 | + if (!unlink_event) |
141 | 170 | return 0; |
142 | 171 |
|
143 | 172 | /* delete it any way */ |
|
147 | 176 | if (ret) |
148 | 177 | return 0; |
149 | 178 |
|
150 | | - birth.delete((struct dentry **)&data->dentry); |
151 | | - events.perf_submit(ctx, data, sizeof(*data)); |
| 179 | + data = events.ringbuf_reserve(sizeof(struct data_t)); |
| 180 | + if (!data) |
| 181 | + return 0; |
| 182 | +
|
| 183 | + data->pid = unlink_event->tid; |
| 184 | + data->delta = unlink_event->delta; |
| 185 | + bpf_get_current_comm(&data->comm, sizeof(data->comm)); |
| 186 | +
|
| 187 | + data->path_depth = 0; |
| 188 | +
|
| 189 | +#ifdef FULLPATH |
| 190 | + if (data->fname[0] != '/') { |
| 191 | + bpf_dentry_full_path(data->fname, NAME_MAX, MAX_ENTRIES, |
| 192 | + unlink_event->dentry, unlink_event->cwd_vfsmnt, |
| 193 | + &data->path_depth); |
| 194 | + } |
| 195 | +#else |
| 196 | + struct qstr d_name = unlink_event->dentry->d_name; |
| 197 | + bpf_probe_read_kernel_str(&data->fname, sizeof(data->fname), d_name.name); |
| 198 | +#endif |
| 199 | +
|
| 200 | + birth.delete((struct dentry **)&unlink_event->dentry); |
| 201 | +
|
| 202 | + events.ringbuf_submit(data, sizeof(*data)); |
152 | 203 |
|
153 | 204 | return 0; |
154 | 205 | } |
|
183 | 234 | 'if (pid != %s) { return 0; }' % args.pid) |
184 | 235 | else: |
185 | 236 | bpf_text = bpf_text.replace('FILTER', '') |
186 | | -if debug or args.ebpf: |
187 | | - print(bpf_text) |
188 | | - if args.ebpf: |
189 | | - exit() |
| 237 | + |
| 238 | +if args.full_path: |
| 239 | + bpf_text = "#define FULLPATH\n" + bpf_text |
| 240 | + |
| 241 | + with open(BPF._find_file("full_path.h".encode("utf-8"))) as fileobj: |
| 242 | + progtxt = fileobj.read() |
| 243 | + bpf_text = bpf_text.replace('INCLUDE_FULL_PATH_H', progtxt) |
| 244 | + |
| 245 | + with open(BPF._find_file("path_helpers.bpf.c".encode("utf-8"))) as fileobj: |
| 246 | + progtxt = fileobj.read() |
| 247 | + bpf_text = bpf_text.replace('INCLUDE_PATH_HELPERS_BPF_H', progtxt) |
190 | 248 |
|
191 | 249 | if BPF.kernel_struct_has_field(b'renamedata', b'new_mnt_idmap') == 1: |
192 | 250 | bpf_text = bpf_text.replace('TRACE_CREATE_FUNC', trace_create_text_3) |
|
198 | 256 | bpf_text = bpf_text.replace('TRACE_CREATE_FUNC', trace_create_text_1) |
199 | 257 | bpf_text = bpf_text.replace('TRACE_UNLINK_FUNC', trace_unlink_text_1) |
200 | 258 |
|
| 259 | +if debug or args.ebpf: |
| 260 | + print(bpf_text) |
| 261 | + if args.ebpf: |
| 262 | + exit() |
| 263 | + |
201 | 264 | # initialize BPF |
202 | 265 | b = BPF(text=bpf_text) |
203 | 266 | b.attach_kprobe(event="vfs_create", fn_name="trace_create") |
|
216 | 279 | # process event |
217 | 280 | def print_event(cpu, data, size): |
218 | 281 | event = b["events"].event(data) |
219 | | - print("%-8s %-7d %-16s %-7.2f %s" % (strftime("%H:%M:%S"), event.pid, |
220 | | - event.comm.decode('utf-8', 'replace'), float(event.delta) / 1000, |
221 | | - event.fname.decode('utf-8', 'replace'))) |
222 | | - |
223 | | -b["events"].open_perf_buffer(print_event) |
| 282 | + printb(b"%-8s %-7d %-16s %-7.2f " % (strftime("%H:%M:%S").encode('utf-8', 'replace'), |
| 283 | + event.pid, event.comm, float(event.delta) / 1000), nl="") |
| 284 | + if args.full_path: |
| 285 | + import os |
| 286 | + import sys |
| 287 | + sys.path.append(os.path.dirname(sys.argv[0])) |
| 288 | + from path_helpers import get_full_path |
| 289 | + result = get_full_path(event.fname, event.path_depth) |
| 290 | + printb(b"%s" % result.encode("utf-8")) |
| 291 | + else: |
| 292 | + printb(b"%s" % event.fname) |
| 293 | + |
| 294 | +b["events"].open_ring_buffer(print_event) |
224 | 295 | while 1: |
225 | 296 | try: |
226 | | - b.perf_buffer_poll() |
| 297 | + b.ring_buffer_poll() |
227 | 298 | except KeyboardInterrupt: |
228 | 299 | exit() |
0 commit comments