Skip to content

Conversation

shun159
Copy link
Contributor

@shun159 shun159 commented Aug 10, 2025

This PR adds initial support for StructOpsMap and StructOps.

  • Introduces initKernStructOps to resolve user and kernel BTF types, copy struct data, and associate function members with programs.
  • Populates kernVData with program FDs during map finalization.
  • Extends map.go and prog.go to support BTF attach information and module BTF.
  • Adds a new file struct_ops.go to consolidate struct_ops types, metadata, and BTF resolution logic.
  • Add unit tests in collection_test.go and struct_ops_test.go to verify basic load with a hand-crafted MapSpec.

see: #1502

@shun159 shun159 requested a review from a team as a code owner August 10, 2025 13:22
@shun159 shun159 force-pushed the feature/struct-ops-2 branch from 7b8a348 to f7a32d3 Compare August 10, 2025 13:27
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.

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.

@shun159
Copy link
Contributor Author

shun159 commented Aug 11, 2025

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.
I was trying to keep the PR small, but I see how it’s unclear without kern_vdata population.
then, let's proceed with including the changes so that make it clear.

@shun159
Copy link
Contributor Author

shun159 commented Aug 15, 2025

vimto tests on 6.1 and 6.6 has passed on my setup, so I feel this is a temporary error.

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.

I had a good look at the PR and there are some high level problems we need to solve:

  1. 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.
  2. 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.

map.go Outdated
}

var b btf.Builder
h, err := btf.NewHandle(&b)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why create an empty BTF blob here?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This question is still unanswered.

@shun159 shun159 force-pushed the feature/struct-ops-2 branch 2 times, most recently from 4126573 to 0e82c52 Compare September 14, 2025 14:18
@lmb
Copy link
Collaborator

lmb commented Sep 16, 2025

@shun159 please ping me explicitly when you need another review.

@shun159
Copy link
Contributor Author

shun159 commented Sep 16, 2025

@shun159 please ping me explicitly when you need another review.

Hi @lmb I believe I've addressed most of your feedbacks earlier.
However, after switching to use AttachTo, it looks like elf_reader_test is now failing.
My assumption is that if the AttachTo string doesn't match the expected format introduced by this PR, we could simply ignore it, in which case the test itself should pass but I would appreciate your thought how we should proceed with this?

https://github.com/cilium/ebpf/actions/runs/17712680837/job/50333512472?pr=1845

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.

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.

}

target := btf.Type((*btf.Struct)(nil))
_, module, err := findTargetInKernel(s, "bpf_struct_ops_bpf_testmod_ops", &target)
Copy link
Collaborator

Choose a reason for hiding this comment

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

TODO: Why is this necessary?

prog.go Outdated
}
defer module.Close()

kType, ok := btf.As[*btf.Struct](target)
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can just use a plain type assertion here since we initialize target just above.

return nil, fmt.Errorf("member %q not found in %s", targetMember, kType.Name)
}

attr.ExpectedAttachType = sys.AttachType(idx)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add a comment explaining this StructOps quirk.

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.

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

lmb commented Sep 16, 2025

Please rebase on top of #1865. The end result will be that you shouldn't have to call btf.LoadKernelSpec explicitly anymore.

…ation

1. Verify creating a StructOpsMap from a hand-crafted MapSpec with metadata.
2. Resolve attr.BtfVmlinuxValueTypeId from vmlinux BTF before MapCreate.
3. Value population is deferred to follow-up PRs.

see: cilium#1502
Signed-off-by: shun159 <[email protected]>
- Add a smoke test for LoadAndAssign with a StructOpsMap (skips on unsupported kernels)
- Call stOps.preLoad / .onProgramLoaded / .postLoad from LoadAndAssign
- postLoad: no-op for now; value population will follow

Signed-off-by: shun159 <[email protected]>
Signed-off-by: shun159 <[email protected]>
1. implemented the above functions so that build kern_vdata
2. support "mod_btf" handling for testing
3. fix newProgramWithOptions() so that load structOps programs
4. fix createMap() so that find struct types from mod_btf

Signed-off-by: shun159 <[email protected]>
`wID` should be resolved in createMap()

Signed-off-by: shun159 <[email protected]>
Replace use of structOpsProgMeta metadata with ProgramSpec.AttachTo
(e.g. "bpf_testmod_ops:test_1") to encode attach target,
and drop structOpsProgMeta, adjust tests accordingly.

Signed-off-by: shun159 <[email protected]>
Signed-off-by: shun159 <[email protected]>
Signed-off-by: shun159 <[email protected]>
- add translateStructData() and populateFuncPtr() and use them in populateDeferredMaps
- delete structOpsMeta and redundant helpers

Signed-off-by: shun159 <[email protected]>
Set MapSpec.Value to the "value struct" (`bpf_struct_ops_<ops>`) instead of the "kern struct".
This matches the actual BTF layout where the sturct_ops struct matches with the `data` member inside the value type.

Also remove unused helper functions (`findStructByNameWithPrefix`, `doFindStructTypeByName`, etc.)
to simplify type resolution.

Signed-off-by: shun159 <[email protected]>
@shun159 shun159 force-pushed the feature/struct-ops-2 branch from b19ded9 to f5f64b2 Compare September 17, 2025 11:09
@shun159
Copy link
Contributor Author

shun159 commented Sep 17, 2025

Please rebase on top of #1865. The end result will be that you shouldn't have to call btf.LoadKernelSpec explicitly anymore.

done

@lmb lmb changed the title struct_ops: add structOpsMeta to carry BTF hints for StructOpsMap creation add StructOpsMap support Sep 18, 2025
@shun159
Copy link
Contributor Author

shun159 commented Sep 18, 2025

@lmb Since the comment disappeared, I’ll respond by quoting below.

> +		return nil, fmt.Errorf("member %s not found in %s", innerName, to.Name)
+	}
+
+	kernIndexByName := make(map[string]btf.Member, len(inner.Members))
+	for _, m := range inner.Members {
+		kernIndexByName[m.Name] = m
+	}
+
+	for _, m := range from.Members {
+		if m.BitfieldSize > 0 {
+			return nil, fmt.Errorf("bitfield %s not supported", m.Name)
+		}
+
+		kernMember, ok := kernIndexByName[m.Name]
+		if !ok {
+			continue
Is this what libbpf does as well? Seems a bit prone to breakage.
> +
+		if kernMember.BitfieldSize > 0 {
+			return nil, fmt.Errorf("bitfield %s not supported in kern struct", kernMember.Name)
+		}
+
+		sz, err := btf.Sizeof(m.Type)
+		if err != nil {
+			return nil, fmt.Errorf("failed to resolve size of %s: %w", m.Name, err)
+		}
+
+		kernSz, err := btf.Sizeof(kernMember.Type)
+		if err != nil {
+			return nil, fmt.Errorf("failed to resolve size of %s: %w", kernMember.Name, err)
+		}
+
+		if sz != kernSz {
This check is pretty basic. Is that all that libbpf does as well?

Might make sense to call btf.CheckTypeCompatibility if libbpf does something similar.

From my understanding, libbpf seems to handle it as follows:

  1. if a field in the “user struct” is not found in the kernel struct and its value is non-zero, it results in an error.
  2. explicitly checks for matching type kinds.

It may be need to keep the parsed result of the ELF somewhere.

@shun159
Copy link
Contributor Author

shun159 commented Sep 18, 2025

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.

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.

2 participants