diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-21 11:51:01 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-21 11:51:01 +0200 |
| commit | 6c912a9d72ae2a43923c638538d320e6bf585952 (patch) | |
| tree | 727f66d158210e01abf8c18a83ef4db6066e0c1a | |
| parent | 32136b8cb18944157ff1f361bc0755f6b627fd47 (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.md | 27 | ||||
| -rw-r--r-- | Magefile.go | 424 | ||||
| -rw-r--r-- | Makefile | 68 | ||||
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | go.sum | 2 | ||||
| -rw-r--r-- | internal/c/Makefile | 50 | ||||
| -rw-r--r-- | internal/c/generated_tracepoints_result.txt | 164 | ||||
| -rw-r--r-- | internal/generate/bpfhandler.go | 152 | ||||
| -rw-r--r-- | internal/generate/classify.go | 214 | ||||
| -rw-r--r-- | internal/generate/classify_test.go | 332 | ||||
| -rw-r--r-- | internal/generate/codegen.go | 152 | ||||
| -rw-r--r-- | internal/generate/codegen_test.go | 263 | ||||
| -rw-r--r-- | internal/generate/format.go | 145 | ||||
| -rw-r--r-- | internal/generate/format_test.go | 174 | ||||
| -rw-r--r-- | internal/generate/retclassify_test.go | 59 | ||||
| -rw-r--r-- | internal/generate/testdata.go | 666 | ||||
| -rw-r--r-- | internal/generate/tracepointsgo.go | 43 | ||||
| -rw-r--r-- | internal/generate/tracepointsgo_test.go | 92 | ||||
| -rw-r--r-- | internal/generate/typesgo.go | 341 | ||||
| -rw-r--r-- | internal/generate/typesgo_test.go | 257 | ||||
| -rw-r--r-- | internal/tracepoints/Makefile | 8 | ||||
| -rw-r--r-- | internal/types/Makefile | 13 |
22 files changed, 3409 insertions, 239 deletions
@@ -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 @@ -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 @@ -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 |
