Repository: cilium/ebpf
Pull request #1615 (2024-11-19 13:04:44)

btf: replace decl and type tags with placeholders on unsupported kernels
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.


Patch:
diff --git a/btf/btf_types.go b/btf/btf_types.go
index f0e327a..320311b 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 d33f9fc..e71c707 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/marshal.go b/btf/marshal.go
index ea6fc99..d7204e6 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)


Test patch:
diff --git a/btf/feature_test.go b/btf/feature_test.go
index 93ffba1..ce4c762 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_test.go b/btf/marshal_test.go
index 90f4a9d..e46bcbf 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]