Skip to content

Commit 55f5bd3

Browse files
authored
libbpf-tools: statsnoop: Support fstat(2) and parse fd/dirfd in userspace (#5233)
Added support for fstat system calls, and resolve file names and paths from fd and dirfd in user mode. Userspace test code: # fstatat.c dirfd = openat(AT_FDCWD, "./", O_RDONLY | O_CLOEXEC); fstatat(dirfd, argv[0], &buf, AT_SYMLINK_NOFOLLOW); fstatat(dirfd, "/etc/os-release", &buf, AT_SYMLINK_NOFOLLOW); # fstat.c fd = open("/etc/os-release", O_RDONLY); fstat(fd, &buf); fstat(AT_FDCWD, &buf); Tracing: Before: $ sudo ./statsnoop -s PID COMM RET ERR SYSCALL PATH 25719 fstatat 0 0 newfstatat ./fstatat <no cwd> 25719 fstatat 0 0 newfstatat /etc/os-release < couldn't tracing fstat(2) > This patch: $ sudo ./statsnoop -s PID COMM RET ERR SYSCALL PATH 26794 fstatat 0 0 newfstatat /home/sdb/Git/tst-linux/syscall/samples/stat/./fstatat 26794 fstatat 0 0 newfstatat /etc/os-release 26797 fstat 0 0 newfstat /usr/lib/os-release 26800 fstat -1 9 newfstat /home/sdb/Git/tst-linux/syscall/samples/stat Maybe this isn't a good approach, as it's possible that both the process and the fd don't exist during parsing the fd and dirfd. Of course, this is problematic for processes and fds that survive for a short period of time, however, for long-running daemons, there is no problem. Signed-off-by: Rong Tao <[email protected]>
1 parent db5b63f commit 55f5bd3

File tree

3 files changed

+91
-9
lines changed

3 files changed

+91
-9
lines changed

libbpf-tools/statsnoop.bpf.c

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ const volatile pid_t target_pid = 0;
1111
const volatile bool trace_failed_only = false;
1212

1313
struct value {
14+
int fd;
15+
int dirfd;
1416
const char *pathname;
1517
enum sys_type type;
1618
};
@@ -28,19 +30,22 @@ struct {
2830
__uint(value_size, sizeof(__u32));
2931
} events SEC(".maps");
3032

31-
static int probe_entry(void *ctx, enum sys_type type, const char *pathname)
33+
static int probe_entry(void *ctx, enum sys_type type, int fd, int dirfd,
34+
const char *pathname)
3235
{
3336
__u64 id = bpf_get_current_pid_tgid();
3437
__u32 pid = id >> 32;
3538
__u32 tid = (__u32)id;
3639
struct value value = {};
3740

38-
if (!pathname)
41+
if (!pathname && fd == INVALID_FD)
3942
return 0;
4043

4144
if (target_pid && target_pid != pid)
4245
return 0;
4346

47+
value.fd = fd;
48+
value.dirfd = dirfd;
4449
value.pathname = pathname;
4550
value.type = type;
4651

@@ -70,7 +75,13 @@ static int probe_return(void *ctx, int ret)
7075
event.ret = ret;
7176
event.type = pvalue->type;
7277
bpf_get_current_comm(&event.comm, sizeof(event.comm));
73-
bpf_probe_read_user_str(event.pathname, sizeof(event.pathname), pvalue->pathname);
78+
event.fd = pvalue->fd;
79+
event.dirfd = pvalue->dirfd;
80+
if (pvalue->pathname)
81+
bpf_probe_read_user_str(event.pathname, sizeof(event.pathname),
82+
pvalue->pathname);
83+
else
84+
event.pathname[0] = '\0';
7485

7586
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));
7687
bpf_map_delete_elem(&values, &tid);
@@ -80,7 +91,8 @@ static int probe_return(void *ctx, int ret)
8091
SEC("tracepoint/syscalls/sys_enter_statfs")
8192
int handle_statfs_entry(struct syscall_trace_enter *ctx)
8293
{
83-
return probe_entry(ctx, SYS_STATFS, (const char *)ctx->args[0]);
94+
return probe_entry(ctx, SYS_STATFS, INVALID_FD, INVALID_FD,
95+
(const char *)ctx->args[0]);
8496
}
8597

8698
SEC("tracepoint/syscalls/sys_exit_statfs")
@@ -92,7 +104,8 @@ int handle_statfs_return(struct syscall_trace_exit *ctx)
92104
SEC("tracepoint/syscalls/sys_enter_newstat")
93105
int handle_newstat_entry(struct syscall_trace_enter *ctx)
94106
{
95-
return probe_entry(ctx, SYS_NEWSTAT, (const char *)ctx->args[0]);
107+
return probe_entry(ctx, SYS_NEWSTAT, INVALID_FD, INVALID_FD,
108+
(const char *)ctx->args[0]);
96109
}
97110

98111
SEC("tracepoint/syscalls/sys_exit_newstat")
@@ -104,7 +117,8 @@ int handle_newstat_return(struct syscall_trace_exit *ctx)
104117
SEC("tracepoint/syscalls/sys_enter_statx")
105118
int handle_statx_entry(struct syscall_trace_enter *ctx)
106119
{
107-
return probe_entry(ctx, SYS_STATX, (const char *)ctx->args[1]);
120+
return probe_entry(ctx, SYS_STATX, INVALID_FD, (int)ctx->args[0],
121+
(const char *)ctx->args[1]);
108122
}
109123

110124
SEC("tracepoint/syscalls/sys_exit_statx")
@@ -113,10 +127,25 @@ int handle_statx_return(struct syscall_trace_exit *ctx)
113127
return probe_return(ctx, (int)ctx->ret);
114128
}
115129

130+
SEC("tracepoint/syscalls/sys_enter_newfstat")
131+
int handle_newfstat_entry(struct syscall_trace_enter *ctx)
132+
{
133+
return probe_entry(ctx, SYS_NEWFSTAT, (int)ctx->args[0], INVALID_FD,
134+
NULL);
135+
}
136+
137+
SEC("tracepoint/syscalls/sys_exit_newfstat")
138+
int handle_newfstat_return(struct syscall_trace_exit *ctx)
139+
{
140+
return probe_return(ctx, (int)ctx->ret);
141+
}
142+
143+
116144
SEC("tracepoint/syscalls/sys_enter_newfstatat")
117145
int handle_newfstatat_entry(struct syscall_trace_enter *ctx)
118146
{
119-
return probe_entry(ctx, SYS_NEWFSTATAT, (const char *)ctx->args[1]);
147+
return probe_entry(ctx, SYS_NEWFSTATAT, INVALID_FD, (int)ctx->args[0],
148+
(const char *)ctx->args[1]);
120149
}
121150

122151
SEC("tracepoint/syscalls/sys_exit_newfstatat")
@@ -128,7 +157,8 @@ int handle_newfstatat_return(struct syscall_trace_exit *ctx)
128157
SEC("tracepoint/syscalls/sys_enter_newlstat")
129158
int handle_newlstat_entry(struct syscall_trace_enter *ctx)
130159
{
131-
return probe_entry(ctx, SYS_NEWLSTAT, (const char *)ctx->args[0]);
160+
return probe_entry(ctx, SYS_NEWLSTAT, INVALID_FD, INVALID_FD,
161+
(const char *)ctx->args[0]);
132162
}
133163

134164
SEC("tracepoint/syscalls/sys_exit_newlstat")

libbpf-tools/statsnoop.c

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
//
44
// Based on statsnoop(8) from BCC by Brendan Gregg.
55
// 09-May-2021 Hengqi Chen Created this.
6+
// 15-Mar-2025 Rong Tao Support fd and dirfd.
67
#include <argp.h>
78
#include <errno.h>
9+
#include <fcntl.h>
810
#include <signal.h>
911
#include <time.h>
1012
#include <unistd.h>
@@ -58,6 +60,7 @@ static char *sys_names[] = {
5860
[SYS_STATFS] = "statfs",
5961
[SYS_NEWSTAT] = "newstat",
6062
[SYS_STATX] = "statx",
63+
[SYS_NEWFSTAT] = "newfstat",
6164
[SYS_NEWFSTATAT] = "newfstatat",
6265
[SYS_NEWLSTAT] = "newlstat",
6366
};
@@ -109,12 +112,44 @@ static void sig_int(int signo)
109112
exiting = 1;
110113
}
111114

115+
char *proc_fd_pathname(pid_t pid, int fd, int is_dir, char *buf, size_t buf_len)
116+
{
117+
int err, n;
118+
char fdpath[PATH_MAX];
119+
120+
if (fd == INVALID_FD)
121+
goto skip;
122+
else if (fd == AT_FDCWD)
123+
snprintf(fdpath, PATH_MAX - 1, "/proc/%d/cwd", pid);
124+
else
125+
snprintf(fdpath, PATH_MAX - 1, "/proc/%d/fd/%d", pid, fd);
126+
127+
err = readlink(fdpath, buf, buf_len);
128+
if (err == -1)
129+
goto skip;
130+
131+
if (is_dir) {
132+
/* Add '/' in the end of string */
133+
n = strlen(buf);
134+
buf[n] = '/';
135+
buf[n + 1] = '\0';
136+
}
137+
138+
return buf;
139+
140+
skip:
141+
/* maybe process already exit or fd already be closed, just ignore cwd
142+
* or skip no-exist pathname */
143+
return "";
144+
}
145+
112146
static void handle_event(void *ctx, int cpu, void *data, __u32 data_sz)
113147
{
114148
static __u64 start_timestamp = 0;
115149
struct event e;
116150
int fd, err;
117151
double ts = 0.0;
152+
char fdpath[PATH_MAX] = {0}, dirfdpath[PATH_MAX] = {0};
118153

119154
if (data_sz < sizeof(e)) {
120155
printf("Error: packet too small\n");
@@ -139,7 +174,11 @@ static void handle_event(void *ctx, int cpu, void *data, __u32 data_sz)
139174
printf("%-7d %-20s %-4d %-4d", e.pid, e.comm, fd, err);
140175
if (emit_sysname)
141176
printf(" %-10s", sys_names[e.type]);
142-
printf(" %-s\n", e.pathname);
177+
178+
printf(" %s%s%-s\n",
179+
e.pathname[0] == '/' ? "" : proc_fd_pathname(e.pid, e.fd, 0, fdpath, PATH_MAX),
180+
e.pathname[0] == '/' ? "" : proc_fd_pathname(e.pid, e.dirfd, 1, dirfdpath, PATH_MAX),
181+
e.pathname);
143182
}
144183

145184
static void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt)
@@ -196,6 +235,10 @@ int main(int argc, char **argv)
196235
bpf_program__set_autoload(obj->progs.handle_newfstatat_entry, false);
197236
bpf_program__set_autoload(obj->progs.handle_newfstatat_return, false);
198237
}
238+
if (!tracepoint_exists("syscalls", "sys_enter_newfstat")) {
239+
bpf_program__set_autoload(obj->progs.handle_newfstat_entry, false);
240+
bpf_program__set_autoload(obj->progs.handle_newfstat_return, false);
241+
}
199242
if (!tracepoint_exists("syscalls", "sys_enter_newlstat")) {
200243
bpf_program__set_autoload(obj->progs.handle_newlstat_entry, false);
201244
bpf_program__set_autoload(obj->progs.handle_newlstat_return, false);

libbpf-tools/statsnoop.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ enum sys_type {
99
SYS_STATFS = 1,
1010
SYS_NEWSTAT,
1111
SYS_STATX,
12+
SYS_NEWFSTAT,
1213
SYS_NEWFSTATAT,
1314
SYS_NEWLSTAT,
1415
};
@@ -19,6 +20,14 @@ struct event {
1920
enum sys_type type;
2021
int ret;
2122
char comm[TASK_COMM_LEN];
23+
24+
/**
25+
* fd: fstat(2)
26+
* dirfd: statx(2), fstatat(2)
27+
*/
28+
#define INVALID_FD (-1)
29+
int fd;
30+
int dirfd;
2231
char pathname[NAME_MAX];
2332
};
2433

0 commit comments

Comments
 (0)