diff --git a/Makefile b/Makefile index 3fb6b36de..03daa184c 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,7 @@ TARGETS := \ btf/testdata/relocs_read \ btf/testdata/relocs_read_tgt \ btf/testdata/relocs_enum \ + btf/testdata/tags \ cmd/bpf2go/testdata/minimal .PHONY: all clean container-all container-shell generate diff --git a/btf/btf.go b/btf/btf.go index 671f680b2..ff9fe4d95 100644 --- a/btf/btf.go +++ b/btf/btf.go @@ -695,5 +695,13 @@ func (iter *TypesIterator) Next() bool { iter.Type, ok = iter.spec.typeByID(iter.id) iter.id++ iter.done = !ok + if !iter.done { + // Skip declTags, during unmarshaling declTags become `Tags` fields of other types. + // We keep them in the spec to avoid holes in the ID space, but for the purposes of + // iteration, they are not useful to the user. + if _, ok := iter.Type.(*declTag); ok { + return iter.Next() + } + } return !iter.done } diff --git a/btf/marshal.go b/btf/marshal.go index f14cfa6e9..ea6fc99aa 100644 --- a/btf/marshal.go +++ b/btf/marshal.go @@ -409,7 +409,7 @@ func (e *encoder) deflateType(typ Type) (err error) { raw.data = &btfDeclTag{uint32(v.Index)} raw.NameOff, err = e.strings.Add(v.Value) - case *typeTag: + case *TypeTag: raw.SetKind(kindTypeTag) raw.SetType(e.id(v.Type)) raw.NameOff, err = e.strings.Add(v.Value) @@ -521,7 +521,7 @@ func (e *encoder) deflateEnum64(raw *rawType, enum *Enum) (err error) { }) } - return e.deflateUnion(raw, &Union{enum.Name, enum.Size, members}) + return e.deflateUnion(raw, &Union{enum.Name, enum.Size, members, nil}) } raw.SetKind(kindEnum64) diff --git a/btf/marshal_test.go b/btf/marshal_test.go index b97fcbf95..90f4a9d70 100644 --- a/btf/marshal_test.go +++ b/btf/marshal_test.go @@ -24,7 +24,7 @@ func TestBuilderMarshal(t *testing.T) { (*Void)(nil), typ, &Pointer{typ}, - &Typedef{"baz", typ}, + &Typedef{"baz", typ, nil}, } b, err := NewBuilder(want) @@ -65,7 +65,7 @@ func TestBuilderAdd(t *testing.T) { qt.Assert(t, qt.IsNil(err)) qt.Assert(t, qt.Equals(id, TypeID(2)), qt.Commentf("Adding a type twice returns different ids")) - id, err = b.Add(&Typedef{"baz", i}) + id, err = b.Add(&Typedef{"baz", i, nil}) qt.Assert(t, qt.IsNil(err)) qt.Assert(t, qt.Equals(id, TypeID(3))) } diff --git a/btf/testdata/tags-eb.elf b/btf/testdata/tags-eb.elf new file mode 100644 index 000000000..04e088d18 Binary files /dev/null and b/btf/testdata/tags-eb.elf differ diff --git a/btf/testdata/tags-el.elf b/btf/testdata/tags-el.elf new file mode 100644 index 000000000..c58f76231 Binary files /dev/null and b/btf/testdata/tags-el.elf differ diff --git a/btf/testdata/tags.c b/btf/testdata/tags.c new file mode 100644 index 000000000..5ad444b0a --- /dev/null +++ b/btf/testdata/tags.c @@ -0,0 +1,37 @@ +#include "../../testdata/common.h" + +#define tagA __attribute__((btf_decl_tag("a"))) +#define tagB __attribute__((btf_decl_tag("b"))) +#define tagC __attribute__((btf_decl_tag("c"))) +#define tagD __attribute__((btf_decl_tag("d"))) +#define tagE __attribute__((btf_decl_tag("e"))) + +struct s { + char tagA foo; + char tagB bar; +} tagC; + +union u { + char tagA foo; + char tagB bar; +} tagC; + +typedef tagB char td; + +struct s tagD s1; +union u tagE u1; +td tagA t1; + +int tagA tagB fwdDecl(char tagC x, char tagD y); + +int tagE normalDecl1(char tagB x, char tagC y) { + return fwdDecl(x, y); +} + +int tagE normalDecl2(char tagB x, char tagC y) { + return fwdDecl(x, y); +} + +__section("syscall") int prog(char *ctx) { + return normalDecl1(ctx[0], ctx[1]) + normalDecl2(ctx[2], ctx[3]); +} diff --git a/btf/traversal.go b/btf/traversal.go index c39dc66e4..13647d931 100644 --- a/btf/traversal.go +++ b/btf/traversal.go @@ -40,9 +40,12 @@ func children(typ Type, yield func(child *Type) bool) bool { // Explicitly type switch on the most common types to allow the inliner to // do its work. This avoids allocating intermediate slices from walk() on // the heap. + var tags []string switch v := typ.(type) { - case *Void, *Int, *Enum, *Fwd, *Float: + case *Void, *Int, *Enum, *Fwd, *Float, *declTag: // No children to traverse. + // declTags is declared as a leaf type since it's parsed into .Tags fields of other types + // during unmarshaling. case *Pointer: if !yield(&v.Target) { return false @@ -59,17 +62,32 @@ func children(typ Type, yield func(child *Type) bool) bool { if !yield(&v.Members[i].Type) { return false } + for _, t := range v.Members[i].Tags { + var tag Type = &declTag{v, t, i} + if !yield(&tag) { + return false + } + } } + tags = v.Tags case *Union: for i := range v.Members { if !yield(&v.Members[i].Type) { return false } + for _, t := range v.Members[i].Tags { + var tag Type = &declTag{v, t, i} + if !yield(&tag) { + return false + } + } } + tags = v.Tags case *Typedef: if !yield(&v.Type) { return false } + tags = v.Tags case *Volatile: if !yield(&v.Type) { return false @@ -86,6 +104,20 @@ func children(typ Type, yield func(child *Type) bool) bool { if !yield(&v.Type) { return false } + if fp, ok := v.Type.(*FuncProto); ok { + for i := range fp.Params { + if len(v.ParamTags) <= i { + continue + } + for _, t := range v.ParamTags[i] { + var tag Type = &declTag{v, t, i} + if !yield(&tag) { + return false + } + } + } + } + tags = v.Tags case *FuncProto: if !yield(&v.Return) { return false @@ -99,17 +131,14 @@ func children(typ Type, yield func(child *Type) bool) bool { if !yield(&v.Type) { return false } + tags = v.Tags case *Datasec: for i := range v.Vars { if !yield(&v.Vars[i].Type) { return false } } - case *declTag: - if !yield(&v.Type) { - return false - } - case *typeTag: + case *TypeTag: if !yield(&v.Type) { return false } @@ -119,5 +148,12 @@ func children(typ Type, yield func(child *Type) bool) bool { panic(fmt.Sprintf("don't know how to walk Type %T", v)) } + for _, t := range tags { + var tag Type = &declTag{typ, t, -1} + if !yield(&tag) { + return false + } + } + return true } diff --git a/btf/types.go b/btf/types.go index a3397460b..bbabfb769 100644 --- a/btf/types.go +++ b/btf/types.go @@ -67,7 +67,7 @@ var ( _ Type = (*Datasec)(nil) _ Type = (*Float)(nil) _ Type = (*declTag)(nil) - _ Type = (*typeTag)(nil) + _ Type = (*TypeTag)(nil) _ Type = (*cycle)(nil) ) @@ -169,6 +169,7 @@ type Struct struct { // The size of the struct including padding, in bytes Size uint32 Members []Member + Tags []string } func (s *Struct) Format(fs fmt.State, verb rune) { @@ -195,6 +196,7 @@ type Union struct { // The size of the union including padding, in bytes. Size uint32 Members []Member + Tags []string } func (u *Union) Format(fs fmt.State, verb rune) { @@ -247,6 +249,7 @@ type Member struct { Type Type Offset Bits BitfieldSize Bits + Tags []string } // Enum lists possible values. @@ -334,6 +337,7 @@ func (f *Fwd) matches(typ Type) bool { type Typedef struct { Name string Type Type + Tags []string } func (td *Typedef) Format(fs fmt.State, verb rune) { @@ -403,6 +407,12 @@ type Func struct { Name string Type Type Linkage FuncLinkage + Tags []string + // ParamTags holds a list of tags for each parameter of the FuncProto to which `Type` points. + // If no tags are present for any param, the outer slice will be nil/len(ParamTags)==0. + // If at least 1 param has a tag, the outer slice will have the same length as the number of params. + // The inner slice contains the tags and may be nil/len(ParamTags[i])==0 if no tags are present for that param. + ParamTags [][]string } func FuncMetadata(ins *asm.Instruction) *Func { @@ -456,6 +466,7 @@ type Var struct { Name string Type Type Linkage VarLinkage + Tags []string } func (v *Var) Format(fs fmt.State, verb rune) { @@ -540,19 +551,25 @@ func (dt *declTag) copy() Type { return &cpy } -// typeTag associates metadata with a type. -type typeTag struct { +// TypeTag associates metadata with a pointer type. Tag types act as a custom +// modifier(const, restrict, volatile) for the target type. Unlike declTags, +// TypeTags are ordered so the order in which they are added matters. +// +// One of their uses is to mark pointers as `__kptr` meaning a pointer points +// to kernel memory. Adding a `__kptr` to pointers in map values allows you +// to store pointers to kernel memory in maps. +type TypeTag struct { Type Type Value string } -func (tt *typeTag) Format(fs fmt.State, verb rune) { +func (tt *TypeTag) Format(fs fmt.State, verb rune) { formatType(fs, verb, tt, "type=", tt.Type, "value=", tt.Value) } -func (tt *typeTag) TypeName() string { return "" } -func (tt *typeTag) qualify() Type { return tt.Type } -func (tt *typeTag) copy() Type { +func (tt *TypeTag) TypeName() string { return "" } +func (tt *TypeTag) qualify() Type { return tt.Type } +func (tt *TypeTag) copy() Type { cpy := *tt return &cpy } @@ -591,7 +608,7 @@ var ( _ qualifier = (*Const)(nil) _ qualifier = (*Restrict)(nil) _ qualifier = (*Volatile)(nil) - _ qualifier = (*typeTag)(nil) + _ qualifier = (*TypeTag)(nil) ) var errUnsizedType = errors.New("type is unsized") @@ -918,7 +935,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt if err != nil { return nil, fmt.Errorf("struct %s (id %d): %w", name, id, err) } - typ = &Struct{name, header.Size(), members} + typ = &Struct{name, header.Size(), members, nil} case kindUnion: vlen := header.Vlen() @@ -935,7 +952,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt if err != nil { return nil, fmt.Errorf("union %s (id %d): %w", name, id, err) } - typ = &Union{name, header.Size(), members} + typ = &Union{name, header.Size(), members, nil} case kindEnum: vlen := header.Vlen() @@ -968,7 +985,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt typ = &Fwd{name, header.FwdKind()} case kindTypedef: - typedef := &Typedef{name, nil} + typedef := &Typedef{name, nil, nil} fixup(header.Type(), &typedef.Type) typ = typedef @@ -988,7 +1005,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt typ = restrict case kindFunc: - fn := &Func{name, nil, header.Linkage()} + fn := &Func{name, nil, header.Linkage(), nil, nil} fixup(header.Type(), &fn.Type) typ = fn @@ -1030,7 +1047,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt return nil, fmt.Errorf("can't read btfVariable, id: %d: %w", id, err) } - v := &Var{name, nil, VarLinkage(bVariable.Linkage)} + v := &Var{name, nil, VarLinkage(bVariable.Linkage), nil} fixup(header.Type(), &v.Type) typ = v @@ -1081,7 +1098,7 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt declTags = append(declTags, dt) case kindTypeTag: - tt := &typeTag{nil, name} + tt := &TypeTag{nil, name} fixup(header.Type(), &tt.Type) typ = tt @@ -1142,26 +1159,69 @@ func readAndInflateTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32, rawSt for _, dt := range declTags { switch t := dt.Type.(type) { - case *Var, *Typedef: + case *Var: + if dt.Index != -1 { + return nil, fmt.Errorf("type %s: component idx %d is not -1", dt, dt.Index) + } + t.Tags = append(t.Tags, dt.Value) + + case *Typedef: if dt.Index != -1 { - return nil, fmt.Errorf("type %s: index %d is not -1", dt, dt.Index) + return nil, fmt.Errorf("type %s: component idx %d is not -1", dt, dt.Index) } + t.Tags = append(t.Tags, dt.Value) case composite: - if dt.Index >= len(t.members()) { - return nil, fmt.Errorf("type %s: index %d exceeds members of %s", dt, dt.Index, t) + if dt.Index >= 0 { + members := t.members() + if dt.Index >= len(members) { + return nil, fmt.Errorf("type %s: component idx %d exceeds members of %s", dt, dt.Index, t) + } + + members[dt.Index].Tags = append(members[dt.Index].Tags, dt.Value) + continue } + if dt.Index == -1 { + switch t2 := t.(type) { + case *Struct: + t2.Tags = append(t2.Tags, dt.Value) + case *Union: + t2.Tags = append(t2.Tags, dt.Value) + } + + continue + } + + return nil, fmt.Errorf("type %s: decl tag for type %s has invalid component idx", dt, t) + case *Func: fp, ok := t.Type.(*FuncProto) if !ok { return nil, fmt.Errorf("type %s: %s is not a FuncProto", dt, t.Type) } - if dt.Index >= len(fp.Params) { - return nil, fmt.Errorf("type %s: index %d exceeds params of %s", dt, dt.Index, t) + // Ensure the number of argument tag lists equals the number of arguments + if len(t.ParamTags) == 0 { + t.ParamTags = make([][]string, len(fp.Params)) } + if dt.Index >= 0 { + if dt.Index >= len(fp.Params) { + return nil, fmt.Errorf("type %s: component idx %d exceeds params of %s", dt, dt.Index, t) + } + + t.ParamTags[dt.Index] = append(t.ParamTags[dt.Index], dt.Value) + continue + } + + if dt.Index == -1 { + t.Tags = append(t.Tags, dt.Value) + continue + } + + return nil, fmt.Errorf("type %s: decl tag for type %s has invalid component idx", dt, t) + default: return nil, fmt.Errorf("type %s: decl tag for type %s is not supported", dt, t) } diff --git a/btf/types_test.go b/btf/types_test.go index 24583b553..3812e1a0a 100644 --- a/btf/types_test.go +++ b/btf/types_test.go @@ -174,8 +174,7 @@ func TestType(t *testing.T) { } }, func() Type { return &Float{} }, - func() Type { return &declTag{Type: &Void{}} }, - func() Type { return &typeTag{Type: &Void{}} }, + func() Type { return &TypeTag{Type: &Void{}} }, func() Type { return &cycle{&Void{}} }, } @@ -213,8 +212,19 @@ func TestType(t *testing.T) { func TestTagMarshaling(t *testing.T) { for _, typ := range []Type{ - &declTag{&Struct{Members: []Member{}}, "foo", -1}, - &typeTag{&Int{}, "foo"}, + &TypeTag{&Int{}, "foo"}, + &Struct{Members: []Member{ + {Type: &Int{}, Tags: []string{"bar"}}, + }, Tags: []string{"foo"}}, + &Union{Members: []Member{ + {Type: &Int{}, Tags: []string{"bar"}}, + {Type: &Int{}, Tags: []string{"baz"}}, + }, Tags: []string{"foo"}}, + &Func{Type: &FuncProto{Return: &Int{}, Params: []FuncParam{ + {Name: "param1", Type: &Int{}}, + }}, Tags: []string{"foo"}, ParamTags: [][]string{{"bar"}}}, + &Var{Name: "var1", Type: &Int{}, Tags: []string{"foo"}}, + &Typedef{Name: "baz", Type: &Int{}, Tags: []string{"foo"}}, } { t.Run(fmt.Sprint(typ), func(t *testing.T) { s := specFromTypes(t, []Type{typ}) @@ -341,7 +351,7 @@ func TestUnderlyingType(t *testing.T) { {"volatile", func(t Type) Type { return &Volatile{Type: t} }}, {"restrict", func(t Type) Type { return &Restrict{Type: t} }}, {"typedef", func(t Type) Type { return &Typedef{Type: t} }}, - {"type tag", func(t Type) Type { return &typeTag{Type: t} }}, + {"type tag", func(t Type) Type { return &TypeTag{Type: t} }}, } for _, test := range wrappers { @@ -477,6 +487,62 @@ func BenchmarkWalk(b *testing.B) { } } +func TestTagUnmarshaling(t *testing.T) { + spec, err := LoadSpec("testdata/tags-el.elf") + qt.Assert(t, qt.IsNil(err)) + + var s *Struct + err = spec.TypeByName("s", &s) + qt.Assert(t, qt.IsNil(err)) + qt.Assert(t, qt.ContentEquals(s.Tags, []string{"c"})) + qt.Assert(t, qt.ContentEquals(s.Members[0].Tags, []string{"a"})) + qt.Assert(t, qt.ContentEquals(s.Members[1].Tags, []string{"b"})) + + var u *Union + err = spec.TypeByName("u", &u) + qt.Assert(t, qt.IsNil(err)) + qt.Assert(t, qt.ContentEquals(u.Tags, []string{"c"})) + qt.Assert(t, qt.ContentEquals(u.Members[0].Tags, []string{"a"})) + qt.Assert(t, qt.ContentEquals(u.Members[1].Tags, []string{"b"})) + + var td *Typedef + err = spec.TypeByName("td", &td) + qt.Assert(t, qt.IsNil(err)) + qt.Assert(t, qt.ContentEquals(td.Tags, []string{"b"})) + + var s1 *Var + err = spec.TypeByName("s1", &s1) + qt.Assert(t, qt.IsNil(err)) + qt.Assert(t, qt.ContentEquals(s1.Tags, []string{"d"})) + + var s2 *Var + err = spec.TypeByName("u1", &s2) + qt.Assert(t, qt.IsNil(err)) + qt.Assert(t, qt.ContentEquals(s2.Tags, []string{"e"})) + + var t1 *Var + err = spec.TypeByName("t1", &t1) + qt.Assert(t, qt.IsNil(err)) + qt.Assert(t, qt.ContentEquals(t1.Tags, []string{"a"})) + + var extFunc *Func + err = spec.TypeByName("fwdDecl", &extFunc) + qt.Assert(t, qt.IsNil(err)) + qt.Assert(t, qt.ContentEquals(extFunc.Tags, []string{"a", "b"})) + qt.Assert(t, qt.ContentEquals(extFunc.ParamTags, [][]string{{"c"}, {"d"}})) + + var normalFunc *Func + err = spec.TypeByName("normalDecl1", &normalFunc) + qt.Assert(t, qt.IsNil(err)) + qt.Assert(t, qt.ContentEquals(normalFunc.Tags, []string{"e"})) + qt.Assert(t, qt.ContentEquals(normalFunc.ParamTags, [][]string{{"b"}, {"c"}})) + + err = spec.TypeByName("normalDecl2", &normalFunc) + qt.Assert(t, qt.IsNil(err)) + qt.Assert(t, qt.ContentEquals(normalFunc.Tags, []string{"e"})) + qt.Assert(t, qt.ContentEquals(normalFunc.ParamTags, [][]string{{"b"}, {"c"}})) +} + func BenchmarkUnderlyingType(b *testing.B) { b.Run("no unwrapping", func(b *testing.B) { v := &Int{} diff --git a/btf/workarounds.go b/btf/workarounds.go index 12a89b87e..eb09047fb 100644 --- a/btf/workarounds.go +++ b/btf/workarounds.go @@ -12,7 +12,7 @@ func datasecResolveWorkaround(b *Builder, ds *Datasec) error { } switch v.Type.(type) { - case *Typedef, *Volatile, *Const, *Restrict, *typeTag: + case *Typedef, *Volatile, *Const, *Restrict, *TypeTag: // NB: We must never call Add on a Datasec, otherwise we risk // infinite recursion. _, err := b.Add(v.Type) diff --git a/btf/workarounds_test.go b/btf/workarounds_test.go index b7b5bf4a5..70fc85446 100644 --- a/btf/workarounds_test.go +++ b/btf/workarounds_test.go @@ -17,14 +17,14 @@ func TestDatasecResolveWorkaround(t *testing.T) { i := &Int{Size: 1} for _, typ := range []Type{ - &Typedef{"foo", i}, + &Typedef{"foo", i, nil}, &Volatile{i}, &Const{i}, &Restrict{i}, - &typeTag{i, "foo"}, + &TypeTag{i, "foo"}, } { t.Run(fmt.Sprint(typ), func(t *testing.T) { - if _, ok := typ.(*typeTag); ok { + if _, ok := typ.(*TypeTag); ok { testutils.SkipOnOldKernel(t, "5.17", "BTF_KIND_TYPE_TAG") }