From 8698671c7cc57811af3e143182bca19bcd3412c9 Mon Sep 17 00:00:00 2001 From: shun159 Date: Mon, 29 Sep 2025 19:44:40 +0900 Subject: [PATCH 01/16] elf_reader: add struct_ops 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: #1845 Signed-off-by: shun159 --- Makefile | 1 + elf_reader.go | 219 ++++++++++++++++++++++++++++++++++--- elf_reader_test.go | 95 ++++++++++++++++ testdata/struct_ops-eb.elf | Bin 0 -> 1832 bytes testdata/struct_ops-el.elf | Bin 0 -> 1832 bytes testdata/struct_ops.c | 18 +++ 6 files changed, 320 insertions(+), 13 deletions(-) create mode 100644 testdata/struct_ops-eb.elf create mode 100644 testdata/struct_ops-el.elf create mode 100644 testdata/struct_ops.c diff --git a/Makefile b/Makefile index 4f53b37f3..c2c0a5d31 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,7 @@ TARGETS := \ testdata/errors \ testdata/variables \ testdata/arena \ + testdata/struct_ops \ btf/testdata/relocs \ btf/testdata/relocs_read \ btf/testdata/relocs_read_tgt \ diff --git a/elf_reader.go b/elf_reader.go index f2c9196b7..1eb57ceb0 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -41,20 +41,30 @@ type ksymMeta struct { Name string } +type structOpsSpec struct { + name string + // section index of .struct_ops / .struct_ops.link + secIdx elf.SectionIndex + // byte offset of the variable in that section + userOff uint64 + userSize uint64 +} + // elfCode is a convenience to reduce the amount of arguments that have to // be passed around explicitly. You should treat its contents as immutable. type elfCode struct { *internal.SafeELFFile - sections map[elf.SectionIndex]*elfSection - license string - version uint32 - btf *btf.Spec - extInfo *btf.ExtInfos - maps map[string]*MapSpec - vars map[string]*VariableSpec - kfuncs map[string]*btf.Func - ksyms map[string]struct{} - kconfig *MapSpec + sections map[elf.SectionIndex]*elfSection + license string + version uint32 + btf *btf.Spec + extInfo *btf.ExtInfos + maps map[string]*MapSpec + vars map[string]*VariableSpec + kfuncs map[string]*btf.Func + ksyms map[string]struct{} + kconfig *MapSpec + structOps map[string]*structOpsSpec } // LoadCollectionSpec parses an ELF file into a CollectionSpec. @@ -116,8 +126,15 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { case sec.Type == elf.SHT_REL: // Store relocations under the section index of the target relSections[elf.SectionIndex(sec.Info)] = sec - case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0: - sections[idx] = newElfSection(sec, programSection) + case sec.Type == elf.SHT_PROGBITS: + if (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0 { + sections[idx] = newElfSection(sec, programSection) + } else if sec.Name == ".struct_ops" || sec.Name == ".struct_ops.link" { + //classification based on sec names so that struct_ops-specific + // sections (.struct_ops, .struct_ops.link) are correctly recognized + // as non-executable PROGBITS, allowing value placement and link metadata to be loaded. + sections[idx] = newElfSection(sec, structOpsSection) + } } } @@ -147,6 +164,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { vars: make(map[string]*VariableSpec), kfuncs: make(map[string]*btf.Func), ksyms: make(map[string]struct{}), + structOps: make(map[string]*structOpsSpec), } symbols, err := f.Symbols() @@ -164,6 +182,10 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { return nil, fmt.Errorf("load maps: %w", err) } + if err := ec.loadStructOpsMaps(); err != nil { + return nil, fmt.Errorf("struct_ops maps: %w", err) + } + if err := ec.loadBTFMaps(); err != nil { return nil, fmt.Errorf("load BTF maps: %w", err) } @@ -186,6 +208,15 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { return nil, fmt.Errorf("load programs: %w", err) } + // assiociate members in structs with ProgramSpecs using relo + if err := ec.associateStructOpsRelocs( + progs, + relSections, + symbols, + ); err != nil { + return nil, fmt.Errorf("load struct_ops: %w", err) + } + return &CollectionSpec{ ec.maps, progs, @@ -239,6 +270,7 @@ const ( btfMapSection programSection dataSection + structOpsSection ) type elfSection struct { @@ -349,6 +381,10 @@ func (ec *elfCode) loadProgramSections() (map[string]*ProgramSpec, error) { continue } + if !(sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0) { + continue + } + if len(sec.symbols) == 0 { return nil, fmt.Errorf("section %v: missing symbols", sec.Name) } @@ -564,7 +600,7 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err ins.Constant = int64(uint64(offset) << 32) ins.Src = asm.PseudoMapValue - case programSection: + case programSection, structOpsSection: switch opCode := ins.OpCode; { case opCode.JumpOp() == asm.Call: if ins.Src != asm.PseudoCall { @@ -1379,6 +1415,163 @@ func (ec *elfCode) loadKsymsSection() error { return nil } +// loadStructOpsMapsFromSections creates StructOps MapSpecs from DataSec sections +// ".struct_ops" and ".struct_ops.link" found in the object BTF. +func (ec *elfCode) loadStructOpsMaps() error { + for secIdx, sec := range ec.sections { + if sec.kind != structOpsSection { + continue + } + + // Process the struct_ops section to create the map + dataType, err := ec.btf.AnyTypeByName(sec.Name) + if err != nil { + return fmt.Errorf("datasec %s: %w", sec.Name, err) + } + + dataSec, ok := btf.As[*btf.Datasec](dataType) + if !ok { + return fmt.Errorf("%s BTF is not a Datasec", sec.Name) + } + + for _, vsi := range dataSec.Vars { + varType, ok := btf.As[*btf.Var](vsi.Type) + if !ok { + return fmt.Errorf("var type in %s: want *btf.Var, got %T", sec.Name, btf.UnderlyingType(vsi.Type)) + } + mapName := varType.Name + + userType, ok := btf.UnderlyingType(varType.Type).(*btf.Struct) + if !ok { + return fmt.Errorf("var %s: expect struct, got %T", varType.Name, varType.Type) + } + + // Retrieve raw data from the ELF section. + // This data contains the initial values for the struct_ops map. + userData, err := sec.Data() + if err != nil { + return fmt.Errorf("failed to read section data: %w", err) + } + + flags := uint32(0) + if sec.Name == ".struct_ops.link" { + flags = sys.BPF_F_LINK + } + + userSize := uint64(userType.Size) + userOff := uint64(vsi.Offset) + if userOff+userSize > uint64(len(userData)) { + return fmt.Errorf("%s exceeds section", mapName) + } + + ec.structOps[mapName] = + &structOpsSpec{ + name: mapName, + secIdx: secIdx, + userOff: userOff, + userSize: userSize, + } + + ec.maps[mapName] = + &MapSpec{ + Name: mapName, + Type: StructOpsMap, + Key: &btf.Int{Size: 4}, + Value: userType, + Flags: flags, + MaxEntries: 1, + Contents: []MapKV{ + { + Key: uint32(0), + Value: append([]byte(nil), userData[userOff:userOff+userSize]...), + }, + }, + } + } + } + + return nil +} + +// associateStructOpsRelocs handles `.struct_ops(.link)` +// and associates the target function with the correct struct member in the map. +func (ec *elfCode) associateStructOpsRelocs( + progs map[string]*ProgramSpec, + relSecs map[elf.SectionIndex]*elf.Section, + symbols []elf.Symbol, +) error { + for _, sec := range relSecs { + if !strings.HasPrefix(sec.Name, ".rel") { + continue + } + + targetIdx := elf.SectionIndex(sec.Info) + targetSec, ok := ec.sections[targetIdx] + if !(ok && strings.HasPrefix(targetSec.Name, ".struct_ops")) { + continue + } + + // Load the relocations from the relocation section + rels, err := ec.loadSectionRelocations(sec, symbols) + if err != nil { + return fmt.Errorf("failed to load relocations for section %s: %w", sec.Name, err) + } + + for relOff, sym := range rels { + var ms *MapSpec + var meta *structOpsSpec + + for _, mapSpec := range ec.maps { + if mapSpec.Type != StructOpsMap || len(mapSpec.Contents) == 0 { + continue + } + + stOps, ok := ec.structOps[mapSpec.Name] + if !ok { + continue + } + + if uint64(targetIdx) == uint64(stOps.secIdx) && + stOps.userOff <= relOff && + (relOff-stOps.userOff) < stOps.userSize { + meta = stOps + ms = mapSpec + } + } + + if ms == nil { + return fmt.Errorf("no struct_ops map found for secIdx %d and relOffset %d", targetIdx, relOff) + } + + moff := btf.Bits((relOff - meta.userOff) * 8) + + userSt, ok := btf.As[*btf.Struct](ms.Value) + if !ok { + return fmt.Errorf("provided value is not a btf.Struct") + } + + for _, m := range userSt.Members { + if m.Offset != moff { + continue + } + + mType := btf.UnderlyingType(m.Type) + if mPtr, isPtr := btf.As[*btf.Pointer](mType); isPtr { + if _, isFuncProto := btf.As[*btf.FuncProto](mPtr.Target); isFuncProto { + p, ok := progs[sym.Name] + if !(ok && p.Type == StructOps) { + return fmt.Errorf("program %q not found or not StructOps", sym.Name) + } + p.AttachTo = userSt.Name + ":" + m.Name + } + } + } + } + } + + return nil +} + type libbpfElfSectionDef struct { pattern string programType sys.ProgType diff --git a/elf_reader_test.go b/elf_reader_test.go index 9b4fcf4aa..3b4891bb1 100644 --- a/elf_reader_test.go +++ b/elf_reader_test.go @@ -12,6 +12,7 @@ import ( "syscall" "testing" + "github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/kallsyms" @@ -932,6 +933,100 @@ func TestArena(t *testing.T) { mustNewCollection(t, coll, nil) } +func TestStructOps(t *testing.T) { + file := testutils.NativeFile(t, "testdata/struct_ops-%s.elf") + coll, err := LoadCollectionSpec(file) + qt.Assert(t, qt.IsNil(err)) + + userData := []byte{ + // test_1 func ptr (8B) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // test_2 func ptr (8B) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // data (4B) + padding (4B) + 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, + } + + want := &CollectionSpec{ + Maps: map[string]*MapSpec{ + "testmod_ops": { + Name: "testmod_ops", + Type: StructOpsMap, + MaxEntries: 1, + Flags: sys.BPF_F_LINK, + Key: &btf.Int{Size: 4}, + Value: &btf.Struct{ + Name: "bpf_testmod_ops", + Size: 24, + Members: []btf.Member{ + { + Name: "test_1", + Type: &btf.Pointer{ + Target: &btf.FuncProto{ + Params: []btf.FuncParam{}, + Return: &btf.Int{Name: "int", Size: 4, Encoding: btf.Signed}}}, + Offset: 0, + }, + { + Name: "test_2", + Type: &btf.Pointer{ + Target: &btf.FuncProto{ + Params: []btf.FuncParam{ + {Type: &btf.Int{Name: "int", Size: 4, Encoding: btf.Signed}}, + {Type: &btf.Int{Name: "int", Size: 4, Encoding: btf.Signed}}, + }, + Return: (*btf.Void)(nil), + }, + }, + Offset: 64, + }, + { + Name: "data", + Type: &btf.Int{Name: "int", Size: 4, Encoding: btf.Signed}, + Offset: 128, // bits + }, + }, + }, + Contents: []MapKV{ + { + Key: uint32(0), + Value: userData, + }, + }, + }, + }, + Programs: map[string]*ProgramSpec{ + "test_1": { + Name: "test_1", + Type: StructOps, + AttachTo: "bpf_testmod_ops:test_1", + License: "GPL", + SectionName: "struct_ops/test_1", + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.Return(), + }, + }, + }, + Variables: map[string]*VariableSpec{}, + } + + testModOps, ok := coll.Maps["testmod_ops"] + if !ok { + t.Fatalf("testmod_ops doesn't exist") + } + + data, ok := testModOps.Contents[0].Value.([]byte) + if !ok { + t.Fatalf("Contents[0].Value should be an array of byte") + } + + qt.Assert(t, qt.CmpEquals(coll.Programs, want.Programs, csCmpOpts)) + qt.Assert(t, qt.CmpEquals(coll.Maps, want.Maps, csCmpOpts)) + qt.Assert(t, qt.CmpEquals(testModOps.Value, want.Maps["testmod_ops"].Value, csCmpOpts)) + qt.Assert(t, qt.CmpEquals(data, userData, csCmpOpts)) +} + var ( elfPath = flag.String("elfs", os.Getenv("CI_KERNEL_SELFTESTS"), "`Path` containing libbpf-compatible ELFs (defaults to $CI_KERNEL_SELFTESTS)") elfPattern = flag.String("elf-pattern", "*.o", "Glob `pattern` for object files that should be tested") diff --git a/testdata/struct_ops-eb.elf b/testdata/struct_ops-eb.elf new file mode 100644 index 0000000000000000000000000000000000000000..d2ea07f79138b4cdcee9e4765c02c60dd5315046 GIT binary patch literal 1832 zcmb_cJ8u&~5T3IgATM4@kpiJBC@9b+AkjeM01gkiu!0MM5Q}495-Wb`?gF8KNc;dQ zYEsfr)A3UfB@zWCC4ArB?q%Y{qG6=DnfZ2JyR)}%YxUJqDUgB!`3+qG#R`=24{KH3 zcV2=oRu%c+zO zYxFq-Tgv?8RDn1JrhMf5sE6R^XYm0|fw{DW1%|R0V$)#KEATA13Vs7-Kl@;-K+I5A zy?PuiTAco#yiWPf)o0JF|NdMq>Fd(jUJev~nv0-{bO)JaahjRyl4i+nJ2S&ky5x|h zcw@9_jZQ0TEgd>UZJAEuY&RK%%eSSy-Acrmm9@2%b@TY))0%0nH)_U+>2=%jAdLlS z40HB}9p=v~-VkQBy2m3`qSqblh)>|=8``c#79h!&z{?Kf{&`kzJaz`>$FR$=Zz05= zAI#%~;i=IV>!XXuapu2!A-@-eBNX0^1>l^&CU-wA#2#E90ddBVK>_!~9-J-L2r9ek z?U~s*iL!VvlPHOM(VgaML_?poT^K*tsMmYhH?2-5NxNHzbK?1DtqqCN*L^Ep(i83m zI`}_SN1Bt)oA8b+!GA}SpD{7&-sV55sj3a7`*S4}gZ~xJIcu`-g9GkC`uOpEG@OsF zN67nAzoP8%_$PD#>3c(4*AHR)H>L_*QQ60_fBqZ(KKgj#FTlDVm<5e;E%;s_xuW;; z4A7+dS6y5P$@>1?(e?QzPN-k<=jSC{E$RBqIjQ~w=O03{zW3AhZw~*|?YZy6;V6~2 bojpDm-ER{<6W){3fs5aRWQ_Oo^%wsFUvY^L literal 0 HcmV?d00001 diff --git a/testdata/struct_ops-el.elf b/testdata/struct_ops-el.elf new file mode 100644 index 0000000000000000000000000000000000000000..0902fd89cde1eb95333634ec1b16748d57bc368f GIT binary patch literal 1832 zcmbtUJ8#oa6h2PMqitF!V#t7y2L=Y@1SA$%Tp;D43{lBI5yGU2+eXbxwhLu}O8fvO zW;(Jkv++|9BN786BYfZWz0U2G#lj~YpL4!@UiV(xZ(Hq+isy-eC%-(Lywoa;FUq62%^YN!Ab-m=H5#`f7bfp4Y_xBcH`*2U?T#bAS0;q}4 z7NMf1WBh3d^&<#iYRL24U)8l_7e0~}I19POd|h5aFGE7e8wlq)f@oBhj3uIGHGIt& z;7tf~3F>#sl2y#C_V+&5=SZ6b&TAvSjz{w+ww zB*892HNW-~OEY*EES|NAyO6c~B$cnu6c%S(Q*VHxi+3~k^1I{x)%C#4qckm_d$xk_G7G#D{#v4pXea-Pcu1ge;*6{$Al0Ybv=-opWgxhM<(C!FTlAofsz>?z;|cS z-On?`l=Z)=`}-^cus{DhZh!t0ofcq3I{Y}Ob5%Uu{;XNn|3OKA#<~4(f~&UwOree- f1@?5lRt4|Q+Qo_ Date: Wed, 1 Oct 2025 18:42:14 +0900 Subject: [PATCH 02/16] elf: fix relocateInstruction since no need to treat structOpsSection ``` Section Headers: [Nr] Name Type Address Off Size ES Flg Lk Inf Al [ 0] NULL 0000000000000000 000000 000000 00 0 0 0 [ 1] .strtab STRTAB 0000000000000000 00036b 000077 00 0 0 1 [ 2] .text PROGBITS 0000000000000000 000040 000000 00 AX 0 0 4 [ 3] struct_ops/test_1 PROGBITS 0000000000000000 000040 000010 00 AX 0 0 8 [ 4] license PROGBITS 0000000000000000 000050 000004 00 WA 0 0 1 [ 5] .struct_ops.link PROGBITS 0000000000000000 000058 000018 00 WA 0 0 8 [ 6] .rel.struct_ops.link REL 0000000000000000 000318 000010 10 I 12 5 8 [ 7] .BTF PROGBITS 0000000000000000 000070 0001e0 00 0 0 4 [ 8] .rel.BTF REL 0000000000000000 000328 000020 10 I 12 7 8 [ 9] .BTF.ext PROGBITS 0000000000000000 000250 000050 00 0 0 4 [10] .rel.BTF.ext REL 0000000000000000 000348 000020 10 I 12 9 8 [11] .llvm_addrsig LLVM_ADDRSIG 0000000000000000 000368 000003 00 E 0 0 1 [12] .symtab SYMTAB 0000000000000000 0002a0 000078 18 1 2 8 ``` Signed-off-by: shun159 --- elf_reader.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/elf_reader.go b/elf_reader.go index 1eb57ceb0..76e7f31f7 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -600,7 +600,7 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err ins.Constant = int64(uint64(offset) << 32) ins.Src = asm.PseudoMapValue - case programSection, structOpsSection: + case programSection: switch opCode := ins.OpCode; { case opCode.JumpOp() == asm.Call: if ins.Src != asm.PseudoCall { @@ -1419,6 +1419,7 @@ func (ec *elfCode) loadKsymsSection() error { // ".struct_ops" and ".struct_ops.link" found in the object BTF. func (ec *elfCode) loadStructOpsMaps() error { for secIdx, sec := range ec.sections { + fmt.Println(sec.Name, sec.Type, sec.kind == structOpsSection) if sec.kind != structOpsSection { continue } From 025f0f20fb1060f962e146a474343707a9f1c7cf Mon Sep 17 00:00:00 2001 From: shun159 Date: Wed, 1 Oct 2025 19:02:56 +0900 Subject: [PATCH 03/16] elf: move logic which finds fp member into a function Signed-off-by: shun159 --- elf_reader.go | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/elf_reader.go b/elf_reader.go index 76e7f31f7..cc4b01366 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -1419,7 +1419,6 @@ func (ec *elfCode) loadKsymsSection() error { // ".struct_ops" and ".struct_ops.link" found in the object BTF. func (ec *elfCode) loadStructOpsMaps() error { for secIdx, sec := range ec.sections { - fmt.Println(sec.Name, sec.Type, sec.kind == structOpsSection) if sec.kind != structOpsSection { continue } @@ -1502,7 +1501,7 @@ func (ec *elfCode) associateStructOpsRelocs( symbols []elf.Symbol, ) error { for _, sec := range relSecs { - if !strings.HasPrefix(sec.Name, ".rel") { + if !(sec.Type == elf.SHT_REL) { continue } @@ -1544,6 +1543,7 @@ func (ec *elfCode) associateStructOpsRelocs( return fmt.Errorf("no struct_ops map found for secIdx %d and relOffset %d", targetIdx, relOff) } + // Member bit offset inside the user struct moff := btf.Bits((relOff - meta.userOff) * 8) userSt, ok := btf.As[*btf.Struct](ms.Value) @@ -1551,21 +1551,13 @@ func (ec *elfCode) associateStructOpsRelocs( return fmt.Errorf("provided value is not a btf.Struct") } - for _, m := range userSt.Members { - if m.Offset != moff { - continue - } - - mType := btf.UnderlyingType(m.Type) - if mPtr, isPtr := btf.As[*btf.Pointer](mType); isPtr { - if _, isFuncProto := btf.As[*btf.FuncProto](mPtr.Target); isFuncProto { - p, ok := progs[sym.Name] - if !(ok && p.Type == StructOps) { - return fmt.Errorf("program %q not found or not StructOps", sym.Name) - } - p.AttachTo = userSt.Name + ":" + m.Name - } + // Find the member at moff and ensure it's a pointer to a FuncProto. + if memberName, found := funcPtrMemberAtOffset(userSt, moff); found { + p, ok := progs[sym.Name] + if !(ok && p.Type == StructOps) { + return fmt.Errorf("program %q not found or not StructOps", sym.Name) } + p.AttachTo = userSt.Name + ":" + memberName } } } @@ -1573,6 +1565,23 @@ func (ec *elfCode) associateStructOpsRelocs( return nil } +// funcPtrMemberAtOffset returns the member name at bit offset `moff` +// if the member is a pointer to a FuncProto. Otherwise returns an empty string. +func funcPtrMemberAtOffset(userSt *btf.Struct, moff btf.Bits) (string, bool) { + for _, m := range userSt.Members { + if m.Offset != moff { + continue + } + mt := btf.UnderlyingType(m.Type) + if ptr, ok := btf.As[*btf.Pointer](mt); ok { + if _, ok := btf.As[*btf.FuncProto](ptr.Target); ok { + return m.Name, true + } + } + } + return "", false +} + type libbpfElfSectionDef struct { pattern string programType sys.ProgType From ef7d704341b25e54eb87a4e6dfe1eaea58ede423 Mon Sep 17 00:00:00 2001 From: shun159 Date: Fri, 17 Oct 2025 00:40:44 +0900 Subject: [PATCH 04/16] elf: KeySize of a struct_ops map should always be 4 Signed-off-by: shun159 --- elf_reader.go | 1 + elf_reader_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/elf_reader.go b/elf_reader.go index cc4b01366..1ca8a02f5 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -1477,6 +1477,7 @@ func (ec *elfCode) loadStructOpsMaps() error { Name: mapName, Type: StructOpsMap, Key: &btf.Int{Size: 4}, + KeySize: 4, Value: userType, Flags: flags, MaxEntries: 1, diff --git a/elf_reader_test.go b/elf_reader_test.go index 3b4891bb1..3bf7764c4 100644 --- a/elf_reader_test.go +++ b/elf_reader_test.go @@ -955,6 +955,7 @@ func TestStructOps(t *testing.T) { MaxEntries: 1, Flags: sys.BPF_F_LINK, Key: &btf.Int{Size: 4}, + KeySize: 4, Value: &btf.Struct{ Name: "bpf_testmod_ops", Size: 24, From 40e973418fa243ff4b7f938ef0a25a8452993d9a Mon Sep 17 00:00:00 2001 From: shun159 Date: Fri, 17 Oct 2025 19:26:15 +0900 Subject: [PATCH 05/16] testing: reenable TestLibBPFCompat Signed-off-by: shun159 --- elf_reader_test.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/elf_reader_test.go b/elf_reader_test.go index 3bf7764c4..1625d1cc3 100644 --- a/elf_reader_test.go +++ b/elf_reader_test.go @@ -1157,12 +1157,6 @@ func TestLibBPFCompat(t *testing.T) { } } - for _, ps := range spec.Programs { - if ps.Type == StructOps { - ps.AttachTo = "" - } - } - coreFiles := sourceOfBTF(t, path) if len(coreFiles) == 0 { // NB: test_core_reloc_kernel.o doesn't have dedicated BTF and From 0be509ec90b026cdf3734cd3540fb87ff4de243b Mon Sep 17 00:00:00 2001 From: Eishun Kondoh Date: Sat, 18 Oct 2025 14:45:47 +0900 Subject: [PATCH 06/16] elf: keep the sec.Size condition Co-authored-by: Lorenz Bauer Signed-off-by: Eishun Kondoh --- elf_reader.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elf_reader.go b/elf_reader.go index 1ca8a02f5..e1a83b6fc 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -126,7 +126,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { case sec.Type == elf.SHT_REL: // Store relocations under the section index of the target relSections[elf.SectionIndex(sec.Info)] = sec - case sec.Type == elf.SHT_PROGBITS: + case sec.Type == elf.SHT_PROGBITS && sec.Size > 0: if (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0 { sections[idx] = newElfSection(sec, programSection) } else if sec.Name == ".struct_ops" || sec.Name == ".struct_ops.link" { From fb276200493c0e269d16cbe449bc4f6520cc09d9 Mon Sep 17 00:00:00 2001 From: shun159 Date: Sat, 18 Oct 2025 14:53:35 +0900 Subject: [PATCH 07/16] elf: fix to refuse ".struct_ops" section explicitly Signed-off-by: shun159 --- elf_reader.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/elf_reader.go b/elf_reader.go index e1a83b6fc..06e9b9f55 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -129,11 +129,13 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { case sec.Type == elf.SHT_PROGBITS && sec.Size > 0: if (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0 { sections[idx] = newElfSection(sec, programSection) - } else if sec.Name == ".struct_ops" || sec.Name == ".struct_ops.link" { - //classification based on sec names so that struct_ops-specific - // sections (.struct_ops, .struct_ops.link) are correctly recognized + } else if sec.Name == ".struct_ops.link" { + // classification based on sec names so that struct_ops-specific + // sections (.struct_ops.link) is correctly recognized // as non-executable PROGBITS, allowing value placement and link metadata to be loaded. sections[idx] = newElfSection(sec, structOpsSection) + } else if sec.Name == ".struct_ops" { + return nil, fmt.Errorf(".struct_ops StructOps is not supported: %s", ErrNotSupported) } } } From 9921ee794b9e36bccac60c5172a1b9319b27eb9d Mon Sep 17 00:00:00 2001 From: shun159 Date: Sat, 18 Oct 2025 14:58:52 +0900 Subject: [PATCH 08/16] elf: reading user data from sec.Data only needs to be done onc. Signed-off-by: shun159 --- elf_reader.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/elf_reader.go b/elf_reader.go index 06e9b9f55..5cca0f9c8 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -1425,6 +1425,13 @@ func (ec *elfCode) loadStructOpsMaps() error { continue } + // Retrieve raw data from the ELF section. + // This data contains the initial values for the struct_ops map. + userData, err := sec.Data() + if err != nil { + return fmt.Errorf("failed to read section data: %w", err) + } + // Process the struct_ops section to create the map dataType, err := ec.btf.AnyTypeByName(sec.Name) if err != nil { @@ -1448,13 +1455,6 @@ func (ec *elfCode) loadStructOpsMaps() error { return fmt.Errorf("var %s: expect struct, got %T", varType.Name, varType.Type) } - // Retrieve raw data from the ELF section. - // This data contains the initial values for the struct_ops map. - userData, err := sec.Data() - if err != nil { - return fmt.Errorf("failed to read section data: %w", err) - } - flags := uint32(0) if sec.Name == ".struct_ops.link" { flags = sys.BPF_F_LINK From 8d2dc6027e0c74edf831d79ef8db1aebfb3de3e3 Mon Sep 17 00:00:00 2001 From: shun159 Date: Sat, 18 Oct 2025 15:09:50 +0900 Subject: [PATCH 09/16] elf: refactored to use TypeByName/UnderlyingType Signed-off-by: shun159 --- elf_reader.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/elf_reader.go b/elf_reader.go index 5cca0f9c8..db701c1a4 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -1433,21 +1433,13 @@ func (ec *elfCode) loadStructOpsMaps() error { } // Process the struct_ops section to create the map - dataType, err := ec.btf.AnyTypeByName(sec.Name) - if err != nil { + var ds *btf.Datasec + if err := ec.btf.TypeByName(sec.Name, &ds); err != nil { return fmt.Errorf("datasec %s: %w", sec.Name, err) } - dataSec, ok := btf.As[*btf.Datasec](dataType) - if !ok { - return fmt.Errorf("%s BTF is not a Datasec", sec.Name) - } - - for _, vsi := range dataSec.Vars { - varType, ok := btf.As[*btf.Var](vsi.Type) - if !ok { - return fmt.Errorf("var type in %s: want *btf.Var, got %T", sec.Name, btf.UnderlyingType(vsi.Type)) - } + for _, vsi := range ds.Vars { + varType := btf.UnderlyingType(vsi.Type).(*btf.Var) mapName := varType.Name userType, ok := btf.UnderlyingType(varType.Type).(*btf.Struct) From 80c5463969d62cd4d320306934d485f40e17e5ba Mon Sep 17 00:00:00 2001 From: shun159 Date: Sat, 18 Oct 2025 15:29:54 +0900 Subject: [PATCH 10/16] elf, struct_ops: move helper function and section name Signed-off-by: shun159 --- elf_reader.go | 23 +++-------------------- struct_ops.go | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/elf_reader.go b/elf_reader.go index db701c1a4..ad32d68d2 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -129,7 +129,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { case sec.Type == elf.SHT_PROGBITS && sec.Size > 0: if (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0 { sections[idx] = newElfSection(sec, programSection) - } else if sec.Name == ".struct_ops.link" { + } else if sec.Name == structOpsLinkSec { // classification based on sec names so that struct_ops-specific // sections (.struct_ops.link) is correctly recognized // as non-executable PROGBITS, allowing value placement and link metadata to be loaded. @@ -1448,7 +1448,7 @@ func (ec *elfCode) loadStructOpsMaps() error { } flags := uint32(0) - if sec.Name == ".struct_ops.link" { + if sec.Name == structOpsLinkSec { flags = sys.BPF_F_LINK } @@ -1547,7 +1547,7 @@ func (ec *elfCode) associateStructOpsRelocs( } // Find the member at moff and ensure it's a pointer to a FuncProto. - if memberName, found := funcPtrMemberAtOffset(userSt, moff); found { + if memberName, found := structOpsFuncPtrMemberAtOffset(userSt, moff); found { p, ok := progs[sym.Name] if !(ok && p.Type == StructOps) { return fmt.Errorf("program %q not found or not StructOps", sym.Name) @@ -1560,23 +1560,6 @@ func (ec *elfCode) associateStructOpsRelocs( return nil } -// funcPtrMemberAtOffset returns the member name at bit offset `moff` -// if the member is a pointer to a FuncProto. Otherwise returns an empty string. -func funcPtrMemberAtOffset(userSt *btf.Struct, moff btf.Bits) (string, bool) { - for _, m := range userSt.Members { - if m.Offset != moff { - continue - } - mt := btf.UnderlyingType(m.Type) - if ptr, ok := btf.As[*btf.Pointer](mt); ok { - if _, ok := btf.As[*btf.FuncProto](ptr.Target); ok { - return m.Name, true - } - } - } - return "", false -} - type libbpfElfSectionDef struct { pattern string programType sys.ProgType diff --git a/struct_ops.go b/struct_ops.go index e70fb779b..771a1bf90 100644 --- a/struct_ops.go +++ b/struct_ops.go @@ -10,6 +10,7 @@ import ( ) const structOpsValuePrefix = "bpf_struct_ops_" +const structOpsLinkSec = ".struct_ops.link" // structOpsFindInnerType returns the "inner" struct inside a value struct_ops type. // @@ -118,3 +119,20 @@ func structOpsCopyMember(m, km btf.Member, data []byte, kernVData []byte) error copy(kernVData[dstOff:dstOff+mSize], data[srcOff:srcOff+mSize]) return nil } + +// funcPtrMemberAtOffset returns the member name at bit offset `moff` +// if the member is a pointer to a FuncProto. Otherwise returns an empty string. +func structOpsFuncPtrMemberAtOffset(userSt *btf.Struct, moff btf.Bits) (string, bool) { + for _, m := range userSt.Members { + if m.Offset != moff { + continue + } + mt := btf.UnderlyingType(m.Type) + if ptr, ok := btf.As[*btf.Pointer](mt); ok { + if _, ok := btf.As[*btf.FuncProto](ptr.Target); ok { + return m.Name, true + } + } + } + return "", false +} From 3ab9db7924be27267b85c3844b14306cd323b2b0 Mon Sep 17 00:00:00 2001 From: shun159 Date: Sat, 18 Oct 2025 15:48:06 +0900 Subject: [PATCH 11/16] elf,struct_ops: fix error message in ".struct_ops" guard Signed-off-by: shun159 --- elf_reader.go | 4 ++-- struct_ops.go | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/elf_reader.go b/elf_reader.go index ad32d68d2..bd561b24a 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -134,8 +134,8 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { // sections (.struct_ops.link) is correctly recognized // as non-executable PROGBITS, allowing value placement and link metadata to be loaded. sections[idx] = newElfSection(sec, structOpsSection) - } else if sec.Name == ".struct_ops" { - return nil, fmt.Errorf(".struct_ops StructOps is not supported: %s", ErrNotSupported) + } else if sec.Name == structOpsSec { + return nil, fmt.Errorf("section %q: got '.struct_ops' section: %w", sec.Name, ErrNotSupported) } } } diff --git a/struct_ops.go b/struct_ops.go index 771a1bf90..19c82a083 100644 --- a/struct_ops.go +++ b/struct_ops.go @@ -11,6 +11,7 @@ import ( const structOpsValuePrefix = "bpf_struct_ops_" const structOpsLinkSec = ".struct_ops.link" +const structOpsSec = ".struct_ops" // structOpsFindInnerType returns the "inner" struct inside a value struct_ops type. // From b2d4a3386365ad73321d5a808c1db72fef05cfda Mon Sep 17 00:00:00 2001 From: shun159 Date: Sat, 18 Oct 2025 16:39:51 +0900 Subject: [PATCH 12/16] add handle unknown struct_ops target Signed-off-by: shun159 --- elf_reader_test.go | 3 +++ prog.go | 3 +++ struct_ops.go | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/elf_reader_test.go b/elf_reader_test.go index 1625d1cc3..ddc484c86 100644 --- a/elf_reader_test.go +++ b/elf_reader_test.go @@ -1064,6 +1064,9 @@ func TestLibBPFCompat(t *testing.T) { t.Fatal("Expected an error during load") } } else if err != nil { + if errors.Is(err, errUnknownStructOps) { + t.Skip("Skipping since the struct_ops target doesn't exist in kernel") + } t.Fatal("Error during loading:", err) } } diff --git a/prog.go b/prog.go index 3e724234d..a6ba888c6 100644 --- a/prog.go +++ b/prog.go @@ -37,6 +37,9 @@ var errBadRelocation = errors.New("bad CO-RE relocation") // This error is detected based on heuristics and therefore may not be reliable. var errUnknownKfunc = errors.New("unknown kfunc") +// errUnknownStructOps is returned when the struct_ops target doesn't exist in kernel +var errUnknownStructOps = errors.New("unknown struct_ops target") + // ProgramID represents the unique ID of an eBPF program. type ProgramID = sys.ProgramID diff --git a/struct_ops.go b/struct_ops.go index 19c82a083..17ce6fc52 100644 --- a/struct_ops.go +++ b/struct_ops.go @@ -1,6 +1,7 @@ package ebpf import ( + "errors" "fmt" "reflect" "strings" @@ -46,6 +47,9 @@ func structOpsFindTarget(userType *btf.Struct, cache *btf.Cache) (vType *btf.Str target := btf.Type((*btf.Struct)(nil)) spec, module, err := findTargetInKernel(vTypeName, &target, cache) + if errors.Is(err, btf.ErrNotFound) { + return nil, 0, nil, fmt.Errorf("%q doesn't exist in kernel: %w", vTypeName, errUnknownStructOps) + } if err != nil { return nil, 0, nil, fmt.Errorf("lookup value type %q: %w", vTypeName, err) } From 558dc8b38f50738a4bd18a17b81ca5bcd54e6218 Mon Sep 17 00:00:00 2001 From: shun159 Date: Sun, 19 Oct 2025 15:44:35 +0900 Subject: [PATCH 13/16] elf: reject standalone struct_ops prog Signed-off-by: shun159 --- Makefile | 1 + elf_reader.go | 11 ++++++++++- elf_reader_test.go | 7 +++++++ testdata/struct_ops_autocreate-eb.elf | Bin 0 -> 1912 bytes testdata/struct_ops_autocreate-el.elf | Bin 0 -> 1912 bytes testdata/struct_ops_autocreate.c | 20 ++++++++++++++++++++ 6 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 testdata/struct_ops_autocreate-eb.elf create mode 100644 testdata/struct_ops_autocreate-el.elf create mode 100644 testdata/struct_ops_autocreate.c diff --git a/Makefile b/Makefile index c2c0a5d31..da7a50864 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,7 @@ TARGETS := \ testdata/variables \ testdata/arena \ testdata/struct_ops \ + testdata/struct_ops_autocreate \ btf/testdata/relocs \ btf/testdata/relocs_read \ btf/testdata/relocs_read_tgt \ diff --git a/elf_reader.go b/elf_reader.go index bd561b24a..1d5f4462b 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -1488,13 +1488,15 @@ func (ec *elfCode) loadStructOpsMaps() error { return nil } -// associateStructOpsRelocs handles `.struct_ops(.link)` +// associateStructOpsRelocs handles `.struct_ops.link` // and associates the target function with the correct struct member in the map. func (ec *elfCode) associateStructOpsRelocs( progs map[string]*ProgramSpec, relSecs map[elf.SectionIndex]*elf.Section, symbols []elf.Symbol, ) error { + willAttachToMap := make(map[string]bool) + for _, sec := range relSecs { if !(sec.Type == elf.SHT_REL) { continue @@ -1553,10 +1555,17 @@ func (ec *elfCode) associateStructOpsRelocs( return fmt.Errorf("program %q not found or not StructOps", sym.Name) } p.AttachTo = userSt.Name + ":" + memberName + willAttachToMap[sym.Name] = true } } } + for name, p := range progs { + if p.Type == StructOps && !willAttachToMap[name] { + return fmt.Errorf("standalone struct_ops program %s: %w", name, ErrNotSupported) + } + } + return nil } diff --git a/elf_reader_test.go b/elf_reader_test.go index ddc484c86..231f92858 100644 --- a/elf_reader_test.go +++ b/elf_reader_test.go @@ -1028,6 +1028,13 @@ func TestStructOps(t *testing.T) { qt.Assert(t, qt.CmpEquals(data, userData, csCmpOpts)) } +func TestStructOpsWithStandAloneProg(t *testing.T) { + file := testutils.NativeFile(t, "testdata/struct_ops_autocreate-%s.elf") + _, err := LoadCollectionSpec(file) + testutils.SkipIfNotSupported(t, err) + qt.Assert(t, qt.IsNil(err)) +} + var ( elfPath = flag.String("elfs", os.Getenv("CI_KERNEL_SELFTESTS"), "`Path` containing libbpf-compatible ELFs (defaults to $CI_KERNEL_SELFTESTS)") elfPattern = flag.String("elf-pattern", "*.o", "Glob `pattern` for object files that should be tested") diff --git a/testdata/struct_ops_autocreate-eb.elf b/testdata/struct_ops_autocreate-eb.elf new file mode 100644 index 0000000000000000000000000000000000000000..916db234836ad9dfbf93c0ece55fd7e974cc23e2 GIT binary patch literal 1912 zcmb_cyKWOf6rHslAP@6WibzmwK|z7mAWKk#jAWJZwETjVK123!;YcsSH zcVBqccpy2 zHVM$tC{NmH9OZG)R*PwzkJ5pE^`_d5Qq|tap!NFp_WF)~c=t(Nw|1I!t(ER|+wmZa z6{>2s=qDYvQ__x2jBoj1(lO|D2QQT^Am{0uFHq_tEcFU>OE~U3zma=K;jwUU7sR@A z7r<>Gow1A&e9Pv7Iw~mW9QAnq()pu2Yx)jPOrgDZuDBnDkb!q(6+GAKip?X(wb}Lk zhR;~$LA+HA^aY!J;k~fzY2Eyk#g7j6xjan5^hb&=6{eJ J8)y5k{RO0Dk>3CS literal 0 HcmV?d00001 diff --git a/testdata/struct_ops_autocreate-el.elf b/testdata/struct_ops_autocreate-el.elf new file mode 100644 index 0000000000000000000000000000000000000000..9537da146d01993f1d44ff581d8393d634786033 GIT binary patch literal 1912 zcmbtVJ#W)c6utT2qis?s>VO181_lP?fW&|VNN%MR5J3thLKGnxCvj;Yi6h%VWhxRI z3oJ|w`~ijzmG~R{4}Jg(5}ae-7rReU7mjp&-#zbs`Q7K_pw-?gmr7z#lHZbBh84M0 z&DUSe68j}l8aV4ljjn)0wD^?Rf| z{T`ErFMZC=Q%Hs6>5LQlJtcvC%`LApd-V)SNDM&_)$`CFAS)2!BZR%V5JkCWR@%>} z%wAoFX0ET9K1aIF2f)1FA=pp(C6WO9k>Mnf-7u6miDv!83#V~23F5@NAw zpZLw(!SiQH*pC7~37ozxML{x)CXMU2rRPV|-^0W6Hnz7ncDyGKp0~X2PN(I0;*Ey= zU=jy{tR72@!vV{QmSd50ZFncI=!}MwH)0ZSo(=X6TvoXTeV+t?SqR^eXGd@#JOj>@ zy0~`<_9mo^1l*h3cVL`=N;c+W9#>#pDDccjG$7=SZS+I%YuOaA(s@9yL)Ox6$!2|J ztL(}5qWn>kbL6|N^8x_0DLfN`Yi`}70T|nym$9E^+w=Y9m0De#B-l^HiGq={+1+wz z6oAVOQ6$8vF~ P Date: Sun, 19 Oct 2025 17:58:39 +0900 Subject: [PATCH 14/16] remove structOpsSpec, and merged loadStructOpsMaps into associateStructOpsRelocs Signed-off-by: shun159 --- elf_reader.go | 208 +++++++++++++++++++++----------------------------- 1 file changed, 88 insertions(+), 120 deletions(-) diff --git a/elf_reader.go b/elf_reader.go index 1d5f4462b..378b1dd31 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -41,30 +41,20 @@ type ksymMeta struct { Name string } -type structOpsSpec struct { - name string - // section index of .struct_ops / .struct_ops.link - secIdx elf.SectionIndex - // byte offset of the variable in that section - userOff uint64 - userSize uint64 -} - // elfCode is a convenience to reduce the amount of arguments that have to // be passed around explicitly. You should treat its contents as immutable. type elfCode struct { *internal.SafeELFFile - sections map[elf.SectionIndex]*elfSection - license string - version uint32 - btf *btf.Spec - extInfo *btf.ExtInfos - maps map[string]*MapSpec - vars map[string]*VariableSpec - kfuncs map[string]*btf.Func - ksyms map[string]struct{} - kconfig *MapSpec - structOps map[string]*structOpsSpec + sections map[elf.SectionIndex]*elfSection + license string + version uint32 + btf *btf.Spec + extInfo *btf.ExtInfos + maps map[string]*MapSpec + vars map[string]*VariableSpec + kfuncs map[string]*btf.Func + ksyms map[string]struct{} + kconfig *MapSpec } // LoadCollectionSpec parses an ELF file into a CollectionSpec. @@ -166,7 +156,6 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { vars: make(map[string]*VariableSpec), kfuncs: make(map[string]*btf.Func), ksyms: make(map[string]struct{}), - structOps: make(map[string]*structOpsSpec), } symbols, err := f.Symbols() @@ -184,10 +173,6 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { return nil, fmt.Errorf("load maps: %w", err) } - if err := ec.loadStructOpsMaps(); err != nil { - return nil, fmt.Errorf("struct_ops maps: %w", err) - } - if err := ec.loadBTFMaps(); err != nil { return nil, fmt.Errorf("load BTF maps: %w", err) } @@ -1417,27 +1402,44 @@ func (ec *elfCode) loadKsymsSection() error { return nil } -// loadStructOpsMapsFromSections creates StructOps MapSpecs from DataSec sections -// ".struct_ops" and ".struct_ops.link" found in the object BTF. -func (ec *elfCode) loadStructOpsMaps() error { +// associateStructOpsRelocs handles `.struct_ops.link` +// and associates the target function with the correct struct member in the map. +func (ec *elfCode) associateStructOpsRelocs( + progs map[string]*ProgramSpec, + relSecs map[elf.SectionIndex]*elf.Section, + symbols []elf.Symbol, +) error { + willAttachToMap := make(map[string]bool) + for secIdx, sec := range ec.sections { if sec.kind != structOpsSection { continue } - // Retrieve raw data from the ELF section. - // This data contains the initial values for the struct_ops map. userData, err := sec.Data() if err != nil { return fmt.Errorf("failed to read section data: %w", err) } - // Process the struct_ops section to create the map + // Resolve the BTF datasec describing variables in this section. var ds *btf.Datasec if err := ec.btf.TypeByName(sec.Name, &ds); err != nil { return fmt.Errorf("datasec %s: %w", sec.Name, err) } + // Set flags for .struct_ops.link (BPF_F_LINK). + flags := uint32(0) + if sec.Name == structOpsLinkSec { + flags = sys.BPF_F_LINK + } + + type structOpsMapOfsSize struct { + userOff uint64 + userSize uint64 + } + + ofsSizes := make(map[string]structOpsMapOfsSize) + for _, vsi := range ds.Vars { varType := btf.UnderlyingType(vsi.Type).(*btf.Var) mapName := varType.Name @@ -1447,115 +1449,81 @@ func (ec *elfCode) loadStructOpsMaps() error { return fmt.Errorf("var %s: expect struct, got %T", varType.Name, varType.Type) } - flags := uint32(0) - if sec.Name == structOpsLinkSec { - flags = sys.BPF_F_LINK - } - userSize := uint64(userType.Size) userOff := uint64(vsi.Offset) if userOff+userSize > uint64(len(userData)) { return fmt.Errorf("%s exceeds section", mapName) } - ec.structOps[mapName] = - &structOpsSpec{ - name: mapName, - secIdx: secIdx, - userOff: userOff, - userSize: userSize, - } - - ec.maps[mapName] = - &MapSpec{ - Name: mapName, - Type: StructOpsMap, - Key: &btf.Int{Size: 4}, - KeySize: 4, - Value: userType, - Flags: flags, - MaxEntries: 1, - Contents: []MapKV{ - { - Key: uint32(0), - Value: append([]byte(nil), userData[userOff:userOff+userSize]...), - }, + // Register the MapSpec for this struct_ops instance. + ec.maps[mapName] = &MapSpec{ + Name: mapName, + Type: StructOpsMap, + Key: &btf.Int{Size: 4}, + KeySize: 4, + Value: userType, + Flags: flags, + MaxEntries: 1, + Contents: []MapKV{ + { + Key: uint32(0), + Value: append([]byte(nil), userData[userOff:userOff+userSize]...), }, - } + }, + } + ofsSizes[mapName] = + structOpsMapOfsSize{userOff, userSize} } - } - - return nil -} - -// associateStructOpsRelocs handles `.struct_ops.link` -// and associates the target function with the correct struct member in the map. -func (ec *elfCode) associateStructOpsRelocs( - progs map[string]*ProgramSpec, - relSecs map[elf.SectionIndex]*elf.Section, - symbols []elf.Symbol, -) error { - willAttachToMap := make(map[string]bool) - for _, sec := range relSecs { - if !(sec.Type == elf.SHT_REL) { - continue - } + // Process relo sections that target this struct_ops section. + for relSecIdx, relSec := range relSecs { + if elf.SectionIndex(relSec.Info) != elf.SectionIndex(secIdx) { + continue + } - targetIdx := elf.SectionIndex(sec.Info) - targetSec, ok := ec.sections[targetIdx] - if !(ok && strings.HasPrefix(targetSec.Name, ".struct_ops")) { - continue - } + if !(relSec.Type == elf.SHT_REL) { + continue + } - // Load the relocations from the relocation section - rels, err := ec.loadSectionRelocations(sec, symbols) - if err != nil { - return fmt.Errorf("failed to load relocations for section %s: %w", sec.Name, err) - } + // Load relocation entries (offset -> symbol). + rels, err := ec.loadSectionRelocations(relSec, symbols) + if err != nil { + return fmt.Errorf("failed to load relocations for section %s (relIdx=%d -> target=%d): %w", + relSec.Name, relSecIdx, secIdx, err) + } - for relOff, sym := range rels { - var ms *MapSpec - var meta *structOpsSpec + for relOff, sym := range rels { + var ms *MapSpec + var msName string + var baseOff uint64 - for _, mapSpec := range ec.maps { - if mapSpec.Type != StructOpsMap || len(mapSpec.Contents) == 0 { - continue + for mapName, ofsSz := range ofsSizes { + if relOff >= ofsSz.userOff && relOff < ofsSz.userOff+ofsSz.userSize { + baseOff = ofsSz.userOff + ms = ec.maps[mapName] + msName = mapName + break + } } - stOps, ok := ec.structOps[mapSpec.Name] - if !ok { - continue + if ms == nil || ms.Type != StructOpsMap { + return fmt.Errorf("struct_ops map %s not found or wrong type", msName) } - if uint64(targetIdx) == uint64(stOps.secIdx) && - stOps.userOff <= relOff && - (relOff-stOps.userOff) < stOps.userSize { - meta = stOps - ms = mapSpec + userSt, ok := btf.As[*btf.Struct](ms.Value) + if !ok { + return fmt.Errorf("map %s value is not a btf.Struct", ms.Name) } - } - if ms == nil { - return fmt.Errorf("no struct_ops map found for secIdx %d and relOffset %d", targetIdx, relOff) - } - - // Member bit offset inside the user struct - moff := btf.Bits((relOff - meta.userOff) * 8) - - userSt, ok := btf.As[*btf.Struct](ms.Value) - if !ok { - return fmt.Errorf("provided value is not a btf.Struct") - } - - // Find the member at moff and ensure it's a pointer to a FuncProto. - if memberName, found := structOpsFuncPtrMemberAtOffset(userSt, moff); found { - p, ok := progs[sym.Name] - if !(ok && p.Type == StructOps) { - return fmt.Errorf("program %q not found or not StructOps", sym.Name) + moff := btf.Bits((relOff - baseOff) * 8) + if memberName, ok := structOpsFuncPtrMemberAtOffset(userSt, moff); ok { + p, ok := progs[sym.Name] + if !(ok && p.Type == StructOps) { + return fmt.Errorf("program %s not found or not StructOps", sym.Name) + } + p.AttachTo = userSt.Name + ":" + memberName + willAttachToMap[sym.Name] = true } - p.AttachTo = userSt.Name + ":" + memberName - willAttachToMap[sym.Name] = true } } } From 0743fe4ddfdee4bcc43a6a97d181971c82a62533 Mon Sep 17 00:00:00 2001 From: shun159 Date: Sun, 19 Oct 2025 18:07:46 +0900 Subject: [PATCH 15/16] testing: fix typo in struct_ops_autocreate Signed-off-by: shun159 --- testdata/struct_ops_autocreate-eb.elf | Bin 1912 -> 1920 bytes testdata/struct_ops_autocreate-el.elf | Bin 1912 -> 1920 bytes testdata/struct_ops_autocreate.c | 6 +++--- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/testdata/struct_ops_autocreate-eb.elf b/testdata/struct_ops_autocreate-eb.elf index 916db234836ad9dfbf93c0ece55fd7e974cc23e2..42e4254809390a8c2bbae8dc32f2a0559630d9ec 100644 GIT binary patch delta 259 zcmeyt*T6qPpV48WK|JHui4)9u{dgD{7#SECSb#Wa;uUqva1aj&KmrOtoXrO$fPfu{ zK~g0^2__&d0K_do%nigK&^_6NQCV{WR5ce+1f+TnKZFLU-~|yt0Mfa3as#6*qy6N$ zjH`i$O?GDrkdy%mJ2Noozzm1-ZI~uIvM5YG!&J}6JlTi&Do~RmOXTEg78#a3Kpm6V xMK-Tz(O_isn0$lPo$=yi1vYcWh{+Lb_KZH0X8_5H$v1%Hj>!`2AToko0RYiXDxClT delta 250 zcmZqR|G_swpV4BXK|JG@i4)9ueR&ud7#SECSb#Wi;uUqvFc1$2KmrOtoW%zufPfu{ zK~lv)2__&d0L0Be%nigK&^6hFQCYJes+tQZ0#ZGjA3}pv@PY^+0O?#axq(r3@;t`X zlYN-{MWulvP7Dk>Fmu8D$s3pyCSPHyXJncj!hChI7E9#hCKg%FEg&O+pnmfT7IQ{M phsh6E-5D=T)?hPd449n2X3yv`c>$0tnEU`pZkVjV4k8oS6#xajDmDNB diff --git a/testdata/struct_ops_autocreate-el.elf b/testdata/struct_ops_autocreate-el.elf index 9537da146d01993f1d44ff581d8393d634786033..e151969208bee7d11b0e59813296d9f289739990 100644 GIT binary patch delta 262 zcmeyt*T6qPgVAB4W-{Z}iM{5$ehdr@j0_AsEI>MF;zf1Ka3BK+7(oIGK%5Q4K)}Zi zB!CJTN`RP&fq_K;NVfnnHxPqB_hdsxWz7i?4j&hg2C1F{q4`0=ydVMyKswh>u4PnU zw4XeeaW&Ae$?i$0QpiX AD*ylh delta 250 zcmZqR|G_swgVAE5W-{ZJiM{5$z6=Zuj0_AsEI>MN;zf1KFdzd67(oIGK%51{K)}Zi zB!CJTih-Dkfq_K;NH+s9HxPqB*JMLRWzBvFhmQ+LgH+Fk(EK1_UJwBUAf0O_*D@+h zp2xU)vJaDws5DT-35c0=AhKZo Date: Tue, 21 Oct 2025 00:14:44 +0900 Subject: [PATCH 16/16] elf: fix to add ignoreExtra flag so that allow standalone prog Signed-off-by: shun159 --- Makefile | 1 - elf_reader.go | 6 ------ elf_reader_test.go | 7 ------- elf_sections.go | 4 ++-- testdata/struct_ops_autocreate-eb.elf | Bin 1920 -> 0 bytes testdata/struct_ops_autocreate-el.elf | Bin 1920 -> 0 bytes testdata/struct_ops_autocreate.c | 20 -------------------- 7 files changed, 2 insertions(+), 36 deletions(-) delete mode 100644 testdata/struct_ops_autocreate-eb.elf delete mode 100644 testdata/struct_ops_autocreate-el.elf delete mode 100644 testdata/struct_ops_autocreate.c diff --git a/Makefile b/Makefile index da7a50864..c2c0a5d31 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,6 @@ TARGETS := \ testdata/variables \ testdata/arena \ testdata/struct_ops \ - testdata/struct_ops_autocreate \ btf/testdata/relocs \ btf/testdata/relocs_read \ btf/testdata/relocs_read_tgt \ diff --git a/elf_reader.go b/elf_reader.go index 378b1dd31..3f4c802e0 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -1528,12 +1528,6 @@ func (ec *elfCode) associateStructOpsRelocs( } } - for name, p := range progs { - if p.Type == StructOps && !willAttachToMap[name] { - return fmt.Errorf("standalone struct_ops program %s: %w", name, ErrNotSupported) - } - } - return nil } diff --git a/elf_reader_test.go b/elf_reader_test.go index 231f92858..ddc484c86 100644 --- a/elf_reader_test.go +++ b/elf_reader_test.go @@ -1028,13 +1028,6 @@ func TestStructOps(t *testing.T) { qt.Assert(t, qt.CmpEquals(data, userData, csCmpOpts)) } -func TestStructOpsWithStandAloneProg(t *testing.T) { - file := testutils.NativeFile(t, "testdata/struct_ops_autocreate-%s.elf") - _, err := LoadCollectionSpec(file) - testutils.SkipIfNotSupported(t, err) - qt.Assert(t, qt.IsNil(err)) -} - var ( elfPath = flag.String("elfs", os.Getenv("CI_KERNEL_SELFTESTS"), "`Path` containing libbpf-compatible ELFs (defaults to $CI_KERNEL_SELFTESTS)") elfPattern = flag.String("elf-pattern", "*.o", "Glob `pattern` for object files that should be tested") diff --git a/elf_sections.go b/elf_sections.go index 43dcfb103..660209aa4 100644 --- a/elf_sections.go +++ b/elf_sections.go @@ -104,8 +104,8 @@ var elfSectionDefs = []libbpfElfSectionDef{ {"cgroup/getsockopt", sys.BPF_PROG_TYPE_CGROUP_SOCKOPT, sys.BPF_CGROUP_GETSOCKOPT, _SEC_ATTACHABLE}, {"cgroup/setsockopt", sys.BPF_PROG_TYPE_CGROUP_SOCKOPT, sys.BPF_CGROUP_SETSOCKOPT, _SEC_ATTACHABLE}, {"cgroup/dev", sys.BPF_PROG_TYPE_CGROUP_DEVICE, sys.BPF_CGROUP_DEVICE, _SEC_ATTACHABLE_OPT}, - {"struct_ops+", sys.BPF_PROG_TYPE_STRUCT_OPS, 0, _SEC_NONE}, - {"struct_ops.s+", sys.BPF_PROG_TYPE_STRUCT_OPS, 0, _SEC_SLEEPABLE}, + {"struct_ops+", sys.BPF_PROG_TYPE_STRUCT_OPS, 0, _SEC_NONE | ignoreExtra}, + {"struct_ops.s+", sys.BPF_PROG_TYPE_STRUCT_OPS, 0, _SEC_SLEEPABLE | ignoreExtra}, {"sk_lookup", sys.BPF_PROG_TYPE_SK_LOOKUP, sys.BPF_SK_LOOKUP, _SEC_ATTACHABLE}, {"netfilter", sys.BPF_PROG_TYPE_NETFILTER, sys.BPF_NETFILTER, _SEC_NONE}, } diff --git a/testdata/struct_ops_autocreate-eb.elf b/testdata/struct_ops_autocreate-eb.elf deleted file mode 100644 index 42e4254809390a8c2bbae8dc32f2a0559630d9ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1920 zcmb_cJ#Q015S?=zAc@15Qly}`f`TGCgG2`i2b2(mg(4FPf{>1jeMzkNBRX$Ts7OIa zlZFn7UqFeV;7{;7_yIIX@ZQ~yJu$Ipc+%|5yq(#Z+a3GON`19d3S@{te!@1ya0f=Y zmV3I+OYkKpMBaNn?mt+s`<;{O$H%iW#|z8bfxGmr%l{r z&-09i&tdL4_%g+%z!@lVjC<F7EFSsxBS4_=Z%|t^MuXX(cYH zjsoK3!1bG1nz%G-%1oBJgRB=`za?8qCe2+ealG7UEN{k79zI`*H#XN-;#lHNyP5U| zso>O@CA$3<%ZV??BDt-6G^!YN+Pyu|1JJmC(~utlT=B95y+#7y-Sazne*~X~_dD&i z-DCcJktKZt@88y3Y@ZGsYMy);bM^AyJZp!Br%zBnx>U?bPWXkhvH+g_T4)|Q*47?> z!DcM;fM5X&ZSM>3hV!BR%`u<%X7`_|KRZ{Mm&m1uPNFRBME5pUBRcjC`SKj2JYf9Y zqE6>kH%?luY|!2r%>m!nC0h~=UUzft4?k@d(aZn91`avxy-0ua1&+K{#XC{ZR-eCb zi<)(et@ll;3;9=m$Up&HM^P=T`H{??P-paECZ7tl;~QDGd8PEl`u{b6$xM!utGgC2W2EuTH36;@uML z(Zbf}nUm`K{-uT(*4H>&|4#pRJ-_!O1f0awSMS?;q-?)0;Op<*hpQ@S{;|)|IIX|< E2L@r1s{jB1 diff --git a/testdata/struct_ops_autocreate-el.elf b/testdata/struct_ops_autocreate-el.elf deleted file mode 100644 index e151969208bee7d11b0e59813296d9f289739990..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1920 zcmbtVJ8u&~5FS5xB(VcQDJUphP*9*VNOXX3Kn_7zC^D8v5YlnBFNqaDqVpP=iWGD- zY3Pvn1(b*s{0V*sKY#`azVB|<5c`qd(8%6sB=^Q9nRHAzIvQ6^JT~D?smAT2z0~MN zCaTUI!f0Yfog|J-9CTzUiOnP#R&Lyuc9clx0FN--Xf`)?!>5m)*TdHCc0CLw?DsnH za2yM=hAc4{by-fd9E+s7#e;dppx+xFicNsFInxUJ2VGIV4!K1Dgn0<}$@3#|C_D$w zmC{{f`~z?v3Ai@1@4)y66|!;v;dK?(B?o6tQUy%h+EzXWzLre`n$87s9lVxyOf>5) z)3PV`#(n)%kn`mJ)_D;?w8=ad5_K-!q5-nDJu_?XWXrSl<&{!d0uxV-1WDWv?zc7r z8tx1^d0wN`!Jk{u@4p&^QMa3nd;9Zy!1Cynk-{LE-Gj!fzml`k_@9*KII{EeJ>k{w5qWA-KviH%( z`1P-0f$vStI;h;zhHQKZIPam2w>azgJu$_U570Mn(ce!OK8xz_X#EP4fLNd3R=+;K zt6m8pA`SgWus@gl`pmhg{#5JNG??}2=hwdrtkC#1f;a}x=v3qVdVa4j5MjS(vtTHv P3Z5v)I6}6*{rCL_c?*)N diff --git a/testdata/struct_ops_autocreate.c b/testdata/struct_ops_autocreate.c deleted file mode 100644 index a0643041d..000000000 --- a/testdata/struct_ops_autocreate.c +++ /dev/null @@ -1,20 +0,0 @@ -#include "common.h" - -char _license[] __section("license") = "GPL"; - -struct bpf_testmod_ops { - int (*test_1)(void); - int data; -}; - -__section("?struct_ops/test_1") int foo(void) { - return 0; -} - -__section("?struct_ops/test_1") int bar(void) { - return 0; -} - -__section(".struct_ops.link") struct bpf_testmod_ops testmod_ops = { - .test_1 = (void *)bar, -};