-
Notifications
You must be signed in to change notification settings - Fork 788
add StructOpsMap support #1845
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 StructOpsMap support #1845
Conversation
7b8a348 to
f7a32d3
Compare
lmb
left a comment
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 PR is missing the part where program fds are inserted into the struct ops map. Without that it's hard to tell what is going on.
@lmb yeah, agreed. |
|
vimto tests on 6.1 and 6.6 has passed on my setup, so I feel this is a temporary error. |
lmb
left a comment
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 had a good look at the PR and there are some high level problems we need to solve:
- Too much metadata which is kept on the side and not in the right places. This makes the logic hard to follow and doesn't integrate well with the rest of the code. The solution is to duplicate some metadata by storing type name and member in ProgramSpec.AttachTo. We also need to store the mapping from MapSpec.Value.Member[].Name to program somehow. For now I think we can get away with setting ProgramSpec.Name to MapSpec.Value.Member[].Name.
- There is a lot of duplication in helper functions that do unconventional stuff with btf types. Most of these should go. Comparisons between types to find the equivalent should always be using the concrete type and name, not offset or index.
4126573 to
0e82c52
Compare
|
@shun159 please ping me explicitly when you need another review. |
Hi @lmb I believe I've addressed most of your feedbacks earlier. https://github.com/cilium/ebpf/actions/runs/17712680837/job/50333512472?pr=1845 |
lmb
left a comment
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.
Looks pretty good! There are a couple of comments which you didn't address. Please go through the conversations and resolve them if you've actioned them.
To fix the CI breakage: for now it's ok to set AttachTo = "" if were dealing with a struct ops program in TestLibBPFCompat.
lmb
left a comment
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.
Looks pretty good! There are a couple of comments which you didn't address. Please go through the conversations and resolve them if you've actioned them.
To fix the CI breakage: for now it's ok to set AttachTo = "" if were dealing with a struct ops program in TestLibBPFCompat.
|
Please rebase on top of #1865. The end result will be that you shouldn't have to call |
b19ded9 to
f5f64b2
Compare
done |
|
@lmb Since the comment disappeared, I’ll respond by quoting below. From my understanding, libbpf seems to handle it as follows:
It may be need to keep the parsed result of the ELF somewhere. |
|
At the moment, we only have a test that writes progFD into kern_vdata, but please let me also add some test to check whether scalar values can be written. |
0072f5c to
8349ed9
Compare
@lmb (It can be store this in the MapKV of each structops map) |
|
My idea is to place |
@lmb Ah! I see, does something like the following match what you have in mind? ms := &MapSpec{
Name: "scx_null",
Type: StructOpsMap,
Value: &btf.Struct{
Name: "sched_ext_ops",
// ... parsed from the ELF reader
Members: []Member{
{Name: "init", Type: ..., Offset: ...., }
.
.
},
},
}I think this would work, however, in practice the map data will be a bytes of the value type, so in this change I used the value type name for Value. In the example above we’d be putting the user struct there, which I think could be a bit confusing. |
|
From bpf_map__init_kern_struct_ops, it appears that libbpf uses the BTF generated from the ELF for type validation and for offsets when copying, etc. For example, for type-size validation, it seems to read and check against the BTF corresponding to what loadSpecFromELF(file) would produce as btf.Spec. Therefore, I think we need a way to pass the BTF generated from the ELF into NewCollectionWithOptions(). It’s probably not sufficient to pass struct members. |
@lmb done. it works. |
|
@lmb Please review it when you have time. |
This commit adds struct_ops support to the ELF reader: it classifies non-executable PROGBITS sections, parses their BTF Datasec to build MapSpecs, associates relocs with func-pointer members to set ps.AttachTo, and adds TestStructOps. Related: cilium#1845 Signed-off-by: shun159 <[email protected]>
Correction: this is not needed. copying userStruct into ms.Value will be sufficient. |
|
CI failures are probably because the PR hasn't been rebased, not exactly sure. |
49faf10 to
a87298a
Compare
|
Squashed and rebased. |
|
I need to rebase this once #1879 is in so that I can test that it doesn't break windows. |
dylandreimerink
left a comment
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.
Sorry to be a bit late to the party. I did find a few issues that should be addresses.
Most of those are comments on the code. Additionally I think PR #1869 is an integral part of this work. It is only with both PRs that you can go from ELF to loading a program.
I had to create a local branch and merge both PR branches into it before I could try loading some of the kernel self test programs, where I found some the issues pointed out.
I mostly tested with https://elixir.bootlin.com/linux/v6.17.1/source/tools/testing/selftests/bpf/progs/tcp_ca_write_sk_pacing.c
| if spec.Type == StructOps { | ||
| attachTo, targetMember, _ = strings.Cut(attachTo, ":") | ||
| if targetMember == "" { | ||
| return nil, fmt.Errorf("struct_ops: AttachTo must be '<ops>:<member>' (got %s)", spec.AttachTo) | ||
| } | ||
| } |
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 not consistent with the standard that libbpf sets out. In this implementation a user is required to make a section like so:
SEC("struct_ops/tcp_congestion_ops:undo_cwnd")
But the libbpf standard is that the name after the struct_ops/ does not matter. Instead, the structure and member are based on how a function is used in the map value:
SEC("struct_ops/write_sk_pacing_undo_cwnd")
__u32 BPF_PROG(write_sk_pacing_undo_cwnd, struct sock *sk)
{
return tcp_sk(sk)->snd_cwnd;
}
SEC(".struct_ops")
struct tcp_congestion_ops write_sk_pacing = {
.undo_cwnd = (void *)write_sk_pacing_undo_cwnd,
.name = "bpf_w_sk_pacing",
};
So we to should use the map value BTF to figure out this info so that we are ELF compatible with libbpf.
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.
- Discussed this with @shun159 before: the ELF reader will fudge up
AttachToto conform to the format we expect based on the struct definition. We'll throw away the section information. - The map value BTF is not enough to capture this, the information is only in relocation entries.
The design constraint here is that we need to know the struct ops type name and member name when loading the program (sigh), so this info has to be in ProgramSpec.
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.
Ack. I guess those changes are just not #1869 yet which is what I was testing with. It currently just passes the section straight through. If we do want to keep this as two separate PRs we should ensure that gets fixed there.
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.
@shun159 please update the ELF reader PR based on this.
| // Populate the map explicitly and keep a reference on cl.programs. | ||
| // This is necessary because we may inline fds into kernVData which | ||
| // may become invalid if the GC frees them. | ||
| if err := m.Put(uint32(0), kernVData); err != nil { | ||
| return err | ||
| } | ||
| mapSpec.Contents = nil | ||
| runtime.KeepAlive(cl.programs) |
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 creates really unintuitive/unexpected behavior. The act of writing a value to a struct_ops_map that has not been created with the F_LINK flag attaches it. So the moment you run ebpf.NewCollection(spec), you have attached.
Even worse, when attached the refcount of the map is incremented. When you call coll.Close() the map value is not deleted, and thus the struct ops programs currently stay attached.
That is not expected by users, since for all other program types you need to explicitly attach using the link package.
I do not think this is necessarily a bad approach, but only if we always load the struct ops map with F_LINK. Loading with F_LINK makes it so writing to the map does not do anything, and attaching is managed via an actual BPF link.
Caveats are that F_LINK was added to the kernel later on, so we would limit the minimum kernel version requirements. And also normally this is user controllable by specifying SEC("struct_ops") vs SEC("struct_ops.link").
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.
Great catch! I think for now we should force BPF_F_LINK by only allowing the ELF reader to consume .link sections. Tests should also only do F_LINK. I don't see how to make the non-link semantics work, they are just too broken / special. Maybe people need to "manually" create such maps if they need further backwards compat.
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.
Unit tests already use BPF_F_LINK, so no change needed 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.
I think for now we should force BPF_F_LINK by only allowing the ELF reader to consume .link sections. Tests should also only do F_LINK. I don't see how to make the non-link semantics work, they are just too broken / special. Maybe people need to "manually" create such maps if they need further backwards compat.
Yes, agreed. Lets throw an error if we do not see BPF_F_LINK when loading the map. Worst case people will need the add the flag to the spec manually in Go.
If we do go that route, we are missing the logic to create the link. We can add that in a followup PR. So this PR adds the logic to load a spec. #1869 has the logic to go from ELF to spec. And some future PR will add the link logic to attach the loaded program/map. Sound good?
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.
Yup that is the plan.
Add preliminary support for struct_ops maps. The MapSpec of a StructOps map has to follow a particular format: - MapSpec.Value: contains a Struct which matches the in-kernel struct. The name of the type is used to find the in-kernel equivalent. - MapSpec.Contents[0]: contains the default values for non-function-pointer fields. Programs inserted into a StructOpsMap require special treatment as well. During load we need to specify which struct and field member to attach to. For this purpose we overload ProgramSpec.AttachTo: it must contain a string in the form "type_name:field_name". This commit does not yet enable full struct_ops support since we are still missing changes to the ELF reader and package link. See: cilium#1502 Signed-off-by: shun159 <[email protected]> Co-developed-by: Lorenz Bauer <[email protected]>
a87298a to
4250dab
Compare
|
thx @lmb and @dylandreimerink for your kind support! |
This commit adds struct_ops support to the ELF reader: it classifies non-executable PROGBITS sections, parses their BTF Datasec to build MapSpecs, associates relocs with func-pointer members to set ps.AttachTo, and adds TestStructOps. Related: cilium#1845 Signed-off-by: shun159 <[email protected]>
This commit adds struct_ops support to the ELF reader: it classifies non-executable PROGBITS sections, parses their BTF Datasec to build MapSpecs, associates relocs with func-pointer members to set ps.AttachTo, and adds TestStructOps. Related: cilium#1845 Signed-off-by: shun159 <[email protected]>
This commit adds struct_ops support to the ELF reader: it classifies non-executable PROGBITS sections, parses their BTF Datasec to build MapSpecs, associates relocs with func-pointer members to set ps.AttachTo, and adds TestStructOps. Related: cilium#1845 Signed-off-by: shun159 <[email protected]>
This commit adds struct_ops support to the ELF reader: it classifies non-executable PROGBITS sections, parses their BTF Datasec to build MapSpecs, associates relocs with func-pointer members to set ps.AttachTo, and adds TestStructOps. Related: #1845 Signed-off-by: shun159 <[email protected]>
This PR adds preliminary support for StructOpsMap and StructOps.
see: #1502