From 843def4f5c23db050cccfae57a9acb5899c110f4 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Tue, 19 May 2026 16:15:18 +0300 Subject: x6: add pipe/eventfd fd-from-air syscall support --- cmd/ioworkload/scenario_ipc.go | 56 +++++++++++ cmd/ioworkload/scenarios.go | 4 + integrationtests/ipc_test.go | 47 +++++++++ internal/c/generated_tracepoints.c | 126 ++++++++++++++++++------ internal/c/generated_tracepoints_result.txt | 16 ++-- internal/c/maps.h | 19 ++++ internal/c/types.h | 26 +++++ internal/event/interface_assertions.go | 6 ++ internal/eventloop_exit.go | 69 +++++++++++++ internal/eventloop_ipc_test.go | 120 +++++++++++++++++++++++ internal/eventloop_runtime.go | 32 +++++++ internal/generate/bpfhandler.go | 26 +++++ internal/generate/classify.go | 18 ++++ internal/generate/classify_test.go | 64 +++++++++++++ internal/generate/codegen_test.go | 36 ++++++- internal/generate/kindregistry.go | 2 + internal/generate/testdata.go | 114 ++++++++++++++++++++++ internal/types/fastdecode.go | 50 ++++++++++ internal/types/fastdecode_test.go | 86 +++++++++++++++++ internal/types/generated_types.go | 144 ++++++++++++++++++++++++++++ internal/types/types_test.go | 54 +++++++++++ 21 files changed, 1078 insertions(+), 37 deletions(-) create mode 100644 cmd/ioworkload/scenario_ipc.go create mode 100644 integrationtests/ipc_test.go create mode 100644 internal/eventloop_ipc_test.go 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} -- cgit v1.2.3