Skip to content

Commit a87ccb2

Browse files
committed
btf: add essentialNameLen() to handle flavor underscores properly
Only treat triple underscores as a CO-RE flavor separator when surrounded by non-underscore characters. This keeps names like ____fput distinct while using the same normalization for lookup and decoder indexing. Add tests for leading underscore runs and valid flavor suffixes. See libbpf's bpf_core_essential_name_len(). Signed-off-by: Leon Hwang <leon.hwang@linux.dev>
1 parent 64ce9bc commit a87ccb2

4 files changed

Lines changed: 58 additions & 36 deletions

File tree

btf/btf_test.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -134,17 +134,23 @@ func TestAnyTypesByNameNoExactMatch(t *testing.T) {
134134
}
135135

136136
func TestAnyTypesByNameLeadingUnderscores(t *testing.T) {
137-
// Kernel function names such as ___pskb_trim start with a triple
138-
// underscore. This isn't a flavour delimiter, so the full name must
139-
// remain queryable.
140-
spec := specFromTypes(t, []Type{
141-
&Int{Name: "___pskb_trim", Size: 4},
142-
})
143-
144-
types, err := spec.AnyTypesByName("___pskb_trim")
145-
qt.Assert(t, qt.IsNil(err))
146-
qt.Assert(t, qt.HasLen(types, 1))
147-
qt.Assert(t, qt.Equals(types[0].TypeName(), "___pskb_trim"))
137+
// Leading sequences of underscores in kernel function names aren't flavour
138+
// delimiters, so the full names must remain independently queryable.
139+
names := []string{"___pskb_trim", "____fput", "____netdev_has_upper_dev"}
140+
types := make([]Type, 0, len(names))
141+
for _, name := range names {
142+
types = append(types, &Int{Name: name, Size: 4})
143+
}
144+
spec := specFromTypes(t, types)
145+
146+
for _, name := range names {
147+
t.Run(name, func(t *testing.T) {
148+
types, err := spec.AnyTypesByName(string(newEssentialName(name)))
149+
qt.Assert(t, qt.IsNil(err))
150+
qt.Assert(t, qt.HasLen(types, 1))
151+
qt.Assert(t, qt.Equals(types[0].TypeName(), name))
152+
})
153+
}
148154
}
149155

150156
func TestAnyTypeByNameNoExactMatch(t *testing.T) {
@@ -564,7 +570,6 @@ func TestSpecConcurrentAccess(t *testing.T) {
564570
var wg sync.WaitGroup
565571
for range maxprocs {
566572
wg.Go(func() {
567-
568573
n := cond.Add(1)
569574
for cond.Load() != int64(maxprocs) {
570575
// Spin to increase the chances of a race.

btf/types.go

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"io"
77
"math"
8-
"strings"
98

109
"github.com/cilium/ebpf/asm"
1110
"github.com/cilium/ebpf/internal"
@@ -800,21 +799,30 @@ type typeDeque = internal.Deque[*Type]
800799
// suffixes after a ___ delimiter.
801800
type essentialName string
802801

803-
// newEssentialName returns name without a ___ suffix.
802+
// essentialNameLen returns the length of name without a ___ suffix.
804803
//
805804
// CO-RE has the concept of 'struct flavors', which are used to deal with
806-
// changes in kernel data structures. Anything after three underscores
807-
// in a type name is ignored for the purpose of finding a candidate type
805+
// changes in kernel data structures. A flavour separator is a sequence of
806+
// three underscores surrounded by non-underscore characters. Anything after
807+
// the last separator is ignored for the purpose of finding a candidate type
808808
// in the kernel's BTF.
809-
func newEssentialName(name string) essentialName {
810-
if name == "" {
811-
return ""
812-
}
813-
lastIdx := strings.LastIndex(name, "___")
814-
if lastIdx > 0 {
815-
return essentialName(name[:lastIdx])
809+
//
810+
// See libbpf bpf_core_essential_name_len() implementation.
811+
func essentialNameLen[T ~string | ~[]byte](name T) int {
812+
for i := len(name) - 5; i >= 0; i-- {
813+
if name[i] != '_' &&
814+
name[i+1] == '_' && name[i+2] == '_' && name[i+3] == '_' &&
815+
name[i+4] != '_' {
816+
return i + 1
817+
}
816818
}
817-
return essentialName(name)
819+
820+
return len(name)
821+
}
822+
823+
// newEssentialName returns name without a ___ suffix.
824+
func newEssentialName(name string) essentialName {
825+
return essentialName(name[:essentialNameLen(name)])
818826
}
819827

820828
// UnderlyingType skips qualifiers and Typedefs.

btf/types_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,26 @@ import (
1212
"github.com/cilium/ebpf/internal/testutils"
1313
)
1414

15+
func TestEssentialName(t *testing.T) {
16+
tests := map[string]string{
17+
"": "",
18+
"foo": "foo",
19+
"foo___flavour": "foo",
20+
"foo___one___two": "foo___one",
21+
"___pskb_trim": "___pskb_trim",
22+
"____fput": "____fput",
23+
"____netdev_has_upper_dev": "____netdev_has_upper_dev",
24+
"foo____flavour": "foo____flavour",
25+
"foo___": "foo___",
26+
}
27+
28+
for name, want := range tests {
29+
t.Run(name, func(t *testing.T) {
30+
qt.Assert(t, qt.Equals(string(newEssentialName(name)), want))
31+
})
32+
}
33+
}
34+
1535
func TestSizeof(t *testing.T) {
1636
testcases := []struct {
1737
size int

btf/unmarshal.go

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package btf
22

33
import (
4-
"bytes"
54
"encoding/binary"
65
"fmt"
76
"hash/maphash"
@@ -118,17 +117,7 @@ func newDecoder(raw []byte, bo binary.ByteOrder, strings *stringTable, base *dec
118117
}
119118

120119
if len(name) > 0 {
121-
if i := bytes.Index(name, []byte("___")); i != -1 {
122-
// Flavours are rare. It's cheaper to find the first index for some
123-
// reason.
124-
//
125-
// A leading "___" isn't a flavour delimiter (matches
126-
// newEssentialName), so only strip if it doesn't start the name.
127-
if i = bytes.LastIndex(name, []byte("___")); i > 0 {
128-
name = name[:i]
129-
}
130-
}
131-
120+
name = name[:essentialNameLen(name)]
132121
namedTypes.Add(name, id)
133122
}
134123

0 commit comments

Comments
 (0)