diff --git a/btf/btf_types.go b/btf/btf_types.go index f0e327abc..320311b33 100644 --- a/btf/btf_types.go +++ b/btf/btf_types.go @@ -39,6 +39,7 @@ const ( kindFloat // Float // Added 5.16 kindDeclTag // DeclTag + // Added 5.17 kindTypeTag // TypeTag // Added 6.0 kindEnum64 // Enum64 diff --git a/btf/feature.go b/btf/feature.go index d33f9fc7c..e71c707fe 100644 --- a/btf/feature.go +++ b/btf/feature.go @@ -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 diff --git a/btf/feature_test.go b/btf/feature_test.go index 93ffba17f..ce4c762f9 100644 --- a/btf/feature_test.go +++ b/btf/feature_test.go @@ -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) } diff --git a/btf/marshal.go b/btf/marshal.go index ea6fc99aa..d7204e624 100644 --- a/btf/marshal.go +++ b/btf/marshal.go @@ -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. @@ -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. } @@ -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) @@ -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) @@ -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) @@ -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) diff --git a/btf/marshal_test.go b/btf/marshal_test.go index 90f4a9d70..e46bcbfcd 100644 --- a/btf/marshal_test.go +++ b/btf/marshal_test.go @@ -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]