summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-19 16:15:18 +0300
committerPaul Buetow <paul@buetow.org>2026-05-19 16:15:18 +0300
commit843def4f5c23db050cccfae57a9acb5899c110f4 (patch)
treed90d94c39ff59c7df7278f29f5402c58c391daec
parent061fb2b2380752eed06a78d10567da172ea8e27c (diff)
x6: add pipe/eventfd fd-from-air syscall support
-rw-r--r--cmd/ioworkload/scenario_ipc.go56
-rw-r--r--cmd/ioworkload/scenarios.go4
-rw-r--r--integrationtests/ipc_test.go47
-rw-r--r--internal/c/generated_tracepoints.c126
-rw-r--r--internal/c/generated_tracepoints_result.txt16
-rw-r--r--internal/c/maps.h19
-rw-r--r--internal/c/types.h26
-rw-r--r--internal/event/interface_assertions.go6
-rw-r--r--internal/eventloop_exit.go69
-rw-r--r--internal/eventloop_ipc_test.go120
-rw-r--r--internal/eventloop_runtime.go32
-rw-r--r--internal/generate/bpfhandler.go26
-rw-r--r--internal/generate/classify.go18
-rw-r--r--internal/generate/classify_test.go64
-rw-r--r--internal/generate/codegen_test.go36
-rw-r--r--internal/generate/kindregistry.go2
-rw-r--r--internal/generate/testdata.go114
-rw-r--r--internal/types/fastdecode.go50
-rw-r--r--internal/types/fastdecode_test.go86
-rw-r--r--internal/types/generated_types.go144
-rw-r--r--internal/types/types_test.go54
21 files changed, 1078 insertions, 37 deletions
diff --git a/cmd/ioworkload/scenario_ipc.go b/cmd/ioworkload/scenario_ipc.go
new file mode 100644
index 0000000..221c3c9
--- /dev/null
+++ b/cmd/ioworkload/scenario_ipc.go
@@ -0,0 +1,56 @@
+package main
+
+import (
+ "fmt"
+ "syscall"
+
+ "golang.org/x/sys/unix"
+)
+
+func pipeBasic() error {
+ var pipefd [2]int
+ if err := syscall.Pipe(pipefd[:]); err != nil {
+ return fmt.Errorf("pipe: %w", err)
+ }
+ defer syscall.Close(pipefd[0])
+ defer syscall.Close(pipefd[1])
+ return nil
+}
+
+func pipe2Basic() error {
+ var pipefd [2]int
+ flags := syscall.O_CLOEXEC | syscall.O_NONBLOCK
+ if err := syscall.Pipe2(pipefd[:], flags); err != nil {
+ return fmt.Errorf("pipe2: %w", err)
+ }
+ defer syscall.Close(pipefd[0])
+ defer syscall.Close(pipefd[1])
+ return nil
+}
+
+func eventfdBasic() error {
+ fd, err := createEventfd(syscall.SYS_EVENTFD, 1, 0)
+ if err != nil {
+ return err
+ }
+ defer syscall.Close(fd)
+ return nil
+}
+
+func eventfd2Basic() error {
+ flags := uintptr(unix.EFD_CLOEXEC | unix.EFD_NONBLOCK)
+ fd, err := createEventfd(syscall.SYS_EVENTFD2, 1, flags)
+ if err != nil {
+ return err
+ }
+ defer syscall.Close(fd)
+ return nil
+}
+
+func createEventfd(number uintptr, initval, flags uintptr) (int, error) {
+ fd, _, errno := syscall.RawSyscall(number, initval, flags, 0)
+ if errno != 0 {
+ return -1, fmt.Errorf("eventfd syscall %d: %w", number, errno)
+ }
+ return int(fd), nil
+}
diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go
index 9700d85..b9c08d3 100644
--- a/cmd/ioworkload/scenarios.go
+++ b/cmd/ioworkload/scenarios.go
@@ -30,6 +30,10 @@ var scenarios = map[string]func() error{
"socket-accept-lifecycle": socketAcceptLifecycle,
"socket-accept-lifecycle-plain": socketAcceptLifecyclePlain,
"socket-introspection": socketIntrospection,
+ "pipe-basic": pipeBasic,
+ "pipe2-basic": pipe2Basic,
+ "eventfd-basic": eventfdBasic,
+ "eventfd2-basic": eventfd2Basic,
"family-mixed": familyMixed,
"close-basic": closeBasic,
"close-range": closeRange,
diff --git a/integrationtests/ipc_test.go b/integrationtests/ipc_test.go
new file mode 100644
index 0000000..b9967b8
--- /dev/null
+++ b/integrationtests/ipc_test.go
@@ -0,0 +1,47 @@
+package integrationtests
+
+import "testing"
+
+func TestPipeBasic(t *testing.T) {
+ result, _ := runScenarioResult(t, "pipe-basic", []ExpectedEvent{
+ {Tracepoint: "enter_pipe", MinCount: 1},
+ {Tracepoint: "enter_close", MinCount: 2},
+ })
+
+ assertTracepointPathPrefix(t, result, "enter_pipe", "pipe:")
+ if got := totalTracepointPathCount(result, "enter_close", "pipe:"); got < 2 {
+ t.Fatalf("enter_close records with tracked pipe descriptor prefix = %d, want >= 2", got)
+ }
+}
+
+func TestPipe2Basic(t *testing.T) {
+ result, _ := runScenarioResult(t, "pipe2-basic", []ExpectedEvent{
+ {Tracepoint: "enter_pipe2", MinCount: 1},
+ {Tracepoint: "enter_close", MinCount: 2},
+ })
+
+ assertTracepointPathPrefix(t, result, "enter_pipe2", "pipe:")
+ if got := totalTracepointPathCount(result, "enter_close", "pipe:"); got < 2 {
+ t.Fatalf("enter_close records with tracked pipe2 descriptor prefix = %d, want >= 2", got)
+ }
+}
+
+func TestEventfdBasic(t *testing.T) {
+ result, _ := runScenarioResult(t, "eventfd-basic", []ExpectedEvent{
+ {Tracepoint: "enter_eventfd", MinCount: 1},
+ {Tracepoint: "enter_close", MinCount: 1},
+ })
+
+ assertTracepointPathPrefix(t, result, "enter_eventfd", "eventfd:")
+ assertTracepointPathPrefix(t, result, "enter_close", "eventfd:")
+}
+
+func TestEventfd2Basic(t *testing.T) {
+ result, _ := runScenarioResult(t, "eventfd2-basic", []ExpectedEvent{
+ {Tracepoint: "enter_eventfd2", MinCount: 1},
+ {Tracepoint: "enter_close", MinCount: 1},
+ })
+
+ assertTracepointPathPrefix(t, result, "enter_eventfd2", "eventfd:")
+ assertTracepointPathPrefix(t, result, "enter_close", "eventfd:")
+}
diff --git a/internal/c/generated_tracepoints.c b/internal/c/generated_tracepoints.c
index 0f83f35..c14c61e 100644
--- a/internal/c/generated_tracepoints.c
+++ b/internal/c/generated_tracepoints.c
@@ -3571,89 +3571,109 @@ int handle_sys_exit_userfaultfd(struct syscall_trace_exit *ctx) {
return 0;
}
-/// sys_enter_eventfd2 is a struct null_event
+/// sys_enter_eventfd2 is a struct eventfd_event
SEC("tracepoint/syscalls/sys_enter_eventfd2")
int handle_sys_enter_eventfd2(struct syscall_trace_enter *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct null_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct null_event), 0);
+ struct eventfd_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct eventfd_event), 0);
if (!ev)
return 0;
- ev->event_type = ENTER_NULL_EVENT;
+ ev->event_type = ENTER_EVENTFD_EVENT;
ev->trace_id = SYS_ENTER_EVENTFD2;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
+ __s32 flags = (__s32)ctx->args[1];
+ bpf_map_update_elem(&eventfd_flags_map, &tid, &flags, BPF_ANY);
+ ev->flags = flags;
+ ev->ret = -1;
bpf_ringbuf_submit(ev, 0);
return 0;
}
-/// sys_exit_eventfd2 is a struct ret_event (UNCLASSIFIED)
+/// sys_exit_eventfd2 is a struct eventfd_event
SEC("tracepoint/syscalls/sys_exit_eventfd2")
int handle_sys_exit_eventfd2(struct syscall_trace_exit *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct ret_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct ret_event), 0);
+ struct eventfd_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct eventfd_event), 0);
if (!ev)
return 0;
- ev->event_type = EXIT_RET_EVENT;
+ ev->event_type = EXIT_EVENTFD_EVENT;
ev->trace_id = SYS_EXIT_EVENTFD2;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
+ __s32 flags = 0;
+ __s32 *pending = bpf_map_lookup_elem(&eventfd_flags_map, &tid);
+ if (pending) {
+ flags = *pending;
+ bpf_map_delete_elem(&eventfd_flags_map, &tid);
+ }
+ ev->flags = flags;
ev->ret = ctx->ret;
- ev->ret_type = UNCLASSIFIED;
bpf_ringbuf_submit(ev, 0);
return 0;
}
-/// sys_enter_eventfd is a struct null_event
+/// sys_enter_eventfd is a struct eventfd_event
SEC("tracepoint/syscalls/sys_enter_eventfd")
int handle_sys_enter_eventfd(struct syscall_trace_enter *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct null_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct null_event), 0);
+ struct eventfd_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct eventfd_event), 0);
if (!ev)
return 0;
- ev->event_type = ENTER_NULL_EVENT;
+ ev->event_type = ENTER_EVENTFD_EVENT;
ev->trace_id = SYS_ENTER_EVENTFD;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
+ __s32 flags = 0;
+ bpf_map_update_elem(&eventfd_flags_map, &tid, &flags, BPF_ANY);
+ ev->flags = flags;
+ ev->ret = -1;
bpf_ringbuf_submit(ev, 0);
return 0;
}
-/// sys_exit_eventfd is a struct ret_event (UNCLASSIFIED)
+/// sys_exit_eventfd is a struct eventfd_event
SEC("tracepoint/syscalls/sys_exit_eventfd")
int handle_sys_exit_eventfd(struct syscall_trace_exit *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct ret_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct ret_event), 0);
+ struct eventfd_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct eventfd_event), 0);
if (!ev)
return 0;
- ev->event_type = EXIT_RET_EVENT;
+ ev->event_type = EXIT_EVENTFD_EVENT;
ev->trace_id = SYS_EXIT_EVENTFD;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
+ __s32 flags = 0;
+ __s32 *pending = bpf_map_lookup_elem(&eventfd_flags_map, &tid);
+ if (pending) {
+ flags = *pending;
+ bpf_map_delete_elem(&eventfd_flags_map, &tid);
+ }
+ ev->flags = flags;
ev->ret = ctx->ret;
- ev->ret_type = UNCLASSIFIED;
bpf_ringbuf_submit(ev, 0);
return 0;
@@ -7771,89 +7791,139 @@ int handle_sys_exit_rename(struct syscall_trace_exit *ctx) {
return 0;
}
-/// sys_enter_pipe2 is a struct null_event
+/// sys_enter_pipe2 is a struct pipe_event
SEC("tracepoint/syscalls/sys_enter_pipe2")
int handle_sys_enter_pipe2(struct syscall_trace_enter *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct null_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct null_event), 0);
+ struct pipe_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct pipe_event), 0);
if (!ev)
return 0;
- ev->event_type = ENTER_NULL_EVENT;
+ ev->event_type = ENTER_PIPE_EVENT;
ev->trace_id = SYS_ENTER_PIPE2;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
+ struct pipe_ctx pending;
+ pending.upipefd = ctx->args[0];
+ pending.flags = (__s32)ctx->args[1];
+ bpf_map_update_elem(&pipe_ctx_map, &tid, &pending, BPF_ANY);
+ ev->flags = pending.flags;
+ ev->fd0 = -1;
+ ev->fd1 = -1;
+ ev->ret = 0;
bpf_ringbuf_submit(ev, 0);
return 0;
}
-/// sys_exit_pipe2 is a struct ret_event (UNCLASSIFIED)
+/// sys_exit_pipe2 is a struct pipe_event
SEC("tracepoint/syscalls/sys_exit_pipe2")
int handle_sys_exit_pipe2(struct syscall_trace_exit *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct ret_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct ret_event), 0);
+ struct pipe_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct pipe_event), 0);
if (!ev)
return 0;
- ev->event_type = EXIT_RET_EVENT;
+ ev->event_type = EXIT_PIPE_EVENT;
ev->trace_id = SYS_EXIT_PIPE2;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
+ __s32 flags = 0;
+ __s32 fd0 = -1;
+ __s32 fd1 = -1;
+ struct pipe_ctx *pending = bpf_map_lookup_elem(&pipe_ctx_map, &tid);
+ if (pending) {
+ flags = pending->flags;
+ if (ctx->ret == 0 && pending->upipefd != 0) {
+ int pipefd[2];
+ if (bpf_probe_read_user(&pipefd, sizeof(pipefd), (void *)pending->upipefd) == 0) {
+ fd0 = (__s32)pipefd[0];
+ fd1 = (__s32)pipefd[1];
+ }
+ }
+ bpf_map_delete_elem(&pipe_ctx_map, &tid);
+ }
+ ev->flags = flags;
+ ev->fd0 = fd0;
+ ev->fd1 = fd1;
ev->ret = ctx->ret;
- ev->ret_type = UNCLASSIFIED;
bpf_ringbuf_submit(ev, 0);
return 0;
}
-/// sys_enter_pipe is a struct null_event
+/// sys_enter_pipe is a struct pipe_event
SEC("tracepoint/syscalls/sys_enter_pipe")
int handle_sys_enter_pipe(struct syscall_trace_enter *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct null_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct null_event), 0);
+ struct pipe_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct pipe_event), 0);
if (!ev)
return 0;
- ev->event_type = ENTER_NULL_EVENT;
+ ev->event_type = ENTER_PIPE_EVENT;
ev->trace_id = SYS_ENTER_PIPE;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
+ struct pipe_ctx pending;
+ pending.upipefd = ctx->args[0];
+ pending.flags = 0;
+ bpf_map_update_elem(&pipe_ctx_map, &tid, &pending, BPF_ANY);
+ ev->flags = pending.flags;
+ ev->fd0 = -1;
+ ev->fd1 = -1;
+ ev->ret = 0;
bpf_ringbuf_submit(ev, 0);
return 0;
}
-/// sys_exit_pipe is a struct ret_event (UNCLASSIFIED)
+/// sys_exit_pipe is a struct pipe_event
SEC("tracepoint/syscalls/sys_exit_pipe")
int handle_sys_exit_pipe(struct syscall_trace_exit *ctx) {
__u32 pid, tid;
if (filter(&pid, &tid))
return 0;
- struct ret_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct ret_event), 0);
+ struct pipe_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct pipe_event), 0);
if (!ev)
return 0;
- ev->event_type = EXIT_RET_EVENT;
+ ev->event_type = EXIT_PIPE_EVENT;
ev->trace_id = SYS_EXIT_PIPE;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
+ __s32 flags = 0;
+ __s32 fd0 = -1;
+ __s32 fd1 = -1;
+ struct pipe_ctx *pending = bpf_map_lookup_elem(&pipe_ctx_map, &tid);
+ if (pending) {
+ flags = pending->flags;
+ if (ctx->ret == 0 && pending->upipefd != 0) {
+ int pipefd[2];
+ if (bpf_probe_read_user(&pipefd, sizeof(pipefd), (void *)pending->upipefd) == 0) {
+ fd0 = (__s32)pipefd[0];
+ fd1 = (__s32)pipefd[1];
+ }
+ }
+ bpf_map_delete_elem(&pipe_ctx_map, &tid);
+ }
+ ev->flags = flags;
+ ev->fd0 = fd0;
+ ev->fd1 = fd1;
ev->ret = ctx->ret;
- ev->ret_type = UNCLASSIFIED;
bpf_ringbuf_submit(ev, 0);
return 0;
diff --git a/internal/c/generated_tracepoints_result.txt b/internal/c/generated_tracepoints_result.txt
index a2ad3ca..8f2564c 100644
--- a/internal/c/generated_tracepoints_result.txt
+++ b/internal/c/generated_tracepoints_result.txt
@@ -38,8 +38,8 @@ sys_enter_epoll_ctl is a struct fd_event
sys_enter_epoll_pwait is a struct null_event
sys_enter_epoll_pwait2 is a struct null_event
sys_enter_epoll_wait is a struct null_event
-sys_enter_eventfd is a struct null_event
-sys_enter_eventfd2 is a struct null_event
+sys_enter_eventfd is a struct eventfd_event
+sys_enter_eventfd2 is a struct eventfd_event
sys_enter_execve is a struct path_event
sys_enter_execveat is a struct fd_event
sys_enter_exit is a struct null_event
@@ -210,8 +210,8 @@ sys_enter_personality is a struct null_event
sys_enter_pidfd_getfd is a struct fd_event
sys_enter_pidfd_open is a struct null_event
sys_enter_pidfd_send_signal is a struct null_event
-sys_enter_pipe is a struct null_event
-sys_enter_pipe2 is a struct null_event
+sys_enter_pipe is a struct pipe_event
+sys_enter_pipe2 is a struct pipe_event
sys_enter_pivot_root is a struct null_event
sys_enter_pkey_alloc is a struct null_event
sys_enter_pkey_free is a struct null_event
@@ -405,8 +405,8 @@ sys_exit_epoll_ctl is a struct ret_event (UNCLASSIFIED)
sys_exit_epoll_pwait is a struct ret_event (UNCLASSIFIED)
sys_exit_epoll_pwait2 is a struct ret_event (UNCLASSIFIED)
sys_exit_epoll_wait is a struct ret_event (UNCLASSIFIED)
-sys_exit_eventfd is a struct ret_event (UNCLASSIFIED)
-sys_exit_eventfd2 is a struct ret_event (UNCLASSIFIED)
+sys_exit_eventfd is a struct eventfd_event
+sys_exit_eventfd2 is a struct eventfd_event
sys_exit_execve is a struct ret_event (UNCLASSIFIED)
sys_exit_execveat is a struct ret_event (UNCLASSIFIED)
sys_exit_exit is a struct ret_event (UNCLASSIFIED)
@@ -577,8 +577,8 @@ sys_exit_personality is a struct ret_event (UNCLASSIFIED)
sys_exit_pidfd_getfd is a struct ret_event (UNCLASSIFIED)
sys_exit_pidfd_open is a struct ret_event (UNCLASSIFIED)
sys_exit_pidfd_send_signal is a struct ret_event (UNCLASSIFIED)
-sys_exit_pipe is a struct ret_event (UNCLASSIFIED)
-sys_exit_pipe2 is a struct ret_event (UNCLASSIFIED)
+sys_exit_pipe is a struct pipe_event
+sys_exit_pipe2 is a struct pipe_event
sys_exit_pivot_root is a struct ret_event (UNCLASSIFIED)
sys_exit_pkey_alloc is a struct ret_event (UNCLASSIFIED)
sys_exit_pkey_free is a struct ret_event (UNCLASSIFIED)
diff --git a/internal/c/maps.h b/internal/c/maps.h
index 1624ff8..665e4ff 100644
--- a/internal/c/maps.h
+++ b/internal/c/maps.h
@@ -12,9 +12,28 @@ struct socketpair_ctx {
__s32 protocol;
};
+struct pipe_ctx {
+ __u64 upipefd;
+ __s32 flags;
+};
+
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 8192);
__type(key, __u32);
__type(value, struct socketpair_ctx);
} socketpair_ctx_map SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 8192);
+ __type(key, __u32);
+ __type(value, struct pipe_ctx);
+} pipe_ctx_map SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 8192);
+ __type(key, __u32);
+ __type(value, __s32);
+} eventfd_flags_map SEC(".maps");
diff --git a/internal/c/types.h b/internal/c/types.h
index 6365e3f..3e06040 100644
--- a/internal/c/types.h
+++ b/internal/c/types.h
@@ -27,6 +27,10 @@
#define EXIT_SOCKETPAIR_EVENT 22
#define ENTER_ACCEPT_EVENT 23
#define EXIT_ACCEPT_EVENT 24
+#define ENTER_PIPE_EVENT 25
+#define EXIT_PIPE_EVENT 26
+#define ENTER_EVENTFD_EVENT 27
+#define EXIT_EVENTFD_EVENT 28
#define UNCLASSIFIED 0
#define READ_CLASSIFIED 1
@@ -155,3 +159,25 @@ struct accept_event {
__s32 fd;
__s64 ret;
};
+
+struct pipe_event {
+ __u32 event_type;
+ __u32 trace_id;
+ __u64 time;
+ __u32 pid;
+ __u32 tid;
+ __s32 flags;
+ __s32 fd0;
+ __s32 fd1;
+ __s64 ret;
+};
+
+struct eventfd_event {
+ __u32 event_type;
+ __u32 trace_id;
+ __u64 time;
+ __u32 pid;
+ __u32 tid;
+ __s32 flags;
+ __s64 ret;
+};
diff --git a/internal/event/interface_assertions.go b/internal/event/interface_assertions.go
index fea20ba..8fa6ae9 100644
--- a/internal/event/interface_assertions.go
+++ b/internal/event/interface_assertions.go
@@ -50,4 +50,10 @@ var (
// *types.AcceptEvent carries listening-fd input and accepted-fd return metadata.
_ Event = (*types.AcceptEvent)(nil)
+
+ // *types.PipeEvent carries pipe flags plus the two returned pipe descriptors.
+ _ Event = (*types.PipeEvent)(nil)
+
+ // *types.EventfdEvent carries eventfd flags and the returned descriptor.
+ _ Event = (*types.EventfdEvent)(nil)
)
diff --git a/internal/eventloop_exit.go b/internal/eventloop_exit.go
index 9953c13..345424c 100644
--- a/internal/eventloop_exit.go
+++ b/internal/eventloop_exit.go
@@ -32,6 +32,10 @@ func (e *eventLoop) handleTracepointExit(ep *event.Pair) bool {
return e.handleSocketpairExit(ep, ev)
case *types.AcceptEvent:
return e.handleAcceptExit(ep, ev)
+ case *types.PipeEvent:
+ return e.handlePipeExit(ep, ev)
+ case *types.EventfdEvent:
+ return e.handleEventfdExit(ep, ev)
case *types.NullEvent:
return e.handleNullExit(ep, ev)
case *types.FcntlEvent:
@@ -322,6 +326,71 @@ func acceptedSocketDescriptorName(listening file.File) string {
return name
}
+func (e *eventLoop) handlePipeExit(ep *event.Pair, pipeEv *types.PipeEvent) bool {
+ exitEv, ok := ep.ExitEv.(*types.PipeEvent)
+ if !ok {
+ e.recyclePair(ep, "Dropped malformed pipe exit event")
+ return false
+ }
+
+ flags := exitEv.Flags
+ if flags == 0 {
+ flags = pipeEv.Flags
+ }
+ if exitEv.Ret == 0 {
+ if exitEv.Fd0 >= 0 {
+ fdFile := file.NewFd(exitEv.Fd0, pipeDescriptorName(flags, exitEv.Fd0, exitEv.Fd1), flags)
+ e.fdState().set(exitEv.Fd0, fdFile)
+ ep.File = fdFile
+ }
+ if exitEv.Fd1 >= 0 {
+ fdFile := file.NewFd(exitEv.Fd1, pipeDescriptorName(flags, exitEv.Fd0, exitEv.Fd1), flags)
+ e.fdState().set(exitEv.Fd1, fdFile)
+ if ep.File == nil {
+ ep.File = fdFile
+ }
+ }
+ }
+ ep.Comm = e.comm(pipeEv.GetTid())
+ if !e.Filter().MatchPair(ep) {
+ ep.Recycle()
+ return false
+ }
+ return true
+}
+
+func (e *eventLoop) handleEventfdExit(ep *event.Pair, eventfdEv *types.EventfdEvent) bool {
+ exitEv, ok := ep.ExitEv.(*types.EventfdEvent)
+ if !ok {
+ e.recyclePair(ep, "Dropped malformed eventfd exit event")
+ return false
+ }
+
+ flags := exitEv.Flags
+ if flags == 0 {
+ flags = eventfdEv.Flags
+ }
+ if fd := int32(exitEv.Ret); fd >= 0 {
+ fdFile := file.NewFd(fd, eventfdDescriptorName(flags), flags)
+ e.fdState().set(fd, fdFile)
+ ep.File = fdFile
+ }
+ ep.Comm = e.comm(eventfdEv.GetTid())
+ if !e.Filter().MatchPair(ep) {
+ ep.Recycle()
+ return false
+ }
+ return true
+}
+
+func pipeDescriptorName(flags, fd0, fd1 int32) string {
+ return fmt.Sprintf("pipe:%d:%d:%d", flags, fd0, fd1)
+}
+
+func eventfdDescriptorName(flags int32) string {
+ return fmt.Sprintf("eventfd:%d", flags)
+}
+
func (e *eventLoop) handleNullExit(ep *event.Pair, nullEv *types.NullEvent) bool {
if ep.Is(types.SYS_ENTER_IO_URING_SETUP) {
retEvent, ok := ep.ExitEv.(*types.RetEvent)
diff --git a/internal/eventloop_ipc_test.go b/internal/eventloop_ipc_test.go
new file mode 100644
index 0000000..eec819b
--- /dev/null
+++ b/internal/eventloop_ipc_test.go
@@ -0,0 +1,120 @@
+package internal
+
+import (
+ "testing"
+
+ "ior/internal/event"
+ "ior/internal/globalfilter"
+ "ior/internal/types"
+)
+
+func TestHandlePipeExitTracksReturnedFds(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{})
+
+ enter := &types.PipeEvent{
+ EventType: types.ENTER_PIPE_EVENT,
+ TraceId: types.SYS_ENTER_PIPE2,
+ Time: 100,
+ Pid: 70,
+ Tid: 71,
+ Flags: 0x80000,
+ Fd0: -1,
+ Fd1: -1,
+ Ret: 0,
+ }
+ exit := &types.PipeEvent{
+ EventType: types.EXIT_PIPE_EVENT,
+ TraceId: types.SYS_EXIT_PIPE2,
+ Time: 200,
+ Pid: 70,
+ Tid: 71,
+ Flags: 0x80000,
+ Fd0: 52,
+ Fd1: 53,
+ Ret: 0,
+ }
+ ep := &event.Pair{EnterEv: enter, ExitEv: exit}
+
+ if ok := el.handlePipeExit(ep, enter); !ok {
+ t.Fatal("handlePipeExit returned false")
+ }
+ verifyFileDescriptor(t, el, 52, "pipe:524288:52:53")
+ verifyFileDescriptor(t, el, 53, "pipe:524288:52:53")
+}
+
+func TestHandleEventfdExitTracksReturnedFd(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{})
+
+ enter := &types.EventfdEvent{
+ EventType: types.ENTER_EVENTFD_EVENT,
+ TraceId: types.SYS_ENTER_EVENTFD2,
+ Time: 100,
+ Pid: 80,
+ Tid: 81,
+ Flags: 0x800,
+ Ret: -1,
+ }
+ exit := &types.EventfdEvent{
+ EventType: types.EXIT_EVENTFD_EVENT,
+ TraceId: types.SYS_EXIT_EVENTFD2,
+ Time: 200,
+ Pid: 80,
+ Tid: 81,
+ Flags: 0x800,
+ Ret: 61,
+ }
+ ep := &event.Pair{EnterEv: enter, ExitEv: exit}
+
+ if ok := el.handleEventfdExit(ep, enter); !ok {
+ t.Fatal("handleEventfdExit returned false")
+ }
+ verifyFileDescriptor(t, el, 61, "eventfd:2048")
+}
+
+func TestHandleEventfdExitAppliesPairFilter(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{
+ filter: globalfilter.Filter{
+ Syscall: &globalfilter.StringFilter{Pattern: "openat"},
+ },
+ })
+
+ enter := &types.EventfdEvent{
+ EventType: types.ENTER_EVENTFD_EVENT,
+ TraceId: types.SYS_ENTER_EVENTFD,
+ Time: 100,
+ Pid: 82,
+ Tid: 83,
+ Flags: 0,
+ Ret: -1,
+ }
+ exit := &types.EventfdEvent{
+ EventType: types.EXIT_EVENTFD_EVENT,
+ TraceId: types.SYS_EXIT_EVENTFD,
+ Time: 200,
+ Pid: 82,
+ Tid: 83,
+ Flags: 0,
+ Ret: 44,
+ }
+ ep := &event.Pair{EnterEv: enter, ExitEv: exit}
+
+ if ok := el.handleEventfdExit(ep, enter); ok {
+ t.Fatal("handleEventfdExit should reject pair due to filter mismatch")
+ }
+}
+
+func TestInitRawHandlersRegistersIPCEvents(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{})
+ if _, ok := el.rawHandlers[types.ENTER_PIPE_EVENT]; !ok {
+ t.Fatal("ENTER_PIPE_EVENT handler is not registered")
+ }
+ if _, ok := el.rawHandlers[types.EXIT_PIPE_EVENT]; !ok {
+ t.Fatal("EXIT_PIPE_EVENT handler is not registered")
+ }
+ if _, ok := el.rawHandlers[types.ENTER_EVENTFD_EVENT]; !ok {
+ t.Fatal("ENTER_EVENTFD_EVENT handler is not registered")
+ }
+ if _, ok := el.rawHandlers[types.EXIT_EVENTFD_EVENT]; !ok {
+ t.Fatal("EXIT_EVENTFD_EVENT handler is not registered")
+ }
+}
diff --git a/internal/eventloop_runtime.go b/internal/eventloop_runtime.go
index 94648e0..cce7299 100644
--- a/internal/eventloop_runtime.go
+++ b/internal/eventloop_runtime.go
@@ -147,6 +147,7 @@ func (e *eventLoop) initRawHandlers() {
e.registerNamePathHandlers()
e.registerMiscHandlers()
e.registerSocketHandlers()
+ e.registerIPCHandlers()
}
// registerOpenHandlers wires enter/exit handlers for open-family events.
@@ -301,6 +302,37 @@ func (e *eventLoop) registerSocketHandlers() {
}
}
+func (e *eventLoop) registerIPCHandlers() {
+ e.rawHandlers[types.ENTER_PIPE_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
+ pipeEv, ok := decodeRawEvent(e, types.ENTER_PIPE_EVENT, raw, types.NewPipeEventFast)
+ if !ok {
+ return
+ }
+ e.tracepointEntered(pipeEv)
+ }
+ e.rawHandlers[types.EXIT_PIPE_EVENT] = func(raw []byte, ch chan<- *event.Pair) {
+ pipeEv, ok := decodeRawEvent(e, types.EXIT_PIPE_EVENT, raw, types.NewPipeEventFast)
+ if !ok {
+ return
+ }
+ e.tracepointExited(pipeEv, ch)
+ }
+ e.rawHandlers[types.ENTER_EVENTFD_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
+ eventfdEv, ok := decodeRawEvent(e, types.ENTER_EVENTFD_EVENT, raw, types.NewEventfdEventFast)
+ if !ok {
+ return
+ }
+ e.tracepointEntered(eventfdEv)
+ }
+ e.rawHandlers[types.EXIT_EVENTFD_EVENT] = func(raw []byte, ch chan<- *event.Pair) {
+ eventfdEv, ok := decodeRawEvent(e, types.EXIT_EVENTFD_EVENT, raw, types.NewEventfdEventFast)
+ if !ok {
+ return
+ }
+ e.tracepointExited(eventfdEv, ch)
+ }
+}
+
func decodeRawEvent[T any](e *eventLoop, eventType types.EventType, raw []byte, decode func([]byte) *T) (*T, bool) {
decoded := decode(raw)
if decoded == nil {
diff --git a/internal/generate/bpfhandler.go b/internal/generate/bpfhandler.go
index bada317..1a6b210 100644
--- a/internal/generate/bpfhandler.go
+++ b/internal/generate/bpfhandler.go
@@ -79,6 +79,10 @@ func generateExtra(tp GeneratedTracepoint, isEnter bool) string {
return generateExtraSocketpair(isEnter)
case KindAccept:
return generateExtraAccept(isEnter)
+ case KindPipe:
+ return generateExtraPipe(f, isEnter)
+ case KindEventfd:
+ return generateExtraEventfd(f, isEnter)
case KindOpen:
return generateExtraOpen(f)
case KindPathname:
@@ -173,6 +177,28 @@ func generateExtraAccept(isEnter bool) string {
return " ev->fd = -1;\n ev->ret = ctx->ret;\n"
}
+func generateExtraPipe(f *Format, isEnter bool) string {
+ if isEnter {
+ flagsExpr := "0"
+ if f.Name == "sys_enter_pipe2" {
+ flagsExpr = "(__s32)ctx->args[1]"
+ }
+ return " struct pipe_ctx pending;\n pending.upipefd = ctx->args[0];\n pending.flags = " + flagsExpr + ";\n bpf_map_update_elem(&pipe_ctx_map, &tid, &pending, BPF_ANY);\n ev->flags = pending.flags;\n ev->fd0 = -1;\n ev->fd1 = -1;\n ev->ret = 0;\n"
+ }
+ return " __s32 flags = 0;\n __s32 fd0 = -1;\n __s32 fd1 = -1;\n struct pipe_ctx *pending = bpf_map_lookup_elem(&pipe_ctx_map, &tid);\n if (pending) {\n flags = pending->flags;\n if (ctx->ret == 0 && pending->upipefd != 0) {\n int pipefd[2];\n if (bpf_probe_read_user(&pipefd, sizeof(pipefd), (void *)pending->upipefd) == 0) {\n fd0 = (__s32)pipefd[0];\n fd1 = (__s32)pipefd[1];\n }\n }\n bpf_map_delete_elem(&pipe_ctx_map, &tid);\n }\n ev->flags = flags;\n ev->fd0 = fd0;\n ev->fd1 = fd1;\n ev->ret = ctx->ret;\n"
+}
+
+func generateExtraEventfd(f *Format, isEnter bool) string {
+ if isEnter {
+ flagsExpr := "0"
+ if f.Name == "sys_enter_eventfd2" {
+ flagsExpr = "(__s32)ctx->args[1]"
+ }
+ return " __s32 flags = " + flagsExpr + ";\n bpf_map_update_elem(&eventfd_flags_map, &tid, &flags, BPF_ANY);\n ev->flags = flags;\n ev->ret = -1;\n"
+ }
+ return " __s32 flags = 0;\n __s32 *pending = bpf_map_lookup_elem(&eventfd_flags_map, &tid);\n if (pending) {\n flags = *pending;\n bpf_map_delete_elem(&eventfd_flags_map, &tid);\n }\n ev->flags = flags;\n ev->ret = ctx->ret;\n"
+}
+
// eventStructName returns the C struct name for a TracepointKind. The mapping
// is driven by kindRegistry so adding a new kind only requires a registry entry.
func eventStructName(kind TracepointKind) string {
diff --git a/internal/generate/classify.go b/internal/generate/classify.go
index 4f6d14c..0bc00b3 100644
--- a/internal/generate/classify.go
+++ b/internal/generate/classify.go
@@ -18,6 +18,8 @@ const (
KindSocket
KindSocketpair
KindAccept
+ KindPipe
+ KindEventfd
)
type RetClassification string
@@ -96,6 +98,22 @@ func classifyNameOnly(name string) (ClassificationResult, bool) {
return ClassificationResult{Kind: KindAccept}, true
case "sys_exit_accept4":
return ClassificationResult{Kind: KindAccept}, true
+ case "sys_enter_pipe":
+ return ClassificationResult{Kind: KindPipe}, true
+ case "sys_exit_pipe":
+ return ClassificationResult{Kind: KindPipe}, true
+ case "sys_enter_pipe2":
+ return ClassificationResult{Kind: KindPipe}, true
+ case "sys_exit_pipe2":
+ return ClassificationResult{Kind: KindPipe}, true
+ case "sys_enter_eventfd":
+ return ClassificationResult{Kind: KindEventfd}, true
+ case "sys_exit_eventfd":
+ return ClassificationResult{Kind: KindEventfd}, true
+ case "sys_enter_eventfd2":
+ return ClassificationResult{Kind: KindEventfd}, true
+ case "sys_exit_eventfd2":
+ return ClassificationResult{Kind: KindEventfd}, true
case "sys_enter_bind":
return ClassificationResult{Kind: KindFd}, true
case "sys_enter_connect":
diff --git a/internal/generate/classify_test.go b/internal/generate/classify_test.go
index 5c7111f..09989b8 100644
--- a/internal/generate/classify_test.go
+++ b/internal/generate/classify_test.go
@@ -313,6 +313,62 @@ func TestClassifyExitSocketpair(t *testing.T) {
}
}
+func TestClassifyPipe(t *testing.T) {
+ r := classifyFromData(t, FormatPipe)
+ if r.Kind != KindPipe {
+ t.Errorf("pipe: got kind %d, want KindPipe", r.Kind)
+ }
+}
+
+func TestClassifyPipe2(t *testing.T) {
+ r := classifyFromData(t, FormatPipe2)
+ if r.Kind != KindPipe {
+ t.Errorf("pipe2: got kind %d, want KindPipe", r.Kind)
+ }
+}
+
+func TestClassifyExitPipe(t *testing.T) {
+ r := classifyFromData(t, FormatExitPipe)
+ if r.Kind != KindPipe {
+ t.Errorf("exit_pipe: got kind %d, want KindPipe", r.Kind)
+ }
+}
+
+func TestClassifyExitPipe2(t *testing.T) {
+ r := classifyFromData(t, FormatExitPipe2)
+ if r.Kind != KindPipe {
+ t.Errorf("exit_pipe2: got kind %d, want KindPipe", r.Kind)
+ }
+}
+
+func TestClassifyEventfd(t *testing.T) {
+ r := classifyFromData(t, FormatEventfd)
+ if r.Kind != KindEventfd {
+ t.Errorf("eventfd: got kind %d, want KindEventfd", r.Kind)
+ }
+}
+
+func TestClassifyEventfd2(t *testing.T) {
+ r := classifyFromData(t, FormatEventfd2)
+ if r.Kind != KindEventfd {
+ t.Errorf("eventfd2: got kind %d, want KindEventfd", r.Kind)
+ }
+}
+
+func TestClassifyExitEventfd(t *testing.T) {
+ r := classifyFromData(t, FormatExitEventfd)
+ if r.Kind != KindEventfd {
+ t.Errorf("exit_eventfd: got kind %d, want KindEventfd", r.Kind)
+ }
+}
+
+func TestClassifyExitEventfd2(t *testing.T) {
+ r := classifyFromData(t, FormatExitEventfd2)
+ if r.Kind != KindEventfd {
+ t.Errorf("exit_eventfd2: got kind %d, want KindEventfd", r.Kind)
+ }
+}
+
func TestClassifyKillRequiresGenerationFallback(t *testing.T) {
r := classifyFromData(t, FormatKill)
if r.Kind != KindNone {
@@ -353,6 +409,10 @@ func TestClassifySyscallPairAccepted(t *testing.T) {
{"accept4", FormatAccept4, FormatExitAccept4, KindAccept},
{"socket", FormatSocket, FormatExitSocket, KindSocket},
{"socketpair", FormatSocketpair, FormatExitSocketpair, KindSocketpair},
+ {"pipe", FormatPipe, FormatExitPipe, KindPipe},
+ {"pipe2", FormatPipe2, FormatExitPipe2, KindPipe},
+ {"eventfd", FormatEventfd, FormatExitEventfd, KindEventfd},
+ {"eventfd2", FormatEventfd2, FormatExitEventfd2, KindEventfd},
{"kill", FormatKill, FormatExitKill, KindNull},
}
@@ -380,6 +440,10 @@ func TestClassifySyscallPairEmitsAllFamilies(t *testing.T) {
{"accept4", FormatAccept4, FormatExitAccept4, FamilyNetwork},
{"socket", FormatSocket, FormatExitSocket, FamilyNetwork},
{"socketpair", FormatSocketpair, FormatExitSocketpair, FamilyNetwork},
+ {"pipe", FormatPipe, FormatExitPipe, FamilyIPC},
+ {"pipe2", FormatPipe2, FormatExitPipe2, FamilyIPC},
+ {"eventfd", FormatEventfd, FormatExitEventfd, FamilyIPC},
+ {"eventfd2", FormatEventfd2, FormatExitEventfd2, FamilyIPC},
{"kill", FormatKill, FormatExitKill, FamilySignals},
}
diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go
index d8130c8..1c99314 100644
--- a/internal/generate/codegen_test.go
+++ b/internal/generate/codegen_test.go
@@ -225,6 +225,36 @@ func TestGenerateAcceptHandler(t *testing.T) {
requireContains(t, output, "ev->ret = ctx->ret;")
}
+func TestGeneratePipeHandler(t *testing.T) {
+ output := generateFromPair(t, FormatPipe2, FormatExitPipe2)
+
+ requireContains(t, output, "struct pipe_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_PIPE_EVENT;")
+ requireContains(t, output, "struct pipe_ctx pending;")
+ requireContains(t, output, "pending.upipefd = ctx->args[0];")
+ requireContains(t, output, "pending.flags = (__s32)ctx->args[1];")
+ requireContains(t, output, "bpf_map_update_elem(&pipe_ctx_map, &tid, &pending, BPF_ANY);")
+ requireContains(t, output, "ev->fd0 = -1;")
+ requireContains(t, output, "ev->fd1 = -1;")
+ requireContains(t, output, "SEC(\"tracepoint/syscalls/sys_exit_pipe2\")")
+ requireContains(t, output, "ev->event_type = EXIT_PIPE_EVENT;")
+ requireContains(t, output, "struct pipe_ctx *pending = bpf_map_lookup_elem(&pipe_ctx_map, &tid);")
+ requireContains(t, output, "ev->ret = ctx->ret;")
+}
+
+func TestGenerateEventfdHandler(t *testing.T) {
+ output := generateFromPair(t, FormatEventfd2, FormatExitEventfd2)
+
+ requireContains(t, output, "struct eventfd_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_EVENTFD_EVENT;")
+ requireContains(t, output, "bpf_map_update_elem(&eventfd_flags_map, &tid, &flags, BPF_ANY);")
+ requireContains(t, output, "ev->flags = flags;")
+ requireContains(t, output, "ev->ret = -1;")
+ requireContains(t, output, "SEC(\"tracepoint/syscalls/sys_exit_eventfd2\")")
+ requireContains(t, output, "ev->event_type = EXIT_EVENTFD_EVENT;")
+ requireContains(t, output, "ev->ret = ctx->ret;")
+}
+
func TestGenerateNameToHandleAtHandler(t *testing.T) {
output := generateFromPair(t, FormatNameToHandleAt, FormatExitNameToHandleAt)
@@ -335,6 +365,8 @@ func TestGenerateAllEventTypes(t *testing.T) {
{KindSocket, "ENTER_SOCKET_EVENT", "EXIT_SOCKET_EVENT"},
{KindSocketpair, "ENTER_SOCKETPAIR_EVENT", "EXIT_SOCKETPAIR_EVENT"},
{KindAccept, "ENTER_ACCEPT_EVENT", "EXIT_ACCEPT_EVENT"},
+ {KindPipe, "ENTER_PIPE_EVENT", "EXIT_PIPE_EVENT"},
+ {KindEventfd, "ENTER_EVENTFD_EVENT", "EXIT_EVENTFD_EVENT"},
}
for _, tt := range tests {
@@ -364,6 +396,8 @@ func TestEventStructNames(t *testing.T) {
{KindSocket, "socket_event"},
{KindSocketpair, "socketpair_event"},
{KindAccept, "accept_event"},
+ {KindPipe, "pipe_event"},
+ {KindEventfd, "eventfd_event"},
}
for _, tt := range tests {
@@ -382,7 +416,7 @@ func TestEnterReject(t *testing.T) {
t.Error("KindNone should be enter-rejected")
}
- accepted := []TracepointKind{KindFd, KindOpen, KindPathname, KindName, KindFcntl, KindNull, KindDup3, KindOpenByHandleAt, KindSocket, KindSocketpair, KindAccept}
+ accepted := []TracepointKind{KindFd, KindOpen, KindPathname, KindName, KindFcntl, KindNull, KindDup3, KindOpenByHandleAt, KindSocket, KindSocketpair, KindAccept, KindPipe, KindEventfd}
for _, k := range accepted {
if isEnterRejected(k) {
t.Errorf("kind %d should NOT be enter-rejected", k)
diff --git a/internal/generate/kindregistry.go b/internal/generate/kindregistry.go
index 186dbbc..17f7542 100644
--- a/internal/generate/kindregistry.go
+++ b/internal/generate/kindregistry.go
@@ -28,6 +28,8 @@ var kindRegistry = map[TracepointKind]kindMeta{
KindSocket: {structName: "socket_event", enterAccepted: true},
KindSocketpair: {structName: "socketpair_event", enterAccepted: true},
KindAccept: {structName: "accept_event", enterAccepted: true},
+ KindPipe: {structName: "pipe_event", enterAccepted: true},
+ KindEventfd: {structName: "eventfd_event", enterAccepted: true},
// KindNone is intentionally absent: it represents "unclassified" and is
// never enter-accepted. lookupKind returns the zero kindMeta (enterAccepted=false)
// for any unregistered kind, so KindNone is implicitly rejected.
diff --git a/internal/generate/testdata.go b/internal/generate/testdata.go
index 090619b..b34f98d 100644
--- a/internal/generate/testdata.go
+++ b/internal/generate/testdata.go
@@ -885,6 +885,120 @@ format:
print fmt: "0x%lx", REC->ret
`
+const FormatPipe = `name: sys_enter_pipe
+ID: 873
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:int * fildes; offset:16; size:8; signed:0;
+
+print fmt: "fildes: 0x%08lx", ((unsigned long)(REC->fildes))
+`
+
+const FormatExitPipe = `name: sys_exit_pipe
+ID: 872
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:long ret; offset:16; size:8; signed:1;
+
+print fmt: "0x%lx", REC->ret
+`
+
+const FormatPipe2 = `name: sys_enter_pipe2
+ID: 875
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:int * fildes; offset:16; size:8; signed:0;
+ field:int flags; offset:24; size:8; signed:0;
+
+print fmt: "fildes: 0x%08lx, flags: 0x%08lx", ((unsigned long)(REC->fildes)), ((unsigned long)(REC->flags))
+`
+
+const FormatExitPipe2 = `name: sys_exit_pipe2
+ID: 874
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:long ret; offset:16; size:8; signed:1;
+
+print fmt: "0x%lx", REC->ret
+`
+
+const FormatEventfd = `name: sys_enter_eventfd
+ID: 1095
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:unsigned int count; offset:16; size:8; signed:0;
+
+print fmt: "count: 0x%08lx", ((unsigned long)(REC->count))
+`
+
+const FormatExitEventfd = `name: sys_exit_eventfd
+ID: 1094
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:long ret; offset:16; size:8; signed:1;
+
+print fmt: "0x%lx", REC->ret
+`
+
+const FormatEventfd2 = `name: sys_enter_eventfd2
+ID: 1097
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:unsigned int count; offset:16; size:8; signed:0;
+ field:int flags; offset:24; size:8; signed:0;
+
+print fmt: "count: 0x%08lx, flags: 0x%08lx", ((unsigned long)(REC->count)), ((unsigned long)(REC->flags))
+`
+
+const FormatExitEventfd2 = `name: sys_exit_eventfd2
+ID: 1096
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:long ret; offset:16; size:8; signed:1;
+
+print fmt: "0x%lx", REC->ret
+`
+
const FormatPread64 = `name: sys_enter_pread64
ID: 840
format:
diff --git a/internal/types/fastdecode.go b/internal/types/fastdecode.go
index e8689da..d87eef0 100644
--- a/internal/types/fastdecode.go
+++ b/internal/types/fastdecode.go
@@ -17,6 +17,10 @@ const (
socketpairEventSizeV1 = 52
acceptEventSize = 40
acceptEventSizeV1 = 36
+ pipeEventSize = 48
+ pipeEventSizeV1 = 44
+ eventfdEventSize = 40
+ eventfdEventSizeV1 = 36
)
func NewOpenEventFast(raw []byte) *OpenEvent {
@@ -244,3 +248,49 @@ func NewAcceptEventFast(raw []byte) *AcceptEvent {
a.Ret = int64(binary.LittleEndian.Uint64(raw[retOffset : retOffset+8]))
return a
}
+
+func NewPipeEventFast(raw []byte) *PipeEvent {
+ if len(raw) < pipeEventSizeV1 {
+ return nil
+ }
+ if len(raw) != pipeEventSize && len(raw) != pipeEventSizeV1 {
+ return NewPipeEvent(raw)
+ }
+ p := poolOfPipeEvents.Get().(*PipeEvent)
+ p.EventType = EventType(binary.LittleEndian.Uint32(raw[0:4]))
+ p.TraceId = TraceId(binary.LittleEndian.Uint32(raw[4:8]))
+ p.Time = binary.LittleEndian.Uint64(raw[8:16])
+ p.Pid = binary.LittleEndian.Uint32(raw[16:20])
+ p.Tid = binary.LittleEndian.Uint32(raw[20:24])
+ p.Flags = int32(binary.LittleEndian.Uint32(raw[24:28]))
+ p.Fd0 = int32(binary.LittleEndian.Uint32(raw[28:32]))
+ p.Fd1 = int32(binary.LittleEndian.Uint32(raw[32:36]))
+ retOffset := 36
+ if len(raw) == pipeEventSize {
+ retOffset = 40
+ }
+ p.Ret = int64(binary.LittleEndian.Uint64(raw[retOffset : retOffset+8]))
+ return p
+}
+
+func NewEventfdEventFast(raw []byte) *EventfdEvent {
+ if len(raw) < eventfdEventSizeV1 {
+ return nil
+ }
+ if len(raw) != eventfdEventSize && len(raw) != eventfdEventSizeV1 {
+ return NewEventfdEvent(raw)
+ }
+ e := poolOfEventfdEvents.Get().(*EventfdEvent)
+ e.EventType = EventType(binary.LittleEndian.Uint32(raw[0:4]))
+ e.TraceId = TraceId(binary.LittleEndian.Uint32(raw[4:8]))
+ e.Time = binary.LittleEndian.Uint64(raw[8:16])
+ e.Pid = binary.LittleEndian.Uint32(raw[16:20])
+ e.Tid = binary.LittleEndian.Uint32(raw[20:24])
+ e.Flags = int32(binary.LittleEndian.Uint32(raw[24:28]))
+ retOffset := 28
+ if len(raw) == eventfdEventSize {
+ retOffset = 32
+ }
+ e.Ret = int64(binary.LittleEndian.Uint64(raw[retOffset : retOffset+8]))
+ return e
+}
diff --git a/internal/types/fastdecode_test.go b/internal/types/fastdecode_test.go
index d806b45..7478561 100644
--- a/internal/types/fastdecode_test.go
+++ b/internal/types/fastdecode_test.go
@@ -166,6 +166,32 @@ func TestFastDecodersMatchGeneratedDecoders(t *testing.T) {
t.Fatalf("accept decode mismatch")
}
})
+
+ t.Run("PipeEvent", func(t *testing.T) {
+ ev := &PipeEvent{EventType: ENTER_PIPE_EVENT, TraceId: SYS_ENTER_PIPE2, Time: 1, Pid: 2, Tid: 3, Flags: 0x80000, Fd0: -1, Fd1: -1, Ret: 0}
+ raw, _ := ev.Bytes()
+
+ slow := NewPipeEvent(raw)
+ fast := NewPipeEventFast(raw)
+ defer slow.Recycle()
+ defer fast.Recycle()
+ if !slow.Equals(fast) {
+ t.Fatalf("pipe decode mismatch")
+ }
+ })
+
+ t.Run("EventfdEvent", func(t *testing.T) {
+ ev := &EventfdEvent{EventType: ENTER_EVENTFD_EVENT, TraceId: SYS_ENTER_EVENTFD2, Time: 1, Pid: 2, Tid: 3, Flags: 0x800, Ret: -1}
+ raw, _ := ev.Bytes()
+
+ slow := NewEventfdEvent(raw)
+ fast := NewEventfdEventFast(raw)
+ defer slow.Recycle()
+ defer fast.Recycle()
+ if !slow.Equals(fast) {
+ t.Fatalf("eventfd decode mismatch")
+ }
+ })
}
func TestNewSocketpairEventFastKernelLayout(t *testing.T) {
@@ -230,6 +256,64 @@ func TestNewAcceptEventFastKernelLayout(t *testing.T) {
}
}
+func TestNewPipeEventFastKernelLayout(t *testing.T) {
+ raw := make([]byte, pipeEventSize)
+ binary.LittleEndian.PutUint32(raw[0:4], uint32(EXIT_PIPE_EVENT))
+ binary.LittleEndian.PutUint32(raw[4:8], uint32(SYS_EXIT_PIPE2))
+ binary.LittleEndian.PutUint64(raw[8:16], 1)
+ binary.LittleEndian.PutUint32(raw[16:20], 2)
+ binary.LittleEndian.PutUint32(raw[20:24], 3)
+ binary.LittleEndian.PutUint32(raw[24:28], uint32(0x80000))
+ binary.LittleEndian.PutUint32(raw[28:32], uint32(10))
+ binary.LittleEndian.PutUint32(raw[32:36], uint32(11))
+ binary.LittleEndian.PutUint64(raw[40:48], uint64(0))
+
+ fast := NewPipeEventFast(raw)
+ if fast == nil {
+ t.Fatalf("expected decoded pipe event for kernel layout payload")
+ }
+ defer fast.Recycle()
+
+ if fast.EventType != EXIT_PIPE_EVENT ||
+ fast.TraceId != SYS_EXIT_PIPE2 ||
+ fast.Time != 1 ||
+ fast.Pid != 2 ||
+ fast.Tid != 3 ||
+ fast.Flags != 0x80000 ||
+ fast.Fd0 != 10 ||
+ fast.Fd1 != 11 ||
+ fast.Ret != 0 {
+ t.Fatalf("unexpected pipe decode: %#v", fast)
+ }
+}
+
+func TestNewEventfdEventFastKernelLayout(t *testing.T) {
+ raw := make([]byte, eventfdEventSize)
+ binary.LittleEndian.PutUint32(raw[0:4], uint32(EXIT_EVENTFD_EVENT))
+ binary.LittleEndian.PutUint32(raw[4:8], uint32(SYS_EXIT_EVENTFD2))
+ binary.LittleEndian.PutUint64(raw[8:16], 1)
+ binary.LittleEndian.PutUint32(raw[16:20], 2)
+ binary.LittleEndian.PutUint32(raw[20:24], 3)
+ binary.LittleEndian.PutUint32(raw[24:28], uint32(0x800))
+ binary.LittleEndian.PutUint64(raw[32:40], uint64(42))
+
+ fast := NewEventfdEventFast(raw)
+ if fast == nil {
+ t.Fatalf("expected decoded eventfd event for kernel layout payload")
+ }
+ defer fast.Recycle()
+
+ if fast.EventType != EXIT_EVENTFD_EVENT ||
+ fast.TraceId != SYS_EXIT_EVENTFD2 ||
+ fast.Time != 1 ||
+ fast.Pid != 2 ||
+ fast.Tid != 3 ||
+ fast.Flags != 0x800 ||
+ fast.Ret != 42 {
+ t.Fatalf("unexpected eventfd decode: %#v", fast)
+ }
+}
+
func TestFastDecodersReturnNilOnShortPayload(t *testing.T) {
cases := []struct {
name string
@@ -247,6 +331,8 @@ func TestFastDecodersReturnNilOnShortPayload(t *testing.T) {
{name: "SocketEvent", decode: func(raw []byte) bool { return NewSocketEventFast(raw) == nil }},
{name: "SocketpairEvent", decode: func(raw []byte) bool { return NewSocketpairEventFast(raw) == nil }},
{name: "AcceptEvent", decode: func(raw []byte) bool { return NewAcceptEventFast(raw) == nil }},
+ {name: "PipeEvent", decode: func(raw []byte) bool { return NewPipeEventFast(raw) == nil }},
+ {name: "EventfdEvent", decode: func(raw []byte) bool { return NewEventfdEventFast(raw) == nil }},
}
for _, tc := range cases {
diff --git a/internal/types/generated_types.go b/internal/types/generated_types.go
index 05c869a..96de5fb 100644
--- a/internal/types/generated_types.go
+++ b/internal/types/generated_types.go
@@ -92,6 +92,10 @@ const ENTER_SOCKETPAIR_EVENT = 21
const EXIT_SOCKETPAIR_EVENT = 22
const ENTER_ACCEPT_EVENT = 23
const EXIT_ACCEPT_EVENT = 24
+const ENTER_PIPE_EVENT = 25
+const EXIT_PIPE_EVENT = 26
+const ENTER_EVENTFD_EVENT = 27
+const EXIT_EVENTFD_EVENT = 28
const UNCLASSIFIED = 0
const READ_CLASSIFIED = 1
const WRITE_CLASSIFIED = 2
@@ -1660,3 +1664,143 @@ func (a *AcceptEvent) Bytes() ([]byte, error) {
func (a *AcceptEvent) Recycle() {
poolOfAcceptEvents.Put(a)
}
+
+type PipeEvent struct {
+ EventType EventType
+ TraceId TraceId
+ Time uint64
+ Pid uint32
+ Tid uint32
+ Flags int32
+ Fd0 int32
+ Fd1 int32
+ Ret int64
+}
+
+func (p PipeEvent) String() string {
+ return fmt.Sprintf("EventType:%v TraceId:%v Time:%v Pid:%v Tid:%v Flags:%v Fd0:%v Fd1:%v Ret:%v", p.EventType, p.TraceId, p.Time, p.Pid, p.Tid, p.Flags, p.Fd0, p.Fd1, p.Ret)
+}
+
+func (p PipeEvent) Equals(other any) bool {
+ otherConcrete, ok := other.(*PipeEvent)
+ if !ok {
+ return false
+ }
+ return p.EventType == otherConcrete.EventType && p.TraceId == otherConcrete.TraceId && p.Time == otherConcrete.Time && p.Pid == otherConcrete.Pid && p.Tid == otherConcrete.Tid && p.Flags == otherConcrete.Flags && p.Fd0 == otherConcrete.Fd0 && p.Fd1 == otherConcrete.Fd1 && p.Ret == otherConcrete.Ret
+}
+
+func (p *PipeEvent) GetEventType() EventType {
+ return p.EventType
+}
+
+func (p *PipeEvent) GetTraceId() TraceId {
+ return p.TraceId
+}
+
+func (p *PipeEvent) GetPid() uint32 {
+ return p.Pid
+}
+
+func (p *PipeEvent) GetTid() uint32 {
+ return p.Tid
+}
+
+func (p *PipeEvent) GetTime() uint64 {
+ return p.Time
+}
+
+var poolOfPipeEvents = sync.Pool{
+ New: func() any { return &PipeEvent{} },
+}
+
+func NewPipeEvent(raw []byte) *PipeEvent {
+ p := poolOfPipeEvents.Get().(*PipeEvent)
+ if err := binary.Read(bytes.NewReader(raw), binary.LittleEndian, p); err != nil {
+ *p = PipeEvent{}
+ poolOfPipeEvents.Put(p)
+ return nil
+ }
+ return p
+}
+
+func (p *PipeEvent) Bytes() ([]byte, error) {
+ buf := new(bytes.Buffer)
+ err := binary.Write(buf, binary.LittleEndian, p)
+ if err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+}
+
+func (p *PipeEvent) Recycle() {
+ poolOfPipeEvents.Put(p)
+}
+
+type EventfdEvent struct {
+ EventType EventType
+ TraceId TraceId
+ Time uint64
+ Pid uint32
+ Tid uint32
+ Flags int32
+ Ret int64
+}
+
+func (e EventfdEvent) String() string {
+ return fmt.Sprintf("EventType:%v TraceId:%v Time:%v Pid:%v Tid:%v Flags:%v Ret:%v", e.EventType, e.TraceId, e.Time, e.Pid, e.Tid, e.Flags, e.Ret)
+}
+
+func (e EventfdEvent) Equals(other any) bool {
+ otherConcrete, ok := other.(*EventfdEvent)
+ if !ok {
+ return false
+ }
+ return e.EventType == otherConcrete.EventType && e.TraceId == otherConcrete.TraceId && e.Time == otherConcrete.Time && e.Pid == otherConcrete.Pid && e.Tid == otherConcrete.Tid && e.Flags == otherConcrete.Flags && e.Ret == otherConcrete.Ret
+}
+
+func (e *EventfdEvent) GetEventType() EventType {
+ return e.EventType
+}
+
+func (e *EventfdEvent) GetTraceId() TraceId {
+ return e.TraceId
+}
+
+func (e *EventfdEvent) GetPid() uint32 {
+ return e.Pid
+}
+
+func (e *EventfdEvent) GetTid() uint32 {
+ return e.Tid
+}
+
+func (e *EventfdEvent) GetTime() uint64 {
+ return e.Time
+}
+
+var poolOfEventfdEvents = sync.Pool{
+ New: func() any { return &EventfdEvent{} },
+}
+
+func NewEventfdEvent(raw []byte) *EventfdEvent {
+ e := poolOfEventfdEvents.Get().(*EventfdEvent)
+ if err := binary.Read(bytes.NewReader(raw), binary.LittleEndian, e); err != nil {
+ *e = EventfdEvent{}
+ poolOfEventfdEvents.Put(e)
+ return nil
+ }
+ return e
+}
+
+func (e *EventfdEvent) Bytes() ([]byte, error) {
+ buf := new(bytes.Buffer)
+ err := binary.Write(buf, binary.LittleEndian, e)
+ if err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+}
+
+func (e *EventfdEvent) Recycle() {
+ poolOfEventfdEvents.Put(e)
+}
diff --git a/internal/types/types_test.go b/internal/types/types_test.go
index d47609a..245f112 100644
--- a/internal/types/types_test.go
+++ b/internal/types/types_test.go
@@ -215,6 +215,60 @@ func TestAcceptEventSerialization(t *testing.T) {
assertEquals(t, acceptEv1.Ret, acceptEv2.Ret)
}
+func TestPipeEventSerialization(t *testing.T) {
+ pipeEv1 := PipeEvent{
+ EventType: ENTER_PIPE_EVENT,
+ TraceId: SYS_ENTER_PIPE2,
+ Time: 4567,
+ Pid: 36,
+ Tid: 37,
+ Flags: 0x80000,
+ Fd0: 10,
+ Fd1: 11,
+ Ret: 0,
+ }
+ bytes, err := pipeEv1.Bytes()
+ if err != nil {
+ t.Error(err)
+ }
+ pipeEv2 := NewPipeEvent(bytes)
+
+ assertEquals(t, pipeEv1.EventType, pipeEv2.EventType)
+ assertEquals(t, pipeEv1.TraceId, pipeEv2.TraceId)
+ assertEquals(t, pipeEv1.Time, pipeEv2.Time)
+ assertEquals(t, pipeEv1.Pid, pipeEv2.Pid)
+ assertEquals(t, pipeEv1.Tid, pipeEv2.Tid)
+ assertEquals(t, pipeEv1.Flags, pipeEv2.Flags)
+ assertEquals(t, pipeEv1.Fd0, pipeEv2.Fd0)
+ assertEquals(t, pipeEv1.Fd1, pipeEv2.Fd1)
+ assertEquals(t, pipeEv1.Ret, pipeEv2.Ret)
+}
+
+func TestEventfdEventSerialization(t *testing.T) {
+ eventfdEv1 := EventfdEvent{
+ EventType: ENTER_EVENTFD_EVENT,
+ TraceId: SYS_ENTER_EVENTFD2,
+ Time: 5678,
+ Pid: 38,
+ Tid: 39,
+ Flags: 0x800,
+ Ret: 12,
+ }
+ bytes, err := eventfdEv1.Bytes()
+ if err != nil {
+ t.Error(err)
+ }
+ eventfdEv2 := NewEventfdEvent(bytes)
+
+ assertEquals(t, eventfdEv1.EventType, eventfdEv2.EventType)
+ assertEquals(t, eventfdEv1.TraceId, eventfdEv2.TraceId)
+ assertEquals(t, eventfdEv1.Time, eventfdEv2.Time)
+ assertEquals(t, eventfdEv1.Pid, eventfdEv2.Pid)
+ assertEquals(t, eventfdEv1.Tid, eventfdEv2.Tid)
+ assertEquals(t, eventfdEv1.Flags, eventfdEv2.Flags)
+ assertEquals(t, eventfdEv1.Ret, eventfdEv2.Ret)
+}
+
func TestEqualsDifferentTypes(t *testing.T) {
openEv := OpenEvent{EventType: ENTER_OPEN_EVENT, TraceId: SYS_ENTER_OPENAT, Time: 1, Pid: 1, Tid: 1}
nullEv := NullEvent{EventType: ENTER_NULL_EVENT, TraceId: SYS_ENTER_SYNC, Time: 1, Pid: 1, Tid: 1}