Skip to content

Commit

Permalink
btf: work around missing ENUM64 support on older kernels
Browse files Browse the repository at this point in the history
Replace an ENUM64 with a 64-bit integer on kernels which don't
support the former. This behaviour is similar to libbpf, which replaces
the ENUM64 with a union instead.

Remove the old heuristic which encoded a 64-bit enum as a 32-bit enum
if none of the values exceeded math.MaxUint32. The heuristic has no
tests and doesn't take the signedness of the enum into account.

Signed-off-by: Lorenz Bauer <lmb@isovalent.com>
  • Loading branch information
lmb committed Sep 13, 2023
1 parent 4cfbe1d commit d02ac56
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 30 deletions.
69 changes: 55 additions & 14 deletions btf/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ type MarshalOptions struct {
Order binary.ByteOrder
// Remove function linkage information for compatibility with <5.6 kernels.
StripFuncLinkage bool
// Replace Enum64 with a placeholder for compatibility with <6.0 kernels.
ReplaceEnum64 bool
}

// KernelMarshalOptions will generate BTF suitable for the current kernel.
func KernelMarshalOptions() *MarshalOptions {
return &MarshalOptions{
Order: internal.NativeEndian,
StripFuncLinkage: haveFuncLinkage() != nil,
ReplaceEnum64: haveEnum64() != nil,
}
}

Expand Down Expand Up @@ -333,16 +336,10 @@ func (e *encoder) deflateType(typ Type) (err error) {
raw.data, err = e.convertMembers(&raw.btfType, v.Members)

case *Enum:
raw.SetSize(v.size())
raw.SetVlen(len(v.Values))
raw.SetSigned(v.Signed)

if v.has64BitValues() {
raw.SetKind(kindEnum64)
raw.data, err = e.deflateEnum64Values(v.Values)
if v.Size == 8 {
err = e.deflateEnum64(&raw, v)
} else {
raw.SetKind(kindEnum)
raw.data, err = e.deflateEnumValues(v.Values)
err = e.deflateEnum(&raw, v)
}

case *Fwd:
Expand Down Expand Up @@ -443,16 +440,33 @@ func (e *encoder) convertMembers(header *btfType, members []Member) ([]btfMember
return bms, nil
}

func (e *encoder) deflateEnumValues(values []EnumValue) ([]btfEnum, error) {
bes := make([]btfEnum, 0, len(values))
for _, value := range values {
func (e *encoder) deflateEnum(raw *rawType, enum *Enum) (err error) {
raw.SetKind(kindEnum)
raw.SetSize(enum.Size)
raw.SetVlen(len(enum.Values))
// Signedness appeared together with ENUM64 support.
raw.SetSigned(enum.Signed && !e.ReplaceEnum64)
raw.data, err = e.deflateEnumValues(enum)
return
}

func (e *encoder) deflateEnumValues(enum *Enum) ([]btfEnum, error) {
bes := make([]btfEnum, 0, len(enum.Values))
for _, value := range enum.Values {
nameOff, err := e.strings.Add(value.Name)
if err != nil {
return nil, err
}

if value.Value > math.MaxUint32 {
return nil, fmt.Errorf("value of enum %q exceeds 32 bits", value.Name)
if enum.Signed {
signedValue := int64(value.Value)
if signedValue != int64(int32(uint32(value.Value))) {
return nil, fmt.Errorf("value %d of enum %q exceeds 32 bits", signedValue, value.Name)
}
} else {
if value.Value > math.MaxUint32 {
return nil, fmt.Errorf("value %d of enum %q exceeds 32 bits", value.Value, value.Name)
}
}

bes = append(bes, btfEnum{
Expand All @@ -464,6 +478,33 @@ func (e *encoder) deflateEnumValues(values []EnumValue) ([]btfEnum, error) {
return bes, nil
}

func (e *encoder) deflateEnum64(raw *rawType, enum *Enum) (err error) {
if e.ReplaceEnum64 {
// Replace an ENUM64 with an integer of the appropriate size. libbpf
// instead uses a union of (u)int64 which allows preserving the
// enum names (but not their values). Using the integer is simpler for
// us.
raw.SetKind(kindInt)
raw.SetSize(enum.Size)

var bi btfInt
if enum.Signed {
bi.SetEncoding(Signed)
}
bi.SetBits(byte(enum.Size) * 8)

raw.data = bi
return
}

raw.SetKind(kindEnum64)
raw.SetSize(enum.Size)
raw.SetVlen(len(enum.Values))
raw.SetSigned(enum.Signed)
raw.data, err = e.deflateEnum64Values(enum.Values)
return
}

func (e *encoder) deflateEnum64Values(values []EnumValue) ([]btfEnum64, error) {
bes := make([]btfEnum64, 0, len(values))
for _, value := range values {
Expand Down
35 changes: 34 additions & 1 deletion btf/marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ limitTypes:
}
}

buf := marshalNativeEndian(t, types)
b, err := NewBuilder(types)
qt.Assert(t, err, qt.IsNil)
buf, err := b.Marshal(nil, KernelMarshalOptions())
qt.Assert(t, err, qt.IsNil)

rebuilt, err := loadRawSpec(bytes.NewReader(buf), binary.LittleEndian, nil)
qt.Assert(t, err, qt.IsNil, qt.Commentf("round tripping BTF failed"))
Expand All @@ -121,6 +124,36 @@ limitTypes:
h.Close()
}

func TestMarshalEnum64(t *testing.T) {
enum := &Enum{
Name: "enum64",
Size: 8,
Signed: true,
Values: []EnumValue{{"MAX", math.MaxUint64}},
}

b, err := NewBuilder([]Type{enum})
qt.Assert(t, err, qt.IsNil)
buf, err := b.Marshal(nil, &MarshalOptions{
Order: internal.NativeEndian,
ReplaceEnum64: true,
})
qt.Assert(t, err, qt.IsNil)

spec, err := loadRawSpec(bytes.NewReader(buf), internal.NativeEndian, nil)
qt.Assert(t, err, qt.IsNil)

var have *Int
err = spec.TypeByName("enum64", &have)
qt.Assert(t, err, qt.IsNil)

qt.Assert(t, have, qt.DeepEquals, &Int{
Name: "enum64",
Size: 8,
Encoding: Signed,
})
}

func BenchmarkMarshaler(b *testing.B) {
spec := vmlinuxTestdataSpec(b)
types := spec.types[:100]
Expand Down
15 changes: 0 additions & 15 deletions btf/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,21 +278,6 @@ func (e *Enum) copy() Type {
return &cpy
}

// has64BitValues returns true if the Enum contains a value larger than 32 bits.
// Kernels before 6.0 have enum values that overrun u32 replaced with zeroes.
//
// 64-bit enums have their Enum.Size attributes correctly set to 8, but if we
// use the size attribute as a heuristic during BTF marshaling, we'll emit
// ENUM64s to kernels that don't support them.
func (e *Enum) has64BitValues() bool {
for _, v := range e.Values {
if v.Value > math.MaxUint32 {
return true
}
}
return false
}

// FwdKind is the type of forward declaration.
type FwdKind int

Expand Down

0 comments on commit d02ac56

Please sign in to comment.