Skip to content

Commit

Permalink
btf: replace decl and type tags with placeholders on unsupported kernels
Browse files Browse the repository at this point in the history
Decl tags and type tags appeared in 5.16 and 5.17 respectively. This patch
makes it possible to load BTF containing such tags on older kernels by
replacing decl tags with integers and type tags with const qualifiers. This
mirrors the logic in libbpf.

This is convenient if the user wants to load an ELF containing decl tags on
global variables (available through CollectionSpec.Variables[].Tags) on older
kernels.

Signed-off-by: Timo Beckers <[email protected]>
  • Loading branch information
ti-mo committed Nov 19, 2024
1 parent 82c5aca commit e2607b5
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 18 deletions.
1 change: 1 addition & 0 deletions btf/btf_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const (
kindFloat // Float
// Added 5.16
kindDeclTag // DeclTag
// Added 5.17
kindTypeTag // TypeTag
// Added 6.0
kindEnum64 // Enum64
Expand Down
35 changes: 35 additions & 0 deletions btf/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,41 @@ var haveFuncLinkage = internal.NewFeatureTest("BTF func linkage", func() error {
return err
}, "5.6")

var haveDeclTags = internal.NewFeatureTest("BTF decl tags", func() error {
if err := haveBTF(); err != nil {
return err
}

t := &Typedef{
Name: "a",
Type: &Int{},
Tags: []string{"a"},
}

err := probeBTF(t)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
return err
}, "5.16")

var haveTypeTags = internal.NewFeatureTest("BTF type tags", func() error {
if err := haveBTF(); err != nil {
return err
}

t := &TypeTag{
Type: &Int{},
Value: "a",
}

err := probeBTF(t)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
return err
}, "5.17")

var haveEnum64 = internal.NewFeatureTest("ENUM64", func() error {
if err := haveBTF(); err != nil {
return err
Expand Down
8 changes: 8 additions & 0 deletions btf/feature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ func TestHaveFuncLinkage(t *testing.T) {
testutils.CheckFeatureTest(t, haveFuncLinkage)
}

func TestHaveDeclTags(t *testing.T) {
testutils.CheckFeatureTest(t, haveDeclTags)
}

func TestHaveTypeTags(t *testing.T) {
testutils.CheckFeatureTest(t, haveTypeTags)
}

func TestHaveEnum64(t *testing.T) {
testutils.CheckFeatureTest(t, haveEnum64)
}
79 changes: 61 additions & 18 deletions btf/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ type MarshalOptions struct {
Order binary.ByteOrder
// Remove function linkage information for compatibility with <5.6 kernels.
StripFuncLinkage bool
// Replace decl tags with a placeholder for compatibility with <5.16 kernels.
ReplaceDeclTags bool
// Replace TypeTags with a placeholder for compatibility with <5.17 kernels.
ReplaceTypeTags bool
// Replace Enum64 with a placeholder for compatibility with <6.0 kernels.
ReplaceEnum64 bool
// Prevent the "No type found" error when loading BTF without any types.
Expand All @@ -29,6 +33,8 @@ func KernelMarshalOptions() *MarshalOptions {
return &MarshalOptions{
Order: internal.NativeEndian,
StripFuncLinkage: haveFuncLinkage() != nil,
ReplaceDeclTags: haveDeclTags() != nil,
ReplaceTypeTags: haveTypeTags() != nil,
ReplaceEnum64: haveEnum64() != nil,
PreventNoTypeFound: true, // All current kernels require this.
}
Expand Down Expand Up @@ -318,15 +324,7 @@ func (e *encoder) deflateType(typ Type) (err error) {
return errors.New("Void is implicit in BTF wire format")

case *Int:
raw.SetKind(kindInt)
raw.SetSize(v.Size)

var bi btfInt
bi.SetEncoding(v.Encoding)
// We need to set bits in addition to size, since btf_type_int_is_regular
// otherwise flags this as a bitfield.
bi.SetBits(byte(v.Size) * 8)
raw.data = bi
e.deflateInt(&raw, v)

case *Pointer:
raw.SetKind(kindPointer)
Expand Down Expand Up @@ -368,8 +366,7 @@ func (e *encoder) deflateType(typ Type) (err error) {
raw.SetType(e.id(v.Type))

case *Const:
raw.SetKind(kindConst)
raw.SetType(e.id(v.Type))
e.deflateConst(&raw, v)

case *Restrict:
raw.SetKind(kindRestrict)
Expand Down Expand Up @@ -404,15 +401,10 @@ func (e *encoder) deflateType(typ Type) (err error) {
raw.SetSize(v.Size)

case *declTag:
raw.SetKind(kindDeclTag)
raw.SetType(e.id(v.Type))
raw.data = &btfDeclTag{uint32(v.Index)}
raw.NameOff, err = e.strings.Add(v.Value)
err = e.deflateDeclTag(&raw, v)

case *TypeTag:
raw.SetKind(kindTypeTag)
raw.SetType(e.id(v.Type))
raw.NameOff, err = e.strings.Add(v.Value)
err = e.deflateTypeTag(&raw, v)

default:
return fmt.Errorf("don't know how to deflate %T", v)
Expand All @@ -425,6 +417,57 @@ func (e *encoder) deflateType(typ Type) (err error) {
return raw.Marshal(e.buf, e.Order)
}

func (e *encoder) deflateInt(raw *rawType, i *Int) {
raw.SetKind(kindInt)
raw.SetSize(i.Size)

var bi btfInt
bi.SetEncoding(i.Encoding)
// We need to set bits in addition to size, since btf_type_int_is_regular
// otherwise flags this as a bitfield.
bi.SetBits(byte(i.Size) * 8)
raw.data = bi
}

func (e *encoder) deflateDeclTag(raw *rawType, tag *declTag) (err error) {
// Replace a decl tag with an integer for compatibility with <5.16 kernels,
// following libbpf behaviour.
if e.ReplaceDeclTags {
typ := &Int{"decl_tag_placeholder", 1, Unsigned}
e.deflateInt(raw, typ)

// Add the placeholder type name to the string table. The encoder added the
// original type name before this call.
raw.NameOff, err = e.strings.Add(typ.TypeName())
return
}

raw.SetKind(kindDeclTag)
raw.SetType(e.id(tag.Type))
raw.data = &btfDeclTag{uint32(tag.Index)}
raw.NameOff, err = e.strings.Add(tag.Value)
return
}

func (e *encoder) deflateConst(raw *rawType, c *Const) {
raw.SetKind(kindConst)
raw.SetType(e.id(c.Type))
}

func (e *encoder) deflateTypeTag(raw *rawType, tag *TypeTag) (err error) {
// Replace a type tag with a const qualifier for compatibility with <5.17
// kernels, following libbpf behaviour.
if e.ReplaceTypeTags {
e.deflateConst(raw, &Const{tag.Type})
return
}

raw.SetKind(kindTypeTag)
raw.SetType(e.id(tag.Type))
raw.NameOff, err = e.strings.Add(tag.Value)
return
}

func (e *encoder) deflateUnion(raw *rawType, union *Union) (err error) {
raw.SetKind(kindUnion)
raw.SetSize(union.Size)
Expand Down
60 changes: 60 additions & 0 deletions btf/marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,66 @@ func TestMarshalEnum64(t *testing.T) {
}))
}

func TestMarshalDeclTags(t *testing.T) {
types := []Type{
// Instead of an adjacent declTag, this will receive a placeholder Int.
&Typedef{
Name: "decl tag typedef",
Tags: []string{"decl tag"},
Type: &Int{Name: "decl tag target"},
},
}

b, err := NewBuilder(types)
qt.Assert(t, qt.IsNil(err))
buf, err := b.Marshal(nil, &MarshalOptions{
Order: internal.NativeEndian,
ReplaceDeclTags: true,
})
qt.Assert(t, qt.IsNil(err))

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

var td *Typedef
qt.Assert(t, qt.IsNil(spec.TypeByName("decl tag typedef", &td)))
var ti *Int
qt.Assert(t, qt.IsNil(spec.TypeByName("decl_tag_placeholder", &ti)))
}

func TestMarshalTypeTags(t *testing.T) {
types := []Type{
// Instead of pointing to a TypeTag, this will point to an intermediary Const.
&Typedef{
Name: "type tag typedef",
Type: &TypeTag{
Value: "type tag",
Type: &Pointer{
Target: &Int{Name: "type tag target"},
},
},
},
}

b, err := NewBuilder(types)
qt.Assert(t, qt.IsNil(err))
buf, err := b.Marshal(nil, &MarshalOptions{
Order: internal.NativeEndian,
ReplaceTypeTags: true,
})
qt.Assert(t, qt.IsNil(err))

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

var td *Typedef
qt.Assert(t, qt.IsNil(spec.TypeByName("type tag typedef", &td)))
qt.Assert(t, qt.Satisfies(td.Type, func(typ Type) bool {
_, ok := typ.(*Const)
return ok
}))
}

func BenchmarkMarshaler(b *testing.B) {
types := typesFromSpec(vmlinuxTestdataSpec(b))[:100]

Expand Down

0 comments on commit e2607b5

Please sign in to comment.