From 940bd6e00cb28af5f076828f6c90e6f3bc729cd8 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sat, 23 May 2026 19:56:33 +0300 Subject: 5c remove tracepoint ID adjacency dependency from aggregate pairing Generated exit handlers now pass the explicit enter trace ID (SYS_ENTER_X) to ior_on_syscall_exit instead of relying on the implicit enter_id == exit_id + 1 arithmetic invariant. filter.c compares directly against the passed enter ID. Co-Authored-By: Claude Opus 4.7 --- internal/generate/bpfhandler.go | 23 +++++++++++++-- internal/generate/codegen_test.go | 59 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) (limited to 'internal/generate') diff --git a/internal/generate/bpfhandler.go b/internal/generate/bpfhandler.go index ed672c8..95a14b0 100644 --- a/internal/generate/bpfhandler.go +++ b/internal/generate/bpfhandler.go @@ -31,10 +31,27 @@ func generateBPFHandler(tp GeneratedTracepoint) string { eventTypeConst := eventTypeConstant(tp.Classification.Kind, isEnter) extra := generateExtra(tp, isEnter) - return renderHandler(f.Name, ctxStruct, eventStruct, comment, eventTypeConst, extra, isEnter) + // Derive the explicit enter trace ID constant for exit handlers so the + // generated ior_on_syscall_exit call does not rely on numeric adjacency + // between kernel-assigned enter/exit IDs. + enterName := enterConstForHandler(f.Name, isEnter) + + return renderHandler(f.Name, ctxStruct, eventStruct, comment, eventTypeConst, extra, isEnter, enterName) +} + +// enterConstForHandler returns the C #define constant name for the +// corresponding enter tracepoint. For enter handlers it returns +// strings.ToUpper(name) directly; for exit handlers it replaces "EXIT" +// with "ENTER" so the generated code passes the explicit enter ID. +func enterConstForHandler(name string, isEnter bool) string { + upper := strings.ToUpper(name) + if isEnter { + return upper + } + return strings.Replace(upper, "SYS_EXIT_", "SYS_ENTER_", 1) } -func renderHandler(name, ctxStruct, eventStruct, comment, eventTypeConst, extra string, isEnter bool) string { +func renderHandler(name, ctxStruct, eventStruct, comment, eventTypeConst, extra string, isEnter bool, enterName string) string { var b strings.Builder fmt.Fprintf(&b, "/// %s is a struct %s\n", name, comment) fmt.Fprintf(&b, "SEC(\"tracepoint/syscalls/%s\")\n", name) @@ -47,7 +64,7 @@ func renderHandler(name, ctxStruct, eventStruct, comment, eventTypeConst, extra fmt.Fprintf(&b, " if (!ior_on_syscall_enter(tid, %s))\n", strings.ToUpper(name)) b.WriteString(" return 0;\n") } else { - fmt.Fprintf(&b, " if (!ior_on_syscall_exit(tid, %s, ctx->ret))\n", strings.ToUpper(name)) + fmt.Fprintf(&b, " if (!ior_on_syscall_exit(tid, %s, ctx->ret))\n", enterName) b.WriteString(" return 0;\n") } b.WriteString("\n") diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go index f44e456..276a832 100644 --- a/internal/generate/codegen_test.go +++ b/internal/generate/codegen_test.go @@ -759,6 +759,65 @@ func TestClassifySyscallNoExit(t *testing.T) { } } +func TestEnterConstForHandler(t *testing.T) { + tests := []struct { + name string + isEnter bool + want string + }{ + {"sys_enter_read", true, "SYS_ENTER_READ"}, + {"sys_exit_read", false, "SYS_ENTER_READ"}, + {"sys_enter_openat", true, "SYS_ENTER_OPENAT"}, + {"sys_exit_openat", false, "SYS_ENTER_OPENAT"}, + {"sys_exit_io_uring_enter", false, "SYS_ENTER_IO_URING_ENTER"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := enterConstForHandler(tt.name, tt.isEnter) + if got != tt.want { + t.Errorf("enterConstForHandler(%q, %v) = %q, want %q", tt.name, tt.isEnter, got, tt.want) + } + }) + } +} + +func TestExitHandlerPassesEnterTraceID(t *testing.T) { + output := generateFromPair(t, FormatRead, FormatExitRead) + + requireContains(t, output, "ior_on_syscall_exit(tid, SYS_ENTER_READ, ctx->ret)") + if strings.Contains(output, "ior_on_syscall_exit(tid, SYS_EXIT_READ") { + t.Error("exit handler must pass the enter trace ID, not the exit trace ID") + } +} + +func TestExitHandlerDoesNotRelyOnIDAdjacency(t *testing.T) { + input := FormatRead + "\n" + FormatExitRead + formats := mustParseAll(t, input) + enterID := -1 + exitID := -1 + for _, f := range formats { + if strings.HasPrefix(f.Name, "sys_enter_") { + enterID = f.ID + } + if strings.HasPrefix(f.Name, "sys_exit_") { + exitID = f.ID + } + } + if enterID < 0 || exitID < 0 { + t.Fatal("missing enter or exit format") + } + if enterID != exitID+1 { + t.Skipf("IDs are not adjacent (enter=%d, exit=%d), adjacency test not applicable", enterID, exitID) + } + + output := GenerateTracepointsC(formats) + if strings.Contains(output, "ior_on_syscall_exit(tid, SYS_EXIT_") { + t.Error("generated exit handler passes exit trace ID; should pass enter trace ID to avoid adjacency dependency") + } + requireContains(t, output, "ior_on_syscall_exit(tid, SYS_ENTER_READ, ctx->ret)") +} + func syntheticPair(syscall string) string { enter := strings.Replace(FormatKill, "sys_enter_kill", "sys_enter_"+syscall, 1) enter = strings.Replace(enter, "ID: 183", "ID: 1001", 1) -- cgit v1.2.3