summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-21 11:51:01 +0200
committerPaul Buetow <paul@buetow.org>2026-02-21 11:51:01 +0200
commit6c912a9d72ae2a43923c638538d320e6bf585952 (patch)
tree727f66d158210e01abf8c18a83ef4db6066e0c1a
parent32136b8cb18944157ff1f361bc0755f6b627fd47 (diff)
Migrate make targets to mage
Amp-Thread-ID: https://ampcode.com/threads/T-019c7f4e-cc5f-76f1-aaf0-dd7cbaabbb18 Co-authored-by: Amp <amp@ampcode.com>
-rw-r--r--AGENTS.md27
-rw-r--r--Magefile.go424
-rw-r--r--Makefile68
-rw-r--r--go.mod2
-rw-r--r--go.sum2
-rw-r--r--internal/c/Makefile50
-rw-r--r--internal/c/generated_tracepoints_result.txt164
-rw-r--r--internal/generate/bpfhandler.go152
-rw-r--r--internal/generate/classify.go214
-rw-r--r--internal/generate/classify_test.go332
-rw-r--r--internal/generate/codegen.go152
-rw-r--r--internal/generate/codegen_test.go263
-rw-r--r--internal/generate/format.go145
-rw-r--r--internal/generate/format_test.go174
-rw-r--r--internal/generate/retclassify_test.go59
-rw-r--r--internal/generate/testdata.go666
-rw-r--r--internal/generate/tracepointsgo.go43
-rw-r--r--internal/generate/tracepointsgo_test.go92
-rw-r--r--internal/generate/typesgo.go341
-rw-r--r--internal/generate/typesgo_test.go257
-rw-r--r--internal/tracepoints/Makefile8
-rw-r--r--internal/types/Makefile13
22 files changed, 3409 insertions, 239 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 051d57f..d3f3e95 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -7,26 +7,26 @@ This file provides guidance to AI coding assistants working with the I/O Riot NG
**Prerequisites**: Ensure libbpfgo is cloned at `../libbpfgo` relative to this repository.
```bash
-make all # Build everything (BPF objects and Go binary)
-make test # Run all tests
-make test_with_name TEST_NAME=TestEventloop # Run specific test
-go test ./internal/event -v # Run tests for specific package
-make generate # Generate code (required after modifying tracepoint definitions)
-make bench # Run benchmarks
-make clean # Clean build artifacts
+mage all # Build everything (BPF objects and Go binary)
+mage test # Run all tests
+TEST_NAME=TestEventloop mage testWithName # Run specific test
+go test ./internal/event -v # Run tests for specific package
+mage generate # Generate code (required after modifying tracepoint definitions)
+mage bench # Run benchmarks
+mage clean # Clean build artifacts
```
## Code Generation
-**Run `make generate` before building when tracepoint definitions change!**
+**Run `mage generate` before building when tracepoint definitions change!**
-A Go program (`cmd/generate/`) generates code from Linux kernel tracepoint data:
+A Mage target generates code from Linux kernel tracepoint data:
```bash
-make generate # Generate all code (C and Go)
-make -C internal/c generate # Generate C tracepoint handlers from /sys/kernel/tracing
-make -C internal/types generate # Generate Go types from C structs
-make -C internal/tracepoints generate # Generate Go tracepoint list
+mage generate # Generate all code (C and Go)
+mage generateTracepointsC # Generate C tracepoint handlers from /sys/kernel/tracing
+mage generateTypesGo # Generate Go types from C structs
+mage generateTracepointsGo # Generate Go tracepoint list
```
Generated files (do not edit manually):
@@ -35,7 +35,6 @@ Generated files (do not edit manually):
- `internal/tracepoints/generated_tracepoints.go` - List of available syscall tracepoints
Generator source code:
-- `cmd/generate/main.go` - Entry point with subcommands: `tracepoints-c`, `tracepoints-go`, `types-go`
- `internal/generate/` - Parser, classifier, and code generation logic
## Architecture
diff --git a/Magefile.go b/Magefile.go
index 7f63a22..8a49bea 100644
--- a/Magefile.go
+++ b/Magefile.go
@@ -1,18 +1,39 @@
//go:build mage
-// Magefile for ior targets: build, test, install.
+// Magefile for ior targets: build, test, install, generate, clean, and BPF builds.
package main
import (
+ "bufio"
+ "errors"
"fmt"
+ "go/format"
+ "io"
"os"
+ "os/exec"
"path/filepath"
+ "strings"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
+
+ "ior/internal/generate"
)
-const binaryName = "ior"
+const (
+ binaryName = "ior"
+ defaultLibbpfgoPath = "../libbpfgo"
+ bpfSourcePath = "internal/c/ior.bpf.c"
+ bpfObjectPath = "internal/c/ior.bpf.o"
+ bpfOutputPath = "ior.bpf.o"
+ tracepointsCPath = "internal/c/generated_tracepoints.c"
+ tracepointsResult = "internal/c/generated_tracepoints_result.txt"
+ tracepointsResultNew = "internal/c/generated_tracepoints_result.txt.new"
+ tracepointsGoPath = "internal/tracepoints/generated_tracepoints.go"
+ typesGoPath = "internal/types/generated_types.go"
+ typesHeaderPath = "internal/c/types.h"
+ VMLINUXPath = "internal/c/vmlinux.h"
+)
// Default builds the project.
func Default() {
@@ -21,12 +42,126 @@ func Default() {
// Build compiles the binary.
func Build() error {
- return sh.RunV("go", "build", "-o", binaryName, "./cmd/ior")
+ return sh.RunWithV(goEnv(), "go", "build", "-tags", "netgo", "-ldflags", "-w -extldflags \"-static\"",
+ "-o", binaryName, "./cmd/ior/main.go")
+}
+
+// GoBuildRace compiles the binary with the race detector enabled.
+func GoBuildRace() error {
+ return sh.RunWithV(goEnv(), "go", "build", "-tags", "netgo", "-ldflags", "-w -extldflags \"-static\"",
+ "-race", "-o", binaryName, "./cmd/ior/main.go")
+}
+
+// All builds the BPF object and the Go binary.
+func All() error {
+ mg.SerialDeps(BpfBuild, Build)
+ return nil
+}
+
+// BpfBuild builds the BPF object and copies it to the repo root.
+func BpfBuild() error {
+ if err := ensureVMLINUX(); err != nil {
+ return err
+ }
+ if err := buildBPFObject(); err != nil {
+ return err
+ }
+ return sh.RunV("cp", "-v", bpfObjectPath, bpfOutputPath)
}
// Test runs the full test suite.
func Test() error {
- return sh.RunV("go", "test", "./...")
+ if err := sh.RunWithV(goEnv(), "go", "clean", "-testcache"); err != nil {
+ return err
+ }
+ return sh.RunWithV(goEnv(), "go", "test", "./...", "-v", "-failfast")
+}
+
+// TestWithName runs a specific test by name.
+func TestWithName() error {
+ if err := sh.RunWithV(goEnv(), "go", "clean", "-testcache"); err != nil {
+ return err
+ }
+ testName := os.Getenv("TEST_NAME")
+ if testName == "" {
+ testName = "TestEventloop"
+ }
+ return sh.RunWithV(goEnv(), "go", "test", "./...", "-run", "^"+testName+"$", "-v", "-failfast")
+}
+
+// Bench runs benchmarks.
+func Bench() error {
+ return sh.RunWithV(goEnv(), "go", "test", "./...", "-v", "-bench=.", "-run", "xxx")
+}
+
+// Generate regenerates all generated files.
+func Generate() error {
+ fmt.Println("Generating tracepoint and type artifacts...")
+ mg.SerialDeps(GenerateTracepointsC, GenerateTracepointsGo, GenerateTypesGo)
+ fmt.Println("Generation complete.")
+ return nil
+}
+
+// GenerateTracepointsC regenerates the tracepoint handlers in C.
+func GenerateTracepointsC() error {
+ fmt.Println("Generating C tracepoints...")
+ return generateTracepointsC(true, false)
+}
+
+// GenerateTracepointsCForce regenerates the tracepoint handlers in C, ignoring diffs.
+func GenerateTracepointsCForce() error {
+ fmt.Println("Generating C tracepoints (force)...")
+ return generateTracepointsC(false, false)
+}
+
+// GenerateTracepointsCStdout prints the tracepoint handlers in C to stdout.
+func GenerateTracepointsCStdout() error {
+ fmt.Println("Generating C tracepoints (stdout)...")
+ return generateTracepointsC(true, true)
+}
+
+// GenerateTracepointsGo regenerates the tracepoint list in Go.
+func GenerateTracepointsGo() error {
+ fmt.Println("Generating Go tracepoints list...")
+ input, err := os.ReadFile(tracepointsCPath)
+ if err != nil {
+ return fmt.Errorf("read %s: %w", tracepointsCPath, err)
+ }
+ output, err := generate.ExtractTracepoints(strings.NewReader(string(input)))
+ if err != nil {
+ return err
+ }
+ formatted, err := format.Source([]byte(output))
+ if err != nil {
+ return fmt.Errorf("format tracepoints go: %w", err)
+ }
+ if err := os.WriteFile(tracepointsGoPath, formatted, 0o644); err != nil {
+ return fmt.Errorf("write %s: %w", tracepointsGoPath, err)
+ }
+ return nil
+}
+
+// GenerateTypesGo regenerates the Go types and constants.
+func GenerateTypesGo() error {
+ fmt.Println("Generating Go types...")
+ input, err := readTypesInput()
+ if err != nil {
+ return err
+ }
+ structs, constants, err := generate.ParseCTypesInput(strings.NewReader(input))
+ if err != nil {
+ return err
+ }
+ output := generate.GenerateTypesGo(structs, constants)
+ output = generate.AddTypesImports(output)
+ formatted, err := format.Source([]byte(output))
+ if err != nil {
+ return fmt.Errorf("format types go: %w", err)
+ }
+ if err := os.WriteFile(typesGoPath, formatted, 0o644); err != nil {
+ return fmt.Errorf("write %s: %w", typesGoPath, err)
+ }
+ return nil
}
// Install copies the binary into GOPATH/bin.
@@ -50,3 +185,284 @@ func Install() error {
dest := filepath.Join(binDir, binaryName)
return sh.RunV("cp", "-v", binaryName, dest)
}
+
+// Clean removes build artifacts.
+func Clean() error {
+ if err := removeFilesByName(binaryName); err != nil {
+ return err
+ }
+ if err := removeFilesByPath(bpfOutputPath); err != nil {
+ return err
+ }
+ if err := cleanBPFArtifacts(); err != nil {
+ return err
+ }
+ return nil
+}
+
+// Mrproper removes build artifacts and generated output files.
+func Mrproper() error {
+ mg.SerialDeps(Clean)
+ patterns := []string{"*.zst", "*.collapsed", "*.svg", "*profile", "*.pdf", "*.tmp", "palete.map"}
+ for _, pattern := range patterns {
+ if err := removeFilesByGlob(pattern); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// World runs clean, generate, test, and build targets.
+func World() error {
+ fmt.Println("World: cleaning...")
+ if err := Clean(); err != nil {
+ return err
+ }
+ fmt.Println("World: generating...")
+ if err := Generate(); err != nil {
+ return err
+ }
+ fmt.Println("World: running tests...")
+ if err := Test(); err != nil {
+ return err
+ }
+ fmt.Println("World: building... (BPF + Go)")
+ if err := All(); err != nil {
+ return err
+ }
+ fmt.Println("World: done.")
+ return nil
+}
+
+// Prof generates CPU and memory profiling PDFs.
+func Prof() error {
+ if err := runShellCommand("go tool pprof -pdf ./ior ior.cpuprofile > cpuprofile.pdf"); err != nil {
+ return err
+ }
+ if err := runShellCommand("go tool pprof -pdf ./ior ior.memprofile > memprofile.pdf"); err != nil {
+ return err
+ }
+ return nil
+}
+
+func buildBPFObject() error {
+ libbpfgo := libbpfgoPath()
+ includeDir := filepath.Join(libbpfgo, "output")
+ return sh.RunWithV(bpfEnv(), "clang", "-g", "-O2", "-Wall", "-fpie", "-target", "bpf",
+ "-D__TARGET_ARCH_amd64", "-I"+includeDir, "-c", bpfSourcePath, "-o", bpfObjectPath)
+}
+
+func bpfEnv() map[string]string {
+ return map[string]string{"CC": "clang"}
+}
+
+func cleanBPFArtifacts() error {
+ for _, pattern := range []string{"internal/c/*.o", VMLINUXPath} {
+ if err := removeFilesByGlob(pattern); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func ensureVMLINUX() error {
+ if _, err := os.Stat(VMLINUXPath); err == nil {
+ return nil
+ } else if !errors.Is(err, os.ErrNotExist) {
+ return fmt.Errorf("stat %s: %w", VMLINUXPath, err)
+ }
+
+ output, err := sudoOutput("bpftool", "btf", "dump", "file", "/sys/kernel/btf/vmlinux", "format", "c")
+ if err != nil {
+ return err
+ }
+ if err := os.WriteFile(VMLINUXPath, []byte(output), 0o644); err != nil {
+ return fmt.Errorf("write %s: %w", VMLINUXPath, err)
+ }
+ return nil
+}
+
+func generateTracepointsC(strict bool, toStdout bool) error {
+ fmt.Println("Reading syscall format files...")
+ formats, err := readSyscallFormats()
+ if err != nil {
+ return err
+ }
+ fmt.Println("Parsing syscall formats...")
+
+ parsed, err := generate.ParseFormats(strings.NewReader(formats))
+ if err != nil {
+ return err
+ }
+ output := generate.GenerateTracepointsC(parsed)
+ fmt.Println("Writing generated C tracepoints...")
+
+ if toStdout {
+ fmt.Print(output)
+ return nil
+ }
+
+ if err := os.WriteFile(tracepointsCPath, []byte(output), 0o644); err != nil {
+ return fmt.Errorf("write %s: %w", tracepointsCPath, err)
+ }
+ return writeTracepointsResult(output, strict)
+}
+
+func goEnv() map[string]string {
+ libbpfgo := libbpfgoPath()
+ cgoCflags := fmt.Sprintf("-I%s -I%s", filepath.Join(libbpfgo, "output"), filepath.Join(libbpfgo, "selftest", "common"))
+ cgoLdflags := fmt.Sprintf("-lelf -lzstd %s", filepath.Join(libbpfgo, "output", "libbpf", "libbpf.a"))
+ return map[string]string{
+ "CGO_CFLAGS": cgoCflags,
+ "CGO_LDFLAGS": cgoLdflags,
+ "GOARCH": "amd64",
+ "GOOS": "linux",
+ "LIBBPFGO": libbpfgo,
+ }
+}
+
+func libbpfgoPath() string {
+ if libbpfgo := os.Getenv("LIBBPFGO"); libbpfgo != "" {
+ return libbpfgo
+ }
+ return filepath.Clean(filepath.Join(repoRoot(), defaultLibbpfgoPath))
+}
+
+func readSyscallFormats() (string, error) {
+ fmt.Println("Reading syscall format files with one sudo call...")
+ output, err := sudoOutput("sh", "-c", "LC_ALL=C find /sys/kernel/tracing/events/syscalls -maxdepth 2 -mindepth 2 -name format | sort | xargs cat")
+ if err != nil {
+ return "", err
+ }
+ if output == "" {
+ return "", fmt.Errorf("no syscall format files found")
+ }
+ return output, nil
+}
+
+func readTypesInput() (string, error) {
+ parts := []string{typesHeaderPath, tracepointsCPath}
+ var b strings.Builder
+ for _, p := range parts {
+ data, err := os.ReadFile(p)
+ if err != nil {
+ return "", fmt.Errorf("read %s: %w", p, err)
+ }
+ b.Write(data)
+ if len(data) > 0 && data[len(data)-1] != '\n' {
+ b.WriteString("\n")
+ }
+ }
+ return b.String(), nil
+}
+
+func removeFilesByGlob(pattern string) error {
+ matches, err := filepath.Glob(pattern)
+ if err != nil {
+ return fmt.Errorf("glob %s: %w", pattern, err)
+ }
+ for _, match := range matches {
+ if err := removeFilesByPath(match); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func removeFilesByName(name string) error {
+ return filepath.WalkDir(".", func(path string, d os.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+ if d.IsDir() {
+ return nil
+ }
+ if d.Name() == name {
+ return removeFilesByPath(path)
+ }
+ return nil
+ })
+}
+
+func removeFilesByPath(path string) error {
+ if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
+ return fmt.Errorf("remove %s: %w", path, err)
+ }
+ return nil
+}
+
+func repoRoot() string {
+ root, err := os.Getwd()
+ if err != nil {
+ return "."
+ }
+ return root
+}
+
+func runShellCommand(command string) error {
+ return sh.RunV("bash", "-c", command)
+}
+
+func sudoOutput(cmd string, args ...string) (string, error) {
+ if os.Geteuid() == 0 {
+ return sh.Output(cmd, args...)
+ }
+ return sh.Output("sudo", append([]string{cmd}, args...)...)
+}
+
+func writeTracepointsResult(output string, strict bool) error {
+ result := extractTracepointReasons(output)
+ if err := os.WriteFile(tracepointsResultNew, []byte(result), 0o644); err != nil {
+ return fmt.Errorf("write %s: %w", tracepointsResultNew, err)
+ }
+ if _, err := os.Stat(tracepointsResult); errors.Is(err, os.ErrNotExist) {
+ return sh.RunV("cp", tracepointsResultNew, tracepointsResult)
+ } else if err != nil {
+ return fmt.Errorf("stat %s: %w", tracepointsResult, err)
+ }
+ if err := sh.RunV("diff", "-u", tracepointsResult, tracepointsResultNew); err != nil {
+ if strict {
+ return err
+ }
+ }
+ return sh.RunV("cp", tracepointsResultNew, tracepointsResult)
+}
+
+func extractTracepointReasons(output string) string {
+ var reasons []string
+ reader := bufio.NewReader(strings.NewReader(output))
+ for {
+ line, err := reader.ReadString('\n')
+ if line != "" {
+ line = strings.TrimRight(line, "\n")
+ if strings.HasPrefix(line, "/// ") {
+ reasons = append(reasons, strings.TrimPrefix(line, "/// "))
+ }
+ }
+ if err != nil {
+ if errors.Is(err, io.EOF) {
+ break
+ }
+ return ""
+ }
+ }
+ if len(reasons) == 0 {
+ return ""
+ }
+ sorted, err := sortLinesWithLocale(reasons)
+ if err != nil {
+ return strings.Join(reasons, "\n") + "\n"
+ }
+ return sorted
+}
+
+func sortLinesWithLocale(lines []string) (string, error) {
+ cmd := exec.Command("sort")
+ cmd.Env = append(os.Environ(), "LC_ALL=C")
+ cmd.Stdin = strings.NewReader(strings.Join(lines, "\n") + "\n")
+ output, err := cmd.Output()
+ if err != nil {
+ return "", err
+ }
+ return string(output), nil
+}
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 7d65af7..0000000
--- a/Makefile
+++ /dev/null
@@ -1,68 +0,0 @@
-export LIBBPFGO = $(CURDIR)/../libbpfgo
-export CC = clang
-export GOOS = linux
-export GOARCH = amd64
-export CGO_CFLAGS = -I$(LIBBPFGO)/output -I$(LIBBPFGO)/selftest/common
-export CGO_LDFLAGS = -lelf -lzstd $(LIBBPFGO)/output/libbpf/libbpf.a
-export GO ?= go
-export TEST_NAME ?= TestEventloop
-
-all: bpfbuild gobuild
-
-.PHONY: bpfbuild
-bpfbuild:
- make -C ./internal/c redo
- cp -v ./internal/c/ior.bpf.o .
-
-gen: generate
-generate: generate
-
-.PHONY: generate
-generate:
- make -C ./internal/c generate
- make -C ./internal/tracepoints generate
- make -C ./internal/types generate
-
-.PHONY: gobuild
-gobuild:
- $(GO) build -tags netgo -ldflags '-w -extldflags "-static"' -o ior ./cmd/ior/main.go
-gobuild_race:
- $(GO) build -tags netgo -ldflags '-w -extldflags "-static"' -race -o ior ./cmd/ior/main.go
-
-.PHONY: clean
-clean:
- find . -type f -name ior -delete
- if [ -e ior.bpf.o ]; then rm ior.bpf.o; fi
- make -C ./internal/c clean
-
-.PHONY: mrproper
-mrproper: clean
- find . -type f -name \*.zst -delete
- find . -type f -name \*.collapsed -delete
- find . -type f -name \*.svg -delete
- find . -type f -name \*profile -delete
- find . -type f -name \*.pdf -delete
- find . -type f -name \*.tmp -delete
- find . -type f -name palete.map -delete
-
-.PHONY: world
-world: clean generate test all
-
-.PHONY: prof
-prof:
- $(GO) tool pprof -pdf ./ior ior.cpuprofile > cpuprofile.pdf && evince cpuprofile.pdf &
- $(GO) tool pprof -pdf ./ior ior.memprofile > memprofile.pdf && evince memprofile.pdf &
-
-.PHONY: test
-test:
- $(GO) clean -testcache
- $(GO) test ./... -v -failfast
-
-.PHONY: test_with_name
-test_with_name:
- $(GO) clean -testcache
- $(GO) test ./... -run ^$(TEST_NAME)$$ -v -failfast
-
-.PHONY: bench
-bench:
- $(GO) test ./... -v -bench=. -run xxx
diff --git a/go.mod b/go.mod
index 7f1eb6a..a89a147 100644
--- a/go.mod
+++ b/go.mod
@@ -6,3 +6,5 @@ require (
github.com/DataDog/zstd v1.5.7
github.com/aquasecurity/libbpfgo v0.6.0-libbpf-1.3.0.20240111220235-90dbffffbdab
)
+
+require github.com/magefile/mage v1.15.0 // indirect
diff --git a/go.sum b/go.sum
index 35c80fa..03e3d7d 100644
--- a/go.sum
+++ b/go.sum
@@ -4,6 +4,8 @@ github.com/aquasecurity/libbpfgo v0.6.0-libbpf-1.3.0.20240111220235-90dbffffbdab
github.com/aquasecurity/libbpfgo v0.6.0-libbpf-1.3.0.20240111220235-90dbffffbdab/go.mod h1:0rEApF1YBHGuZ4C8OYI9q5oDBVpgqtRqYATePl9mCDk=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
+github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
diff --git a/internal/c/Makefile b/internal/c/Makefile
deleted file mode 100644
index 03c1f9f..0000000
--- a/internal/c/Makefile
+++ /dev/null
@@ -1,50 +0,0 @@
-export LIBBPFGO = $(CURDIR)/../../../libbpfgo
-export CC = clang
-
-SOURCES := $(wildcard *.bpf.c)
-TARGETS := $(SOURCES:.bpf.c=.bpf.o)
-
-all: $(TARGETS)
-
-redo: clean all
-
-%.bpf.o: %.bpf.c vmlinux.h
- $(CC) -g -O2 -Wall -fpie -target bpf -D__TARGET_ARCH_amd64 \
- -I$(LIBBPFGO)/output -c $< -o $@
-
-vmlinux.h:
- bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
-
-.PHONY: clean
-clean:
- find . -name \*.o -delete
- find . -name vmlinux.h -delete
-
-.PHONY: generate
-generate: generate_tracepoints
-
-.PHONY: generate_tracepoints
-generate_tracepoints:
- sudo sh -c 'sudo find /sys/kernel/tracing/events/syscalls -maxdepth 2 -mindepth 2 -name format' \
- | sort -t_ -k3 | sudo xargs cat \
- | go run ../../cmd/generate tracepoints-c > ./generated_tracepoints.c
- grep '^/// ' ./generated_tracepoints.c | sort | sed 's|/// ||' > ./generated_tracepoints_result.txt.new
- diff -u ./generated_tracepoints_result.txt ./generated_tracepoints_result.txt.new
- cp ./generated_tracepoints_result.txt.new ./generated_tracepoints_result.txt
-
-# TODO: Document what to do, when a syscall is missing. E.g. we also need to add the new syscall maybe
-# to the classifier in cmd/generate and internal/generate!
-.PHONY: generate_tracepoints_force
-generate_tracepoints_force:
- sudo sh -c 'sudo find /sys/kernel/tracing/events/syscalls -maxdepth 2 -mindepth 2 -name format' \
- | sort -t_ -k3 | sudo xargs cat \
- | go run ../../cmd/generate tracepoints-c > ./generated_tracepoints.c
- grep '^/// ' ./generated_tracepoints.c | sort | sed 's|/// ||' > ./generated_tracepoints_result.txt.new
- sh -c 'diff -u ./generated_tracepoints_result.txt ./generated_tracepoints_result.txt.new; exit 0'
- cp ./generated_tracepoints_result.txt.new ./generated_tracepoints_result.txt
-
-.PHONY: generate_tracepoints_stdout
-generate_tracepoints_stdout:
- sudo sh -c 'sudo find /sys/kernel/tracing/events/syscalls -maxdepth 2 -mindepth 2 -name format' \
- | sort -t_ -k3 | sudo xargs cat \
- | go run ../../cmd/generate tracepoints-c
diff --git a/internal/c/generated_tracepoints_result.txt b/internal/c/generated_tracepoints_result.txt
index 2176d6b..fb3867c 100644
--- a/internal/c/generated_tracepoints_result.txt
+++ b/internal/c/generated_tracepoints_result.txt
@@ -1,5 +1,5 @@
-Ignoring sys_enter_accept4 sys_exit_accept4 as possibly not file I/O related
Ignoring sys_enter_accept sys_exit_accept as possibly not file I/O related
+Ignoring sys_enter_accept4 sys_exit_accept4 as possibly not file I/O related
Ignoring sys_enter_acct sys_exit_acct as possibly not file I/O related
Ignoring sys_enter_add_key sys_exit_add_key as possibly not file I/O related
Ignoring sys_enter_adjtimex sys_exit_adjtimex as possibly not file I/O related
@@ -15,32 +15,34 @@ Ignoring sys_enter_clock_getres sys_exit_clock_getres as possibly not file I/O r
Ignoring sys_enter_clock_gettime sys_exit_clock_gettime as possibly not file I/O related
Ignoring sys_enter_clock_nanosleep sys_exit_clock_nanosleep as possibly not file I/O related
Ignoring sys_enter_clock_settime sys_exit_clock_settime as possibly not file I/O related
-Ignoring sys_enter_clone3 sys_exit_clone3 as possibly not file I/O related
Ignoring sys_enter_clone sys_exit_clone as possibly not file I/O related
+Ignoring sys_enter_clone3 sys_exit_clone3 as possibly not file I/O related
Ignoring sys_enter_connect sys_exit_connect as possibly not file I/O related
Ignoring sys_enter_copy_file_range sys_exit_copy_file_range as possibly not file I/O related
Ignoring sys_enter_delete_module sys_exit_delete_module as possibly not file I/O related
-Ignoring sys_enter_epoll_create1 sys_exit_epoll_create1 as possibly not file I/O related
Ignoring sys_enter_epoll_create sys_exit_epoll_create as possibly not file I/O related
+Ignoring sys_enter_epoll_create1 sys_exit_epoll_create1 as possibly not file I/O related
Ignoring sys_enter_epoll_ctl sys_exit_epoll_ctl as possibly not file I/O related
-Ignoring sys_enter_epoll_pwait2 sys_exit_epoll_pwait2 as possibly not file I/O related
Ignoring sys_enter_epoll_pwait sys_exit_epoll_pwait as possibly not file I/O related
+Ignoring sys_enter_epoll_pwait2 sys_exit_epoll_pwait2 as possibly not file I/O related
Ignoring sys_enter_epoll_wait sys_exit_epoll_wait as possibly not file I/O related
-Ignoring sys_enter_eventfd2 sys_exit_eventfd2 as possibly not file I/O related
Ignoring sys_enter_eventfd sys_exit_eventfd as possibly not file I/O related
-Ignoring sys_enter_execveat sys_exit_execveat as possibly not file I/O related
+Ignoring sys_enter_eventfd2 sys_exit_eventfd2 as possibly not file I/O related
Ignoring sys_enter_execve sys_exit_execve as possibly not file I/O related
-Ignoring sys_enter_exit_group sys_exit_exit_group as possibly not file I/O related
+Ignoring sys_enter_execveat sys_exit_execveat as possibly not file I/O related
Ignoring sys_enter_exit sys_exit_exit as possibly not file I/O related
+Ignoring sys_enter_exit_group sys_exit_exit_group as possibly not file I/O related
Ignoring sys_enter_fanotify_init sys_exit_fanotify_init as possibly not file I/O related
Ignoring sys_enter_fork sys_exit_fork as possibly not file I/O related
Ignoring sys_enter_fsmount sys_exit_fsmount as possibly not file I/O related
Ignoring sys_enter_fsopen sys_exit_fsopen as possibly not file I/O related
-Ignoring sys_enter_futex_requeue sys_exit_futex_requeue as possibly not file I/O related
Ignoring sys_enter_futex sys_exit_futex as possibly not file I/O related
+Ignoring sys_enter_futex_requeue sys_exit_futex_requeue as possibly not file I/O related
Ignoring sys_enter_futex_wait sys_exit_futex_wait as possibly not file I/O related
Ignoring sys_enter_futex_waitv sys_exit_futex_waitv as possibly not file I/O related
Ignoring sys_enter_futex_wake sys_exit_futex_wake as possibly not file I/O related
+Ignoring sys_enter_get_mempolicy sys_exit_get_mempolicy as possibly not file I/O related
+Ignoring sys_enter_get_robust_list sys_exit_get_robust_list as possibly not file I/O related
Ignoring sys_enter_getcpu sys_exit_getcpu as possibly not file I/O related
Ignoring sys_enter_getcwd sys_exit_getcwd as possibly not file I/O related
Ignoring sys_enter_getegid sys_exit_getegid as possibly not file I/O related
@@ -48,7 +50,6 @@ Ignoring sys_enter_geteuid sys_exit_geteuid as possibly not file I/O related
Ignoring sys_enter_getgid sys_exit_getgid as possibly not file I/O related
Ignoring sys_enter_getgroups sys_exit_getgroups as possibly not file I/O related
Ignoring sys_enter_getitimer sys_exit_getitimer as possibly not file I/O related
-Ignoring sys_enter_get_mempolicy sys_exit_get_mempolicy as possibly not file I/O related
Ignoring sys_enter_getpeername sys_exit_getpeername as possibly not file I/O related
Ignoring sys_enter_getpgid sys_exit_getpgid as possibly not file I/O related
Ignoring sys_enter_getpgrp sys_exit_getpgrp as possibly not file I/O related
@@ -59,7 +60,6 @@ Ignoring sys_enter_getrandom sys_exit_getrandom as possibly not file I/O related
Ignoring sys_enter_getresgid sys_exit_getresgid as possibly not file I/O related
Ignoring sys_enter_getresuid sys_exit_getresuid as possibly not file I/O related
Ignoring sys_enter_getrlimit sys_exit_getrlimit as possibly not file I/O related
-Ignoring sys_enter_get_robust_list sys_exit_get_robust_list as possibly not file I/O related
Ignoring sys_enter_getrusage sys_exit_getrusage as possibly not file I/O related
Ignoring sys_enter_getsid sys_exit_getsid as possibly not file I/O related
Ignoring sys_enter_getsockname sys_exit_getsockname as possibly not file I/O related
@@ -69,8 +69,8 @@ Ignoring sys_enter_gettimeofday sys_exit_gettimeofday as possibly not file I/O r
Ignoring sys_enter_getuid sys_exit_getuid as possibly not file I/O related
Ignoring sys_enter_init_module sys_exit_init_module as possibly not file I/O related
Ignoring sys_enter_inotify_add_watch sys_exit_inotify_add_watch as possibly not file I/O related
-Ignoring sys_enter_inotify_init1 sys_exit_inotify_init1 as possibly not file I/O related
Ignoring sys_enter_inotify_init sys_exit_inotify_init as possibly not file I/O related
+Ignoring sys_enter_inotify_init1 sys_exit_inotify_init1 as possibly not file I/O related
Ignoring sys_enter_inotify_rm_watch sys_exit_inotify_rm_watch as possibly not file I/O related
Ignoring sys_enter_ioperm sys_exit_ioperm as possibly not file I/O related
Ignoring sys_enter_iopl sys_exit_iopl as possibly not file I/O related
@@ -97,11 +97,11 @@ Ignoring sys_enter_memfd_create sys_exit_memfd_create as possibly not file I/O r
Ignoring sys_enter_memfd_secret sys_exit_memfd_secret as possibly not file I/O related
Ignoring sys_enter_migrate_pages sys_exit_migrate_pages as possibly not file I/O related
Ignoring sys_enter_mincore sys_exit_mincore as possibly not file I/O related
-Ignoring sys_enter_mknodat sys_exit_mknodat as possibly not file I/O related
Ignoring sys_enter_mknod sys_exit_mknod as possibly not file I/O related
+Ignoring sys_enter_mknodat sys_exit_mknodat as possibly not file I/O related
+Ignoring sys_enter_mlock sys_exit_mlock as possibly not file I/O related
Ignoring sys_enter_mlock2 sys_exit_mlock2 as possibly not file I/O related
Ignoring sys_enter_mlockall sys_exit_mlockall as possibly not file I/O related
-Ignoring sys_enter_mlock sys_exit_mlock as possibly not file I/O related
Ignoring sys_enter_modify_ldt sys_exit_modify_ldt as possibly not file I/O related
Ignoring sys_enter_mount sys_exit_mount as possibly not file I/O related
Ignoring sys_enter_move_mount sys_exit_move_mount as possibly not file I/O related
@@ -120,8 +120,8 @@ Ignoring sys_enter_msgget sys_exit_msgget as possibly not file I/O related
Ignoring sys_enter_msgrcv sys_exit_msgrcv as possibly not file I/O related
Ignoring sys_enter_msgsnd sys_exit_msgsnd as possibly not file I/O related
Ignoring sys_enter_msync sys_exit_msync as possibly not file I/O related
-Ignoring sys_enter_munlockall sys_exit_munlockall as possibly not file I/O related
Ignoring sys_enter_munlock sys_exit_munlock as possibly not file I/O related
+Ignoring sys_enter_munlockall sys_exit_munlockall as possibly not file I/O related
Ignoring sys_enter_munmap sys_exit_munmap as possibly not file I/O related
Ignoring sys_enter_name_to_handle_at sys_exit_name_to_handle_at as possibly not file I/O related
Ignoring sys_enter_nanosleep sys_exit_nanosleep as possibly not file I/O related
@@ -132,8 +132,8 @@ Ignoring sys_enter_personality sys_exit_personality as possibly not file I/O rel
Ignoring sys_enter_pidfd_getfd sys_exit_pidfd_getfd as possibly not file I/O related
Ignoring sys_enter_pidfd_open sys_exit_pidfd_open as possibly not file I/O related
Ignoring sys_enter_pidfd_send_signal sys_exit_pidfd_send_signal as possibly not file I/O related
-Ignoring sys_enter_pipe2 sys_exit_pipe2 as possibly not file I/O related
Ignoring sys_enter_pipe sys_exit_pipe as possibly not file I/O related
+Ignoring sys_enter_pipe2 sys_exit_pipe2 as possibly not file I/O related
Ignoring sys_enter_pivot_root sys_exit_pivot_root as possibly not file I/O related
Ignoring sys_enter_pkey_alloc sys_exit_pkey_alloc as possibly not file I/O related
Ignoring sys_enter_pkey_free sys_exit_pkey_free as possibly not file I/O related
@@ -165,11 +165,11 @@ Ignoring sys_enter_rt_sigreturn sys_exit_rt_sigreturn as possibly not file I/O r
Ignoring sys_enter_rt_sigsuspend sys_exit_rt_sigsuspend as possibly not file I/O related
Ignoring sys_enter_rt_sigtimedwait sys_exit_rt_sigtimedwait as possibly not file I/O related
Ignoring sys_enter_rt_tgsigqueueinfo sys_exit_rt_tgsigqueueinfo as possibly not file I/O related
+Ignoring sys_enter_sched_get_priority_max sys_exit_sched_get_priority_max as possibly not file I/O related
+Ignoring sys_enter_sched_get_priority_min sys_exit_sched_get_priority_min as possibly not file I/O related
Ignoring sys_enter_sched_getaffinity sys_exit_sched_getaffinity as possibly not file I/O related
Ignoring sys_enter_sched_getattr sys_exit_sched_getattr as possibly not file I/O related
Ignoring sys_enter_sched_getparam sys_exit_sched_getparam as possibly not file I/O related
-Ignoring sys_enter_sched_get_priority_max sys_exit_sched_get_priority_max as possibly not file I/O related
-Ignoring sys_enter_sched_get_priority_min sys_exit_sched_get_priority_min as possibly not file I/O related
Ignoring sys_enter_sched_getscheduler sys_exit_sched_getscheduler as possibly not file I/O related
Ignoring sys_enter_sched_rr_get_interval sys_exit_sched_rr_get_interval as possibly not file I/O related
Ignoring sys_enter_sched_setaffinity sys_exit_sched_setaffinity as possibly not file I/O related
@@ -187,6 +187,10 @@ Ignoring sys_enter_sendfile64 sys_exit_sendfile64 as possibly not file I/O relat
Ignoring sys_enter_sendmmsg sys_exit_sendmmsg as possibly not file I/O related
Ignoring sys_enter_sendmsg sys_exit_sendmsg as possibly not file I/O related
Ignoring sys_enter_sendto sys_exit_sendto as possibly not file I/O related
+Ignoring sys_enter_set_mempolicy sys_exit_set_mempolicy as possibly not file I/O related
+Ignoring sys_enter_set_mempolicy_home_node sys_exit_set_mempolicy_home_node as possibly not file I/O related
+Ignoring sys_enter_set_robust_list sys_exit_set_robust_list as possibly not file I/O related
+Ignoring sys_enter_set_tid_address sys_exit_set_tid_address as possibly not file I/O related
Ignoring sys_enter_setdomainname sys_exit_setdomainname as possibly not file I/O related
Ignoring sys_enter_setfsgid sys_exit_setfsgid as possibly not file I/O related
Ignoring sys_enter_setfsuid sys_exit_setfsuid as possibly not file I/O related
@@ -194,8 +198,6 @@ Ignoring sys_enter_setgid sys_exit_setgid as possibly not file I/O related
Ignoring sys_enter_setgroups sys_exit_setgroups as possibly not file I/O related
Ignoring sys_enter_sethostname sys_exit_sethostname as possibly not file I/O related
Ignoring sys_enter_setitimer sys_exit_setitimer as possibly not file I/O related
-Ignoring sys_enter_set_mempolicy_home_node sys_exit_set_mempolicy_home_node as possibly not file I/O related
-Ignoring sys_enter_set_mempolicy sys_exit_set_mempolicy as possibly not file I/O related
Ignoring sys_enter_setns sys_exit_setns as possibly not file I/O related
Ignoring sys_enter_setpgid sys_exit_setpgid as possibly not file I/O related
Ignoring sys_enter_setpriority sys_exit_setpriority as possibly not file I/O related
@@ -204,10 +206,8 @@ Ignoring sys_enter_setresgid sys_exit_setresgid as possibly not file I/O related
Ignoring sys_enter_setresuid sys_exit_setresuid as possibly not file I/O related
Ignoring sys_enter_setreuid sys_exit_setreuid as possibly not file I/O related
Ignoring sys_enter_setrlimit sys_exit_setrlimit as possibly not file I/O related
-Ignoring sys_enter_set_robust_list sys_exit_set_robust_list as possibly not file I/O related
Ignoring sys_enter_setsid sys_exit_setsid as possibly not file I/O related
Ignoring sys_enter_setsockopt sys_exit_setsockopt as possibly not file I/O related
-Ignoring sys_enter_set_tid_address sys_exit_set_tid_address as possibly not file I/O related
Ignoring sys_enter_settimeofday sys_exit_settimeofday as possibly not file I/O related
Ignoring sys_enter_setuid sys_exit_setuid as possibly not file I/O related
Ignoring sys_enter_shmat sys_exit_shmat as possibly not file I/O related
@@ -216,10 +216,10 @@ Ignoring sys_enter_shmdt sys_exit_shmdt as possibly not file I/O related
Ignoring sys_enter_shmget sys_exit_shmget as possibly not file I/O related
Ignoring sys_enter_shutdown sys_exit_shutdown as possibly not file I/O related
Ignoring sys_enter_sigaltstack sys_exit_sigaltstack as possibly not file I/O related
-Ignoring sys_enter_signalfd4 sys_exit_signalfd4 as possibly not file I/O related
Ignoring sys_enter_signalfd sys_exit_signalfd as possibly not file I/O related
-Ignoring sys_enter_socketpair sys_exit_socketpair as possibly not file I/O related
+Ignoring sys_enter_signalfd4 sys_exit_signalfd4 as possibly not file I/O related
Ignoring sys_enter_socket sys_exit_socket as possibly not file I/O related
+Ignoring sys_enter_socketpair sys_exit_socketpair as possibly not file I/O related
Ignoring sys_enter_splice sys_exit_splice as possibly not file I/O related
Ignoring sys_enter_statmount sys_exit_statmount as possibly not file I/O related
Ignoring sys_enter_swapoff sys_exit_swapoff as possibly not file I/O related
@@ -228,16 +228,16 @@ Ignoring sys_enter_sysfs sys_exit_sysfs as possibly not file I/O related
Ignoring sys_enter_sysinfo sys_exit_sysinfo as possibly not file I/O related
Ignoring sys_enter_tee sys_exit_tee as possibly not file I/O related
Ignoring sys_enter_tgkill sys_exit_tgkill as possibly not file I/O related
+Ignoring sys_enter_time sys_exit_time as possibly not file I/O related
Ignoring sys_enter_timer_create sys_exit_timer_create as possibly not file I/O related
Ignoring sys_enter_timer_delete sys_exit_timer_delete as possibly not file I/O related
-Ignoring sys_enter_timerfd_create sys_exit_timerfd_create as possibly not file I/O related
-Ignoring sys_enter_timerfd_gettime sys_exit_timerfd_gettime as possibly not file I/O related
-Ignoring sys_enter_timerfd_settime sys_exit_timerfd_settime as possibly not file I/O related
Ignoring sys_enter_timer_getoverrun sys_exit_timer_getoverrun as possibly not file I/O related
Ignoring sys_enter_timer_gettime sys_exit_timer_gettime as possibly not file I/O related
Ignoring sys_enter_timer_settime sys_exit_timer_settime as possibly not file I/O related
+Ignoring sys_enter_timerfd_create sys_exit_timerfd_create as possibly not file I/O related
+Ignoring sys_enter_timerfd_gettime sys_exit_timerfd_gettime as possibly not file I/O related
+Ignoring sys_enter_timerfd_settime sys_exit_timerfd_settime as possibly not file I/O related
Ignoring sys_enter_times sys_exit_times as possibly not file I/O related
-Ignoring sys_enter_time sys_exit_time as possibly not file I/O related
Ignoring sys_enter_tkill sys_exit_tkill as possibly not file I/O related
Ignoring sys_enter_umask sys_exit_umask as possibly not file I/O related
Ignoring sys_enter_umount sys_exit_umount as possibly not file I/O related
@@ -246,8 +246,8 @@ Ignoring sys_enter_uprobe sys_exit_uprobe as possibly not file I/O related
Ignoring sys_enter_uretprobe sys_exit_uretprobe as possibly not file I/O related
Ignoring sys_enter_userfaultfd sys_exit_userfaultfd as possibly not file I/O related
Ignoring sys_enter_ustat sys_exit_ustat as possibly not file I/O related
-Ignoring sys_enter_utimes sys_exit_utimes as possibly not file I/O related
Ignoring sys_enter_utime sys_exit_utime as possibly not file I/O related
+Ignoring sys_enter_utimes sys_exit_utimes as possibly not file I/O related
Ignoring sys_enter_vfork sys_exit_vfork as possibly not file I/O related
Ignoring sys_enter_vhangup sys_exit_vhangup as possibly not file I/O related
Ignoring sys_enter_wait4 sys_exit_wait4 as possibly not file I/O related
@@ -261,20 +261,20 @@ sys_enter_chroot is a struct path_event
sys_enter_close is a struct fd_event
sys_enter_close_range is a struct fd_event
sys_enter_creat is a struct path_event
+sys_enter_dup is a struct fd_event
sys_enter_dup2 is a struct fd_event
sys_enter_dup3 is a struct dup3_event
-sys_enter_dup is a struct fd_event
-sys_enter_faccessat2 is a struct path_event
sys_enter_faccessat is a struct path_event
+sys_enter_faccessat2 is a struct path_event
sys_enter_fadvise64 is a struct fd_event
sys_enter_fallocate is a struct fd_event
sys_enter_fanotify_mark is a struct path_event
sys_enter_fchdir is a struct fd_event
-sys_enter_fchmodat2 is a struct path_event
-sys_enter_fchmodat is a struct path_event
sys_enter_fchmod is a struct fd_event
-sys_enter_fchownat is a struct path_event
+sys_enter_fchmodat is a struct path_event
+sys_enter_fchmodat2 is a struct path_event
sys_enter_fchown is a struct fd_event
+sys_enter_fchownat is a struct path_event
sys_enter_fcntl is a struct fcntl_event
sys_enter_fdatasync is a struct fd_event
sys_enter_fgetxattr is a struct fd_event
@@ -291,12 +291,11 @@ sys_enter_fstatfs is a struct fd_event
sys_enter_fsync is a struct fd_event
sys_enter_ftruncate is a struct fd_event
sys_enter_futimesat is a struct path_event
-sys_enter_getdents64 is a struct fd_event
sys_enter_getdents is a struct fd_event
-sys_enter_getxattrat is a struct path_event
+sys_enter_getdents64 is a struct fd_event
sys_enter_getxattr is a struct path_event
+sys_enter_getxattrat is a struct path_event
sys_enter_io_cancel is a struct null_event
-sys_enter_ioctl is a struct fd_event
sys_enter_io_destroy is a struct null_event
sys_enter_io_getevents is a struct null_event
sys_enter_io_pgetevents is a struct null_event
@@ -305,61 +304,62 @@ sys_enter_io_submit is a struct null_event
sys_enter_io_uring_enter is a struct null_event
sys_enter_io_uring_register is a struct null_event
sys_enter_io_uring_setup is a struct null_event
+sys_enter_ioctl is a struct fd_event
sys_enter_lchown is a struct path_event
sys_enter_lgetxattr is a struct path_event
-sys_enter_linkat is a struct name_event
sys_enter_link is a struct name_event
-sys_enter_listxattrat is a struct path_event
+sys_enter_linkat is a struct name_event
sys_enter_listxattr is a struct path_event
+sys_enter_listxattrat is a struct path_event
sys_enter_llistxattr is a struct path_event
sys_enter_lremovexattr is a struct path_event
sys_enter_lseek is a struct fd_event
sys_enter_lsetxattr is a struct path_event
-sys_enter_mkdirat is a struct path_event
sys_enter_mkdir is a struct path_event
+sys_enter_mkdirat is a struct path_event
sys_enter_mmap is a struct fd_event
sys_enter_mount_setattr is a struct path_event
-sys_enter_newfstatat is a struct path_event
sys_enter_newfstat is a struct fd_event
+sys_enter_newfstatat is a struct path_event
sys_enter_newlstat is a struct path_event
sys_enter_newstat is a struct path_event
-sys_enter_openat2 is a struct open_event
-sys_enter_openat is a struct open_event
-sys_enter_open_by_handle_at is a struct open_by_handle_at_event
sys_enter_open is a struct open_event
-sys_enter_open_tree_attr is a struct open_event
+sys_enter_open_by_handle_at is a struct open_by_handle_at_event
sys_enter_open_tree is a struct open_event
+sys_enter_open_tree_attr is a struct open_event
+sys_enter_openat is a struct open_event
+sys_enter_openat2 is a struct open_event
sys_enter_pread64 is a struct fd_event
-sys_enter_preadv2 is a struct fd_event
sys_enter_preadv is a struct fd_event
+sys_enter_preadv2 is a struct fd_event
sys_enter_pwrite64 is a struct fd_event
-sys_enter_pwritev2 is a struct fd_event
sys_enter_pwritev is a struct fd_event
+sys_enter_pwritev2 is a struct fd_event
sys_enter_quotactl_fd is a struct fd_event
-sys_enter_readahead is a struct fd_event
sys_enter_read is a struct fd_event
-sys_enter_readlinkat is a struct path_event
+sys_enter_readahead is a struct fd_event
sys_enter_readlink is a struct path_event
+sys_enter_readlinkat is a struct path_event
sys_enter_readv is a struct fd_event
-sys_enter_removexattrat is a struct path_event
sys_enter_removexattr is a struct path_event
-sys_enter_renameat2 is a struct name_event
-sys_enter_renameat is a struct name_event
+sys_enter_removexattrat is a struct path_event
sys_enter_rename is a struct name_event
+sys_enter_renameat is a struct name_event
+sys_enter_renameat2 is a struct name_event
sys_enter_rmdir is a struct path_event
-sys_enter_setxattrat is a struct path_event
sys_enter_setxattr is a struct path_event
+sys_enter_setxattrat is a struct path_event
sys_enter_statfs is a struct path_event
sys_enter_statx is a struct path_event
-sys_enter_symlinkat is a struct name_event
sys_enter_symlink is a struct name_event
+sys_enter_symlinkat is a struct name_event
+sys_enter_sync is a struct null_event
sys_enter_sync_file_range is a struct fd_event
sys_enter_syncfs is a struct fd_event
-sys_enter_sync is a struct null_event
sys_enter_syslog is a struct null_event
sys_enter_truncate is a struct path_event
-sys_enter_unlinkat is a struct path_event
sys_enter_unlink is a struct path_event
+sys_enter_unlinkat is a struct path_event
sys_enter_utimensat is a struct path_event
sys_enter_vmsplice is a struct fd_event
sys_enter_write is a struct fd_event
@@ -373,20 +373,20 @@ sys_exit_chroot is a struct ret_event (UNCLASSIFIED)
sys_exit_close is a struct ret_event (UNCLASSIFIED)
sys_exit_close_range is a struct ret_event (UNCLASSIFIED)
sys_exit_creat is a struct ret_event (UNCLASSIFIED)
+sys_exit_dup is a struct ret_event (UNCLASSIFIED)
sys_exit_dup2 is a struct ret_event (UNCLASSIFIED)
sys_exit_dup3 is a struct ret_event (UNCLASSIFIED)
-sys_exit_dup is a struct ret_event (UNCLASSIFIED)
-sys_exit_faccessat2 is a struct ret_event (UNCLASSIFIED)
sys_exit_faccessat is a struct ret_event (UNCLASSIFIED)
+sys_exit_faccessat2 is a struct ret_event (UNCLASSIFIED)
sys_exit_fadvise64 is a struct ret_event (UNCLASSIFIED)
sys_exit_fallocate is a struct ret_event (UNCLASSIFIED)
sys_exit_fanotify_mark is a struct ret_event (UNCLASSIFIED)
sys_exit_fchdir is a struct ret_event (UNCLASSIFIED)
-sys_exit_fchmodat2 is a struct ret_event (UNCLASSIFIED)
-sys_exit_fchmodat is a struct ret_event (UNCLASSIFIED)
sys_exit_fchmod is a struct ret_event (UNCLASSIFIED)
-sys_exit_fchownat is a struct ret_event (UNCLASSIFIED)
+sys_exit_fchmodat is a struct ret_event (UNCLASSIFIED)
+sys_exit_fchmodat2 is a struct ret_event (UNCLASSIFIED)
sys_exit_fchown is a struct ret_event (UNCLASSIFIED)
+sys_exit_fchownat is a struct ret_event (UNCLASSIFIED)
sys_exit_fcntl is a struct ret_event (UNCLASSIFIED)
sys_exit_fdatasync is a struct ret_event (UNCLASSIFIED)
sys_exit_fgetxattr is a struct ret_event (READ_CLASSIFIED)
@@ -403,12 +403,11 @@ sys_exit_fstatfs is a struct ret_event (UNCLASSIFIED)
sys_exit_fsync is a struct ret_event (UNCLASSIFIED)
sys_exit_ftruncate is a struct ret_event (UNCLASSIFIED)
sys_exit_futimesat is a struct ret_event (UNCLASSIFIED)
-sys_exit_getdents64 is a struct ret_event (READ_CLASSIFIED)
sys_exit_getdents is a struct ret_event (READ_CLASSIFIED)
-sys_exit_getxattrat is a struct ret_event (UNCLASSIFIED)
+sys_exit_getdents64 is a struct ret_event (READ_CLASSIFIED)
sys_exit_getxattr is a struct ret_event (READ_CLASSIFIED)
+sys_exit_getxattrat is a struct ret_event (UNCLASSIFIED)
sys_exit_io_cancel is a struct ret_event (UNCLASSIFIED)
-sys_exit_ioctl is a struct ret_event (UNCLASSIFIED)
sys_exit_io_destroy is a struct ret_event (UNCLASSIFIED)
sys_exit_io_getevents is a struct ret_event (UNCLASSIFIED)
sys_exit_io_pgetevents is a struct ret_event (UNCLASSIFIED)
@@ -417,61 +416,62 @@ sys_exit_io_submit is a struct ret_event (UNCLASSIFIED)
sys_exit_io_uring_enter is a struct ret_event (UNCLASSIFIED)
sys_exit_io_uring_register is a struct ret_event (UNCLASSIFIED)
sys_exit_io_uring_setup is a struct ret_event (UNCLASSIFIED)
+sys_exit_ioctl is a struct ret_event (UNCLASSIFIED)
sys_exit_lchown is a struct ret_event (UNCLASSIFIED)
sys_exit_lgetxattr is a struct ret_event (READ_CLASSIFIED)
-sys_exit_linkat is a struct ret_event (UNCLASSIFIED)
sys_exit_link is a struct ret_event (UNCLASSIFIED)
-sys_exit_listxattrat is a struct ret_event (UNCLASSIFIED)
+sys_exit_linkat is a struct ret_event (UNCLASSIFIED)
sys_exit_listxattr is a struct ret_event (READ_CLASSIFIED)
+sys_exit_listxattrat is a struct ret_event (UNCLASSIFIED)
sys_exit_llistxattr is a struct ret_event (READ_CLASSIFIED)
sys_exit_lremovexattr is a struct ret_event (UNCLASSIFIED)
sys_exit_lseek is a struct ret_event (UNCLASSIFIED)
sys_exit_lsetxattr is a struct ret_event (UNCLASSIFIED)
-sys_exit_mkdirat is a struct ret_event (UNCLASSIFIED)
sys_exit_mkdir is a struct ret_event (UNCLASSIFIED)
+sys_exit_mkdirat is a struct ret_event (UNCLASSIFIED)
sys_exit_mmap is a struct ret_event (UNCLASSIFIED)
sys_exit_mount_setattr is a struct ret_event (UNCLASSIFIED)
-sys_exit_newfstatat is a struct ret_event (UNCLASSIFIED)
sys_exit_newfstat is a struct ret_event (UNCLASSIFIED)
+sys_exit_newfstatat is a struct ret_event (UNCLASSIFIED)
sys_exit_newlstat is a struct ret_event (UNCLASSIFIED)
sys_exit_newstat is a struct ret_event (UNCLASSIFIED)
-sys_exit_openat2 is a struct ret_event (UNCLASSIFIED)
-sys_exit_openat is a struct ret_event (UNCLASSIFIED)
-sys_exit_open_by_handle_at is a struct ret_event (UNCLASSIFIED)
sys_exit_open is a struct ret_event (UNCLASSIFIED)
-sys_exit_open_tree_attr is a struct ret_event (UNCLASSIFIED)
+sys_exit_open_by_handle_at is a struct ret_event (UNCLASSIFIED)
sys_exit_open_tree is a struct ret_event (UNCLASSIFIED)
+sys_exit_open_tree_attr is a struct ret_event (UNCLASSIFIED)
+sys_exit_openat is a struct ret_event (UNCLASSIFIED)
+sys_exit_openat2 is a struct ret_event (UNCLASSIFIED)
sys_exit_pread64 is a struct ret_event (READ_CLASSIFIED)
-sys_exit_preadv2 is a struct ret_event (READ_CLASSIFIED)
sys_exit_preadv is a struct ret_event (READ_CLASSIFIED)
+sys_exit_preadv2 is a struct ret_event (READ_CLASSIFIED)
sys_exit_pwrite64 is a struct ret_event (WRITE_CLASSIFIED)
-sys_exit_pwritev2 is a struct ret_event (WRITE_CLASSIFIED)
sys_exit_pwritev is a struct ret_event (WRITE_CLASSIFIED)
+sys_exit_pwritev2 is a struct ret_event (WRITE_CLASSIFIED)
sys_exit_quotactl_fd is a struct ret_event (UNCLASSIFIED)
-sys_exit_readahead is a struct ret_event (UNCLASSIFIED)
sys_exit_read is a struct ret_event (READ_CLASSIFIED)
-sys_exit_readlinkat is a struct ret_event (READ_CLASSIFIED)
+sys_exit_readahead is a struct ret_event (UNCLASSIFIED)
sys_exit_readlink is a struct ret_event (READ_CLASSIFIED)
+sys_exit_readlinkat is a struct ret_event (READ_CLASSIFIED)
sys_exit_readv is a struct ret_event (READ_CLASSIFIED)
-sys_exit_removexattrat is a struct ret_event (UNCLASSIFIED)
sys_exit_removexattr is a struct ret_event (UNCLASSIFIED)
-sys_exit_renameat2 is a struct ret_event (UNCLASSIFIED)
-sys_exit_renameat is a struct ret_event (UNCLASSIFIED)
+sys_exit_removexattrat is a struct ret_event (UNCLASSIFIED)
sys_exit_rename is a struct ret_event (UNCLASSIFIED)
+sys_exit_renameat is a struct ret_event (UNCLASSIFIED)
+sys_exit_renameat2 is a struct ret_event (UNCLASSIFIED)
sys_exit_rmdir is a struct ret_event (UNCLASSIFIED)
-sys_exit_setxattrat is a struct ret_event (UNCLASSIFIED)
sys_exit_setxattr is a struct ret_event (UNCLASSIFIED)
+sys_exit_setxattrat is a struct ret_event (UNCLASSIFIED)
sys_exit_statfs is a struct ret_event (UNCLASSIFIED)
sys_exit_statx is a struct ret_event (UNCLASSIFIED)
-sys_exit_symlinkat is a struct ret_event (UNCLASSIFIED)
sys_exit_symlink is a struct ret_event (UNCLASSIFIED)
+sys_exit_symlinkat is a struct ret_event (UNCLASSIFIED)
+sys_exit_sync is a struct ret_event (UNCLASSIFIED)
sys_exit_sync_file_range is a struct ret_event (UNCLASSIFIED)
sys_exit_syncfs is a struct ret_event (UNCLASSIFIED)
-sys_exit_sync is a struct ret_event (UNCLASSIFIED)
sys_exit_syslog is a struct ret_event (READ_CLASSIFIED)
sys_exit_truncate is a struct ret_event (UNCLASSIFIED)
-sys_exit_unlinkat is a struct ret_event (UNCLASSIFIED)
sys_exit_unlink is a struct ret_event (UNCLASSIFIED)
+sys_exit_unlinkat is a struct ret_event (UNCLASSIFIED)
sys_exit_utimensat is a struct ret_event (UNCLASSIFIED)
sys_exit_vmsplice is a struct ret_event (TRANSFER_CLASSIFIED)
sys_exit_write is a struct ret_event (WRITE_CLASSIFIED)
diff --git a/internal/generate/bpfhandler.go b/internal/generate/bpfhandler.go
new file mode 100644
index 0000000..1ce6d3e
--- /dev/null
+++ b/internal/generate/bpfhandler.go
@@ -0,0 +1,152 @@
+package generate
+
+import (
+ "fmt"
+ "strings"
+)
+
+func generateBPFHandler(tp GeneratedTracepoint) string {
+ f := tp.Format
+ isEnter := strings.Split(f.Name, "_")[1] == "enter"
+
+ ctxStruct := "trace_event_raw_sys_exit"
+ if isEnter {
+ ctxStruct = "trace_event_raw_sys_enter"
+ }
+
+ eventStruct := eventStructName(tp.Classification.Kind)
+ comment := eventStruct
+ if tp.Classification.Kind == KindRet {
+ comment = fmt.Sprintf("%s (%s)", eventStruct, ClassifyRet(f.Name))
+ }
+
+ eventTypeConst := eventTypeConstant(tp.Classification.Kind, isEnter)
+ extra := generateExtra(tp, isEnter)
+
+ return renderHandler(f.Name, ctxStruct, eventStruct, comment, eventTypeConst, extra)
+}
+
+func renderHandler(name, ctxStruct, eventStruct, comment, eventTypeConst, extra 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)
+ fmt.Fprintf(&b, "int handle_%s(struct %s *ctx) {\n", strings.ToLower(name), ctxStruct)
+ b.WriteString(" __u32 pid, tid;\n")
+ b.WriteString(" if (filter(&pid, &tid))\n")
+ b.WriteString(" return 0;\n")
+ b.WriteString("\n")
+ fmt.Fprintf(&b, " struct %s *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct %s), 0);\n", eventStruct, eventStruct)
+ b.WriteString(" if (!ev)\n")
+ b.WriteString(" return 0;\n")
+ b.WriteString("\n")
+ fmt.Fprintf(&b, " ev->event_type = %s;\n", eventTypeConst)
+ fmt.Fprintf(&b, " ev->trace_id = %s;\n", strings.ToUpper(name))
+ b.WriteString(" ev->pid = pid;\n")
+ b.WriteString(" ev->tid = tid;\n")
+ b.WriteString(" ev->time = bpf_ktime_get_boot_ns();\n")
+ if extra != "" {
+ b.WriteString(extra)
+ }
+ b.WriteString("\n")
+ b.WriteString(" bpf_ringbuf_submit(ev, 0);\n")
+ b.WriteString(" return 0;\n")
+ b.WriteString("}\n")
+ return b.String()
+}
+
+func generateExtra(tp GeneratedTracepoint, isEnter bool) string {
+ f := tp.Format
+
+ switch tp.Classification.Kind {
+ case KindFd:
+ return " ev->fd = (__s32)ctx->args[0];\n"
+
+ case KindDup3:
+ return " ev->fd = (__s32)ctx->args[0];\n ev->flags = (__s32)ctx->args[2];\n"
+
+ case KindOpenByHandleAt:
+ return " ev->flags = (__s32)ctx->args[2];\n"
+
+ case KindOpen:
+ filenameIdx := f.FieldNumber("filename")
+ flagsIdx := f.FieldNumber("flags")
+ var b strings.Builder
+ b.WriteString(" __builtin_memset(&(ev->filename), 0, sizeof(ev->filename) + sizeof(ev->comm));\n")
+ fmt.Fprintf(&b, " bpf_probe_read_user_str(ev->filename, sizeof(ev->filename), (void *)ctx->args[%d]);\n", filenameIdx)
+ b.WriteString(" bpf_get_current_comm(&ev->comm, sizeof(ev->comm));\n")
+ if flagsIdx > -1 {
+ fmt.Fprintf(&b, " ev->flags = ctx->args[%d];\n", flagsIdx)
+ } else {
+ b.WriteString(" ev->flags = -1; // Probably OK\n")
+ }
+ return b.String()
+
+ case KindPathname:
+ fieldName := tp.Classification.PathnameField
+ fieldIdx := f.FieldNumber(fieldName)
+ var b strings.Builder
+ b.WriteString(" __builtin_memset(&(ev->pathname), 0, sizeof(ev->pathname));\n")
+ fmt.Fprintf(&b, " bpf_probe_read_user_str(ev->pathname, sizeof(ev->pathname), (void*)ctx->args[%d]);\n", fieldIdx)
+ return b.String()
+
+ case KindName:
+ oldIdx := f.FieldNumber("oldname")
+ newIdx := f.FieldNumber("newname")
+ var b strings.Builder
+ b.WriteString(" __builtin_memset(&(ev->oldname), 0, sizeof(ev->oldname) + sizeof(ev->newname));\n")
+ fmt.Fprintf(&b, " bpf_probe_read_user_str(ev->oldname, sizeof(ev->oldname), (void*)ctx->args[%d]);\n", oldIdx)
+ fmt.Fprintf(&b, " bpf_probe_read_user_str(ev->newname, sizeof(ev->newname), (void*)ctx->args[%d]);\n", newIdx)
+ return b.String()
+
+ case KindFcntl:
+ fdIdx := f.FieldNumber("fd")
+ cmdIdx := f.FieldNumber("cmd")
+ argIdx := f.FieldNumber("arg")
+ return fmt.Sprintf(
+ " ev->fd = ctx->args[%d];\n ev->cmd = ctx->args[%d];\n ev->arg = ctx->args[%d];\n",
+ fdIdx, cmdIdx, argIdx,
+ )
+
+ case KindRet:
+ classification := ClassifyRet(f.Name)
+ return fmt.Sprintf(" ev->ret = ctx->ret;\n ev->ret_type = %s;\n", classification)
+
+ case KindNull:
+ return ""
+ }
+
+ return ""
+}
+
+func eventStructName(kind TracepointKind) string {
+ switch kind {
+ case KindFd:
+ return "fd_event"
+ case KindOpen:
+ return "open_event"
+ case KindPathname:
+ return "path_event"
+ case KindName:
+ return "name_event"
+ case KindRet:
+ return "ret_event"
+ case KindFcntl:
+ return "fcntl_event"
+ case KindNull:
+ return "null_event"
+ case KindDup3:
+ return "dup3_event"
+ case KindOpenByHandleAt:
+ return "open_by_handle_at_event"
+ default:
+ return "unknown_event"
+ }
+}
+
+func eventTypeConstant(kind TracepointKind, isEnter bool) string {
+ prefix := "EXIT_"
+ if isEnter {
+ prefix = "ENTER_"
+ }
+ return prefix + strings.ToUpper(eventStructName(kind))
+}
diff --git a/internal/generate/classify.go b/internal/generate/classify.go
new file mode 100644
index 0000000..75a12fe
--- /dev/null
+++ b/internal/generate/classify.go
@@ -0,0 +1,214 @@
+package generate
+
+import "strings"
+
+type TracepointKind int
+
+const (
+ KindNone TracepointKind = iota
+ KindFd
+ KindOpen
+ KindPathname
+ KindName
+ KindRet
+ KindFcntl
+ KindNull
+ KindDup3
+ KindOpenByHandleAt
+)
+
+type RetClassification string
+
+const (
+ Unclassified RetClassification = "UNCLASSIFIED"
+ ReadClassified RetClassification = "READ_CLASSIFIED"
+ WriteClassified RetClassification = "WRITE_CLASSIFIED"
+ TransferClassified RetClassification = "TRANSFER_CLASSIFIED"
+)
+
+type ClassificationResult struct {
+ Kind TracepointKind
+ PathnameField string // for KindPathname: "pathname", "path", or "filename"
+}
+
+// ClassifyFormat determines the tracepoint kind for a parsed format section.
+// It mirrors the Raku multi-dispatch: name-based ignores take priority,
+// then name-only mappings, then each external field is tried in order until
+// one matches a name+field or generic field pattern.
+func ClassifyFormat(f *Format) ClassificationResult {
+ if len(f.ExternalFields) == 0 {
+ return ClassificationResult{Kind: KindNone}
+ }
+
+ if shouldIgnore(f.Name) {
+ return ClassificationResult{Kind: KindNone}
+ }
+
+ if r, ok := classifyNameOnly(f.Name); ok {
+ return r
+ }
+
+ for _, field := range f.ExternalFields {
+ if field.Name == "__syscall_nr" {
+ continue
+ }
+ if r, ok := classifyNameAndField(f.Name, field.Type, field.Name); ok {
+ return r
+ }
+ if r, ok := classifyByField(field.Type, field.Name); ok {
+ return r
+ }
+ }
+
+ return ClassificationResult{Kind: KindNone}
+}
+
+func shouldIgnore(name string) bool {
+ prefixIgnores := []string{
+ "sys_enter_mknod",
+ "sys_enter_execve",
+ "sys_enter_accept",
+ "sys_enter_listen",
+ "sys_enter_epoll",
+ }
+ for _, p := range prefixIgnores {
+ if strings.HasPrefix(name, p) {
+ return true
+ }
+ }
+
+ if strings.HasPrefix(name, "sys_enter_") {
+ containsIgnores := []string{"recv", "send", "sock", "inotify", "pidfd"}
+ for _, sub := range containsIgnores {
+ if strings.Contains(name, sub) {
+ return true
+ }
+ }
+ }
+
+ exactIgnores := map[string]bool{
+ "sys_enter_bind": true,
+ "sys_enter_setns": true,
+ "sys_enter_shutdown": true,
+ "sys_enter_connect": true,
+ "sys_enter_fanotify_init": true,
+ "sys_enter_getpeername": true,
+ }
+ return exactIgnores[name]
+}
+
+// classifyNameOnly handles tracepoints classified by name alone,
+// independent of any field.
+func classifyNameOnly(name string) (ClassificationResult, bool) {
+ switch name {
+ case "sys_enter_open_by_handle_at":
+ return ClassificationResult{Kind: KindOpenByHandleAt}, true
+ case "sys_enter_fcntl":
+ return ClassificationResult{Kind: KindFcntl}, true
+ case "sys_enter_syslog":
+ return ClassificationResult{Kind: KindNull}, true
+ case "sys_enter_sync":
+ return ClassificationResult{Kind: KindNull}, true
+ }
+ if strings.HasPrefix(name, "sys_enter_io_") {
+ return ClassificationResult{Kind: KindNull}, true
+ }
+ return ClassificationResult{}, false
+}
+
+// classifyNameAndField handles tracepoints that need both the name and
+// a specific field to classify.
+func classifyNameAndField(name, fieldType, fieldName string) (ClassificationResult, bool) {
+ switch name {
+ case "sys_enter_dup":
+ if fieldType == "unsigned int" && fieldName == "fildes" {
+ return ClassificationResult{Kind: KindFd}, true
+ }
+ case "sys_enter_dup2":
+ if fieldType == "unsigned int" && fieldName == "oldfd" {
+ return ClassificationResult{Kind: KindFd}, true
+ }
+ case "sys_enter_dup3":
+ if fieldType == "unsigned int" && fieldName == "oldfd" {
+ return ClassificationResult{Kind: KindDup3}, true
+ }
+ }
+
+ if strings.HasPrefix(name, "sys_enter") &&
+ strings.Contains(name, "open") &&
+ fieldType == "const char *" && fieldName == "filename" {
+ return ClassificationResult{Kind: KindOpen}, true
+ }
+
+ return ClassificationResult{}, false
+}
+
+func classifyByField(fieldType, fieldName string) (ClassificationResult, bool) {
+ switch {
+ case fieldName == "fd" && isFdType(fieldType):
+ return ClassificationResult{Kind: KindFd}, true
+ case fieldType == "const char *" && fieldName == "newname":
+ return ClassificationResult{Kind: KindName}, true
+ case fieldType == "const char *" && fieldName == "pathname":
+ return ClassificationResult{Kind: KindPathname, PathnameField: "pathname"}, true
+ case fieldType == "const char *" && fieldName == "path":
+ return ClassificationResult{Kind: KindPathname, PathnameField: "path"}, true
+ case fieldType == "const char *" && fieldName == "filename":
+ return ClassificationResult{Kind: KindPathname, PathnameField: "filename"}, true
+ case fieldType == "long" && fieldName == "ret":
+ return ClassificationResult{Kind: KindRet}, true
+ }
+ return ClassificationResult{}, false
+}
+
+func isFdType(t string) bool {
+ return t == "unsigned int" || t == "unsigned long" || t == "int"
+}
+
+// ClassifyRet returns the RetClassification for a syscall exit name.
+func ClassifyRet(name string) RetClassification {
+ syscall := strings.ToLower(strings.TrimPrefix(name, "sys_exit_"))
+ if c, ok := retClassifications[syscall]; ok {
+ return c
+ }
+ return Unclassified
+}
+
+var retClassifications = map[string]RetClassification{
+ "fgetxattr": ReadClassified,
+ "flistxattr": ReadClassified,
+ "getdents": ReadClassified,
+ "getdents64": ReadClassified,
+ "getxattr": ReadClassified,
+ "lgetxattr": ReadClassified,
+ "listxattr": ReadClassified,
+ "llistxattr": ReadClassified,
+ "pread64": ReadClassified,
+ "preadv": ReadClassified,
+ "preadv2": ReadClassified,
+ "process_vm_readv": ReadClassified,
+ "read": ReadClassified,
+ "readlink": ReadClassified,
+ "readlinkat": ReadClassified,
+ "readv": ReadClassified,
+ "recvmmsg": ReadClassified,
+ "recvmsg": ReadClassified,
+ "recvfrom": ReadClassified,
+ "syslog": ReadClassified,
+
+ "copy_file_range": TransferClassified,
+ "sendfile64": TransferClassified,
+ "splice": TransferClassified,
+ "tee": TransferClassified,
+ "vmsplice": TransferClassified,
+
+ "process_vm_writev": WriteClassified,
+ "pwrite64": WriteClassified,
+ "pwritev": WriteClassified,
+ "pwritev2": WriteClassified,
+ "sendmmsg": WriteClassified,
+ "sendmsg": WriteClassified,
+ "sendto": WriteClassified,
+ "write": WriteClassified,
+ "writev": WriteClassified,
+}
diff --git a/internal/generate/classify_test.go b/internal/generate/classify_test.go
new file mode 100644
index 0000000..c94e359
--- /dev/null
+++ b/internal/generate/classify_test.go
@@ -0,0 +1,332 @@
+package generate
+
+import (
+ "strings"
+ "testing"
+)
+
+func classifyFromData(t *testing.T, data string) ClassificationResult {
+ t.Helper()
+ f := mustParseOne(t, data)
+ return ClassifyFormat(&f)
+}
+
+func TestClassifyFdRead(t *testing.T) {
+ r := classifyFromData(t, FormatRead)
+ if r.Kind != KindFd {
+ t.Errorf("read: got kind %d, want KindFd", r.Kind)
+ }
+}
+
+func TestClassifyFdClose(t *testing.T) {
+ r := classifyFromData(t, FormatClose)
+ if r.Kind != KindFd {
+ t.Errorf("close: got kind %d, want KindFd", r.Kind)
+ }
+}
+
+func TestClassifyFdPread64(t *testing.T) {
+ r := classifyFromData(t, FormatPread64)
+ if r.Kind != KindFd {
+ t.Errorf("pread64: got kind %d, want KindFd", r.Kind)
+ }
+}
+
+func TestClassifyFdWrite(t *testing.T) {
+ r := classifyFromData(t, FormatWrite)
+ if r.Kind != KindFd {
+ t.Errorf("write: got kind %d, want KindFd", r.Kind)
+ }
+}
+
+func TestClassifyOpenOpenat(t *testing.T) {
+ r := classifyFromData(t, FormatOpenat)
+ if r.Kind != KindOpen {
+ t.Errorf("openat: got kind %d, want KindOpen", r.Kind)
+ }
+}
+
+func TestClassifyOpenOpen(t *testing.T) {
+ r := classifyFromData(t, FormatOpen)
+ if r.Kind != KindOpen {
+ t.Errorf("open: got kind %d, want KindOpen", r.Kind)
+ }
+}
+
+func TestClassifyOpenOpenat2(t *testing.T) {
+ r := classifyFromData(t, FormatOpenat2)
+ if r.Kind != KindOpen {
+ t.Errorf("openat2: got kind %d, want KindOpen", r.Kind)
+ }
+}
+
+func TestClassifyPathnameCreat(t *testing.T) {
+ r := classifyFromData(t, FormatCreat)
+ if r.Kind != KindPathname {
+ t.Errorf("creat: got kind %d, want KindPathname", r.Kind)
+ }
+ if r.PathnameField != "pathname" {
+ t.Errorf("creat: PathnameField = %q, want pathname", r.PathnameField)
+ }
+}
+
+func TestClassifyPathnameUnlink(t *testing.T) {
+ r := classifyFromData(t, FormatUnlink)
+ if r.Kind != KindPathname {
+ t.Errorf("unlink: got kind %d, want KindPathname", r.Kind)
+ }
+ if r.PathnameField != "pathname" {
+ t.Errorf("unlink: PathnameField = %q, want pathname", r.PathnameField)
+ }
+}
+
+func TestClassifyNameRename(t *testing.T) {
+ r := classifyFromData(t, FormatRename)
+ if r.Kind != KindName {
+ t.Errorf("rename: got kind %d, want KindName", r.Kind)
+ }
+}
+
+func TestClassifyNameLinkat(t *testing.T) {
+ r := classifyFromData(t, FormatLinkat)
+ if r.Kind != KindName {
+ t.Errorf("linkat: got kind %d, want KindName", r.Kind)
+ }
+}
+
+func TestClassifyNameSymlink(t *testing.T) {
+ r := classifyFromData(t, FormatSymlink)
+ if r.Kind != KindName {
+ t.Errorf("symlink: got kind %d, want KindName", r.Kind)
+ }
+}
+
+func TestClassifyFcntl(t *testing.T) {
+ r := classifyFromData(t, FormatFcntl)
+ if r.Kind != KindFcntl {
+ t.Errorf("fcntl: got kind %d, want KindFcntl", r.Kind)
+ }
+}
+
+func TestClassifyDup(t *testing.T) {
+ r := classifyFromData(t, FormatDup)
+ if r.Kind != KindFd {
+ t.Errorf("dup: got kind %d, want KindFd", r.Kind)
+ }
+}
+
+func TestClassifyDup2(t *testing.T) {
+ r := classifyFromData(t, FormatDup2)
+ if r.Kind != KindFd {
+ t.Errorf("dup2: got kind %d, want KindFd", r.Kind)
+ }
+}
+
+func TestClassifyDup3(t *testing.T) {
+ r := classifyFromData(t, FormatDup3)
+ if r.Kind != KindDup3 {
+ t.Errorf("dup3: got kind %d, want KindDup3", r.Kind)
+ }
+}
+
+func TestClassifyOpenByHandleAt(t *testing.T) {
+ r := classifyFromData(t, FormatOpenByHandleAt)
+ if r.Kind != KindOpenByHandleAt {
+ t.Errorf("open_by_handle_at: got kind %d, want KindOpenByHandleAt", r.Kind)
+ }
+}
+
+func TestClassifyNullSync(t *testing.T) {
+ r := classifyFromData(t, FormatSync)
+ if r.Kind != KindNull {
+ t.Errorf("sync: got kind %d, want KindNull", r.Kind)
+ }
+}
+
+func TestClassifyNullSyslog(t *testing.T) {
+ r := classifyFromData(t, FormatSyslog)
+ if r.Kind != KindNull {
+ t.Errorf("syslog: got kind %d, want KindNull", r.Kind)
+ }
+}
+
+func TestClassifyNullIoUring(t *testing.T) {
+ r := classifyFromData(t, FormatIoUringEnter)
+ if r.Kind != KindNull {
+ t.Errorf("io_uring_enter: got kind %d, want KindNull", r.Kind)
+ }
+}
+
+func TestClassifyRetExitRead(t *testing.T) {
+ r := classifyFromData(t, FormatExitRead)
+ if r.Kind != KindRet {
+ t.Errorf("exit_read: got kind %d, want KindRet", r.Kind)
+ }
+}
+
+func TestClassifyRetExitWrite(t *testing.T) {
+ r := classifyFromData(t, FormatExitWrite)
+ if r.Kind != KindRet {
+ t.Errorf("exit_write: got kind %d, want KindRet", r.Kind)
+ }
+}
+
+func TestClassifyRetExitOpenat(t *testing.T) {
+ r := classifyFromData(t, FormatExitOpenat)
+ if r.Kind != KindRet {
+ t.Errorf("exit_openat: got kind %d, want KindRet", r.Kind)
+ }
+}
+
+func TestClassifyRetExitPread64(t *testing.T) {
+ r := classifyFromData(t, FormatExitPread64)
+ if r.Kind != KindRet {
+ t.Errorf("exit_pread64: got kind %d, want KindRet", r.Kind)
+ }
+}
+
+func TestClassifyRetExitSymlink(t *testing.T) {
+ r := classifyFromData(t, FormatExitSymlink)
+ if r.Kind != KindRet {
+ t.Errorf("exit_symlink: got kind %d, want KindRet", r.Kind)
+ }
+}
+
+// --- Ignore tests ---
+
+func TestIgnoreMknod(t *testing.T) {
+ r := classifyFromData(t, FormatMknod)
+ if r.Kind != KindNone {
+ t.Errorf("mknod: got kind %d, want KindNone (ignored)", r.Kind)
+ }
+}
+
+func TestIgnoreExecve(t *testing.T) {
+ r := classifyFromData(t, FormatExecve)
+ if r.Kind != KindNone {
+ t.Errorf("execve: got kind %d, want KindNone (ignored)", r.Kind)
+ }
+}
+
+func TestIgnoreAccept(t *testing.T) {
+ r := classifyFromData(t, FormatAccept)
+ if r.Kind != KindNone {
+ t.Errorf("accept: got kind %d, want KindNone (ignored)", r.Kind)
+ }
+}
+
+func TestIgnoreSocket(t *testing.T) {
+ r := classifyFromData(t, FormatSocket)
+ if r.Kind != KindNone {
+ t.Errorf("socket: got kind %d, want KindNone (ignored)", r.Kind)
+ }
+}
+
+func TestIgnoreKill(t *testing.T) {
+ r := classifyFromData(t, FormatKill)
+ if r.Kind != KindNone {
+ t.Errorf("kill: got kind %d, want KindNone (no matching type)", r.Kind)
+ }
+}
+
+func TestShouldIgnorePatterns(t *testing.T) {
+ ignoreNames := []string{
+ "sys_enter_mknod", "sys_enter_mknodat",
+ "sys_enter_execve", "sys_enter_execveat",
+ "sys_enter_accept", "sys_enter_accept4",
+ "sys_enter_listen",
+ "sys_enter_epoll_ctl", "sys_enter_epoll_pwait",
+ "sys_enter_recvfrom", "sys_enter_recvmsg", "sys_enter_recvmmsg",
+ "sys_enter_sendto", "sys_enter_sendmsg", "sys_enter_sendmmsg",
+ "sys_enter_socket", "sys_enter_socketpair", "sys_enter_getsockname",
+ "sys_enter_inotify_init", "sys_enter_inotify_add_watch",
+ "sys_enter_pidfd_open", "sys_enter_pidfd_getfd",
+ "sys_enter_bind", "sys_enter_setns", "sys_enter_shutdown",
+ "sys_enter_connect", "sys_enter_fanotify_init", "sys_enter_getpeername",
+ }
+ for _, name := range ignoreNames {
+ if !shouldIgnore(name) {
+ t.Errorf("shouldIgnore(%q) = false, want true", name)
+ }
+ }
+}
+
+func TestShouldNotIgnore(t *testing.T) {
+ noIgnore := []string{
+ "sys_enter_read", "sys_enter_write", "sys_enter_openat",
+ "sys_enter_close", "sys_enter_rename", "sys_enter_unlink",
+ "sys_exit_read", "sys_exit_openat",
+ }
+ for _, name := range noIgnore {
+ if shouldIgnore(name) {
+ t.Errorf("shouldIgnore(%q) = true, want false", name)
+ }
+ }
+}
+
+// --- End-to-end classification with enter+exit pairs ---
+
+func TestClassifySyscallPairAccepted(t *testing.T) {
+ tests := []struct {
+ name string
+ enter string
+ exit string
+ enterKind TracepointKind
+ }{
+ {"read", FormatRead, FormatExitRead, KindFd},
+ {"openat", FormatOpenat, FormatExitOpenat, KindOpen},
+ {"rename", FormatRename, FormatExitRename, KindName},
+ {"close", FormatClose, FormatExitClose, KindFd},
+ {"dup3", FormatDup3, FormatExitDup3, KindDup3},
+ {"fcntl", FormatFcntl, FormatExitFcntl, KindFcntl},
+ {"sync", FormatSync, FormatExitSync, KindNull},
+ {"syslog", FormatSyslog, FormatExitSyslog, KindNull},
+ {"open_by_handle_at", FormatOpenByHandleAt, FormatExitOpenByHandleAt, KindOpenByHandleAt},
+ {"io_uring_enter", FormatIoUringEnter, FormatExitIoUringEnter, KindNull},
+ {"pread64", FormatPread64, FormatExitPread64, KindFd},
+ {"symlink", FormatSymlink, FormatExitSymlink, KindName},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ input := tt.enter + "\n" + tt.exit
+ output := GenerateTracepointsC(mustParseAll(t, input))
+ if strings.Contains(output, "Ignoring") {
+ t.Errorf("syscall %s was ignored, expected accepted", tt.name)
+ }
+ })
+ }
+}
+
+func TestClassifySyscallPairIgnored(t *testing.T) {
+ tests := []struct {
+ name string
+ enter string
+ exit string
+ }{
+ {"mknod", FormatMknod, FormatExitMknod},
+ {"execve", FormatExecve, FormatExitExecve},
+ {"accept", FormatAccept, FormatExitAccept},
+ {"socket", FormatSocket, FormatExitSocket},
+ {"kill", FormatKill, FormatExitKill},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ input := tt.enter + "\n" + tt.exit
+ output := GenerateTracepointsC(mustParseAll(t, input))
+ if !strings.Contains(output, "Ignoring") {
+ t.Errorf("syscall %s was accepted, expected ignored", tt.name)
+ }
+ })
+ }
+}
+
+func mustParseAll(t *testing.T, data string) []Format {
+ t.Helper()
+ formats, err := ParseFormats(strings.NewReader(data))
+ if err != nil {
+ t.Fatalf("ParseFormats failed: %v", err)
+ }
+ return formats
+}
diff --git a/internal/generate/codegen.go b/internal/generate/codegen.go
new file mode 100644
index 0000000..9b9f52c
--- /dev/null
+++ b/internal/generate/codegen.go
@@ -0,0 +1,152 @@
+package generate
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+)
+
+// Syscall groups enter+exit formats by syscall name.
+type Syscall struct {
+ Name string
+ Enter *Format
+ Exit *Format
+}
+
+// GeneratedTracepoint holds a classified format ready for code generation.
+type GeneratedTracepoint struct {
+ Format *Format
+ Classification ClassificationResult
+}
+
+// GenerateTracepointsC produces the full generated_tracepoints.c content from
+// concatenated sysfs format data parsed into formats.
+func GenerateTracepointsC(formats []Format) string {
+ syscalls := groupBySyscall(formats)
+ var b strings.Builder
+
+ b.WriteString("// Code generated - don't change manually!\n\n")
+
+ var accepted []GeneratedTracepoint
+ for _, sc := range syscalls {
+ tracepoints, reason := classifySyscall(sc)
+ if reason != "" {
+ fmt.Fprintf(&b, "/// %s\n", reason)
+ continue
+ }
+ accepted = append(accepted, tracepoints...)
+ }
+
+ sort.Slice(accepted, func(i, j int) bool {
+ return accepted[i].Format.ID > accepted[j].Format.ID
+ })
+
+ b.WriteString("\n")
+ for _, tp := range accepted {
+ fmt.Fprintf(&b, "#define %s %d\n", strings.ToUpper(tp.Format.Name), tp.Format.ID)
+ }
+ b.WriteString("\n")
+
+ for _, tp := range accepted {
+ b.WriteString(generateBPFHandler(tp))
+ b.WriteString("\n")
+ }
+
+ return b.String()
+}
+
+func groupBySyscall(formats []Format) []Syscall {
+ m := make(map[string]*Syscall)
+ var order []string
+
+ for i := range formats {
+ f := &formats[i]
+ parts := strings.SplitN(f.Name, "_", 3)
+ if len(parts) < 3 {
+ continue
+ }
+ enterExit := parts[1]
+ what := parts[2]
+
+ sc, ok := m[what]
+ if !ok {
+ sc = &Syscall{Name: what}
+ m[what] = sc
+ order = append(order, what)
+ }
+ if enterExit == "enter" {
+ sc.Enter = f
+ } else {
+ sc.Exit = f
+ }
+ }
+
+ result := make([]Syscall, 0, len(order))
+ for _, name := range order {
+ result = append(result, *m[name])
+ }
+ return result
+}
+
+func classifySyscall(sc Syscall) ([]GeneratedTracepoint, string) {
+ var enterClass, exitClass ClassificationResult
+ allCanGenerate := true
+
+ if sc.Enter != nil {
+ enterClass = ClassifyFormat(sc.Enter)
+ if enterClass.Kind == KindNone {
+ allCanGenerate = false
+ }
+ } else {
+ allCanGenerate = false
+ }
+
+ if sc.Exit != nil {
+ exitClass = ClassifyFormat(sc.Exit)
+ if exitClass.Kind == KindNone {
+ allCanGenerate = false
+ }
+ } else {
+ allCanGenerate = false
+ }
+
+ if !allCanGenerate {
+ names := syscallFormatNames(sc)
+ return nil, fmt.Sprintf("Ignoring %s as possibly not file I/O related", strings.Join(names, " "))
+ }
+
+ if isEnterRejected(enterClass.Kind) {
+ names := syscallFormatNames(sc)
+ return nil, fmt.Sprintf("Ignoring %s as enter-rejected", strings.Join(names, " "))
+ }
+
+ var result []GeneratedTracepoint
+ if sc.Enter != nil {
+ result = append(result, GeneratedTracepoint{Format: sc.Enter, Classification: enterClass})
+ }
+ if sc.Exit != nil {
+ result = append(result, GeneratedTracepoint{Format: sc.Exit, Classification: exitClass})
+ }
+ return result, ""
+}
+
+func isEnterRejected(kind TracepointKind) bool {
+ switch kind {
+ case KindFd, KindName, KindOpen, KindPathname, KindFcntl, KindNull, KindDup3, KindOpenByHandleAt:
+ return false
+ default:
+ return true
+ }
+}
+
+func syscallFormatNames(sc Syscall) []string {
+ var names []string
+ if sc.Enter != nil {
+ names = append(names, sc.Enter.Name)
+ }
+ if sc.Exit != nil {
+ names = append(names, sc.Exit.Name)
+ }
+ sort.Strings(names)
+ return names
+}
diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go
new file mode 100644
index 0000000..b19a824
--- /dev/null
+++ b/internal/generate/codegen_test.go
@@ -0,0 +1,263 @@
+package generate
+
+import (
+ "strings"
+ "testing"
+)
+
+func generateFromPair(t *testing.T, enter, exit string) string {
+ t.Helper()
+ input := enter + "\n" + exit
+ formats := mustParseAll(t, input)
+ return GenerateTracepointsC(formats)
+}
+
+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 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;")
+ requireContains(t, output, "ev->fd = (__s32)ctx->args[0];")
+ requireContains(t, output, "#define SYS_ENTER_READ 844")
+}
+
+func TestGenerateOpenHandler(t *testing.T) {
+ output := generateFromPair(t, FormatOpenat, FormatExitOpenat)
+
+ requireContains(t, output, `SEC("tracepoint/syscalls/sys_enter_openat")`)
+ requireContains(t, output, "struct open_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_OPEN_EVENT;")
+ requireContains(t, output, "ev->trace_id = SYS_ENTER_OPENAT;")
+ requireContains(t, output, "__builtin_memset(&(ev->filename), 0, sizeof(ev->filename) + sizeof(ev->comm));")
+ requireContains(t, output, "bpf_probe_read_user_str(ev->filename, sizeof(ev->filename), (void *)ctx->args[1]);")
+ requireContains(t, output, "bpf_get_current_comm(&ev->comm, sizeof(ev->comm));")
+ requireContains(t, output, "ev->flags = ctx->args[2];")
+}
+
+func TestGenerateOpenHandlerDirect(t *testing.T) {
+ output := generateFromPair(t, FormatOpen, FormatExitOpen)
+
+ requireContains(t, output, "bpf_probe_read_user_str(ev->filename, sizeof(ev->filename), (void *)ctx->args[0]);")
+ requireContains(t, output, "ev->flags = ctx->args[1];")
+}
+
+func TestGenerateOpenat2Handler(t *testing.T) {
+ f := mustParseOne(t, FormatOpenat2)
+ r := ClassifyFormat(&f)
+ if r.Kind != KindOpen {
+ t.Fatalf("openat2 classified as %d, want KindOpen", r.Kind)
+ }
+ // openat2 has filename at args[1] but flags field name = "how" (not "flags"),
+ // so FieldNumber("flags") returns -1
+ if n := f.FieldNumber("flags"); n != -1 {
+ t.Errorf("openat2 FieldNumber(flags) = %d, want -1", n)
+ }
+}
+
+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 ret_event *ev")
+ requireContains(t, output, "ev->event_type = EXIT_RET_EVENT;")
+ requireContains(t, output, "ev->trace_id = SYS_EXIT_READ;")
+ requireContains(t, output, "ev->ret = ctx->ret;")
+ requireContains(t, output, "ev->ret_type = READ_CLASSIFIED;")
+}
+
+func TestGenerateRetHandlerWrite(t *testing.T) {
+ output := generateFromPair(t, FormatWrite, FormatExitWrite)
+
+ requireContains(t, output, "ev->ret_type = WRITE_CLASSIFIED;")
+ requireContains(t, output, "ev->trace_id = SYS_EXIT_WRITE;")
+}
+
+func TestGenerateRetHandlerOpenat(t *testing.T) {
+ output := generateFromPair(t, FormatOpenat, FormatExitOpenat)
+
+ requireContains(t, output, "ev->ret_type = UNCLASSIFIED;")
+ requireContains(t, output, "ev->trace_id = SYS_EXIT_OPENAT;")
+}
+
+func TestGenerateNameHandler(t *testing.T) {
+ output := generateFromPair(t, FormatRename, FormatExitRename)
+
+ requireContains(t, output, `SEC("tracepoint/syscalls/sys_enter_rename")`)
+ requireContains(t, output, "struct name_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_NAME_EVENT;")
+ requireContains(t, output, "ev->trace_id = SYS_ENTER_RENAME;")
+ requireContains(t, output, "__builtin_memset(&(ev->oldname), 0, sizeof(ev->oldname) + sizeof(ev->newname));")
+ requireContains(t, output, "bpf_probe_read_user_str(ev->oldname, sizeof(ev->oldname), (void*)ctx->args[0]);")
+ requireContains(t, output, "bpf_probe_read_user_str(ev->newname, sizeof(ev->newname), (void*)ctx->args[1]);")
+}
+
+func TestGeneratePathnameHandler(t *testing.T) {
+ // Use exit_unlink (same structure as exit_read) paired with enter_unlink
+ exitUnlink := strings.Replace(FormatExitRead, "sys_exit_read", "sys_exit_unlink", 1)
+ exitUnlink = strings.Replace(exitUnlink, "ID: 843", "ID: 883", 1)
+ output := generateFromPair(t, FormatUnlink, exitUnlink)
+
+ requireContains(t, output, "struct path_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_PATH_EVENT;")
+ requireContains(t, output, "__builtin_memset(&(ev->pathname), 0, sizeof(ev->pathname));")
+ requireContains(t, output, "bpf_probe_read_user_str(ev->pathname, sizeof(ev->pathname), (void*)ctx->args[0]);")
+}
+
+func TestGenerateFcntlHandler(t *testing.T) {
+ output := generateFromPair(t, FormatFcntl, FormatExitFcntl)
+
+ requireContains(t, output, "struct fcntl_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_FCNTL_EVENT;")
+ requireContains(t, output, "ev->fd = ctx->args[0];")
+ requireContains(t, output, "ev->cmd = ctx->args[1];")
+ requireContains(t, output, "ev->arg = ctx->args[2];")
+}
+
+func TestGenerateNullHandler(t *testing.T) {
+ output := generateFromPair(t, FormatSync, FormatExitSync)
+
+ requireContains(t, output, "struct null_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_NULL_EVENT;")
+ requireContains(t, output, "ev->trace_id = SYS_ENTER_SYNC;")
+ // Null handler should NOT have ev->fd, ev->filename, etc.
+ if strings.Contains(output, "ev->fd") {
+ t.Error("null handler should not have ev->fd")
+ }
+}
+
+func TestGenerateDup3Handler(t *testing.T) {
+ output := generateFromPair(t, FormatDup3, FormatExitDup3)
+
+ requireContains(t, output, "struct dup3_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_DUP3_EVENT;")
+ requireContains(t, output, "ev->fd = (__s32)ctx->args[0];")
+ requireContains(t, output, "ev->flags = (__s32)ctx->args[2];")
+}
+
+func TestGenerateOpenByHandleAtHandler(t *testing.T) {
+ output := generateFromPair(t, FormatOpenByHandleAt, FormatExitOpenByHandleAt)
+
+ requireContains(t, output, "struct open_by_handle_at_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_OPEN_BY_HANDLE_AT_EVENT;")
+ requireContains(t, output, "ev->flags = (__s32)ctx->args[2];")
+}
+
+func TestGenerateIgnoredComment(t *testing.T) {
+ output := generateFromPair(t, FormatKill, FormatExitKill)
+
+ requireContains(t, output, "/// Ignoring sys_enter_kill sys_exit_kill as possibly not file I/O related")
+}
+
+func TestGenerateDefineConstants(t *testing.T) {
+ output := generateFromPair(t, FormatRead, FormatExitRead)
+
+ requireContains(t, output, "#define SYS_ENTER_READ 844")
+ requireContains(t, output, "#define SYS_EXIT_READ 843")
+}
+
+func TestGenerateDefinesSortedByIDDesc(t *testing.T) {
+ input := FormatRead + "\n" + FormatExitRead + "\n" + FormatClose + "\n" + FormatExitClose
+ formats := mustParseAll(t, input)
+ output := GenerateTracepointsC(formats)
+
+ enterReadPos := strings.Index(output, "#define SYS_ENTER_READ")
+ enterClosePos := strings.Index(output, "#define SYS_ENTER_CLOSE")
+ if enterReadPos < 0 || enterClosePos < 0 {
+ t.Fatal("missing #define lines")
+ }
+ if enterReadPos > enterClosePos {
+ t.Error("#define SYS_ENTER_READ (844) should come before SYS_ENTER_CLOSE (778)")
+ }
+}
+
+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, "__u32 pid, tid;")
+ requireContains(t, output, "if (filter(&pid, &tid))")
+ requireContains(t, output, "ev->pid = pid;")
+ requireContains(t, output, "ev->tid = tid;")
+ requireContains(t, output, "ev->time = bpf_ktime_get_boot_ns();")
+ requireContains(t, output, "bpf_ringbuf_submit(ev, 0);")
+ requireContains(t, output, "return 0;")
+}
+
+func TestGenerateAllEventTypes(t *testing.T) {
+ // Verify every event type constant appears correctly
+ tests := []struct {
+ kind TracepointKind
+ enter string
+ exit string
+ }{
+ {KindFd, "ENTER_FD_EVENT", "EXIT_FD_EVENT"},
+ {KindOpen, "ENTER_OPEN_EVENT", "EXIT_OPEN_EVENT"},
+ {KindPathname, "ENTER_PATH_EVENT", "EXIT_PATH_EVENT"},
+ {KindName, "ENTER_NAME_EVENT", "EXIT_NAME_EVENT"},
+ {KindRet, "ENTER_RET_EVENT", "EXIT_RET_EVENT"},
+ {KindFcntl, "ENTER_FCNTL_EVENT", "EXIT_FCNTL_EVENT"},
+ {KindNull, "ENTER_NULL_EVENT", "EXIT_NULL_EVENT"},
+ {KindDup3, "ENTER_DUP3_EVENT", "EXIT_DUP3_EVENT"},
+ {KindOpenByHandleAt, "ENTER_OPEN_BY_HANDLE_AT_EVENT", "EXIT_OPEN_BY_HANDLE_AT_EVENT"},
+ }
+
+ for _, tt := range tests {
+ if got := eventTypeConstant(tt.kind, true); got != tt.enter {
+ t.Errorf("eventTypeConstant(%d, true) = %q, want %q", tt.kind, got, tt.enter)
+ }
+ if got := eventTypeConstant(tt.kind, false); got != tt.exit {
+ t.Errorf("eventTypeConstant(%d, false) = %q, want %q", tt.kind, got, tt.exit)
+ }
+ }
+}
+
+func TestEventStructNames(t *testing.T) {
+ tests := []struct {
+ kind TracepointKind
+ want string
+ }{
+ {KindFd, "fd_event"},
+ {KindOpen, "open_event"},
+ {KindPathname, "path_event"},
+ {KindName, "name_event"},
+ {KindRet, "ret_event"},
+ {KindFcntl, "fcntl_event"},
+ {KindNull, "null_event"},
+ {KindDup3, "dup3_event"},
+ {KindOpenByHandleAt, "open_by_handle_at_event"},
+ }
+
+ for _, tt := range tests {
+ if got := eventStructName(tt.kind); got != tt.want {
+ t.Errorf("eventStructName(%d) = %q, want %q", tt.kind, got, tt.want)
+ }
+ }
+}
+
+func TestEnterReject(t *testing.T) {
+ // RetTracepoint as enter type should be rejected
+ if !isEnterRejected(KindRet) {
+ t.Error("KindRet should be enter-rejected")
+ }
+ if !isEnterRejected(KindNone) {
+ t.Error("KindNone should be enter-rejected")
+ }
+
+ accepted := []TracepointKind{KindFd, KindOpen, KindPathname, KindName, KindFcntl, KindNull, KindDup3, KindOpenByHandleAt}
+ for _, k := range accepted {
+ if isEnterRejected(k) {
+ t.Errorf("kind %d should NOT be enter-rejected", k)
+ }
+ }
+}
+
+func requireContains(t *testing.T, haystack, needle string) {
+ t.Helper()
+ if !strings.Contains(haystack, needle) {
+ t.Errorf("output missing expected string: %q", needle)
+ }
+}
diff --git a/internal/generate/format.go b/internal/generate/format.go
new file mode 100644
index 0000000..ea579b6
--- /dev/null
+++ b/internal/generate/format.go
@@ -0,0 +1,145 @@
+package generate
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+)
+
+type Field struct {
+ Type string
+ Name string
+ Offset int
+ Size int
+ Signed bool
+}
+
+type Format struct {
+ Name string
+ ID int
+ InternalFields []Field
+ ExternalFields []Field
+}
+
+// FieldNumber returns the 0-based index of a named field in ExternalFields,
+// minus 1 (to skip __syscall_nr), matching the Raku behavior where args[0]
+// is the first field after __syscall_nr.
+func (f *Format) FieldNumber(name string) int {
+ for i, field := range f.ExternalFields {
+ if field.Name == name {
+ return i - 1
+ }
+ }
+ return 0 - 1
+}
+
+// ParseFormats parses concatenated sysfs tracepoint format files from r.
+// Each section has: name, ID, format fields, print fmt.
+func ParseFormats(r io.Reader) ([]Format, error) {
+ scanner := bufio.NewScanner(r)
+ var formats []Format
+ var current *Format
+ isExternal := false
+
+ for scanner.Scan() {
+ line := scanner.Text()
+ trimmed := strings.TrimSpace(line)
+
+ switch {
+ case strings.HasPrefix(trimmed, "name:"):
+ f := Format{}
+ f.Name = strings.TrimSpace(strings.TrimPrefix(trimmed, "name:"))
+ formats = append(formats, f)
+ current = &formats[len(formats)-1]
+ isExternal = false
+
+ case strings.HasPrefix(trimmed, "ID:"):
+ if current == nil {
+ return nil, fmt.Errorf("ID without name")
+ }
+ id, err := strconv.Atoi(strings.TrimSpace(strings.TrimPrefix(trimmed, "ID:")))
+ if err != nil {
+ return nil, fmt.Errorf("parsing ID: %w", err)
+ }
+ current.ID = id
+
+ case strings.HasPrefix(trimmed, "field:"):
+ if current == nil {
+ return nil, fmt.Errorf("field without name")
+ }
+ field, err := parseField(trimmed)
+ if err != nil {
+ return nil, fmt.Errorf("parsing field in %s: %w", current.Name, err)
+ }
+ if field.Name == "__syscall_nr" {
+ isExternal = true
+ }
+ if isExternal {
+ current.ExternalFields = append(current.ExternalFields, field)
+ } else {
+ current.InternalFields = append(current.InternalFields, field)
+ }
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return nil, fmt.Errorf("scanning input: %w", err)
+ }
+ return formats, nil
+}
+
+func parseField(line string) (Field, error) {
+ // Format: "field:TYPE NAME; offset:N; size:N; signed:N;"
+ line = strings.TrimPrefix(line, "field:")
+ parts := strings.Split(line, ";")
+ if len(parts) < 4 {
+ return Field{}, fmt.Errorf("not enough field parts: %q", line)
+ }
+
+ decl := strings.TrimSpace(parts[0])
+ fieldType, fieldName := splitDeclaration(decl)
+
+ offset, err := parseFieldInt(parts[1], "offset:")
+ if err != nil {
+ return Field{}, err
+ }
+ size, err := parseFieldInt(parts[2], "size:")
+ if err != nil {
+ return Field{}, err
+ }
+ signedVal, err := parseFieldInt(parts[3], "signed:")
+ if err != nil {
+ return Field{}, err
+ }
+
+ return Field{
+ Type: fieldType,
+ Name: fieldName,
+ Offset: offset,
+ Size: size,
+ Signed: signedVal != 0,
+ }, nil
+}
+
+// splitDeclaration splits "const char * filename" into ("const char *", "filename").
+func splitDeclaration(decl string) (string, string) {
+ tokens := strings.Fields(decl)
+ if len(tokens) == 0 {
+ return "", ""
+ }
+ if len(tokens) == 1 {
+ return "", tokens[0]
+ }
+ name := tokens[len(tokens)-1]
+ typePart := strings.Join(tokens[:len(tokens)-1], " ")
+ return typePart, name
+}
+
+func parseFieldInt(s, prefix string) (int, error) {
+ s = strings.TrimSpace(s)
+ s = strings.TrimPrefix(s, prefix)
+ s = strings.TrimSpace(s)
+ return strconv.Atoi(s)
+}
diff --git a/internal/generate/format_test.go b/internal/generate/format_test.go
new file mode 100644
index 0000000..f8f078b
--- /dev/null
+++ b/internal/generate/format_test.go
@@ -0,0 +1,174 @@
+package generate
+
+import (
+ "strings"
+ "testing"
+)
+
+func mustParseOne(t *testing.T, data string) Format {
+ t.Helper()
+ formats, err := ParseFormats(strings.NewReader(data))
+ if err != nil {
+ t.Fatalf("ParseFormats failed: %v", err)
+ }
+ if len(formats) != 1 {
+ t.Fatalf("expected 1 format, got %d", len(formats))
+ }
+ return formats[0]
+}
+
+func TestParseFormatOpenat(t *testing.T) {
+ f := mustParseOne(t, FormatOpenat)
+
+ if f.Name != "sys_enter_openat" {
+ t.Errorf("name = %q, want sys_enter_openat", f.Name)
+ }
+ if f.ID != 784 {
+ t.Errorf("ID = %d, want 784", f.ID)
+ }
+ if len(f.InternalFields) != 4 {
+ t.Errorf("internal fields = %d, want 4", len(f.InternalFields))
+ }
+ if len(f.ExternalFields) != 5 {
+ t.Errorf("external fields = %d, want 5", len(f.ExternalFields))
+ }
+ if f.ExternalFields[0].Name != "__syscall_nr" {
+ t.Errorf("first external field = %q, want __syscall_nr", f.ExternalFields[0].Name)
+ }
+ if f.ExternalFields[2].Type != "const char *" {
+ t.Errorf("filename type = %q, want 'const char *'", f.ExternalFields[2].Type)
+ }
+ if f.ExternalFields[2].Name != "filename" {
+ t.Errorf("field 2 name = %q, want filename", f.ExternalFields[2].Name)
+ }
+}
+
+func TestParseFormatExitRead(t *testing.T) {
+ f := mustParseOne(t, FormatExitRead)
+
+ if f.Name != "sys_exit_read" {
+ t.Errorf("name = %q, want sys_exit_read", f.Name)
+ }
+ if f.ID != 843 {
+ t.Errorf("ID = %d, want 843", f.ID)
+ }
+ if len(f.ExternalFields) != 2 {
+ t.Errorf("external fields = %d, want 2", len(f.ExternalFields))
+ }
+ if f.ExternalFields[1].Type != "long" {
+ t.Errorf("ret type = %q, want long", f.ExternalFields[1].Type)
+ }
+ if f.ExternalFields[1].Name != "ret" {
+ t.Errorf("ret name = %q, want ret", f.ExternalFields[1].Name)
+ }
+ if !f.ExternalFields[1].Signed {
+ t.Error("ret should be signed")
+ }
+}
+
+func TestParseFormatSync(t *testing.T) {
+ f := mustParseOne(t, FormatSync)
+
+ if f.Name != "sys_enter_sync" {
+ t.Errorf("name = %q, want sys_enter_sync", f.Name)
+ }
+ if f.ID != 1027 {
+ t.Errorf("ID = %d, want 1027", f.ID)
+ }
+ if len(f.ExternalFields) != 1 {
+ t.Errorf("external fields = %d, want 1 (__syscall_nr only)", len(f.ExternalFields))
+ }
+}
+
+func TestParseMultiSection(t *testing.T) {
+ input := FormatRead + "\n" + FormatExitRead
+ formats, err := ParseFormats(strings.NewReader(input))
+ if err != nil {
+ t.Fatalf("ParseFormats failed: %v", err)
+ }
+ if len(formats) != 2 {
+ t.Fatalf("expected 2 formats, got %d", len(formats))
+ }
+ if formats[0].Name != "sys_enter_read" {
+ t.Errorf("first = %q", formats[0].Name)
+ }
+ if formats[1].Name != "sys_exit_read" {
+ t.Errorf("second = %q", formats[1].Name)
+ }
+}
+
+func TestParseFieldDetails(t *testing.T) {
+ f := mustParseOne(t, FormatOpenat)
+
+ // common_type: internal field
+ ct := f.InternalFields[0]
+ if ct.Name != "common_type" || ct.Type != "unsigned short" || ct.Offset != 0 || ct.Size != 2 || ct.Signed {
+ t.Errorf("common_type = %+v", ct)
+ }
+
+ // dfd: first non-syscall_nr external field
+ dfd := f.ExternalFields[1]
+ if dfd.Name != "dfd" || dfd.Type != "int" || dfd.Offset != 16 || dfd.Size != 8 {
+ t.Errorf("dfd = %+v", dfd)
+ }
+}
+
+func TestFieldNumber(t *testing.T) {
+ f := mustParseOne(t, FormatOpenat)
+
+ if n := f.FieldNumber("filename"); n != 1 {
+ t.Errorf("FieldNumber(filename) = %d, want 1", n)
+ }
+ if n := f.FieldNumber("flags"); n != 2 {
+ t.Errorf("FieldNumber(flags) = %d, want 2", n)
+ }
+ if n := f.FieldNumber("dfd"); n != 0 {
+ t.Errorf("FieldNumber(dfd) = %d, want 0", n)
+ }
+ if n := f.FieldNumber("nonexistent"); n != -1 {
+ t.Errorf("FieldNumber(nonexistent) = %d, want -1", n)
+ }
+}
+
+func TestFieldNumberFcntl(t *testing.T) {
+ f := mustParseOne(t, FormatFcntl)
+
+ if n := f.FieldNumber("fd"); n != 0 {
+ t.Errorf("FieldNumber(fd) = %d, want 0", n)
+ }
+ if n := f.FieldNumber("cmd"); n != 1 {
+ t.Errorf("FieldNumber(cmd) = %d, want 1", n)
+ }
+ if n := f.FieldNumber("arg"); n != 2 {
+ t.Errorf("FieldNumber(arg) = %d, want 2", n)
+ }
+}
+
+func TestFieldNumberRename(t *testing.T) {
+ f := mustParseOne(t, FormatRename)
+
+ if n := f.FieldNumber("oldname"); n != 0 {
+ t.Errorf("FieldNumber(oldname) = %d, want 0", n)
+ }
+ if n := f.FieldNumber("newname"); n != 1 {
+ t.Errorf("FieldNumber(newname) = %d, want 1", n)
+ }
+}
+
+func TestFieldNumberLinkat(t *testing.T) {
+ f := mustParseOne(t, FormatLinkat)
+
+ if n := f.FieldNumber("oldname"); n != 1 {
+ t.Errorf("FieldNumber(oldname) = %d, want 1", n)
+ }
+ if n := f.FieldNumber("newname"); n != 3 {
+ t.Errorf("FieldNumber(newname) = %d, want 3", n)
+ }
+}
+
+func TestParseFormatError(t *testing.T) {
+ _, err := ParseFormats(strings.NewReader("field:broken"))
+ if err == nil {
+ t.Error("expected error for field without name")
+ }
+}
diff --git a/internal/generate/retclassify_test.go b/internal/generate/retclassify_test.go
new file mode 100644
index 0000000..3152005
--- /dev/null
+++ b/internal/generate/retclassify_test.go
@@ -0,0 +1,59 @@
+package generate
+
+import "testing"
+
+func TestClassifyRetRead(t *testing.T) {
+ reads := []string{
+ "fgetxattr", "flistxattr", "getdents", "getdents64", "getxattr",
+ "lgetxattr", "listxattr", "llistxattr", "pread64", "preadv",
+ "preadv2", "process_vm_readv", "read", "readlink", "readlinkat",
+ "readv", "recvmmsg", "recvmsg", "recvfrom", "syslog",
+ }
+ for _, name := range reads {
+ if got := ClassifyRet("sys_exit_" + name); got != ReadClassified {
+ t.Errorf("ClassifyRet(sys_exit_%s) = %q, want READ_CLASSIFIED", name, got)
+ }
+ }
+}
+
+func TestClassifyRetWrite(t *testing.T) {
+ writes := []string{
+ "process_vm_writev", "pwrite64", "pwritev", "pwritev2",
+ "sendmmsg", "sendmsg", "sendto", "write", "writev",
+ }
+ for _, name := range writes {
+ if got := ClassifyRet("sys_exit_" + name); got != WriteClassified {
+ t.Errorf("ClassifyRet(sys_exit_%s) = %q, want WRITE_CLASSIFIED", name, got)
+ }
+ }
+}
+
+func TestClassifyRetTransfer(t *testing.T) {
+ transfers := []string{
+ "copy_file_range", "sendfile64", "splice", "tee", "vmsplice",
+ }
+ for _, name := range transfers {
+ if got := ClassifyRet("sys_exit_" + name); got != TransferClassified {
+ t.Errorf("ClassifyRet(sys_exit_%s) = %q, want TRANSFER_CLASSIFIED", name, got)
+ }
+ }
+}
+
+func TestClassifyRetUnclassified(t *testing.T) {
+ unclassified := []string{
+ "openat", "close", "rename", "unlink", "fcntl", "dup", "dup2", "dup3",
+ "mkdir", "rmdir", "chmod", "chown", "chdir", "stat", "lseek",
+ "truncate", "fallocate", "mmap", "fsync", "flock",
+ }
+ for _, name := range unclassified {
+ if got := ClassifyRet("sys_exit_" + name); got != Unclassified {
+ t.Errorf("ClassifyRet(sys_exit_%s) = %q, want UNCLASSIFIED", name, got)
+ }
+ }
+}
+
+func TestClassifyRetCaseInsensitive(t *testing.T) {
+ if got := ClassifyRet("sys_exit_READ"); got != ReadClassified {
+ t.Errorf("ClassifyRet(sys_exit_READ) = %q, want READ_CLASSIFIED", got)
+ }
+}
diff --git a/internal/generate/testdata.go b/internal/generate/testdata.go
new file mode 100644
index 0000000..7ca29cd
--- /dev/null
+++ b/internal/generate/testdata.go
@@ -0,0 +1,666 @@
+package generate
+
+// Real sysfs tracepoint format data captured from a Linux 6.18 kernel,
+// used as test fixtures.
+
+const FormatOpenat = `name: sys_enter_openat
+ID: 784
+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 dfd; offset:16; size:8; signed:0;
+ field:const char * filename; offset:24; size:8; signed:0;
+ field:int flags; offset:32; size:8; signed:0;
+ field:umode_t mode; offset:40; size:8; signed:0;
+
+print fmt: "dfd: 0x%08lx, filename: 0x%08lx, flags: 0x%08lx, mode: 0x%08lx", ((unsigned long)(REC->dfd)), ((unsigned long)(REC->filename)), ((unsigned long)(REC->flags)), ((unsigned long)(REC->mode))
+`
+
+const FormatExitOpenat = `name: sys_exit_openat
+ID: 783
+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 FormatOpen = `name: sys_enter_open
+ID: 786
+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:const char * filename; offset:16; size:8; signed:0;
+ field:int flags; offset:24; size:8; signed:0;
+ field:umode_t mode; offset:32; size:8; signed:0;
+
+print fmt: "filename: 0x%08lx, flags: 0x%08lx, mode: 0x%08lx", ((unsigned long)(REC->filename)), ((unsigned long)(REC->flags)), ((unsigned long)(REC->mode))
+`
+
+const FormatExitOpen = `name: sys_exit_open
+ID: 785
+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 FormatRead = `name: sys_enter_read
+ID: 844
+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 fd; offset:16; size:8; signed:0;
+ field:char * buf; offset:24; size:8; signed:0;
+ field:size_t count; offset:32; size:8; signed:0;
+
+print fmt: "fd: 0x%08lx, buf: 0x%08lx, count: 0x%08lx", ((unsigned long)(REC->fd)), ((unsigned long)(REC->buf)), ((unsigned long)(REC->count))
+`
+
+const FormatExitRead = `name: sys_exit_read
+ID: 843
+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 FormatWrite = `name: sys_enter_write
+ID: 842
+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 fd; offset:16; size:8; signed:0;
+ field:const char * buf; offset:24; size:8; signed:0;
+ field:size_t count; offset:32; size:8; signed:0;
+
+print fmt: "fd: 0x%08lx, buf: 0x%08lx, count: 0x%08lx", ((unsigned long)(REC->fd)), ((unsigned long)(REC->buf)), ((unsigned long)(REC->count))
+`
+
+const FormatExitWrite = `name: sys_exit_write
+ID: 841
+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 FormatClose = `name: sys_enter_close
+ID: 778
+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 fd; offset:16; size:8; signed:0;
+
+print fmt: "fd: 0x%08lx", ((unsigned long)(REC->fd))
+`
+
+const FormatExitClose = `name: sys_exit_close
+ID: 777
+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 FormatRename = `name: sys_enter_rename
+ID: 870
+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:const char * oldname; offset:16; size:8; signed:0;
+ field:const char * newname; offset:24; size:8; signed:0;
+
+print fmt: "oldname: 0x%08lx, newname: 0x%08lx", ((unsigned long)(REC->oldname)), ((unsigned long)(REC->newname))
+`
+
+const FormatExitRename = `name: sys_exit_rename
+ID: 869
+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 FormatLinkat = `name: sys_enter_linkat
+ID: 878
+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 olddfd; offset:16; size:8; signed:0;
+ field:const char * oldname; offset:24; size:8; signed:0;
+ field:int newdfd; offset:32; size:8; signed:0;
+ field:const char * newname; offset:40; size:8; signed:0;
+ field:int flags; offset:48; size:8; signed:0;
+
+print fmt: "olddfd: 0x%08lx, oldname: 0x%08lx, newdfd: 0x%08lx, newname: 0x%08lx, flags: 0x%08lx", ((unsigned long)(REC->olddfd)), ((unsigned long)(REC->oldname)), ((unsigned long)(REC->newdfd)), ((unsigned long)(REC->newname)), ((unsigned long)(REC->flags))
+`
+
+const FormatUnlink = `name: sys_enter_unlink
+ID: 884
+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:const char * pathname; offset:16; size:8; signed:0;
+
+print fmt: "pathname: 0x%08lx", ((unsigned long)(REC->pathname))
+`
+
+const FormatDup3 = `name: sys_enter_dup3
+ID: 922
+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 oldfd; offset:16; size:8; signed:0;
+ field:unsigned int newfd; offset:24; size:8; signed:0;
+ field:int flags; offset:32; size:8; signed:0;
+
+print fmt: "oldfd: 0x%08lx, newfd: 0x%08lx, flags: 0x%08lx", ((unsigned long)(REC->oldfd)), ((unsigned long)(REC->newfd)), ((unsigned long)(REC->flags))
+`
+
+const FormatExitDup3 = `name: sys_exit_dup3
+ID: 921
+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 FormatDup = `name: sys_enter_dup
+ID: 918
+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 fildes; offset:16; size:8; signed:0;
+
+print fmt: "fildes: 0x%08lx", ((unsigned long)(REC->fildes))
+`
+
+const FormatDup2 = `name: sys_enter_dup2
+ID: 920
+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 oldfd; offset:16; size:8; signed:0;
+ field:unsigned int newfd; offset:24; size:8; signed:0;
+
+print fmt: "oldfd: 0x%08lx, newfd: 0x%08lx", ((unsigned long)(REC->oldfd)), ((unsigned long)(REC->newfd))
+`
+
+const FormatFcntl = `name: sys_enter_fcntl
+ID: 898
+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 fd; offset:16; size:8; signed:0;
+ field:unsigned int cmd; offset:24; size:8; signed:0;
+ field:unsigned long arg; offset:32; size:8; signed:0;
+
+print fmt: "fd: 0x%08lx, cmd: 0x%08lx, arg: 0x%08lx", ((unsigned long)(REC->fd)), ((unsigned long)(REC->cmd)), ((unsigned long)(REC->arg))
+`
+
+const FormatExitFcntl = `name: sys_exit_fcntl
+ID: 897
+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 FormatSync = `name: sys_enter_sync
+ID: 1027
+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;
+
+print fmt: ""
+`
+
+const FormatExitSync = `name: sys_exit_sync
+ID: 1026
+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 FormatSyslog = `name: sys_enter_syslog
+ID: 347
+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 type; offset:16; size:8; signed:0;
+ field:char * buf; offset:24; size:8; signed:0;
+ field:int len; offset:32; size:8; signed:0;
+
+print fmt: "type: 0x%08lx, buf: 0x%08lx, len: 0x%08lx", ((unsigned long)(REC->type)), ((unsigned long)(REC->buf)), ((unsigned long)(REC->len))
+`
+
+const FormatExitSyslog = `name: sys_exit_syslog
+ID: 346
+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 FormatOpenByHandleAt = `name: sys_enter_open_by_handle_at
+ID: 1133
+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 mountdirfd; offset:16; size:8; signed:0;
+ field:struct file_handle * handle; offset:24; size:8; signed:0;
+ field:int flags; offset:32; size:8; signed:0;
+
+print fmt: "mountdirfd: 0x%08lx, handle: 0x%08lx, flags: 0x%08lx", ((unsigned long)(REC->mountdirfd)), ((unsigned long)(REC->handle)), ((unsigned long)(REC->flags))
+`
+
+const FormatExitOpenByHandleAt = `name: sys_exit_open_by_handle_at
+ID: 1132
+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 FormatIoUringEnter = `name: sys_enter_io_uring_enter
+ID: 1496
+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 fd; offset:16; size:8; signed:0;
+ field:u32 to_submit; offset:24; size:8; signed:0;
+ field:u32 min_complete; offset:32; size:8; signed:0;
+ field:u32 flags; offset:40; size:8; signed:0;
+ field:const void * argp; offset:48; size:8; signed:0;
+ field:size_t argsz; offset:56; size:8; signed:0;
+
+print fmt: "fd: 0x%08lx, to_submit: 0x%08lx, min_complete: 0x%08lx, flags: 0x%08lx, argp: 0x%08lx, argsz: 0x%08lx", ((unsigned long)(REC->fd)), ((unsigned long)(REC->to_submit)), ((unsigned long)(REC->min_complete)), ((unsigned long)(REC->flags)), ((unsigned long)(REC->argp)), ((unsigned long)(REC->argsz))
+`
+
+const FormatExitIoUringEnter = `name: sys_exit_io_uring_enter
+ID: 1495
+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 FormatOpenat2 = `name: sys_enter_openat2
+ID: 782
+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 dfd; offset:16; size:8; signed:0;
+ field:const char * filename; offset:24; size:8; signed:0;
+ field:struct open_how * how; offset:32; size:8; signed:0;
+ field:size_t usize; offset:40; size:8; signed:0;
+
+print fmt: "dfd: 0x%08lx, filename: 0x%08lx, how: 0x%08lx, usize: 0x%08lx", ((unsigned long)(REC->dfd)), ((unsigned long)(REC->filename)), ((unsigned long)(REC->how)), ((unsigned long)(REC->usize))
+`
+
+const FormatCreat = `name: sys_enter_creat
+ID: 780
+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:const char * pathname; offset:16; size:8; signed:0;
+ field:umode_t mode; offset:24; size:8; signed:0;
+
+print fmt: "pathname: 0x%08lx, mode: 0x%08lx", ((unsigned long)(REC->pathname)), ((unsigned long)(REC->mode))
+`
+
+// Ignored tracepoints
+
+const FormatExecve = `name: sys_enter_execve
+ID: 864
+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:const char * filename; offset:16; size:8; signed:0;
+ field:const char *const * argv; offset:24; size:8; signed:0;
+ field:const char *const * envp; offset:32; size:8; signed:0;
+
+print fmt: "filename: 0x%08lx, argv: 0x%08lx, envp: 0x%08lx", ((unsigned long)(REC->filename)), ((unsigned long)(REC->argv)), ((unsigned long)(REC->envp))
+`
+
+const FormatExitExecve = `name: sys_exit_execve
+ID: 863
+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 FormatMknod = `name: sys_enter_mknod
+ID: 894
+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:const char * filename; offset:16; size:8; signed:0;
+ field:umode_t mode; offset:24; size:8; signed:0;
+ field:unsigned dev; offset:32; size:8; signed:0;
+
+print fmt: "filename: 0x%08lx, mode: 0x%08lx, dev: 0x%08lx", ((unsigned long)(REC->filename)), ((unsigned long)(REC->mode)), ((unsigned long)(REC->dev))
+`
+
+const FormatExitMknod = `name: sys_exit_mknod
+ID: 893
+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 FormatKill = `name: sys_enter_kill
+ID: 183
+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:pid_t pid; offset:16; size:8; signed:0;
+ field:int sig; offset:24; size:8; signed:0;
+
+print fmt: "pid: 0x%08lx, sig: 0x%08lx", ((unsigned long)(REC->pid)), ((unsigned long)(REC->sig))
+`
+
+const FormatExitKill = `name: sys_exit_kill
+ID: 182
+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 FormatAccept = `name: sys_enter_accept
+ID: 1808
+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 fd; offset:16; size:8; signed:0;
+ field:struct sockaddr * upeer_sockaddr; offset:24; size:8; signed:0;
+ field:int * upeer_addrlen; offset:32; size:8; signed:0;
+
+print fmt: "fd: 0x%08lx, upeer_sockaddr: 0x%08lx, upeer_addrlen: 0x%08lx", ((unsigned long)(REC->fd)), ((unsigned long)(REC->upeer_sockaddr)), ((unsigned long)(REC->upeer_addrlen))
+`
+
+const FormatExitAccept = `name: sys_exit_accept
+ID: 1807
+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 FormatSocket = `name: sys_enter_socket
+ID: 1818
+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 family; offset:16; size:8; signed:0;
+ field:int type; offset:24; size:8; signed:0;
+ field:int protocol; offset:32; size:8; signed:0;
+
+print fmt: "family: 0x%08lx, type: 0x%08lx, protocol: 0x%08lx", ((unsigned long)(REC->family)), ((unsigned long)(REC->type)), ((unsigned long)(REC->protocol))
+`
+
+const FormatExitSocket = `name: sys_exit_socket
+ID: 1817
+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:
+ 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 fd; offset:16; size:8; signed:0;
+ field:char * buf; offset:24; size:8; signed:0;
+ field:size_t count; offset:32; size:8; signed:0;
+ field:loff_t pos; offset:40; size:8; signed:0;
+
+print fmt: "fd: 0x%08lx, buf: 0x%08lx, count: 0x%08lx, pos: 0x%08lx", ((unsigned long)(REC->fd)), ((unsigned long)(REC->buf)), ((unsigned long)(REC->count)), ((unsigned long)(REC->pos))
+`
+
+const FormatExitPread64 = `name: sys_exit_pread64
+ID: 839
+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 FormatSymlink = `name: sys_enter_symlink
+ID: 880
+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:const char * oldname; offset:16; size:8; signed:0;
+ field:const char * newname; offset:24; size:8; signed:0;
+
+print fmt: "oldname: 0x%08lx, newname: 0x%08lx", ((unsigned long)(REC->oldname)), ((unsigned long)(REC->newname))
+`
+
+const FormatExitSymlink = `name: sys_exit_symlink
+ID: 879
+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
+`
diff --git a/internal/generate/tracepointsgo.go b/internal/generate/tracepointsgo.go
new file mode 100644
index 0000000..0542f64
--- /dev/null
+++ b/internal/generate/tracepointsgo.go
@@ -0,0 +1,43 @@
+package generate
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "regexp"
+ "strings"
+)
+
+var secRe = regexp.MustCompile(`^SEC.*sys_((?:enter|exit)_[a-z_0-9]+)`)
+
+// ExtractTracepoints reads generated C code and extracts tracepoint names from
+// SEC annotations, producing the generated_tracepoints.go content.
+func ExtractTracepoints(r io.Reader) (string, error) {
+ scanner := bufio.NewScanner(r)
+ var tracepoints []string
+
+ for scanner.Scan() {
+ line := scanner.Text()
+ if m := secRe.FindStringSubmatch(line); m != nil {
+ tracepoints = append(tracepoints, "sys_"+m[1])
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return "", fmt.Errorf("scanning input: %w", err)
+ }
+
+ return formatTracepointsGo(tracepoints), nil
+}
+
+func formatTracepointsGo(tracepoints []string) string {
+ var b strings.Builder
+ b.WriteString("// Code generated - don't change manually!\n")
+ b.WriteString("package tracepoints\n\n")
+ b.WriteString("var List = []string{\n")
+ for _, tp := range tracepoints {
+ fmt.Fprintf(&b, "\t%q,\n", tp)
+ }
+ b.WriteString("}\n")
+ return b.String()
+}
diff --git a/internal/generate/tracepointsgo_test.go b/internal/generate/tracepointsgo_test.go
new file mode 100644
index 0000000..d0f90db
--- /dev/null
+++ b/internal/generate/tracepointsgo_test.go
@@ -0,0 +1,92 @@
+package generate
+
+import (
+ "strings"
+ "testing"
+)
+
+const sampleGeneratedC = `// Code generated - don't change manually!
+
+/// Ignoring sys_enter_kill sys_exit_kill as possibly not file I/O related
+
+#define SYS_ENTER_READ 844
+#define SYS_EXIT_READ 843
+#define SYS_ENTER_CLOSE 778
+#define SYS_EXIT_CLOSE 777
+
+/// 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) {
+ 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) {
+ 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) {
+ 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) {
+ return 0;
+}
+`
+
+func TestExtractTracepoints(t *testing.T) {
+ output, err := ExtractTracepoints(strings.NewReader(sampleGeneratedC))
+ if err != nil {
+ t.Fatalf("ExtractTracepoints failed: %v", err)
+ }
+
+ requireContains(t, output, "package tracepoints")
+ requireContains(t, output, `"sys_enter_read",`)
+ requireContains(t, output, `"sys_exit_read",`)
+ requireContains(t, output, `"sys_enter_close",`)
+ requireContains(t, output, `"sys_exit_close",`)
+ requireContains(t, output, "var List = []string{")
+
+ // Should NOT contain ignore comments or defines
+ if strings.Contains(output, "kill") {
+ t.Error("output should not contain ignored tracepoints")
+ }
+}
+
+func TestExtractTracepointsOrder(t *testing.T) {
+ output, err := ExtractTracepoints(strings.NewReader(sampleGeneratedC))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ enterReadPos := strings.Index(output, `"sys_enter_read"`)
+ exitReadPos := strings.Index(output, `"sys_exit_read"`)
+ enterClosePos := strings.Index(output, `"sys_enter_close"`)
+ if enterReadPos > exitReadPos || exitReadPos > enterClosePos {
+ t.Error("tracepoints should maintain source order")
+ }
+}
+
+func TestExtractTracepointsEmpty(t *testing.T) {
+ output, err := ExtractTracepoints(strings.NewReader("// no SEC lines here\n"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ requireContains(t, output, "var List = []string{")
+ requireContains(t, output, "}")
+}
+
+func TestExtractTracepointsPackageHeader(t *testing.T) {
+ output, err := ExtractTracepoints(strings.NewReader(sampleGeneratedC))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !strings.HasPrefix(output, "// Code generated - don't change manually!\npackage tracepoints\n") {
+ t.Errorf("unexpected header: %s", output[:60])
+ }
+}
diff --git a/internal/generate/typesgo.go b/internal/generate/typesgo.go
new file mode 100644
index 0000000..ee24845
--- /dev/null
+++ b/internal/generate/typesgo.go
@@ -0,0 +1,341 @@
+package generate
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "regexp"
+ "strings"
+)
+
+type CConstant struct {
+ Name string
+ Value string
+}
+
+type CMember struct {
+ TypeName string
+ FieldName string
+ ArraySize string
+}
+
+type CStruct struct {
+ Name string
+ Members []CMember
+}
+
+// ParseCTypesInput parses C struct definitions and #define constants.
+func ParseCTypesInput(r io.Reader) ([]CStruct, []CConstant, error) {
+ scanner := bufio.NewScanner(r)
+ var structs []CStruct
+ var constants []CConstant
+ var current *CStruct
+
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+
+ if strings.HasPrefix(line, "#define") {
+ c, ok := parseDefine(line)
+ if ok {
+ constants = append(constants, c)
+ }
+ continue
+ }
+
+ if isCommentLine(line) || line == "" {
+ continue
+ }
+
+ if strings.HasPrefix(line, "struct") && strings.HasSuffix(line, "{") {
+ name := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "struct"), "{"))
+ current = &CStruct{Name: name}
+ continue
+ }
+
+ if line == "};" && current != nil {
+ structs = append(structs, *current)
+ current = nil
+ continue
+ }
+
+ if current != nil {
+ m, ok := parseMember(line)
+ if ok {
+ current.Members = append(current.Members, m)
+ }
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return nil, nil, fmt.Errorf("scanning C input: %w", err)
+ }
+ return structs, constants, nil
+}
+
+// GenerateTypesGo produces the generated_types.go content.
+func GenerateTypesGo(structs []CStruct, constants []CConstant) string {
+ var b strings.Builder
+
+ b.WriteString("// Code generated - don't change manually!\n")
+ b.WriteString("package types\n\n")
+
+ writeTypeDefsAndMaps(&b, constants)
+
+ for _, c := range constants {
+ constType := ""
+ if strings.HasPrefix(c.Name, "SYS_") {
+ constType = " TraceId "
+ }
+ fmt.Fprintf(&b, "const %s%s = %s\n", c.Name, constType, c.Value)
+ }
+
+ for _, s := range structs {
+ writeGoStruct(&b, s)
+ }
+
+ return b.String()
+}
+
+// AddTypesImports inserts the import block needed by the generated types code.
+func AddTypesImports(code string) string {
+ needsImports := strings.Contains(code, "fmt.") ||
+ strings.Contains(code, "sync.") ||
+ strings.Contains(code, "binary.") ||
+ strings.Contains(code, "bytes.")
+
+ if !needsImports {
+ return code
+ }
+
+ importBlock := `import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "sync"
+)
+
+`
+ return strings.Replace(code, "package types\n\n", "package types\n\n"+importBlock, 1)
+}
+
+func parseDefine(line string) (CConstant, bool) {
+ fields := strings.Fields(line)
+ if len(fields) < 3 {
+ return CConstant{}, false
+ }
+ return CConstant{Name: fields[1], Value: fields[2]}, true
+}
+
+func isCommentLine(line string) bool {
+ return strings.HasPrefix(line, "//") || strings.HasPrefix(line, "/*") || strings.HasPrefix(line, "*")
+}
+
+var arrayRe = regexp.MustCompile(`^(\w+)\s+(\w+)\[(\w+)\];?$`)
+var simpleRe = regexp.MustCompile(`^(\w+)\s+(\w+);?$`)
+
+func parseMember(line string) (CMember, bool) {
+ line = strings.TrimSuffix(strings.TrimSpace(line), ";")
+ line = strings.TrimSpace(line)
+
+ if m := arrayRe.FindStringSubmatch(line + ";"); m != nil {
+ return CMember{TypeName: m[1], FieldName: m[2], ArraySize: m[3]}, true
+ }
+ if m := simpleRe.FindStringSubmatch(line + ";"); m != nil {
+ return CMember{TypeName: m[1], FieldName: m[2]}, true
+ }
+ return CMember{}, false
+}
+
+func writeTypeDefsAndMaps(b *strings.Builder, constants []CConstant) {
+ b.WriteString("type EventType uint32\n")
+ b.WriteString("type TraceId uint32\n\n")
+
+ var sysConstants []CConstant
+ for _, c := range constants {
+ if strings.HasPrefix(c.Name, "SYS_") {
+ sysConstants = append(sysConstants, c)
+ }
+ }
+
+ writeTraceIdMap(b, "traceId2String", sysConstants, func(name string) string {
+ return strings.ToLower(strings.TrimPrefix(name, "SYS_"))
+ })
+
+ writeTraceIdMap(b, "traceId2Name", sysConstants, func(name string) string {
+ s := strings.TrimPrefix(name, "SYS_ENTER_")
+ s = strings.TrimPrefix(s, "SYS_EXIT_")
+ return strings.ToLower(s)
+ })
+
+ writeTraceIdStringMethod(b)
+ writeTraceIdNameMethod(b)
+ b.WriteString("\n")
+}
+
+func writeTraceIdMap(b *strings.Builder, mapName string, constants []CConstant, transform func(string) string) {
+ fmt.Fprintf(b, "var %s = map[TraceId]string{\n\t", mapName)
+ entries := make([]string, 0, len(constants))
+ for _, c := range constants {
+ entries = append(entries, fmt.Sprintf("%s: %q", c.Value, transform(c.Name)))
+ }
+ b.WriteString(strings.Join(entries, ", "))
+ b.WriteString(",\n}\n\n")
+}
+
+func writeTraceIdStringMethod(b *strings.Builder) {
+ b.WriteString(`func (s TraceId) String() string {
+ str, ok := traceId2String[s]
+ if !ok {
+ panic(fmt.Sprintf("no string representation for trace ID %d found", s))
+ }
+ return str
+}
+
+`)
+}
+
+func writeTraceIdNameMethod(b *strings.Builder) {
+ b.WriteString(`func (s TraceId) Name() string {
+ str, ok := traceId2Name[s]
+ if !ok {
+ panic(fmt.Sprintf("no name for trace ID %d found", s))
+ }
+ return str
+}
+
+`)
+}
+
+func writeGoStruct(b *strings.Builder, s CStruct) {
+ goName := snakeToCamel(s.Name)
+ selfRef := strings.ToLower(goName[:1])
+
+ b.WriteString("\n")
+ fmt.Fprintf(b, "type %s struct {\n\t", goName)
+ memberDefs := make([]string, 0, len(s.Members))
+ for _, m := range s.Members {
+ memberDefs = append(memberDefs, goMemberDef(m))
+ }
+ b.WriteString(strings.Join(memberDefs, "; "))
+ b.WriteString(" \n}\n\n")
+
+ writeStringMethod(b, goName, selfRef, s.Members)
+ writeEqualsMethod(b, goName, selfRef, s.Members)
+ writeGetterMethods(b, goName, selfRef)
+
+ if strings.HasSuffix(goName, "Event") {
+ b.WriteString("\n")
+ writeSyncPool(b, goName, selfRef)
+ }
+}
+
+func goMemberDef(m CMember) string {
+ goField := snakeToCamel(m.FieldName)
+ goType := cTypeToGoType(m.TypeName)
+
+ if goField == "TraceId" {
+ goType = "TraceId"
+ }
+ if goField == "EventType" {
+ goType = "EventType"
+ }
+
+ if m.ArraySize != "" {
+ return fmt.Sprintf("%s [%s]%s", goField, m.ArraySize, goType)
+ }
+ return fmt.Sprintf("%s %s", goField, goType)
+}
+
+func writeStringMethod(b *strings.Builder, goName, selfRef string, members []CMember) {
+ fmtParts := make([]string, 0, len(members))
+ argParts := make([]string, 0, len(members))
+ for _, m := range members {
+ goField := snakeToCamel(m.FieldName)
+ fmtParts = append(fmtParts, goField+":%v")
+ ref := selfRef + "." + goField
+ if m.TypeName == "char" && m.ArraySize != "" {
+ ref = fmt.Sprintf("string(%s[:])", ref)
+ }
+ argParts = append(argParts, ref)
+ }
+ fmt.Fprintf(b, "func (%s %s) String() string {\n", selfRef, goName)
+ fmt.Fprintf(b, "\treturn fmt.Sprintf(\"%s\", %s)\n", strings.Join(fmtParts, " "), strings.Join(argParts, ", "))
+ b.WriteString("}\n\n")
+}
+
+func writeEqualsMethod(b *strings.Builder, goName, selfRef string, members []CMember) {
+ fmt.Fprintf(b, "func (%s %s) Equals(other any) bool {\n", selfRef, goName)
+ fmt.Fprintf(b, "\totherConcrete, ok := other.(*%s)\n", goName)
+ b.WriteString("\tif !ok {\n\t\treturn false\n\t}\n")
+ conds := make([]string, 0, len(members))
+ for _, m := range members {
+ goField := snakeToCamel(m.FieldName)
+ conds = append(conds, fmt.Sprintf("%s.%s == otherConcrete.%s", selfRef, goField, goField))
+ }
+ fmt.Fprintf(b, "\treturn %s\n", strings.Join(conds, " && "))
+ b.WriteString("}\n\n")
+}
+
+func writeGetterMethods(b *strings.Builder, goName, selfRef string) {
+ getters := []struct {
+ method string
+ returnType string
+ field string
+ }{
+ {"GetEventType", "EventType", "EventType"},
+ {"GetTraceId", "TraceId", "TraceId"},
+ {"GetPid", "uint32", "Pid"},
+ {"GetTid", "uint32", "Tid"},
+ {"GetTime", "uint64", "Time"},
+ }
+ for _, g := range getters {
+ fmt.Fprintf(b, "func (%s *%s) %s() %s {\n\treturn %s.%s\n}\n\n",
+ selfRef, goName, g.method, g.returnType, selfRef, g.field)
+ }
+}
+
+func writeSyncPool(b *strings.Builder, goName, selfRef string) {
+ fmt.Fprintf(b, "var poolOf%ss = sync.Pool{\n\tNew: func() interface{} { return &%s{} },\n}\n\n", goName, goName)
+ fmt.Fprintf(b, "func New%s(raw []byte) *%s {\n", goName, goName)
+ fmt.Fprintf(b, "\t%s := poolOf%ss.Get().(*%s)\n", selfRef, goName, goName)
+ fmt.Fprintf(b, "\tif err := binary.Read(bytes.NewReader(raw), binary.LittleEndian, %s); err != nil {\n", selfRef)
+ fmt.Fprintf(b, "\t\tfmt.Println(%s, raw, len(raw), err)\n", selfRef)
+ b.WriteString("\t\tpanic(raw)\n\t}\n")
+ fmt.Fprintf(b, "\treturn %s\n}\n\n", selfRef)
+
+ fmt.Fprintf(b, "func (%s *%s) Bytes() ([]byte, error) {\n", selfRef, goName)
+ b.WriteString("\tbuf := new(bytes.Buffer)\n")
+ fmt.Fprintf(b, "\terr := binary.Write(buf, binary.LittleEndian, %s)\n", selfRef)
+ b.WriteString("\tif err != nil {\n\t\treturn nil, err\n\t}\n")
+ b.WriteString("\treturn buf.Bytes(), nil\n}\n\n")
+
+ fmt.Fprintf(b, "func (%s *%s) Recycle() {\n\tpoolOf%ss.Put(%s)\n}\n", selfRef, goName, goName, selfRef)
+}
+
+func snakeToCamel(s string) string {
+ parts := strings.Split(s, "_")
+ for i, p := range parts {
+ if p == "" {
+ continue
+ }
+ parts[i] = strings.ToUpper(p[:1]) + p[1:]
+ }
+ return strings.Join(parts, "")
+}
+
+func cTypeToGoType(t string) string {
+ switch t {
+ case "char":
+ return "byte"
+ case "__s32":
+ return "int32"
+ case "__u32":
+ return "uint32"
+ case "__s64":
+ return "int64"
+ case "__u64":
+ return "uint64"
+ default:
+ return t
+ }
+}
diff --git a/internal/generate/typesgo_test.go b/internal/generate/typesgo_test.go
new file mode 100644
index 0000000..f1085b5
--- /dev/null
+++ b/internal/generate/typesgo_test.go
@@ -0,0 +1,257 @@
+package generate
+
+import (
+ "strings"
+ "testing"
+)
+
+const testTypesH = `//+build ignore
+
+#define MAX_FILENAME_LENGTH 256
+#define MAX_PROGNAME_LENGTH 16
+
+#define ENTER_OPEN_EVENT 1
+#define EXIT_OPEN_EVENT 2
+
+#define UNCLASSIFIED 0
+#define READ_CLASSIFIED 1
+
+struct open_event {
+ __u32 event_type;
+ __u32 trace_id;
+ __u64 time;
+ __u32 pid;
+ __u32 tid;
+ __s32 flags;
+ char filename[MAX_FILENAME_LENGTH];
+ char comm[MAX_PROGNAME_LENGTH];
+};
+
+struct null_event {
+ __u32 event_type;
+ __u32 trace_id;
+ __u64 time;
+ __u32 pid;
+ __u32 tid;
+};
+
+struct fd_event {
+ __u32 event_type;
+ __u32 trace_id;
+ __u64 time;
+ __u32 pid;
+ __u32 tid;
+ __s32 fd;
+};
+`
+
+const testDefines = `#define SYS_ENTER_OPENAT 784
+#define SYS_EXIT_OPENAT 783
+`
+
+func TestParseCTypesInput(t *testing.T) {
+ input := testTypesH + testDefines
+ structs, constants, err := ParseCTypesInput(strings.NewReader(input))
+ if err != nil {
+ t.Fatalf("ParseCTypesInput failed: %v", err)
+ }
+ if len(structs) != 3 {
+ t.Fatalf("expected 3 structs, got %d", len(structs))
+ }
+ if structs[0].Name != "open_event" {
+ t.Errorf("first struct name = %q, want open_event", structs[0].Name)
+ }
+ if len(structs[0].Members) != 8 {
+ t.Errorf("open_event members = %d, want 8", len(structs[0].Members))
+ }
+
+ // Check array member
+ filenameMember := structs[0].Members[6]
+ if filenameMember.FieldName != "filename" || filenameMember.ArraySize != "MAX_FILENAME_LENGTH" {
+ t.Errorf("filename member = %+v", filenameMember)
+ }
+
+ // Check constants
+ expectedConsts := 8 // MAX_FILENAME_LENGTH, MAX_PROGNAME_LENGTH, 2 event types, 2 classified, 2 SYS_
+ if len(constants) != expectedConsts {
+ t.Errorf("constants = %d, want %d", len(constants), expectedConsts)
+ }
+}
+
+func TestParseCStructMembers(t *testing.T) {
+ input := testTypesH
+ structs, _, err := ParseCTypesInput(strings.NewReader(input))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ fd := structs[2] // fd_event
+ if fd.Name != "fd_event" {
+ t.Fatalf("third struct = %q, want fd_event", fd.Name)
+ }
+ if len(fd.Members) != 6 {
+ t.Fatalf("fd_event members = %d, want 6", len(fd.Members))
+ }
+ if fd.Members[5].TypeName != "__s32" || fd.Members[5].FieldName != "fd" {
+ t.Errorf("fd member = %+v", fd.Members[5])
+ }
+}
+
+func TestSnakeToCamel(t *testing.T) {
+ tests := []struct {
+ input, want string
+ }{
+ {"open_event", "OpenEvent"},
+ {"trace_id", "TraceId"},
+ {"event_type", "EventType"},
+ {"fd", "Fd"},
+ {"pid", "Pid"},
+ {"open_by_handle_at_event", "OpenByHandleAtEvent"},
+ {"filename", "Filename"},
+ }
+ for _, tt := range tests {
+ if got := snakeToCamel(tt.input); got != tt.want {
+ t.Errorf("snakeToCamel(%q) = %q, want %q", tt.input, got, tt.want)
+ }
+ }
+}
+
+func TestCTypeToGoType(t *testing.T) {
+ tests := []struct {
+ input, want string
+ }{
+ {"char", "byte"},
+ {"__s32", "int32"},
+ {"__u32", "uint32"},
+ {"__s64", "int64"},
+ {"__u64", "uint64"},
+ }
+ for _, tt := range tests {
+ if got := cTypeToGoType(tt.input); got != tt.want {
+ t.Errorf("cTypeToGoType(%q) = %q, want %q", tt.input, got, tt.want)
+ }
+ }
+}
+
+func TestGenerateTypesGoStructs(t *testing.T) {
+ input := testTypesH + testDefines
+ structs, constants, err := ParseCTypesInput(strings.NewReader(input))
+ if err != nil {
+ t.Fatal(err)
+ }
+ output := GenerateTypesGo(structs, constants)
+
+ requireContains(t, output, "type OpenEvent struct {")
+ requireContains(t, output, "EventType EventType")
+ requireContains(t, output, "TraceId TraceId")
+ requireContains(t, output, "Time uint64")
+ requireContains(t, output, "Pid uint32")
+ requireContains(t, output, "Flags int32")
+ requireContains(t, output, "Filename [MAX_FILENAME_LENGTH]byte")
+ requireContains(t, output, "Comm [MAX_PROGNAME_LENGTH]byte")
+}
+
+func TestGenerateTypesGoMethods(t *testing.T) {
+ input := testTypesH + testDefines
+ structs, constants, err := ParseCTypesInput(strings.NewReader(input))
+ if err != nil {
+ t.Fatal(err)
+ }
+ output := GenerateTypesGo(structs, constants)
+
+ // String method with char array conversion
+ requireContains(t, output, `string(o.Filename[:])`)
+ requireContains(t, output, `string(o.Comm[:])`)
+ requireContains(t, output, "func (o OpenEvent) String() string")
+
+ // Equals method
+ requireContains(t, output, "func (o OpenEvent) Equals(other any) bool")
+ requireContains(t, output, "other.(*OpenEvent)")
+
+ // Getters
+ requireContains(t, output, "func (o *OpenEvent) GetEventType() EventType")
+ requireContains(t, output, "func (o *OpenEvent) GetTraceId() TraceId")
+ requireContains(t, output, "func (o *OpenEvent) GetPid() uint32")
+ requireContains(t, output, "func (o *OpenEvent) GetTid() uint32")
+ requireContains(t, output, "func (o *OpenEvent) GetTime() uint64")
+}
+
+func TestGenerateTypesGoSyncPool(t *testing.T) {
+ input := testTypesH + testDefines
+ structs, constants, err := ParseCTypesInput(strings.NewReader(input))
+ if err != nil {
+ t.Fatal(err)
+ }
+ output := GenerateTypesGo(structs, constants)
+
+ requireContains(t, output, "var poolOfOpenEvents = sync.Pool{")
+ requireContains(t, output, "func NewOpenEvent(raw []byte) *OpenEvent")
+ requireContains(t, output, "func (o *OpenEvent) Bytes() ([]byte, error)")
+ requireContains(t, output, "func (o *OpenEvent) Recycle()")
+ requireContains(t, output, "poolOfOpenEvents.Put(o)")
+
+ requireContains(t, output, "var poolOfNullEvents = sync.Pool{")
+ requireContains(t, output, "func NewNullEvent(raw []byte) *NullEvent")
+
+ requireContains(t, output, "var poolOfFdEvents = sync.Pool{")
+ requireContains(t, output, "func NewFdEvent(raw []byte) *FdEvent")
+}
+
+func TestGenerateTypesGoConstants(t *testing.T) {
+ input := testTypesH + testDefines
+ structs, constants, err := ParseCTypesInput(strings.NewReader(input))
+ if err != nil {
+ t.Fatal(err)
+ }
+ output := GenerateTypesGo(structs, constants)
+
+ requireContains(t, output, "const MAX_FILENAME_LENGTH = 256")
+ requireContains(t, output, "const MAX_PROGNAME_LENGTH = 16")
+ requireContains(t, output, "const ENTER_OPEN_EVENT = 1")
+ requireContains(t, output, "const SYS_ENTER_OPENAT TraceId = 784")
+ requireContains(t, output, "const SYS_EXIT_OPENAT TraceId = 783")
+}
+
+func TestGenerateTypesGoTraceIdMaps(t *testing.T) {
+ input := testTypesH + testDefines
+ structs, constants, err := ParseCTypesInput(strings.NewReader(input))
+ if err != nil {
+ t.Fatal(err)
+ }
+ output := GenerateTypesGo(structs, constants)
+
+ requireContains(t, output, "type EventType uint32")
+ requireContains(t, output, "type TraceId uint32")
+ requireContains(t, output, "var traceId2String = map[TraceId]string{")
+ requireContains(t, output, `784: "enter_openat"`)
+ requireContains(t, output, `783: "exit_openat"`)
+ requireContains(t, output, "var traceId2Name = map[TraceId]string{")
+ requireContains(t, output, `784: "openat"`)
+ requireContains(t, output, `783: "openat"`)
+}
+
+func TestGenerateTypesGoTraceIdMethods(t *testing.T) {
+ input := testTypesH + testDefines
+ structs, constants, err := ParseCTypesInput(strings.NewReader(input))
+ if err != nil {
+ t.Fatal(err)
+ }
+ output := GenerateTypesGo(structs, constants)
+
+ requireContains(t, output, "func (s TraceId) String() string")
+ requireContains(t, output, "func (s TraceId) Name() string")
+ requireContains(t, output, `panic(fmt.Sprintf("no string representation for trace ID %d found", s))`)
+}
+
+func TestGenerateTypesGoPackageDecl(t *testing.T) {
+ input := testTypesH
+ structs, constants, err := ParseCTypesInput(strings.NewReader(input))
+ if err != nil {
+ t.Fatal(err)
+ }
+ output := GenerateTypesGo(structs, constants)
+
+ if !strings.HasPrefix(output, "// Code generated - don't change manually!\npackage types\n") {
+ t.Errorf("unexpected package header: %s", output[:80])
+ }
+}
diff --git a/internal/tracepoints/Makefile b/internal/tracepoints/Makefile
deleted file mode 100644
index fd4a237..0000000
--- a/internal/tracepoints/Makefile
+++ /dev/null
@@ -1,8 +0,0 @@
-generate: generate_tracepoints
-
-.PHONY: generate_tracepoints
-generate_tracepoints:
- cat ../c/generated_tracepoints.c \
- | go run ../../cmd/generate tracepoints-go \
- | goimports | gofmt \
- > ./generated_tracepoints.go
diff --git a/internal/types/Makefile b/internal/types/Makefile
deleted file mode 100644
index 0c00898..0000000
--- a/internal/types/Makefile
+++ /dev/null
@@ -1,13 +0,0 @@
-generate: generate_types
-
-.PHONY: generate_types
-generate_types:
- ( cat ../c/types.h; grep -h '^#define' ../c/generated_tracepoints.c ) \
- | go run ../../cmd/generate types-go \
- | goimports | gofmt \
- > ./generated_types.go
-
-.PHONY: generate_types_stdout
-generate_types_stdout:
- ( cat ../c/types.h; grep -h '^#define' ../c/generated_tracepoints.c ) \
- | go run ../../cmd/generate types-go