-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
libbpf-tools: add BPF CO-RE filegone #4978
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ | |
/f2fsdist | ||
/f2fsslower | ||
/filelife | ||
/filegone | ||
/filetop | ||
/fsdist | ||
/fsslower | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,6 +55,7 @@ APPS = \ | |
execsnoop \ | ||
exitsnoop \ | ||
filelife \ | ||
filegone \ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. filegone comes before filelife in the dictionary. |
||
filetop \ | ||
fsdist \ | ||
fsslower \ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
// Copyright 2024 Sony Group Corporation | ||
|
||
#include <vmlinux.h> | ||
#include <bpf/bpf_helpers.h> | ||
#include <bpf/bpf_core_read.h> | ||
#include <bpf/bpf_tracing.h> | ||
#include "filegone.h" | ||
#include "core_fixes.bpf.h" | ||
|
||
#define FMODE_CREATED 0x100000 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not used as I check. |
||
|
||
const volatile pid_t targ_tgid = 0; | ||
|
||
struct { | ||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); | ||
__uint(key_size, sizeof(u32)); | ||
__uint(value_size, sizeof(u32)); | ||
} events SEC(".maps"); | ||
|
||
struct { | ||
__uint(type, BPF_MAP_TYPE_HASH); | ||
__uint(max_entries, 8192); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about using macro instead of magic number? |
||
__type(key, u32); /* tid */ | ||
__type(value, struct event); | ||
} currevent SEC(".maps"); | ||
|
||
/* In different kernel versions, function vfs_unlink() has three declarations, | ||
* and their parameter lists are as follows: | ||
* | ||
* int vfs_unlink(struct inode *dir, struct dentry *dentry, | ||
* struct inode **delegated_inode); | ||
* int vfs_unlink(struct user_namespace *mnt_userns, struct inode *dir, | ||
* struct dentry *dentry, struct inode **delegated_inode); | ||
* int vfs_unlink(struct mnt_idmap *idmap, struct inode *dir, | ||
* struct dentry *dentry, struct inode **delegated_inode); | ||
*/ | ||
SEC("kprobe/vfs_unlink") | ||
int BPF_KPROBE(vfs_unlink, void *arg0, void *arg1, void *arg2) | ||
{ | ||
u64 id = bpf_get_current_pid_tgid(); | ||
u32 tid = (u32)id; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently, 'tid' is used for thread ID and 'tgid' for process ID. 'tid' and 'pid' or |
||
struct event event = {}; | ||
const u8 *qs_name_ptr; | ||
u32 tgid = id >> 32; | ||
|
||
if (targ_tgid && targ_tgid != tgid) | ||
return 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please check indent. |
||
|
||
bool has_arg = renamedata_has_old_mnt_userns_field() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please declare variables first. |
||
|| renamedata_has_new_mnt_idmap_field(); | ||
qs_name_ptr = has_arg | ||
? BPF_CORE_READ((struct dentry *)arg2, d_name.name) | ||
: BPF_CORE_READ((struct dentry *)arg1, d_name.name); | ||
bpf_probe_read_kernel_str(&event.fname, sizeof(event.fname), qs_name_ptr); | ||
bpf_get_current_comm(&event.task, sizeof(event.task)); | ||
event.action = 'D'; | ||
event.tgid = tgid; | ||
|
||
bpf_map_update_elem(&currevent, &tid, &event, BPF_ANY); | ||
return 0; | ||
} | ||
|
||
/* vfs_rename() has two declarations in different kernel versions with the following parameter lists- | ||
* int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, | ||
struct dentry *new_dentry, struct inode **delegated_inode, unsigned int flags); | ||
* int vfs_rename(struct renamedata *); | ||
*/ | ||
SEC("kprobe/vfs_rename") | ||
int BPF_KPROBE(vfs_rename, void *arg0, void *arg1, void *arg2, void *arg3) | ||
{ | ||
u64 id = bpf_get_current_pid_tgid(); | ||
u32 tid = (u32)id; | ||
struct event event = {}; | ||
struct qstr qs_name_ptr; | ||
struct qstr qd_name_ptr; | ||
u32 tgid = id >> 32; | ||
|
||
if (targ_tgid && targ_tgid != tgid) | ||
return 0; | ||
|
||
bool has_arg = renamedata_has_old_mnt_userns_field() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please declare variables first. |
||
|| renamedata_has_new_mnt_idmap_field(); | ||
qs_name_ptr = has_arg | ||
? BPF_CORE_READ((struct renamedata *)arg0, old_dentry, d_name) | ||
: BPF_CORE_READ((struct dentry *)arg1, d_name); | ||
qd_name_ptr = has_arg | ||
? BPF_CORE_READ((struct renamedata *)arg0, new_dentry, d_name) | ||
: BPF_CORE_READ((struct dentry *)arg3, d_name); | ||
bpf_get_current_comm(&event.task, sizeof(event.task)); | ||
bpf_probe_read_kernel_str(&event.fname, sizeof(event.fname), qs_name_ptr.name); | ||
bpf_probe_read_kernel_str(&event.fname2, sizeof(event.fname2), qd_name_ptr.name); | ||
event.action = 'R'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about using an enum type for action values? |
||
event.tgid = tgid; | ||
|
||
bpf_map_update_elem(&currevent, &tid, &event, BPF_ANY); | ||
return 0; | ||
} | ||
|
||
static int handle_kretprobe(struct pt_regs *ctx) | ||
{ | ||
u64 id = bpf_get_current_pid_tgid(); | ||
u32 tid = (u32)id; | ||
int ret = PT_REGS_RC(ctx); | ||
struct event *event; | ||
|
||
event = bpf_map_lookup_elem(&currevent, &tid); | ||
if (!event) | ||
return 0; | ||
|
||
bpf_map_delete_elem(&currevent, &tid); | ||
|
||
/* Skip failed unlink or rename */ | ||
if (ret) | ||
return 0; | ||
|
||
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, event, sizeof(*event)); | ||
return 0; | ||
} | ||
|
||
SEC("kretprobe/vfs_unlink") | ||
int BPF_KRETPROBE(vfs_unlink_ret) | ||
{ | ||
return handle_kretprobe(ctx); | ||
} | ||
|
||
SEC("kretprobe/vfs_rename") | ||
int BPF_KRETPROBE(vfs_rename_ret) | ||
{ | ||
return handle_kretprobe(ctx); | ||
} | ||
|
||
char LICENSE[] SEC("license") = "GPL"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ | ||
// filegone Trace why a file has vanished (either deleted or renamed). | ||
// Copyright 2024 Sony Group Corporation | ||
// | ||
// Based on filegone from BCC by Curu. | ||
// | ||
|
||
#include <argp.h> | ||
#include <signal.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <unistd.h> | ||
#include <time.h> | ||
#include <bpf/libbpf.h> | ||
#include <bpf/bpf.h> | ||
#include "filegone.h" | ||
#include "filegone.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 verbose; | ||
} env = {}; | ||
|
||
const char *argp_program_version = "filegone 0.1"; | ||
const char *argp_program_bug_address = | ||
"https://github.com/iovisor/bcc/tree/master/libbpf-tools"; | ||
const char argp_program_doc[] = | ||
"Trace why a file has vanished (either deleted or renamed).\n" | ||
"\n" | ||
"USAGE: filegone [--help] [-p PID]\n" | ||
"\n" | ||
"EXAMPLES:\n" | ||
" filegone # trace all events\n" | ||
" filegone -p 123 # trace pid 123\n"; | ||
|
||
static const struct argp_option opts[] = { | ||
{"pid", 'p', "PID", 0, "Process PID to trace"}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please check the missing initialize group field. |
||
{"verbose", 'v', NULL, 0, "Verbose debug output"}, | ||
{NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help"}, | ||
{}, | ||
}; | ||
|
||
static error_t parse_arg(int key, char *arg, struct argp_state *state) | ||
{ | ||
int pid; | ||
|
||
switch (key) { | ||
case 'h': | ||
argp_state_help(state, stderr, ARGP_HELP_STD_HELP); | ||
break; | ||
case 'v': | ||
env.verbose = true; | ||
break; | ||
case 'p': | ||
errno = 0; | ||
pid = strtol(arg, NULL, 10); | ||
if (errno || pid <= 0) { | ||
fprintf(stderr, "invalid PID: %s\n", arg); | ||
argp_usage(state); | ||
} | ||
env.pid = pid; | ||
break; | ||
default: | ||
return ARGP_ERR_UNKNOWN; | ||
} | ||
return 0; | ||
} | ||
|
||
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) | ||
{ | ||
if (level == LIBBPF_DEBUG && !env.verbose) | ||
return 0; | ||
return vfprintf(stderr, format, args); | ||
} | ||
|
||
static void sig_int(int signo) | ||
{ | ||
exiting = 1; | ||
} | ||
|
||
const char *action2str(char action) | ||
{ | ||
return (action == 'D') ? "DELETE" : "RENAME"; | ||
} | ||
|
||
void handle_event(void *ctx, int cpu, void *data, __u32 data_sz) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how about declaring static if available? |
||
{ | ||
const char *action_str; | ||
char file_str[96]; | ||
struct event e; | ||
struct tm *tm; | ||
char ts[32]; | ||
time_t t; | ||
|
||
if (data_sz < sizeof(e)) { | ||
printf("Error: packet too small\n"); | ||
return; | ||
} | ||
|
||
/* Copy data as alignment in the perf buffer isn't guaranteed. */ | ||
memcpy(&e, data, sizeof(e)); | ||
action_str = action2str(e.action); | ||
|
||
if (strcmp(action_str, "RENAME") == 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can classify the values of action using an enum type, without string comparison. |
||
strncpy(file_str, e.fname, sizeof(file_str) - 1); | ||
strncat(file_str, " > ", sizeof(file_str) - strlen(file_str) - 1); | ||
strncat(file_str, e.fname2, sizeof(file_str) - strlen(file_str) - 1); | ||
} else { | ||
strncpy(file_str, e.fname, sizeof(file_str) - 1); | ||
} | ||
|
||
time(&t); | ||
tm = localtime(&t); | ||
strftime(ts, sizeof(ts), "%H:%M:%S", tm); | ||
|
||
printf("%-8s %-6d %-16s %-6s %s\n", | ||
ts, e.tgid, e.task, action_str, | ||
file_str); | ||
} | ||
|
||
void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt) | ||
{ | ||
fprintf(stderr, "lost %llu events on CPU #%d\n", lost_cnt, cpu); | ||
} | ||
|
||
int main(int argc, char **argv) | ||
{ | ||
LIBBPF_OPTS(bpf_object_open_opts, open_opts); | ||
static const struct argp argp = { | ||
.options = opts, | ||
.parser = parse_arg, | ||
.doc = argp_program_doc, | ||
}; | ||
struct perf_buffer *pb = NULL; | ||
struct filegone_bpf *obj; | ||
int err; | ||
|
||
err = argp_parse(&argp, argc, argv, 0, NULL, NULL); | ||
if (err) | ||
return err; | ||
|
||
libbpf_set_print(libbpf_print_fn); | ||
|
||
err = ensure_core_btf(&open_opts); | ||
if (err) { | ||
fprintf(stderr, "failed to fetch necessary BTF for CO-RE: %s\n", strerror(-err)); | ||
return 1; | ||
} | ||
|
||
obj = filegone_bpf__open_opts(&open_opts); | ||
if (!obj) { | ||
fprintf(stderr, "failed to open BPF object\n"); | ||
return 1; | ||
} | ||
|
||
/* initialize global data (filtering options) */ | ||
obj->rodata->targ_tgid = env.pid; | ||
|
||
err = filegone_bpf__load(obj); | ||
if (err) { | ||
fprintf(stderr, "failed to load BPF object: %d\n", err); | ||
goto cleanup; | ||
} | ||
|
||
err = filegone_bpf__attach(obj); | ||
if (err) { | ||
fprintf(stderr, "failed to attach BPF programs\n"); | ||
goto cleanup; | ||
} | ||
|
||
printf("Tracing deleted or renamed files ... Hit Ctrl-C to end.\n"); | ||
printf("%-8s %-6s %-16s %-6s %s\n", "TIME", "PID", "COMM", "ACTION", "FILE"); | ||
|
||
pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES, | ||
handle_event, handle_lost_events, NULL, NULL); | ||
if (!pb) { | ||
err = -errno; | ||
fprintf(stderr, "failed to open perf buffer: %d\n", err); | ||
goto cleanup; | ||
} | ||
|
||
if (signal(SIGINT, sig_int) == SIG_ERR) { | ||
fprintf(stderr, "can't set signal handler: %s\n", strerror(errno)); | ||
err = 1; | ||
goto cleanup; | ||
} | ||
|
||
while (!exiting) { | ||
err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS); | ||
if (err < 0 && err != -EINTR) { | ||
fprintf(stderr, "error polling perf buffer: %s\n", strerror(-err)); | ||
goto cleanup; | ||
} | ||
/* Reset err to return 0 if exiting */ | ||
err = 0; | ||
} | ||
|
||
cleanup: | ||
perf_buffer__free(pb); | ||
filegone_bpf__destroy(obj); | ||
cleanup_core_btf(&open_opts); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) | ||
// | ||
// Copyright 2024 Sony Group Corporation | ||
|
||
#ifndef __FILEGONE_H | ||
#define __FILEGONE_H | ||
|
||
#define DNAME_INLINE_LEN 32 | ||
#define TASK_COMM_LEN 16 | ||
|
||
struct event { | ||
char fname[DNAME_INLINE_LEN]; | ||
char fname2[DNAME_INLINE_LEN]; | ||
char task[TASK_COMM_LEN]; | ||
__u8 action; | ||
pid_t tgid; | ||
}; | ||
|
||
#endif /* __FILEGONE_H */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
filegone comes before filelife in the dictionary.
Please keep the list sorted.