diff --git a/libbpf-tools/Makefile b/libbpf-tools/Makefile index 065e3833939f..a442da985a6e 100644 --- a/libbpf-tools/Makefile +++ b/libbpf-tools/Makefile @@ -113,6 +113,7 @@ COMMON_OBJ = \ $(OUTPUT)/uprobe_helpers.o \ $(OUTPUT)/btf_helpers.o \ $(OUTPUT)/compat.o \ + $(OUTPUT)/path_helpers.o \ $(if $(ENABLE_MIN_CORE_BTFS),$(OUTPUT)/min_core_btf_tar.o) \ # diff --git a/libbpf-tools/filelife.bpf.c b/libbpf-tools/filelife.bpf.c index 6b3e6c31daa6..4ab26731e9dc 100644 --- a/libbpf-tools/filelife.bpf.c +++ b/libbpf-tools/filelife.bpf.c @@ -5,31 +5,42 @@ #include #include #include "filelife.h" +#include "compat.bpf.h" #include "core_fixes.bpf.h" +#include "path_helpers.bpf.h" /* linux: include/linux/fs.h */ #define FMODE_CREATED 0x100000 const volatile pid_t targ_tgid = 0; +const volatile bool full_path = false; + +struct create_arg { + u64 ts; + struct dentry *cwd_dentry; + struct vfsmount *cwd_vfsmnt; +}; struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 8192); __type(key, struct dentry *); - __type(value, u64); + __type(value, struct create_arg); } start SEC(".maps"); -struct { - __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); - __uint(key_size, sizeof(u32)); - __uint(value_size, sizeof(u32)); -} events SEC(".maps"); +struct unlink_event { + __u64 delta_ns; + pid_t tgid; + struct dentry *dentry; + struct dentry *cwd_dentry; + struct vfsmount *cwd_vfsmnt; +}; struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 8192); __type(key, u32); /* tid */ - __type(value, struct event); + __type(value, struct unlink_event); } currevent SEC(".maps"); static __always_inline int @@ -37,13 +48,19 @@ probe_create(struct dentry *dentry) { u64 id = bpf_get_current_pid_tgid(); u32 tgid = id >> 32; - u64 ts; + struct create_arg arg = {}; + struct task_struct *task; if (targ_tgid && targ_tgid != tgid) return 0; - ts = bpf_ktime_get_ns(); - bpf_map_update_elem(&start, &dentry, &ts, 0); + task = (struct task_struct *)bpf_get_current_task_btf(); + + arg.ts = bpf_ktime_get_ns(); + arg.cwd_dentry = BPF_CORE_READ(task, fs, pwd.dentry); + arg.cwd_vfsmnt = BPF_CORE_READ(task, fs, pwd.mnt); + + bpf_map_update_elem(&start, &dentry, &arg, 0); return 0; } @@ -102,33 +119,29 @@ SEC("kprobe/vfs_unlink") int BPF_KPROBE(vfs_unlink, void *arg0, void *arg1, void *arg2) { u64 id = bpf_get_current_pid_tgid(); - struct event event = {}; - const u8 *qs_name_ptr; + struct unlink_event unlink_event = {}; + struct create_arg *arg; u32 tgid = id >> 32; u32 tid = (u32)id; - u64 *tsp, delta_ns; + u64 delta_ns; bool has_arg = renamedata_has_old_mnt_userns_field() || renamedata_has_new_mnt_idmap_field(); - tsp = has_arg + arg = has_arg ? bpf_map_lookup_elem(&start, &arg2) : bpf_map_lookup_elem(&start, &arg1); - if (!tsp) + if (!arg) return 0; // missed entry - delta_ns = bpf_ktime_get_ns() - *tsp; + delta_ns = bpf_ktime_get_ns() - arg->ts; - qs_name_ptr = has_arg - ? BPF_CORE_READ((struct dentry *)arg2, d_name.name) - : BPF_CORE_READ((struct dentry *)arg1, d_name.name); + unlink_event.delta_ns = delta_ns; + unlink_event.tgid = tgid; + unlink_event.dentry = has_arg ? arg2 : arg1; + unlink_event.cwd_dentry = arg->cwd_dentry; + unlink_event.cwd_vfsmnt = arg->cwd_vfsmnt; - bpf_probe_read_kernel_str(&event.file, sizeof(event.file), qs_name_ptr); - bpf_get_current_comm(&event.task, sizeof(event.task)); - event.delta_ns = delta_ns; - event.tgid = tgid; - event.dentry = has_arg ? arg2 : arg1; - - bpf_map_update_elem(&currevent, &tid, &event, BPF_ANY); + bpf_map_update_elem(&currevent, &tid, &unlink_event, BPF_ANY); return 0; } @@ -138,10 +151,13 @@ int BPF_KRETPROBE(vfs_unlink_ret) u64 id = bpf_get_current_pid_tgid(); u32 tid = (u32)id; int ret = PT_REGS_RC(ctx); - struct event *event; + struct unlink_event *unlink_event; + struct event *eventp; + struct dentry *dentry; + const u8 *qs_name_ptr; - event = bpf_map_lookup_elem(&currevent, &tid); - if (!event) + unlink_event = bpf_map_lookup_elem(&currevent, &tid); + if (!unlink_event) return 0; bpf_map_delete_elem(&currevent, &tid); @@ -149,11 +165,32 @@ int BPF_KRETPROBE(vfs_unlink_ret) if (ret) return 0; - bpf_map_delete_elem(&start, &event->dentry); + eventp = reserve_buf(sizeof(*eventp)); + if (!eventp) + return 0; + + eventp->tgid = unlink_event->tgid; + eventp->delta_ns = unlink_event->delta_ns; + bpf_get_current_comm(&eventp->task, sizeof(eventp->task)); + + dentry = unlink_event->dentry; + qs_name_ptr = BPF_CORE_READ(dentry, d_name.name); + bpf_probe_read_kernel_str(&eventp->fname.pathes, sizeof(eventp->fname.pathes), + qs_name_ptr); + eventp->fname.depth = 0; + + /* get full-path */ + if (full_path && eventp->fname.pathes[0] != '/') + bpf_dentry_full_path(eventp->fname.pathes + NAME_MAX, NAME_MAX, + MAX_PATH_DEPTH - 1, + unlink_event->cwd_dentry, + unlink_event->cwd_vfsmnt, + &eventp->fname.failed, &eventp->fname.depth); + + bpf_map_delete_elem(&start, &unlink_event->dentry); /* output */ - bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, - event, sizeof(*event)); + submit_buf(ctx, eventp, sizeof(*eventp)); return 0; } diff --git a/libbpf-tools/filelife.c b/libbpf-tools/filelife.c index c0f0cdf2e21a..91a5b169e734 100644 --- a/libbpf-tools/filelife.c +++ b/libbpf-tools/filelife.c @@ -15,18 +15,18 @@ #include #include #include +#include "compat.h" #include "filelife.h" #include "filelife.skel.h" #include "btf_helpers.h" #include "trace_helpers.h" -#define PERF_BUFFER_PAGES 16 -#define PERF_POLL_TIMEOUT_MS 100 static volatile sig_atomic_t exiting = 0; static struct env { pid_t pid; + bool full_path; bool verbose; } env = { }; @@ -40,10 +40,12 @@ const char argp_program_doc[] = "\n" "EXAMPLES:\n" " filelife # trace all events\n" +" filelife -F # trace full-path of file\n" " filelife -p 123 # trace pid 123\n"; static const struct argp_option opts[] = { { "pid", 'p', "PID", 0, "Process PID to trace", 0 }, + { "full-path", 'F', NULL, 0, "Show full path", 0 }, { "verbose", 'v', NULL, 0, "Verbose debug output", 0 }, { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help", 0 }, {}, @@ -69,6 +71,9 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state) } env.pid = pid; break; + case 'F': + env.full_path = true; + break; default: return ARGP_ERR_UNKNOWN; } @@ -87,7 +92,7 @@ static void sig_int(int signo) exiting = 1; } -void handle_event(void *ctx, int cpu, void *data, __u32 data_sz) +int handle_event(void *ctx, void *data, size_t data_sz) { struct event e; struct tm *tm; @@ -96,17 +101,22 @@ void handle_event(void *ctx, int cpu, void *data, __u32 data_sz) if (data_sz < sizeof(e)) { printf("Error: packet too small\n"); - return; + return -EINVAL; } - /* Copy data as alignment in the perf buffer isn't guaranteed. */ + /* Copy data as alignment in the ring buffer isn't guaranteed. */ memcpy(&e, data, sizeof(e)); time(&t); tm = localtime(&t); strftime(ts, sizeof(ts), "%H:%M:%S", tm); - printf("%-8s %-6d %-16s %-7.2f %s\n", - ts, e.tgid, e.task, (double)e.delta_ns / 1000000000, - e.file); + printf("%-8s %-6d %-16s %-7.2f ", + ts, e.tgid, e.task, (double)e.delta_ns / 1000000000); + if (env.full_path) { + print_full_path(&e.fname); + printf("\n"); + } else + printf("%s\n", e.fname.pathes); + return 0; } void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt) @@ -122,7 +132,7 @@ int main(int argc, char **argv) .parser = parse_arg, .doc = argp_program_doc, }; - struct perf_buffer *pb = NULL; + struct bpf_buffer *buf = NULL; struct filelife_bpf *obj; int err; @@ -146,6 +156,7 @@ int main(int argc, char **argv) /* initialize global data (filtering options) */ obj->rodata->targ_tgid = env.pid; + obj->rodata->full_path = env.full_path; if (!kprobe_exists("security_inode_create")) bpf_program__set_autoload(obj->progs.security_inode_create, false); @@ -165,11 +176,17 @@ int main(int argc, char **argv) printf("Tracing the lifespan of short-lived files ... Hit Ctrl-C to end.\n"); printf("%-8s %-6s %-16s %-7s %s\n", "TIME", "PID", "COMM", "AGE(s)", "FILE"); - pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES, - handle_event, handle_lost_events, NULL, NULL); - if (!pb) { + buf = bpf_buffer__new(obj->maps.events, obj->maps.heap); + if (!buf) { + err = -errno; + fprintf(stderr, "failed to create ring/perf buffer: %d", err); + goto cleanup; + } + + err = bpf_buffer__open(buf, handle_event, handle_lost_events, NULL); + if (err) { err = -errno; - fprintf(stderr, "failed to open perf buffer: %d\n", err); + fprintf(stderr, "failed to open ring/perf buffer: %d\n", err); goto cleanup; } @@ -180,9 +197,9 @@ int main(int argc, char **argv) } while (!exiting) { - err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS); + err = bpf_buffer__poll(buf, POLL_TIMEOUT_MS); if (err < 0 && err != -EINTR) { - fprintf(stderr, "error polling perf buffer: %s\n", strerror(-err)); + fprintf(stderr, "error polling ring/perf buffer: %s\n", strerror(-err)); goto cleanup; } /* reset err to return 0 if exiting */ @@ -190,7 +207,7 @@ int main(int argc, char **argv) } cleanup: - perf_buffer__free(pb); + bpf_buffer__free(buf); filelife_bpf__destroy(obj); cleanup_core_btf(&open_opts); diff --git a/libbpf-tools/filelife.h b/libbpf-tools/filelife.h index 5ac280a98373..57564fe3136b 100644 --- a/libbpf-tools/filelife.h +++ b/libbpf-tools/filelife.h @@ -2,16 +2,15 @@ #ifndef __FILELIFE_H #define __FILELIFE_H -#define DNAME_INLINE_LEN 32 +#include "path_helpers.h" + #define TASK_COMM_LEN 16 struct event { - char file[DNAME_INLINE_LEN]; + struct full_path fname; char task[TASK_COMM_LEN]; __u64 delta_ns; pid_t tgid; - /* private */ - void *dentry; }; #endif /* __FILELIFE_H */ diff --git a/libbpf-tools/opensnoop.bpf.c b/libbpf-tools/opensnoop.bpf.c index 64d931a8ff7e..3cf41070b60f 100644 --- a/libbpf-tools/opensnoop.bpf.c +++ b/libbpf-tools/opensnoop.bpf.c @@ -6,6 +6,7 @@ #include #include "compat.bpf.h" #include "opensnoop.h" +#include "path_helpers.bpf.h" #ifndef O_CREAT #define O_CREAT 00000100 @@ -134,9 +135,9 @@ int trace_exit(struct syscall_trace_exit* ctx) eventp->pid = bpf_get_current_pid_tgid() >> 32; eventp->uid = bpf_get_current_uid_gid(); bpf_get_current_comm(&eventp->comm, sizeof(eventp->comm)); - bpf_probe_read_user_str(&eventp->fname, sizeof(eventp->fname), + bpf_probe_read_user_str(&eventp->fname.pathes, sizeof(eventp->fname.pathes), ap->fname); - eventp->path_depth = 0; + eventp->fname.depth = 0; eventp->flags = ap->flags; if (ap->flags & O_CREAT || (ap->flags & O_TMPFILE) == O_TMPFILE) @@ -152,64 +153,10 @@ int trace_exit(struct syscall_trace_exit* ctx) eventp->callers[0] = stack[1]; eventp->callers[1] = stack[2]; - if (full_path && eventp->fname[0] != '/') { - int depth; - struct task_struct *task; - struct dentry *dentry, *parent_dentry, *mnt_root; - struct vfsmount *vfsmnt; - struct mount *mnt; - size_t filepart_length; - char *payload = eventp->fname; - - - task = (struct task_struct *)bpf_get_current_task_btf(); - dentry = BPF_CORE_READ(task, fs, pwd.dentry); - vfsmnt = BPF_CORE_READ(task, fs, pwd.mnt); - mnt = container_of(vfsmnt, struct mount, mnt); - mnt_root = BPF_CORE_READ(vfsmnt, mnt_root); - - for (depth = 1, payload += NAME_MAX; depth < MAX_PATH_DEPTH; depth++) { - filepart_length = - bpf_probe_read_kernel_str(payload, NAME_MAX, - BPF_CORE_READ(dentry, d_name.name)); - - if (filepart_length < 0) { - eventp->get_path_failed = 1; - break; - } - - if (filepart_length > NAME_MAX) - break; - - parent_dentry = BPF_CORE_READ(dentry, d_parent); - - if (dentry == parent_dentry || dentry == mnt_root) { - struct mount *mnt_parent; - mnt_parent = BPF_CORE_READ(mnt, mnt_parent); - - if (mnt != mnt_parent) { - dentry = BPF_CORE_READ(mnt, mnt_mountpoint); - - mnt = mnt_parent; - vfsmnt = &mnt->mnt; - - mnt_root = BPF_CORE_READ(vfsmnt, mnt_root); - - eventp->path_depth++; - payload += NAME_MAX; - continue; - } else { - /* Real root directory */ - break; - } - } - - payload += NAME_MAX; - - dentry = parent_dentry; - eventp->path_depth++; - } - } + if (full_path && eventp->fname.pathes[0] != '/') + bpf_getcwd(eventp->fname.pathes + NAME_MAX, NAME_MAX, + MAX_PATH_DEPTH - 1, + &eventp->fname.failed, &eventp->fname.depth); /* emit event */ submit_buf(ctx, eventp, sizeof(*eventp)); diff --git a/libbpf-tools/opensnoop.c b/libbpf-tools/opensnoop.c index b8cd301bcfea..c155c66bd8b1 100644 --- a/libbpf-tools/opensnoop.c +++ b/libbpf-tools/opensnoop.c @@ -26,6 +26,7 @@ #ifdef USE_BLAZESYM #include "blazesym.h" #endif +#include "path_helpers.h" #define NSEC_PER_SEC 1000000000ULL @@ -268,33 +269,10 @@ int handle_event(void *ctx, void *data, size_t data_sz) sps_cnt += 9; } if (env.full_path) { - for (int depth = e.path_depth; depth >= 0; depth--) { - char *fname = (char *)&e.fname[NAME_MAX * depth]; - - /** - * If it is a mount point, there will be a '/', because - * the '/' will be added below, so just skip this '/'. - */ - if (fname[0] == '/' && fname[1] == '\0') - continue; - - /** - * 1. If the file/path name starts with '/', do not - * print the '/' prefix. - * 2. If bpf_probe_read_kernel_str() fails, or the - * directory depth reaches the upper limit - * MAX_PATH_DEPTH, the top-level directory - * is printed without the prefix '/'. - */ - printf("%s%s", - "/\0" + (e.fname[NAME_MAX * depth] == '/' || - ((e.get_path_failed || e.path_depth == MAX_PATH_DEPTH - 1) && - depth == e.path_depth)), - fname); - } + print_full_path(&e.fname); printf("\n"); } else - printf("%s\n", e.fname); + printf("%s\n", e.fname.pathes); #ifdef USE_BLAZESYM for (i = 0; result && i < result->size; i++) { diff --git a/libbpf-tools/opensnoop.h b/libbpf-tools/opensnoop.h index 10db02bc8acf..002711f1bf98 100644 --- a/libbpf-tools/opensnoop.h +++ b/libbpf-tools/opensnoop.h @@ -2,10 +2,10 @@ #ifndef __OPENSNOOP_H #define __OPENSNOOP_H +#include "path_helpers.h" + #define TASK_COMM_LEN 16 -#define NAME_MAX 255 #define INVALID_UID ((uid_t)-1) -#define MAX_PATH_DEPTH 32 struct args_t { const char *fname; @@ -23,15 +23,7 @@ struct event { __u32 mode; __u64 callers[2]; char comm[TASK_COMM_LEN]; - - /** - * Example: "/a/b/c/d" - * fname[]: "|d\0 |c\0 |b\0 |a\0 | |..." - * |NAME_MAX| - */ - char fname[NAME_MAX * MAX_PATH_DEPTH]; - __u32 path_depth; - int get_path_failed; + struct full_path fname; }; #endif /* __OPENSNOOP_H */ diff --git a/libbpf-tools/path_helpers.bpf.h b/libbpf-tools/path_helpers.bpf.h new file mode 100644 index 000000000000..a982d3808c1c --- /dev/null +++ b/libbpf-tools/path_helpers.bpf.h @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Rong Tao */ +#ifndef __PATH_HELPERS_BPF_H +#define __PATH_HELPERS_BPF_H 1 + +#include +#include +#include +#include "path_helpers.h" + + +static __always_inline +int bpf_dentry_full_path(char *pathes, int name_len, int max_depth, + struct dentry *dentry, struct vfsmount *vfsmnt, + int *failed, __u32 *path_depth) +{ + int depth; + struct dentry *parent_dentry, *mnt_root; + struct mount *mnt; + size_t filepart_length; + char *payload = pathes; + + mnt = container_of(vfsmnt, struct mount, mnt); + mnt_root = BPF_CORE_READ(vfsmnt, mnt_root); + + for (depth = 0; depth < max_depth; depth++) { + filepart_length = + bpf_probe_read_kernel_str(payload, name_len, + BPF_CORE_READ(dentry, d_name.name)); + + if (filepart_length < 0) { + *failed = 1; + break; + } + + if (filepart_length > name_len) + break; + + parent_dentry = BPF_CORE_READ(dentry, d_parent); + + if (dentry == parent_dentry || dentry == mnt_root) { + struct mount *mnt_parent; + mnt_parent = BPF_CORE_READ(mnt, mnt_parent); + + if (mnt != mnt_parent) { + dentry = BPF_CORE_READ(mnt, mnt_mountpoint); + + mnt = mnt_parent; + vfsmnt = &mnt->mnt; + + mnt_root = BPF_CORE_READ(vfsmnt, mnt_root); + + (*path_depth)++; + payload += name_len; + continue; + } else { + /* Real root directory */ + break; + } + } + + payload += name_len; + + dentry = parent_dentry; + (*path_depth)++; + } + + return 0; +} + +static __always_inline +int bpf_getcwd(char *pathes, int name_len, int max_depth, int *failed, + __u32 *path_depth) +{ + struct task_struct *task; + struct dentry *dentry; + struct vfsmount *vfsmnt; + + task = (struct task_struct *)bpf_get_current_task_btf(); + dentry = BPF_CORE_READ(task, fs, pwd.dentry); + vfsmnt = BPF_CORE_READ(task, fs, pwd.mnt); + + return bpf_dentry_full_path(pathes, name_len, max_depth, dentry, vfsmnt, + failed, path_depth); +} +#endif diff --git a/libbpf-tools/path_helpers.c b/libbpf-tools/path_helpers.c new file mode 100644 index 000000000000..8e5460f1f7e6 --- /dev/null +++ b/libbpf-tools/path_helpers.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Rong Tao */ +#include +#include "path_helpers.h" + + +int print_full_path(struct full_path *path) +{ + int n = 0, depth; + + for (depth = path->depth; depth >= 0; depth--) { + char *fname = (char *)&path->pathes[NAME_MAX * depth]; + + /** + * If it is a mount point, there will be a '/', because + * the '/' will be added below, so just skip this '/'. + */ + if (fname[0] == '/' && fname[1] == '\0') + continue; + + /** + * 1. If the file/path name starts with '/', do not + * print the '/' prefix. + * 2. If bpf_probe_read_kernel_str() fails, or the + * directory depth reaches the upper limit + * MAX_PATH_DEPTH, the top-level directory + * is printed without the prefix '/'. + */ + n = printf("%s%s", + "/\0" + (fname[0] == '/' || + ((path->failed || path->depth == MAX_PATH_DEPTH - 1) && + depth == path->depth)), + fname); + } + return n; +} diff --git a/libbpf-tools/path_helpers.h b/libbpf-tools/path_helpers.h new file mode 100644 index 000000000000..280299ba85a8 --- /dev/null +++ b/libbpf-tools/path_helpers.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Rong Tao */ +#ifndef __PATH_HELPERS_H +#define __PATH_HELPERS_H 1 + +#define NAME_MAX 255 +#define MAX_PATH_DEPTH 32 + +struct full_path { + /** + * Example: "/a/b/c/d" + * pathes[]: "|d\0 |c\0 |b\0 |a\0 | |..." + * |NAME_MAX| + */ + char pathes[NAME_MAX * MAX_PATH_DEPTH]; + unsigned int depth; + int failed; +}; + +int print_full_path(struct full_path *path); +#endif