-
Notifications
You must be signed in to change notification settings - Fork 651
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
Add support for freplace programs #288
Conversation
Thank you! This change should probably be broken up into smaller PRs so they can be reviewed separately. (for example, the new APIs in
Agreed, this will be necessary for #235 as well and should also go into a separate PR. Perhaps this should be left to @lmb, but not sure about his current time budget. I'll mark this as a draft for now. |
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.
Thanks for investing so much work already :) Here are my takeaways:
- Freplace itself seems not too complicated. The worst bit is that we need a BTF ID to attach to. We don't have a good way of specifying that right now.
- The current code has problems with resource ownership / lifetime management. ProgramInfo mustn't contain *btf.Handle. We shouldn't use a plain int for AttachTarget.
- We can drop the feature test and with that BTF writing. It's nice if a user gets a decent error, but the cost of maintaining BTF writing isn't worth it.
internal/btf/strings.go
Outdated
@@ -8,9 +8,12 @@ import ( | |||
"io/ioutil" | |||
) | |||
|
|||
type stringTable []byte |
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.
We should keep stringTable a read-only structure and add a stringTableBuilder instead. We only need to modify it when building BTF on the fly, which is rare.
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.
Will keep in mind for a future PR to reintroduce this functionality, but, as discussed, this is not needed for the functionality this PR is trying to introduce so it has been removed.
internal/btf/btf.go
Outdated
// Returns ErrNotExist, if there is no BTF with the given id. | ||
// Returns ErrNotSupported if BTF is not supported. | ||
func NewHandleFromID(id TypeID) (*Handle, error) { | ||
if err := haveBTF(); 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.
Not necessary I think.
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 could make a function that goes directly from ID to Spec
, but it seemed likely that once the BTF library is exported that there will be desire for going from a BTF ID to a Handle
in other use cases as well.
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.
It's been a while, but I think I meant the call to haveBTF
is probably superfluous here.
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.
Ah, yes. I think I had that for when it was possible to construct BTF specs from a consumer of the library (when I had all of the type changes) since this could have been the first entry to BTF in the kernel in that case. It is not needed now that all of that is gone. Will remove.
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.
Addressed in b87cf4a
internal/btf/info.go
Outdated
return nil, err | ||
} | ||
|
||
rawTypes, rawStrings, err := parseBTF(bytes.NewReader(btfBuffer), internal.NativeEndian) |
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 this call loadNakedSpec
instead?
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.
Ah, maybe! I saw that loadNakedSpec
took section information. Didn't realize it was valid to pass nil
for that.
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.
Addressed in 3df3789
My apologies for having to put this down for so long. I appreciate all of the excellent feedback and am going through it now. |
1235b3b
to
ae43156
Compare
PR updated. Original version is maintained at zeffron/freplace.old in my fork for reference for the BTF types code. |
8ed2747
to
32d5c3e
Compare
I believe the |
Ah. I misunderstood the failure situation. They weren't failing the attach, they were failing the load because they require a second program to be attached to. I'll have to figure out how to update the unit tests for that. |
And I misunderstood again. Those object files wouldn't be there if they couldn't be loaded, so the kernel version isn't the issue. Which means it's just the missing attach target part that needs to be worked out. |
1dbfcba
to
eddfca3
Compare
link/freplace.go
Outdated
return nil, fmt.Errorf("eBPF program type %s is not an Extension: %w", prog.Type(), errInvalidInput) | ||
} | ||
|
||
info, err := targetProg.Info() |
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 is the same logic as in resolveBTFType
? What happens if the name (and ultimately the BTF ID) here is different than what was passed at load time? It's a shame that we need this twice.
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 prompted me to go through a deeper dive on how libbpf and the kernel handles this. I think there's a way to avoid this duplicate execution in the common case (but not the duplicate code). I should have an updated PR sometime this week.
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.
To answer your question more specifically, it looks like libbpf and the kernel optionally only actually do this lookup once. If it's done at load time, it gets cached on the kernel program object. If the at link time, no program and function name are provided, then the cached values are used. If a program and function name are provided, they override the cached values (in which case the name and program provided at load time are completely wasted). As long as the final target function (supplied at load time or link time) has a matching signature to the extension function, the link is successfully created.
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.
Agreed that it's a shame to need it twice. We could potentially create a function to get BTF from a program and put it into a package that's accessible from both link
and ebpf
. Maybe put it into the btf
package?
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.
Just re-discovered that extension programs MUST provide attachment information at load time. It can still be overridden at link time (this allows replacing two different functions with the same program, for example).
link/link.go
Outdated
@@ -49,6 +50,8 @@ type RawLinkOptions struct { | |||
Program *ebpf.Program | |||
// Attach must match the attach type of Program. | |||
Attach ebpf.AttachType | |||
// BTF is the BTF of the attachment target (or 0). | |||
BTF btf.Type |
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 should be btf.TypeID
I think.
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.
Why make this a btf.TypeID
instead of a btf.Type
? Other fields here use the Go native type instead of the ID, such as Program
.
(The comment about it being 0 is incorrect based on current implementation. It should have said nil, but I think it also doesn't need to be said.)
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.
My thinking was this (if I remember correctly): most of the code we have constructs a *Func
to pass here, only for the Go type to be discarded. To look at it another way: since this is a low-level interface (hence the Raw in the name) it's better to stay close to the kernel interface.
15b1981
to
d4a98bd
Compare
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 this is looking really good! Left some nits, the only open question for me is the behaviour of CO-RE relocations as mentioned in one of the comments.
prog.go
Outdated
@@ -185,6 +209,25 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand | |||
if err != nil { | |||
return nil, fmt.Errorf("load target BTF: %w", err) | |||
} | |||
} else if spec.AttachTarget != 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.
I think this else if
branch needs to move into spec.AttachTo != ""
.
- opts.TargetBTF specifies the location of the vmlinux BTF (for environments where that isn't available from a convenient location)
- Even if opts.TargetBTF is specified, we need to use spec.AttachTarget to resolve the type ID for freplace programs.
targetBTF = nil
if opts.TargetBTF {
targetBTF = ...
}
if spec.BTF {
btf.ProgramFixups(spec.BTF, targetBTF)
}
if spec.AttachTo {
if spec.AttachTarget {
targetBTF = ...
}
resolveBTFType(targetBTF)
}
- btf.ProgramFixups is always invoked with the kernel BTF or nil
- resolveBTFType is invoked with the kernel BTF, the AttachTarget BTF or nil
The implication is that freplace programs are CO-RE relocated against the kernel, not against the program they are attaching to. Is that the correct behaviour?
24f14e4
to
e7812fa
Compare
The btf package needs access to BPF_BTF_GET_FD_BY_ID, which is implemented via bpfObjGetFDByID which lives in the root ebpf package. The btf cannot access it there without creating an import cycle, so it is now moved into the internal collection of syscalls to allow access from the btf package.
Starting with the 5.10 kernel it is possible to replace functions (with some limitations) using eBPF. This is useful for XDP multi-attach. This commit adds a new link type to support that functionality.
This adds a unit test that loads a program with a subprogram, then replaces the subprogram. The resulting link is tested with the common link tests, excluding the update test because tracing links (the kind used by freplace) don't support link updates.
Thank you for your hard work, and sorry the review took so long! |
Thank you for the quality review. I think we ended up in a pretty good place. 😀 |
Test base program, extension program, loader, and associated vmlinux.h can be found in this gist
Key changes:
FreplaceLink
and associatedAttachFreplace
are added to thelink
packageProgram.Info
btf.Specs
can be constructed from loaded BTFAttachTarget
was added toProgramSpec
as extension programs need to specify both the file handle of the intended (or a template) attach program and provide the name of the function to attach so BTF ID can be located.