Skip to content

bpf2go: support multiple source files #1762

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open

bpf2go: support multiple source files #1762

wants to merge 6 commits into from

Conversation

yonch
Copy link

@yonch yonch commented Apr 21, 2025

See issue #1758

Has been used successfully in unvariance/collector#109, PR unvariance/collector#110.

Tested in a dev container on MacOs and on a Linux bare metal box.

Closes #1758

@yonch yonch requested a review from a team as a code owner April 21, 2025 19:37
yonch added 6 commits April 21, 2025 14:40
- 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 <[email protected]>
- Moved the temporary object renaming logic from compileOne() to convert()
- Simplified naming and cleanup

Signed-off-by: Jonathan Perry <[email protected]>
…fic endianness handling

Signed-off-by: Jonathan Perry <[email protected]>
Copy link
Collaborator

@ti-mo ti-mo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good overall, thank you! Left a few nits.

Source: filepath.Join(dir, "func1.c"),
Dest: obj1,
})
if err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and everywhere: use qt.Assert().

(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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifying multiple source files incurs an extra dependency (bpftool). I'd document that clearly here.

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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} else ... { is not necessary here since the opposing case returns. The typical pattern is:

_, err := ...
if errors.Is(err, fs.ErrNotExist) { // Note: os.IsFoo functions are unofficially deprecated.
    return fmt.Errorf(...)
}
if err != nil {
    return err
}

Also, the PathError returned by os.Stat always contains the path provided to the function call, so it's not necessary to generate your own error. Doing so also swallows the fs.ErrNotExist sentinel, making error handling confusing for the user.

for _, source := range b2g.sourceFiles {
// Determine the target object file name
var targetObjFileName string
if len(b2g.sourceFiles) > 1 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider initializing targetObjFileName := objFileName and drop the the else below.

for _, deps := range depsSlices {
for _, dep := range deps {
// If we've seen this file before, merge prerequisites
if existing, ok := merged[dep.file]; ok {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider removing one level of indentation for the majority of the body using continue:

existing, ok := merged[dep.file]
if !ok {
    // First time seeing this file, just copy prerequisites
    merged[dep.file] = make([]string, len(dep.prerequisites))
    copy(merged[dep.file], dep.prerequisites)

    continue
}
...

@ti-mo ti-mo requested review from lmb and dylandreimerink May 6, 2025 13:18
Copy link
Collaborator

@lmb lmb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with Timo, this is nice :) Be aware that libbpf linking may produce linked object files which we don't parse: #739 I don't think it's a pre-requisite to get this fixed, but something to be aware of which might make this less useful to you.


// LinkArgs specifies the arguments for linking multiple BPF object files together.
type LinkArgs struct {
// Destination object file name
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention that paths must be absolute?

)

// LinkArgs specifies the arguments for linking multiple BPF object files together.
type LinkArgs struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document that this only supports host endianness.

t.Fatal("Can't get module root:", err)
}

if _, err := os.Stat(filepath.Join(modRoot, "go.mod")); os.IsNotExist(err) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move creating the temporary module into a helper instead of copying the code?

{"/foo/third.c", []string{"/foo/bar.h"}},
}

if !reflect.DeepEqual(merged, want) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if !reflect.DeepEqual(merged, want) {
qt.Assert(t, qt.DeepEquals(merged, want))

// 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{})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might be able to simplify this by appending to a slice. At the end, sort the slice and use https://pkg.go.dev/slices#Compact in a second pass.

})

want := []dependency{
{"/foo/main.c", []string{"/foo/bar.h", "/foo/baz.h", "/foo/qux.h"}},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can we get different dependencies for two compilations of the same file? Wouldn't it be more appropriate to check that both sets of dependencies are identical? Or that the sets are disjoint?

if err != nil {
return nil, fmt.Errorf("convert source file to absolute path: %w", err)
}
b2g.sourceFiles[i] = absPath
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: just appending to b2g.sourceFiles without pre-allocating the slice is probably fine / easier to reason about.

Suggested change
b2g.sourceFiles[i] = absPath
b2g.sourceFiles = append(b2g.sourceFiles, absPath)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

bpf2go: support multiple source files
3 participants