-
Notifications
You must be signed in to change notification settings - Fork 741
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
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: Jonathan Perry <[email protected]>
- 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]>
Signed-off-by: Jonathan Perry <[email protected]>
…fic endianness handling Signed-off-by: Jonathan Perry <[email protected]>
… name Signed-off-by: Jonathan Perry <[email protected]>
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 return
s. 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 { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
}
...
There was a problem hiding this 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 |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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{}) |
There was a problem hiding this comment.
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"}}, |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
b2g.sourceFiles[i] = absPath | |
b2g.sourceFiles = append(b2g.sourceFiles, absPath) |
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