From 13e5cfbf6072486073b326089adaa735c9749fba Mon Sep 17 00:00:00 2001 From: Dylan Reimerink Date: Tue, 20 Aug 2024 14:01:43 +0200 Subject: [PATCH 1/2] btf: Export TypeTag This commit makes the TypeTag BTF type exported. It had not been exported because it was lumped in with the DeclTag type. However, no modification to the codebase seems to be needed to accommodate the usage of TypeTags. Signed-off-by: Dylan Reimerink --- btf/marshal.go | 2 +- btf/traversal.go | 2 +- btf/types.go | 24 +++++++++++++++--------- btf/types_test.go | 6 +++--- btf/workarounds.go | 2 +- btf/workarounds_test.go | 4 ++-- 6 files changed, 23 insertions(+), 17 deletions(-) diff --git a/btf/marshal.go b/btf/marshal.go index f14cfa6e9..5ed79c1f5 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) diff --git a/btf/traversal.go b/btf/traversal.go index c39dc66e4..384ae774f 100644 --- a/btf/traversal.go +++ b/btf/traversal.go @@ -109,7 +109,7 @@ func children(typ Type, yield func(child *Type) bool) bool { if !yield(&v.Type) { return false } - case *typeTag: + case *TypeTag: if !yield(&v.Type) { return false } diff --git a/btf/types.go b/btf/types.go index a3397460b..229d95310 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) ) @@ -540,19 +540,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 +597,7 @@ var ( _ qualifier = (*Const)(nil) _ qualifier = (*Restrict)(nil) _ qualifier = (*Volatile)(nil) - _ qualifier = (*typeTag)(nil) + _ qualifier = (*TypeTag)(nil) ) var errUnsizedType = errors.New("type is unsized") @@ -1081,7 +1087,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 diff --git a/btf/types_test.go b/btf/types_test.go index 24583b553..0704a9a3b 100644 --- a/btf/types_test.go +++ b/btf/types_test.go @@ -175,7 +175,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{}} }, } @@ -214,7 +214,7 @@ 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"}, } { t.Run(fmt.Sprint(typ), func(t *testing.T) { s := specFromTypes(t, []Type{typ}) @@ -341,7 +341,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 { 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..ce260ccf0 100644 --- a/btf/workarounds_test.go +++ b/btf/workarounds_test.go @@ -21,10 +21,10 @@ func TestDatasecResolveWorkaround(t *testing.T) { &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") } From 2c0d9a6e59c8d8fe744a8339b612748b55f2c008 Mon Sep 17 00:00:00 2001 From: Dylan Reimerink Date: Thu, 3 Oct 2024 15:00:41 +0200 Subject: [PATCH 2/2] btf: Export and marshal DeclTags This commit makes the DeclTag BTF type exported. The DeclTag has been unexported because its encoding is a bit tricky. DeclTags on the wire refer to a type by its ID, and an component index. Unlike other BTF types, the type ID isn't always the actual "target", instead if the component index is not `-1` it targets a child object of the type. This commit adds a `Tags` field to all types that can have DeclTags including Members. Exception is function parameters. Due to the func -> funcProto -> parameter encoding, it is possible that BTF deduplication reuses the same funcProto for multiple functions. The declTags point to the func to annotate the arguments. Thus, adding a `Tags` field to Param would cause inaccurate BTF marshalling. Instead, a `ParamTags` field is added the func as `[][]string` to match the write format more closely to avoid issues caused by BTF deduplication. This is a compromise between technical feasibility and user experience. Users need to manually match up the `ParamTags` with the `Params`. This shouldn't be a huge issue in most cases however. This means we now effectively have less "types" in our Go BTF representation then we have on the wire since declTags become strings on other types. This might leave "holes" in our BTF type numbering if we were to remove them. So instead we leave the `declTag` types in the BTF type list, but we hide them from the user during iteration. Technically, users can still access them by ID if they know the ID, but like before this commit, the returned value can only be printed. Marshalling relies on the iteration of the BTF types. When go to load a BTF blob from a collection, we iterate from a collection of root types which are directly used. This causes us to load only BTF info that is actually in use. This commit introduces a trick into the `children` logic used during iteration. When we iterate over a type with declTags, we create internal `declTag` types at-hoc with the correct component index. So even though the tags are just strings on other types, when iterating they appear as actual types. This causes only declTags that are on types which are in use to be marshalled. Signed-off-by: Dylan Reimerink --- Makefile | 1 + btf/btf.go | 8 +++++ btf/marshal.go | 2 +- btf/marshal_test.go | 4 +-- btf/testdata/tags-eb.elf | Bin 0 -> 3168 bytes btf/testdata/tags-el.elf | Bin 0 -> 3168 bytes btf/testdata/tags.c | 37 +++++++++++++++++++ btf/traversal.go | 46 +++++++++++++++++++++--- btf/types.go | 76 +++++++++++++++++++++++++++++++++------ btf/types_test.go | 70 ++++++++++++++++++++++++++++++++++-- btf/workarounds_test.go | 2 +- 11 files changed, 224 insertions(+), 22 deletions(-) create mode 100644 btf/testdata/tags-eb.elf create mode 100644 btf/testdata/tags-el.elf create mode 100644 btf/testdata/tags.c 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 5ed79c1f5..ea6fc99aa 100644 --- a/btf/marshal.go +++ b/btf/marshal.go @@ -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 0000000000000000000000000000000000000000..04e088d1841b649b40045c090462b3f585e06e87 GIT binary patch literal 3168 zcmbtWJ!oWA6h3bfv&sHt{9D2jyyM#ChN6jVT3oOkYb{_nYWZu0S)n|CUe zL?)ETA6N&9i3N)V=TS?)mn8WFo=-rZdzDCxAE%|^!SyodgDWNOSG~OXy~@5*tkUv4 zKJa=${0vu!uTcKi_{DSd7P0<*{laf;snk}!5g{iRfD_#`{cC>3b?1JX$w-c5cfXMRVYZhJ zZudHarnIFaJ!urZQ6X!+_QB3tyVzMPdU?@Ji*&6>`+1`y3t6u?%=X>R2VI8MMyu|q zeoGWeSJEKqhIERN9Av}3szaivbj}vueuZKhPQ60^~M(3%4KIbls0ypH|09? zNN|EHm#nLf_sfAfPZEElRWR@N2+Y}%o(EI!!1N(q2e(8H_!^}lr#bes}z8I#R zx%tMBs2_`b#7~>^x`oZg%z)VxDV=9|4E?*l&oxi||ho{w2c4r?DSnkT0o5b{FCG2)7JZ zrIGapQ%WKWiVo*IL}`+M7Ix0~g=eQD%HR-|pnqdhN~5{XVd zZG23cd{<6hpeDNb|HQMi`xRxI|56nnMFu}W)#1;VKRd$sez&LuIsZNR@U9s#7w;OW z<-e29Ii3s2fiHr2W+aQJ{UtDSkmCC9c`Y6fl_o5nnD5$I^?B|j2WEY{zZTE>OJKwC z9FJimSzL)u`X#pCirGS`@iRY%O02_t_khpp|E|WZL$ZJUZ*$Ub6*~LBNnJxR`(ud` z%VWl{{skz{sz26v2g&;8XY*qH*N0Cw{oKX?r)mC?Vmn_mhjIvj6}9 literal 0 HcmV?d00001 diff --git a/btf/testdata/tags-el.elf b/btf/testdata/tags-el.elf new file mode 100644 index 0000000000000000000000000000000000000000..c58f7623149879cea993e74c4f0ef0038986dbf8 GIT binary patch literal 3168 zcmbtWO=w(I6h4_rV`HZ2M6DFDat$go8YY=kq|nVEvAD=0i-H9S>6@QS1Cvbh-b*_{ z&};-3F0yfLUAyVhg;008aN*vKdqKJo6hzwZyYHO$UatcR9&+C~-}%4i-FGLSytjFy zGBYDaGxCQN$h0DEzK}a!?8pUpep(lK4EjRNA%6H`W;p6*S&yO&4*{=1{9gI6qFA}* zdH9UQSG{l)!2$GN<1Ze1cd`Dy{B=eyXDayyskj!<1O7;U66Y1dbb(dpXuyKr^Jl>+ z&f?*~7YRga?Cd2h&=PH=mow%ubG%Z*oExFlp-dkV`3G3A)mYc^*lXB<4zxr}--t*t z^I9LYhjqgJ^x%UWHSWXWE#fS*|dkqi9&OwPY=f|(H>|Y!fi81sg zQI_7FD3*zw$lgIJ2jh4@8f^@EqqcOVCj)7vgGnl@gYMz(YB$|oO$SNZkJ4y0jfP39 zC-dBF_{6-VCQZo0QzVO{sub+umIMq7#Oj>po)j+3?= z%W+%MzNBqwbrT#ke%p>S0j=no#gNwgptA4RLC<-T_>JQF^245htD9U>y#u%J_zlP% zta;ogJg0^$z|3KI{#5+^Vz>g#+zVJF>Q5;5#KuJNvM{*eCw-{IA3QSqWEw|Bb5oB%k)I=2I*5F~sLe{&h7q!A&l_ zMYh%7Y{uJOZ*N89OoMpeEcu%{Q`kG+Q@vq($0OBezbeJM&!RN0wfbH8fdnd5hr4Tk@k;ZbJT-O` z*_Gw5r`Bk6cYi19_v2)5D6J%o)2Iu6vhO)lBGFS<8!wYK@0H>P;$i!rw1Id#W93v| z@i2fs&RG7`?dSL$BeZ|DA1=%&@b z4L#{BWBJqKO>qUnwEAmP>hla(yw$&~x^RZ&b7!^4!};-h33c?d_@9Hs?4e9I0jBl8 zrTwp|Gv`ND*gt>Br`7*a3vC6)u>Jz#r`12T`Dr}s(=V+54s_Mc$Gjd-v2wbvjzRf< Y^ty=%o{Ny29!Q%b^Kg__pYM(I1a=A+{Qv*} literal 0 HcmV?d00001 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 384ae774f..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,16 +131,13 @@ 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: 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 229d95310..bbabfb769 100644 --- a/btf/types.go +++ b/btf/types.go @@ -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) { @@ -924,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() @@ -941,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() @@ -974,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 @@ -994,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 @@ -1036,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 @@ -1148,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: 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 *Typedef: + 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 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 0704a9a3b..3812e1a0a 100644 --- a/btf/types_test.go +++ b/btf/types_test.go @@ -174,7 +174,6 @@ func TestType(t *testing.T) { } }, func() Type { return &Float{} }, - func() Type { return &declTag{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"}, + &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}) @@ -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_test.go b/btf/workarounds_test.go index ce260ccf0..70fc85446 100644 --- a/btf/workarounds_test.go +++ b/btf/workarounds_test.go @@ -17,7 +17,7 @@ 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},