From 4500480fc65dfa24d4eb1548c41c6f9503ea9ce2 Mon Sep 17 00:00:00 2001 From: Jonathan Perry Date: Fri, 18 Apr 2025 17:32:17 -0500 Subject: [PATCH 1/6] bpf2go: support multiple source files Signed-off-by: Jonathan Perry --- cmd/bpf2go/main.go | 165 ++++++++++++++++++++++++++----------- cmd/bpf2go/main_test.go | 2 +- cmd/bpf2go/makedep.go | 45 ++++++++++ cmd/bpf2go/makedep_test.go | 32 +++++++ 4 files changed, 196 insertions(+), 48 deletions(-) diff --git a/cmd/bpf2go/main.go b/cmd/bpf2go/main.go index 3732333f6..9e46bdc5c 100644 --- a/cmd/bpf2go/main.go +++ b/cmd/bpf2go/main.go @@ -21,13 +21,14 @@ import ( "github.com/cilium/ebpf/cmd/bpf2go/gen" ) -const helpText = `Usage: %[1]s [options] [-- ] +const helpText = `Usage: %[1]s [options] [-- ] ident is used as the stem of all generated Go types and functions, and must be a valid Go identifier. -source is a single C file that is compiled using the specified compiler -(usually some version of clang). +source files are C files that are compiled using the specified compiler +(usually some version of clang) and linked together into a single +BPF program. You can pass options to the compiler by appending them after a '--' argument or by supplying -cflags. Flags passed as arguments take precedence @@ -60,8 +61,8 @@ func run(stdout io.Writer, args []string) (err error) { type bpf2go struct { stdout io.Writer verbose bool - // Absolute path to a .c file. - sourceFile string + // Absolute paths to .c files. + sourceFiles []string // Absolute path to a directory where .go are written outputDir string // Alternative output stem. If empty, identStem is used. @@ -186,13 +187,18 @@ func newB2G(stdout io.Writer, args []string) (*bpf2go, error) { b2g.identStem = args[0] - sourceFile, err := filepath.Abs(args[1]) - if err != nil { - return nil, err + sourceFiles := args[1:] + b2g.sourceFiles = make([]string, len(sourceFiles)) + for i, source := range sourceFiles { + absPath, err := filepath.Abs(source) + if err != nil { + return nil, fmt.Errorf("convert source file to absolute path: %w", err) + } + b2g.sourceFiles[i] = absPath } - b2g.sourceFile = sourceFile if b2g.makeBase != "" { + var err error b2g.makeBase, err = filepath.Abs(b2g.makeBase) if err != nil { return nil, err @@ -298,10 +304,13 @@ func getBool(key string, defaultVal bool) bool { } func (b2g *bpf2go) convertAll() (err error) { - if _, err := os.Stat(b2g.sourceFile); os.IsNotExist(err) { - return fmt.Errorf("file %s doesn't exist", b2g.sourceFile) - } else if err != nil { - return err + // Check all source files exist + for _, source := range b2g.sourceFiles { + if _, err := os.Stat(source); os.IsNotExist(err) { + return fmt.Errorf("file %s doesn't exist", source) + } else if err != nil { + return err + } } if !b2g.disableStripping { @@ -320,6 +329,65 @@ func (b2g *bpf2go) convertAll() (err error) { return nil } +// compileOne compiles a single source file and returns the temporary object file name +// and any dependencies found during compilation. +func (b2g *bpf2go) compileOne(tgt gen.Target, cwd, source, objFileName, outputStem string) (tmpObjFileName string, deps []dependency, err error) { + var depInput *os.File + cFlags := slices.Clone(b2g.cFlags) + if b2g.makeBase != "" { + depInput, err = os.CreateTemp("", "bpf2go") + if err != nil { + return "", nil, err + } + defer depInput.Close() + defer os.Remove(depInput.Name()) + + cFlags = append(cFlags, + // Output dependency information. + "-MD", + // Create phony targets so that deleting a dependency doesn't + // break the build. + "-MP", + // Write it to temporary file + "-MF"+depInput.Name(), + ) + } + + // Compile to final object file name first + err = gen.Compile(gen.CompileArgs{ + CC: b2g.cc, + Strip: b2g.strip, + DisableStripping: b2g.disableStripping, + Flags: cFlags, + Target: tgt, + Workdir: cwd, + Source: source, + Dest: objFileName, + }) + if err != nil { + return "", nil, fmt.Errorf("compile %s: %w", source, err) + } + + // Move the compiled object to a temporary file + tmpObjFileName = filepath.Join(filepath.Dir(objFileName), fmt.Sprintf("%s_%s_%s.o", + outputStem, + filepath.Base(source), + tgt.Suffix())) + if err := os.Rename(objFileName, tmpObjFileName); err != nil { + return "", nil, fmt.Errorf("move object file: %w", err) + } + + // Parse dependencies if enabled + if b2g.makeBase != "" { + deps, err = parseDependencies(cwd, depInput) + if err != nil { + return "", nil, fmt.Errorf("parse dependencies for %s: %w", source, err) + } + } + + return tmpObjFileName, deps, nil +} + func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) { removeOnError := func(f *os.File) { if err != nil { @@ -341,6 +409,7 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) { } objFileName := filepath.Join(absOutPath, stem+".o") + goFileName := filepath.Join(absOutPath, stem+".go") cwd, err := os.Getwd() if err != nil { @@ -354,39 +423,43 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) { return fmt.Errorf("remove obsolete output: %w", err) } - var depInput *os.File - cFlags := slices.Clone(b2g.cFlags) - if b2g.makeBase != "" { - depInput, err = os.CreateTemp("", "bpf2go") + // Compile each source file + var allDeps []dependency + var tmpObjFileNames []string + for _, source := range b2g.sourceFiles { + tmpObjFileName, deps, err := b2g.compileOne(tgt, cwd, source, objFileName, outputStem) if err != nil { return err } - defer depInput.Close() - defer os.Remove(depInput.Name()) + tmpObjFileNames = append(tmpObjFileNames, tmpObjFileName) - cFlags = append(cFlags, - // Output dependency information. - "-MD", - // Create phony targets so that deleting a dependency doesn't - // break the build. - "-MP", - // Write it to temporary file - "-MF"+depInput.Name(), - ) + if len(deps) > 0 { + // There is always at least a dependency for the main file. + deps[0].file = goFileName + allDeps = append(allDeps, deps...) + } } - err = gen.Compile(gen.CompileArgs{ - CC: b2g.cc, - Strip: b2g.strip, - DisableStripping: b2g.disableStripping, - Flags: cFlags, - Target: tgt, - Workdir: cwd, - Source: b2g.sourceFile, - Dest: objFileName, - }) - if err != nil { - return fmt.Errorf("compile: %w", err) + // If we have multiple object files, link them together + if len(tmpObjFileNames) > 1 { + cmd := exec.Command("bpftool", "gen", "object", objFileName) + cmd.Args = append(cmd.Args, tmpObjFileNames...) + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("link object files: %w", err) + } + } else { + // Single file, just rename it back to the final name + if err := os.Rename(tmpObjFileNames[0], objFileName); err != nil { + return fmt.Errorf("rename object file: %w", err) + } + } + + // Clean up temporary object files + for _, tmpObj := range tmpObjFileNames { + if err := os.Remove(tmpObj); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("remove temporary object file: %w", err) + } } if b2g.disableStripping { @@ -428,7 +501,6 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) { } // Write out generated go - goFileName := filepath.Join(absOutPath, stem+".go") goFile, err := os.Create(goFileName) if err != nil { return err @@ -456,9 +528,10 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) { return } - deps, err := parseDependencies(cwd, depInput) - if err != nil { - return fmt.Errorf("can't read dependency information: %s", err) + // Merge dependencies if we have multiple source files + var finalDeps []dependency + if len(allDeps) > 0 { + finalDeps = mergeDependencies(allDeps) } depFileName := goFileName + ".d" @@ -468,9 +541,7 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) { } defer depOutput.Close() - // There is always at least a dependency for the main file. - deps[0].file = goFileName - if err := adjustDependencies(depOutput, b2g.makeBase, deps); err != nil { + if err := adjustDependencies(depOutput, b2g.makeBase, finalDeps); err != nil { return fmt.Errorf("can't adjust dependency information: %s", err) } diff --git a/cmd/bpf2go/main_test.go b/cmd/bpf2go/main_test.go index a7c960585..0a67c7780 100644 --- a/cmd/bpf2go/main_test.go +++ b/cmd/bpf2go/main_test.go @@ -159,7 +159,7 @@ func TestConvertGOARCH(t *testing.T) { identStem: "test", cc: testutils.ClangBin(t), disableStripping: true, - sourceFile: tmp + "/test.c", + sourceFiles: []string{tmp + "/test.c"}, outputDir: tmp, } diff --git a/cmd/bpf2go/makedep.go b/cmd/bpf2go/makedep.go index b5d34b7c4..ccb6d6780 100644 --- a/cmd/bpf2go/makedep.go +++ b/cmd/bpf2go/makedep.go @@ -106,3 +106,48 @@ func parseDependencies(baseDir string, in io.Reader) ([]dependency, error) { } return deps, nil } + +// mergeDependencies combines multiple dependency slices into one, merging prerequisites +// for files that appear in multiple slices. +func mergeDependencies(depsSlices ...[]dependency) []dependency { + // Map to track merged dependencies by file + merged := make(map[string][]string) + + // Process each slice of dependencies + for _, deps := range depsSlices { + for _, dep := range deps { + // If we've seen this file before, merge prerequisites + if existing, ok := merged[dep.file]; ok { + // Combine prerequisites, avoiding duplicates + prereqs := make(map[string]struct{}) + for _, p := range existing { + prereqs[p] = struct{}{} + } + for _, p := range dep.prerequisites { + prereqs[p] = struct{}{} + } + + // Convert back to slice + merged[dep.file] = make([]string, 0, len(prereqs)) + for p := range prereqs { + merged[dep.file] = append(merged[dep.file], p) + } + } else { + // First time seeing this file, just copy prerequisites + merged[dep.file] = make([]string, len(dep.prerequisites)) + copy(merged[dep.file], dep.prerequisites) + } + } + } + + // Convert map back to slice + result := make([]dependency, 0, len(merged)) + for file, prereqs := range merged { + result = append(result, dependency{ + file: file, + prerequisites: prereqs, + }) + } + + return result +} diff --git a/cmd/bpf2go/makedep_test.go b/cmd/bpf2go/makedep_test.go index 5f879883b..de99f12c8 100644 --- a/cmd/bpf2go/makedep_test.go +++ b/cmd/bpf2go/makedep_test.go @@ -5,6 +5,7 @@ package main import ( "bytes" "reflect" + "sort" "strings" "testing" ) @@ -59,3 +60,34 @@ nothing: t.Error("Output doesn't match") } } + +func TestMergeDependencies(t *testing.T) { + deps1 := []dependency{ + {"/foo/main.c", []string{"/foo/bar.h", "/foo/baz.h"}}, + {"/foo/other.c", []string{"/foo/bar.h"}}, + } + + deps2 := []dependency{ + {"/foo/main.c", []string{"/foo/qux.h"}}, + {"/foo/third.c", []string{"/foo/bar.h"}}, + } + + merged := mergeDependencies(deps1, deps2) + + // Sort merged dependencies for stable comparison + sort.Slice(merged, func(i, j int) bool { + return merged[i].file < merged[j].file + }) + + want := []dependency{ + {"/foo/main.c", []string{"/foo/bar.h", "/foo/baz.h", "/foo/qux.h"}}, + {"/foo/other.c", []string{"/foo/bar.h"}}, + {"/foo/third.c", []string{"/foo/bar.h"}}, + } + + if !reflect.DeepEqual(merged, want) { + t.Logf("Have: %#v", merged) + t.Logf("Want: %#v", want) + t.Error("Merged dependencies don't match") + } +} From 38f89b69762500abeecb0598857b8e304a6d7cf0 Mon Sep 17 00:00:00 2001 From: Jonathan Perry Date: Fri, 18 Apr 2025 20:58:16 -0500 Subject: [PATCH 2/6] bpf2go: refactor object file linking into `gen` and add tests - replace the direct command execution for linking multiple BPF object files with a new `Link` function in the `gen` package. - add unit tests for linking Signed-off-by: Jonathan Perry --- cmd/bpf2go/gen/link.go | 34 ++++++++++++++++++ cmd/bpf2go/gen/link_test.go | 72 +++++++++++++++++++++++++++++++++++++ cmd/bpf2go/main.go | 9 ++--- 3 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 cmd/bpf2go/gen/link.go create mode 100644 cmd/bpf2go/gen/link_test.go diff --git a/cmd/bpf2go/gen/link.go b/cmd/bpf2go/gen/link.go new file mode 100644 index 000000000..0dadb1704 --- /dev/null +++ b/cmd/bpf2go/gen/link.go @@ -0,0 +1,34 @@ +//go:build !windows + +package gen + +import ( + "fmt" + "os" + "os/exec" +) + +// LinkArgs specifies the arguments for linking multiple BPF object files together. +type LinkArgs struct { + // Destination object file name + Dest string + // Source object files to link together + Sources []string +} + +// Link combines multiple BPF object files into a single object file. +func Link(args LinkArgs) error { + if len(args.Sources) == 0 { + return fmt.Errorf("no source files to link") + } + + cmd := exec.Command("bpftool", "gen", "object", args.Dest) + cmd.Args = append(cmd.Args, args.Sources...) + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("bpftool gen object returned error: %w", err) + } + + return nil +} diff --git a/cmd/bpf2go/gen/link_test.go b/cmd/bpf2go/gen/link_test.go new file mode 100644 index 000000000..10f2cc420 --- /dev/null +++ b/cmd/bpf2go/gen/link_test.go @@ -0,0 +1,72 @@ +//go:build !windows + +package gen + +import ( + "os" + "path/filepath" + "testing" + + "github.com/cilium/ebpf/internal/testutils" +) + +const ( + func1 = `__attribute__((section("socket"), used)) int func1() { return 1; }` + func2 = `__attribute__((section("socket"), used)) int func2() { return 2; }` +) + +func TestLink(t *testing.T) { + if testing.Short() { + t.SkipNow() + } + + dir := t.TempDir() + mustWriteFile(t, dir, "func1.c", func1) + mustWriteFile(t, dir, "func2.c", func2) + + // Compile first object + obj1 := filepath.Join(dir, "func1.o") + err := Compile(CompileArgs{ + CC: testutils.ClangBin(t), + DisableStripping: true, + Workdir: dir, + Source: filepath.Join(dir, "func1.c"), + Dest: obj1, + }) + if err != nil { + t.Fatal("Can't compile func1:", err) + } + + // Compile second object + obj2 := filepath.Join(dir, "func2.o") + err = Compile(CompileArgs{ + CC: testutils.ClangBin(t), + DisableStripping: true, + Workdir: dir, + Source: filepath.Join(dir, "func2.c"), + Dest: obj2, + }) + if err != nil { + t.Fatal("Can't compile func2:", err) + } + + // Link both objects + linked := filepath.Join(dir, "linked.o") + err = Link(LinkArgs{ + Dest: linked, + Sources: []string{obj1, obj2}, + }) + if err != nil { + t.Fatal("Can't link objects:", err) + } + + // Verify the linked file exists and has content + stat, err := os.Stat(linked) + if err != nil { + t.Fatal("Can't stat linked file:", err) + } + + if stat.Size() == 0 { + t.Error("Linked file is empty") + } +} diff --git a/cmd/bpf2go/main.go b/cmd/bpf2go/main.go index 9e46bdc5c..db69d5b2d 100644 --- a/cmd/bpf2go/main.go +++ b/cmd/bpf2go/main.go @@ -442,10 +442,11 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) { // If we have multiple object files, link them together if len(tmpObjFileNames) > 1 { - cmd := exec.Command("bpftool", "gen", "object", objFileName) - cmd.Args = append(cmd.Args, tmpObjFileNames...) - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { + err = gen.Link(gen.LinkArgs{ + Dest: objFileName, + Sources: tmpObjFileNames, + }) + if err != nil { return fmt.Errorf("link object files: %w", err) } } else { From 0104501e98f01b136ee891aa4c584e240fc5b55f Mon Sep 17 00:00:00 2001 From: Jonathan Perry Date: Fri, 18 Apr 2025 21:14:10 -0500 Subject: [PATCH 3/6] bpf2go: refactor compileOne function to simplify object file handling - Moved the temporary object renaming logic from compileOne() to convert() - Simplified naming and cleanup Signed-off-by: Jonathan Perry --- cmd/bpf2go/main.go | 54 ++++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/cmd/bpf2go/main.go b/cmd/bpf2go/main.go index db69d5b2d..854de4253 100644 --- a/cmd/bpf2go/main.go +++ b/cmd/bpf2go/main.go @@ -329,15 +329,14 @@ func (b2g *bpf2go) convertAll() (err error) { return nil } -// compileOne compiles a single source file and returns the temporary object file name -// and any dependencies found during compilation. -func (b2g *bpf2go) compileOne(tgt gen.Target, cwd, source, objFileName, outputStem string) (tmpObjFileName string, deps []dependency, err error) { +// compileOne compiles a single source file and returns any dependencies found during compilation. +func (b2g *bpf2go) compileOne(tgt gen.Target, cwd, source, objFileName string) (deps []dependency, err error) { var depInput *os.File cFlags := slices.Clone(b2g.cFlags) if b2g.makeBase != "" { depInput, err = os.CreateTemp("", "bpf2go") if err != nil { - return "", nil, err + return nil, err } defer depInput.Close() defer os.Remove(depInput.Name()) @@ -353,7 +352,7 @@ func (b2g *bpf2go) compileOne(tgt gen.Target, cwd, source, objFileName, outputSt ) } - // Compile to final object file name first + // Compile to final object file name err = gen.Compile(gen.CompileArgs{ CC: b2g.cc, Strip: b2g.strip, @@ -365,27 +364,18 @@ func (b2g *bpf2go) compileOne(tgt gen.Target, cwd, source, objFileName, outputSt Dest: objFileName, }) if err != nil { - return "", nil, fmt.Errorf("compile %s: %w", source, err) - } - - // Move the compiled object to a temporary file - tmpObjFileName = filepath.Join(filepath.Dir(objFileName), fmt.Sprintf("%s_%s_%s.o", - outputStem, - filepath.Base(source), - tgt.Suffix())) - if err := os.Rename(objFileName, tmpObjFileName); err != nil { - return "", nil, fmt.Errorf("move object file: %w", err) + return nil, fmt.Errorf("compile %s: %w", source, err) } // Parse dependencies if enabled if b2g.makeBase != "" { deps, err = parseDependencies(cwd, depInput) if err != nil { - return "", nil, fmt.Errorf("parse dependencies for %s: %w", source, err) + return nil, fmt.Errorf("parse dependencies for %s: %w", source, err) } } - return tmpObjFileName, deps, nil + return deps, nil } func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) { @@ -427,17 +417,31 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) { var allDeps []dependency var tmpObjFileNames []string for _, source := range b2g.sourceFiles { - tmpObjFileName, deps, err := b2g.compileOne(tgt, cwd, source, objFileName, outputStem) + deps, err := b2g.compileOne(tgt, cwd, source, objFileName) if err != nil { return err } - tmpObjFileNames = append(tmpObjFileNames, tmpObjFileName) if len(deps) > 0 { // There is always at least a dependency for the main file. deps[0].file = goFileName allDeps = append(allDeps, deps...) } + + // For multiple source files, we need to move the compiled object to a temporary file + if len(b2g.sourceFiles) > 1 { + tmpObj, err := os.CreateTemp("", filepath.Base(source)) + if err != nil { + return fmt.Errorf("create temporary object file: %w", err) + } + tmpObj.Close() + defer os.Remove(tmpObj.Name()) + + if err := os.Rename(objFileName, tmpObj.Name()); err != nil { + return fmt.Errorf("move object file: %w", err) + } + tmpObjFileNames = append(tmpObjFileNames, tmpObj.Name()) + } } // If we have multiple object files, link them together @@ -449,18 +453,6 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) { if err != nil { return fmt.Errorf("link object files: %w", err) } - } else { - // Single file, just rename it back to the final name - if err := os.Rename(tmpObjFileNames[0], objFileName); err != nil { - return fmt.Errorf("rename object file: %w", err) - } - } - - // Clean up temporary object files - for _, tmpObj := range tmpObjFileNames { - if err := os.Remove(tmpObj); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("remove temporary object file: %w", err) - } } if b2g.disableStripping { From 438ad985640f68d0f0517c0cbae18d226f25ab9b Mon Sep 17 00:00:00 2001 From: Jonathan Perry Date: Fri, 18 Apr 2025 21:43:24 -0500 Subject: [PATCH 4/6] bpf2go: add test for handling multiple source files Signed-off-by: Jonathan Perry --- cmd/bpf2go/main_test.go | 85 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/cmd/bpf2go/main_test.go b/cmd/bpf2go/main_test.go index 0a67c7780..40a98a373 100644 --- a/cmd/bpf2go/main_test.go +++ b/cmd/bpf2go/main_test.go @@ -406,6 +406,91 @@ func TestParseArgs(t *testing.T) { }) } +func TestMultipleSourceFiles(t *testing.T) { + modRoot, err := filepath.Abs("../..") + if err != nil { + t.Fatal("Can't get module root:", err) + } + + if _, err := os.Stat(filepath.Join(modRoot, "go.mod")); os.IsNotExist(err) { + t.Fatal("No go.mod file in", modRoot) + } + + dir := t.TempDir() + + // Create two source files with different functions + mustWriteFile(t, dir, "func1.c", `__attribute__((section("socket"), used)) int func1() { return 1; }`) + mustWriteFile(t, dir, "func2.c", `__attribute__((section("socket"), used)) int func2() { return 2; }`) + + // Set up module directory + modDir := t.TempDir() + execInModule := func(name string, args ...string) { + t.Helper() + cmd := exec.Command(name, args...) + cmd.Dir = modDir + if out, err := cmd.CombinedOutput(); err != nil { + if out := string(out); out != "" { + t.Log(out) + } + t.Fatalf("Can't execute %s: %v", name, args) + } + } + + // Initialize module + execInModule("go", "mod", "init", "bpf2go-test") + execInModule("go", "mod", "edit", + fmt.Sprintf("-require=%s@v0.0.0", internal.CurrentModule), + fmt.Sprintf("-replace=%s=%s", internal.CurrentModule, modRoot), + ) + + // Run bpf2go with both source files + err = run(io.Discard, []string{ + "-go-package", "main", + "-output-dir", modDir, + "-cc", testutils.ClangBin(t), + "-target", "bpfel,bpfeb", + "bar", + filepath.Join(dir, "func1.c"), + filepath.Join(dir, "func2.c"), + }) + + if err != nil { + t.Fatal("Can't run bpf2go with multiple source files:", err) + } + + // Create a main.go that uses both functions + mustWriteFile(t, modDir, "main.go", + ` +package main + +func main() { + var obj barObjects + println(obj.Func1) + println(obj.Func2) +}`) + + // Test compilation for different architectures + for _, arch := range []string{"amd64", "arm64", "s390x"} { + t.Run(arch, func(t *testing.T) { + goBuild := exec.Command("go", "build", "-mod=mod", "-o", "/dev/null") + goBuild.Dir = modDir + goBuild.Env = append(os.Environ(), + "GOOS=linux", + "GOARCH="+arch, + "GOPROXY=off", + "GOSUMDB=off", + ) + out, err := goBuild.CombinedOutput() + if err != nil { + if out := string(out); out != "" { + t.Log(out) + } + t.Error("Can't compile package:", err) + } + }) + } +} + func mustWriteFile(tb testing.TB, dir, name, contents string) { tb.Helper() tmpFile := filepath.Join(dir, name) From 70844c7d435ce9331378f4c398ba189455160ff1 Mon Sep 17 00:00:00 2001 From: Jonathan Perry Date: Fri, 18 Apr 2025 21:51:43 -0500 Subject: [PATCH 5/6] bpf2go: fix tests for multiple source files to use architecture-specific endianness handling Signed-off-by: Jonathan Perry --- cmd/bpf2go/main_test.go | 48 ++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/cmd/bpf2go/main_test.go b/cmd/bpf2go/main_test.go index 40a98a373..d3d23d979 100644 --- a/cmd/bpf2go/main_test.go +++ b/cmd/bpf2go/main_test.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "testing" @@ -416,6 +417,18 @@ func TestMultipleSourceFiles(t *testing.T) { t.Fatal("No go.mod file in", modRoot) } + // bpftool appears to support the endianness of the machine it is running on. + //Determine native endianness based on GOARCH + var target string + switch runtime.GOARCH { + case "amd64", "arm64", "riscv64": + target = "bpfel" // little-endian + case "s390x", "ppc64": + target = "bpfeb" // big-endian + default: + t.Fatalf("Unsupported architecture: %s", runtime.GOARCH) + } + dir := t.TempDir() // Create two source files with different functions @@ -448,7 +461,7 @@ func TestMultipleSourceFiles(t *testing.T) { "-go-package", "main", "-output-dir", modDir, "-cc", testutils.ClangBin(t), - "-target", "bpfel,bpfeb", + "-target", target, "bar", filepath.Join(dir, "func1.c"), filepath.Join(dir, "func2.c"), @@ -469,25 +482,20 @@ func main() { println(obj.Func2) }`) - // Test compilation for different architectures - for _, arch := range []string{"amd64", "arm64", "s390x"} { - t.Run(arch, func(t *testing.T) { - goBuild := exec.Command("go", "build", "-mod=mod", "-o", "/dev/null") - goBuild.Dir = modDir - goBuild.Env = append(os.Environ(), - "GOOS=linux", - "GOARCH="+arch, - "GOPROXY=off", - "GOSUMDB=off", - ) - out, err := goBuild.CombinedOutput() - if err != nil { - if out := string(out); out != "" { - t.Log(out) - } - t.Error("Can't compile package:", err) - } - }) + // Test compilation for the native architecture + goBuild := exec.Command("go", "build", "-mod=mod", "-o", "/dev/null") + goBuild.Dir = modDir + goBuild.Env = append(os.Environ(), + "GOOS=linux", + "GOPROXY=off", + "GOSUMDB=off", + ) + out, err := goBuild.CombinedOutput() + if err != nil { + if out := string(out); out != "" { + t.Log(out) + } + t.Error("Can't compile package:", err) } } From e83058dc6f88c417b6d4ba35149a9aede5ecbc65 Mon Sep 17 00:00:00 2001 From: Jonathan Perry Date: Fri, 18 Apr 2025 22:13:03 -0500 Subject: [PATCH 6/6] bpf2go: avoid os.Rename, instead compiling to the desired object file name Signed-off-by: Jonathan Perry --- cmd/bpf2go/main.go | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/cmd/bpf2go/main.go b/cmd/bpf2go/main.go index 854de4253..adc489819 100644 --- a/cmd/bpf2go/main.go +++ b/cmd/bpf2go/main.go @@ -417,30 +417,32 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) { var allDeps []dependency var tmpObjFileNames []string for _, source := range b2g.sourceFiles { - deps, err := b2g.compileOne(tgt, cwd, source, objFileName) - if err != nil { - return err - } - - if len(deps) > 0 { - // There is always at least a dependency for the main file. - deps[0].file = goFileName - allDeps = append(allDeps, deps...) - } - - // For multiple source files, we need to move the compiled object to a temporary file + // Determine the target object file name + var targetObjFileName string if len(b2g.sourceFiles) > 1 { + // For multiple source files, use a temporary file tmpObj, err := os.CreateTemp("", filepath.Base(source)) if err != nil { return fmt.Errorf("create temporary object file: %w", err) } tmpObj.Close() defer os.Remove(tmpObj.Name()) + targetObjFileName = tmpObj.Name() + tmpObjFileNames = append(tmpObjFileNames, targetObjFileName) + } else { + // For single source file, use the final object file name + targetObjFileName = objFileName + } - if err := os.Rename(objFileName, tmpObj.Name()); err != nil { - return fmt.Errorf("move object file: %w", err) - } - tmpObjFileNames = append(tmpObjFileNames, tmpObj.Name()) + deps, err := b2g.compileOne(tgt, cwd, source, targetObjFileName) + if err != nil { + return err + } + + if len(deps) > 0 { + // There is always at least a dependency for the main file. + deps[0].file = goFileName + allDeps = append(allDeps, deps...) } }