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. Match libbpf behaviour by replacing the enum with
a union.

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 14, 2023
1 parent 4cfbe1d commit f0149a8
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 33 deletions.
88 changes: 71 additions & 17 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 @@ -328,21 +331,13 @@ func (e *encoder) deflateType(typ Type) (err error) {
raw.data, err = e.convertMembers(&raw.btfType, v.Members)

case *Union:
raw.SetKind(kindUnion)
raw.SetSize(v.Size)
raw.data, err = e.convertMembers(&raw.btfType, v.Members)
err = e.deflateUnion(&raw, v)

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 @@ -415,6 +410,13 @@ func (e *encoder) deflateType(typ Type) (err error) {
return raw.Marshal(e.buf, e.Order)
}

func (e *encoder) deflateUnion(raw *rawType, union *Union) (err error) {
raw.SetKind(kindUnion)
raw.SetSize(union.Size)
raw.data, err = e.convertMembers(&raw.btfType, union.Members)
return
}

func (e *encoder) convertMembers(header *btfType, members []Member) ([]btfMember, error) {
bms := make([]btfMember, 0, len(members))
isBitfield := false
Expand Down Expand Up @@ -443,16 +445,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 +483,41 @@ 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 the ENUM64 with a union of fields with the correct size.
// This matches libbpf behaviour.
placeholder := &Int{
"enum64_placeholder",
enum.Size,
Unsigned,
}
if enum.Signed {
placeholder.Encoding = Signed
}
if err := e.allocateID(placeholder); err != nil {
return fmt.Errorf("add enum64 placeholder: %w", err)
}

members := make([]Member, 0, len(enum.Values))
for _, v := range enum.Values {
members = append(members, Member{
Name: v.Name,
Type: placeholder,
})
}

return e.deflateUnion(raw, &Union{enum.Name, enum.Size, members})
}

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
42 changes: 41 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,43 @@ limitTypes:
h.Close()
}

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

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 *Union
err = spec.TypeByName("enum64", &have)
qt.Assert(t, err, qt.IsNil)

placeholder := &Int{Name: "enum64_placeholder", Size: 8, Encoding: Signed}
qt.Assert(t, have, qt.DeepEquals, &Union{
Name: "enum64",
Size: 8,
Members: []Member{
{Name: "A", Type: placeholder},
{Name: "B", Type: placeholder},
},
})
}

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 f0149a8

Please sign in to comment.