diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-02 10:01:15 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-02 10:01:15 +0300 |
| commit | 0528557ee9f14ed292de49be09e65b8662185c38 (patch) | |
| tree | 42b6d3ccd45f975cc9fa4f282a0813a81a707e0b /internal/generate | |
| parent | 0dc3dc4e0c8367bc8399d3987251015a0e135fd9 (diff) | |
fix BPF tracepoint context type for RHEL 9 stock kernel
The BPF handler generator emitted struct trace_event_raw_sys_enter/
trace_event_raw_sys_exit (the BTF-blessed aliases). RHEL 9 carries an
rt-tree backport that adds preempt_lazy_count to struct trace_entry,
which widens those aliases by 8 bytes and shifts args/ret. The actual
tracepoint context the kernel hands the program is still
syscall_trace_enter / syscall_trace_exit, where the offsets did not
move. Programs typed against the wider alias read past max_ctx_offset
and the verifier rejects the attach with EACCES.
Switching the generator to emit syscall_trace_enter/exit lines up with
the real context on RHEL 9 (and is identical on every other distro,
since the two structs only diverge there). Same fix bcc shipped in
iovisor/bcc#4920 and inspektor-gadget did in inspektor-gadget#2546.
Field accesses (ctx->args[N], ctx->ret) are unchanged.
Verified end-to-end on Rocky Linux 9.7 stock 5.14.0-611.5.1.el9_7
(no kernel-ml needed) and Fedora 6.19. README rewritten accordingly:
drops the elrepo kernel-ml step and the trailing 'permission denied'
troubleshooting paragraph; adds a historical note explaining why the
old workaround existed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Diffstat (limited to 'internal/generate')
| -rw-r--r-- | internal/generate/bpfhandler.go | 12 | ||||
| -rw-r--r-- | internal/generate/codegen_test.go | 6 | ||||
| -rw-r--r-- | internal/generate/tracepointsgo_test.go | 10 |
3 files changed, 18 insertions, 10 deletions
diff --git a/internal/generate/bpfhandler.go b/internal/generate/bpfhandler.go index 2c0d648..3d76ac4 100644 --- a/internal/generate/bpfhandler.go +++ b/internal/generate/bpfhandler.go @@ -9,9 +9,17 @@ func generateBPFHandler(tp GeneratedTracepoint) string { f := tp.Format isEnter := strings.Split(f.Name, "_")[1] == "enter" - ctxStruct := "trace_event_raw_sys_exit" + // Use the kernel's actual tracepoint context structs (syscall_trace_enter/exit) + // rather than the BTF-emitted trace_event_raw_sys_enter/exit aliases. On RHEL 9 + // kernels (5.14 with the rt-merge backport that added preempt_lazy_count to + // trace_entry) the two diverge: trace_event_raw_sys_* grows by 8 bytes and + // the args/ret offsets shift, but the real context handed to the BPF program + // is still syscall_trace_*. Reading via the wider alias trips the verifier's + // max_ctx_offset check and the attach fails with EACCES. The two structs are + // identical on non-RHEL kernels, so this is a no-op everywhere else. + ctxStruct := "syscall_trace_exit" if isEnter { - ctxStruct = "trace_event_raw_sys_enter" + ctxStruct = "syscall_trace_enter" } eventStruct := eventStructName(tp.Classification.Kind) diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go index caad340..7a7d469 100644 --- a/internal/generate/codegen_test.go +++ b/internal/generate/codegen_test.go @@ -16,7 +16,7 @@ func TestGenerateFdHandler(t *testing.T) { output := generateFromPair(t, FormatRead, FormatExitRead) requireContains(t, output, `SEC("tracepoint/syscalls/sys_enter_read")`) - requireContains(t, output, "struct trace_event_raw_sys_enter *ctx") + requireContains(t, output, "struct syscall_trace_enter *ctx") requireContains(t, output, "struct fd_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct fd_event), 0);") requireContains(t, output, "ev->event_type = ENTER_FD_EVENT;") requireContains(t, output, "ev->trace_id = SYS_ENTER_READ;") @@ -70,7 +70,7 @@ func TestGenerateRetHandlerRead(t *testing.T) { output := generateFromPair(t, FormatRead, FormatExitRead) requireContains(t, output, `SEC("tracepoint/syscalls/sys_exit_read")`) - requireContains(t, output, "struct trace_event_raw_sys_exit *ctx") + requireContains(t, output, "struct syscall_trace_exit *ctx") requireContains(t, output, "struct ret_event *ev") requireContains(t, output, "ev->event_type = EXIT_RET_EVENT;") requireContains(t, output, "ev->trace_id = SYS_EXIT_READ;") @@ -226,7 +226,7 @@ func TestGenerateDefinesSortedByIDDesc(t *testing.T) { func TestGenerateHandlerStructure(t *testing.T) { output := generateFromPair(t, FormatClose, FormatExitClose) - requireContains(t, output, "int handle_sys_enter_close(struct trace_event_raw_sys_enter *ctx) {") + requireContains(t, output, "int handle_sys_enter_close(struct syscall_trace_enter *ctx) {") requireContains(t, output, "__u32 pid, tid;") requireContains(t, output, "if (filter(&pid, &tid))") requireContains(t, output, "ev->pid = pid;") diff --git a/internal/generate/tracepointsgo_test.go b/internal/generate/tracepointsgo_test.go index cd24942..978633b 100644 --- a/internal/generate/tracepointsgo_test.go +++ b/internal/generate/tracepointsgo_test.go @@ -16,25 +16,25 @@ const sampleGeneratedC = `// Code generated - don't change manually! /// sys_enter_read is a struct fd_event SEC("tracepoint/syscalls/sys_enter_read") -int handle_sys_enter_read(struct trace_event_raw_sys_enter *ctx) { +int handle_sys_enter_read(struct syscall_trace_enter *ctx) { return 0; } /// sys_exit_read is a struct ret_event (READ_CLASSIFIED) SEC("tracepoint/syscalls/sys_exit_read") -int handle_sys_exit_read(struct trace_event_raw_sys_exit *ctx) { +int handle_sys_exit_read(struct syscall_trace_exit *ctx) { return 0; } /// sys_enter_close is a struct fd_event SEC("tracepoint/syscalls/sys_enter_close") -int handle_sys_enter_close(struct trace_event_raw_sys_enter *ctx) { +int handle_sys_enter_close(struct syscall_trace_enter *ctx) { return 0; } /// sys_exit_close is a struct ret_event (UNCLASSIFIED) SEC("tracepoint/syscalls/sys_exit_close") -int handle_sys_exit_close(struct trace_event_raw_sys_exit *ctx) { +int handle_sys_exit_close(struct syscall_trace_exit *ctx) { return 0; } ` @@ -93,7 +93,7 @@ func TestExtractTracepointsPackageHeader(t *testing.T) { func TestExtractTracepointsMalformedSEC(t *testing.T) { input := `SEC("tracepoint/not_matching_pattern") -int handle_something(struct trace_event_raw_sys_enter *ctx) { +int handle_something(struct syscall_trace_enter *ctx) { return 0; } SEC("some/garbage") |
