diff --git a/pkg/btfhelpers/bpfhelpers_test.go b/pkg/btfhelpers/bpfhelpers_test.go new file mode 100644 index 00000000000..2220c747181 --- /dev/null +++ b/pkg/btfhelpers/bpfhelpers_test.go @@ -0,0 +1,271 @@ +// Copyright 2024 The Inspektor Gadget authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package btfhelpers + +import ( + "reflect" + "testing" + + "github.com/cilium/ebpf/btf" + "github.com/stretchr/testify/assert" +) + +var int32Type = &btf.Int{ + Encoding: btf.Signed, + Size: 4, + Name: "int32", +} + +func TestGetType(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + typ btf.Type + expectedType reflect.Type + expectedNames []string + }{ + { + name: "int8", + typ: &btf.Int{ + Encoding: btf.Signed, + Size: 1, + Name: "int8", + }, + expectedType: reflect.TypeOf(int8(0)), + expectedNames: []string{"int8"}, + }, + { + name: "int16", + typ: &btf.Int{ + Encoding: btf.Signed, + Size: 2, + Name: "int16", + }, + expectedType: reflect.TypeOf(int16(0)), + expectedNames: []string{"int16"}, + }, + { + name: "int32", + typ: &btf.Int{ + Encoding: btf.Signed, + Size: 4, + Name: "int32", + }, + expectedType: reflect.TypeOf(int32(0)), + expectedNames: []string{"int32"}, + }, + { + name: "int64", + typ: &btf.Int{ + Encoding: btf.Signed, + Size: 8, + Name: "int64", + }, + expectedType: reflect.TypeOf(int64(0)), + expectedNames: []string{"int64"}, + }, + { + name: "uint8", + typ: &btf.Int{ + Encoding: btf.Unsigned, + Size: 1, + Name: "uint8", + }, + expectedType: reflect.TypeOf(uint8(0)), + expectedNames: []string{"uint8"}, + }, + { + name: "uint16", + typ: &btf.Int{ + Encoding: btf.Unsigned, + Size: 2, + Name: "uint16", + }, + expectedType: reflect.TypeOf(uint16(0)), + expectedNames: []string{"uint16"}, + }, + { + name: "uint32", + typ: &btf.Int{ + Encoding: btf.Unsigned, + Size: 4, + Name: "uint32", + }, + expectedType: reflect.TypeOf(uint32(0)), + expectedNames: []string{"uint32"}, + }, + { + name: "uint64", + typ: &btf.Int{ + Encoding: btf.Unsigned, + Size: 8, + Name: "uint64", + }, + expectedType: reflect.TypeOf(uint64(0)), + expectedNames: []string{"uint64"}, + }, + { + name: "bool", + typ: &btf.Int{ + Encoding: btf.Bool, + Size: 1, + Name: "bool", + }, + expectedType: reflect.TypeOf(false), + expectedNames: []string{"bool"}, + }, + { + name: "char", + typ: &btf.Int{ + Encoding: btf.Char, + Size: 1, + Name: "char", + }, + expectedType: reflect.TypeOf(uint8(0)), + expectedNames: []string{"char"}, + }, + { + name: "float32", + typ: &btf.Float{ + Size: 4, + Name: "float32", + }, + expectedType: reflect.TypeOf(float32(0)), + expectedNames: []string{"float32"}, + }, + { + name: "float64", + typ: &btf.Float{ + Size: 8, + Name: "float64", + }, + expectedType: reflect.TypeOf(float64(0)), + expectedNames: []string{"float64"}, + }, + { + name: "typedef", + typ: &btf.Typedef{ + Type: &btf.Int{ + Encoding: btf.Signed, + Size: 4, + Name: "int32", + }, + Name: "typedef", + }, + expectedType: reflect.TypeOf(int32(0)), + expectedNames: []string{"typedef", "int32"}, + }, + { + name: "typedef typedef", + typ: &btf.Typedef{ + Type: &btf.Typedef{ + Type: int32Type, + Name: "typedef2", + }, + Name: "typedef1", + }, + expectedType: reflect.TypeOf(int32(0)), + expectedNames: []string{"typedef1", "typedef2", "int32"}, + }, + { + name: "array", + typ: &btf.Array{ + Type: int32Type, + Nelems: 10, + }, + expectedType: reflect.ArrayOf(10, reflect.TypeOf(int32(0))), + expectedNames: []string{"int32"}, + }, + { + name: "array of arrays", + typ: &btf.Array{ + Type: &btf.Array{ + Type: int32Type, + Nelems: 10, + }, + Nelems: 10, + }, + expectedType: nil, + expectedNames: nil, + }, + { + name: "unknown", + typ: &btf.Void{}, + expectedNames: []string{}, + }, + { + name: "unnamed", + typ: &btf.Int{ + Encoding: btf.Unsigned, + Size: 2, + }, + expectedType: reflect.TypeOf(uint16(0)), + expectedNames: []string{}, + }, + // TODO: checks structures + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + retTyp, retNames := GetType(tt.typ) + assert.Equal(t, tt.expectedType, retTyp) + assert.Equal(t, tt.expectedNames, retNames) + }) + } +} + +func TestGetUnderlyingType(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + typDef *btf.Typedef + expectedType btf.Type + }{ + { + name: "typedef", + typDef: &btf.Typedef{ + Type: int32Type, + Name: "typedef", + }, + expectedType: int32Type, + }, + { + name: "typedef typedef", + typDef: &btf.Typedef{ + Type: &btf.Typedef{ + Type: int32Type, + Name: "typedef", + }, + Name: "typedef", + }, + expectedType: int32Type, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + retTyp := GetUnderlyingType(tt.typDef) + assert.Equal(t, tt.expectedType, retTyp) + }) + } +} diff --git a/pkg/btfhelpers/btfhelpers.go b/pkg/btfhelpers/btfhelpers.go new file mode 100644 index 00000000000..7f8d215d485 --- /dev/null +++ b/pkg/btfhelpers/btfhelpers.go @@ -0,0 +1,111 @@ +// Copyright 2024 The Inspektor Gadget authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package btfhelpers provides a couple of helper functions to bridge Go's reflection system with +// types from BTF +package btfhelpers + +import ( + "reflect" + + "github.com/cilium/ebpf/btf" +) + +// GetType returns the reflect.Type for a given BTF type and the list of type names found while +// resolving it. +func GetType(typ btf.Type) (reflect.Type, []string) { + var refType reflect.Type + typeNames := []string{} + + if typ.TypeName() != "" { + typeNames = append(typeNames, typ.TypeName()) + } + + switch typed := typ.(type) { + case *btf.Array: + arrType := getSimpleType(typed.Type) + if arrType == nil { + return nil, nil + } + if typed.Type.TypeName() != "" { + typeNames = append(typeNames, typed.Type.TypeName()) + } + refType = reflect.ArrayOf(int(typed.Nelems), arrType) + case *btf.Typedef: + switch typed := typ.(type) { + case *btf.Typedef: + refType, typeNames2 := GetType(typed.Type) + typeNames = append(typeNames, typeNames2...) + return refType, typeNames + default: + return GetType(typed) + } + default: + refType = getSimpleType(typ) + } + + return refType, typeNames +} + +// GetUnderlyingType returns the underlying type of a typedef +func GetUnderlyingType(tf *btf.Typedef) btf.Type { + switch typed := tf.Type.(type) { + case *btf.Typedef: + return GetUnderlyingType(typed) + default: + return typed + } +} + +func getSimpleType(typ btf.Type) reflect.Type { + switch typed := typ.(type) { + case *btf.Int: + switch typed.Encoding { + case btf.Signed: + switch typed.Size { + case 1: + return reflect.TypeOf(int8(0)) + case 2: + return reflect.TypeOf(int16(0)) + case 4: + return reflect.TypeOf(int32(0)) + case 8: + return reflect.TypeOf(int64(0)) + } + case btf.Unsigned: + switch typed.Size { + case 1: + return reflect.TypeOf(uint8(0)) + case 2: + return reflect.TypeOf(uint16(0)) + case 4: + return reflect.TypeOf(uint32(0)) + case 8: + return reflect.TypeOf(uint64(0)) + } + case btf.Bool: + return reflect.TypeOf(false) + case btf.Char: + return reflect.TypeOf(uint8(0)) + } + case *btf.Float: + switch typed.Size { + case 4: + return reflect.TypeOf(float32(0)) + case 8: + return reflect.TypeOf(float64(0)) + } + } + return nil +} diff --git a/pkg/gadgets/run/tracer/run.go b/pkg/gadgets/run/tracer/run.go index f5532ed6e53..738128f2e4a 100644 --- a/pkg/gadgets/run/tracer/run.go +++ b/pkg/gadgets/run/tracer/run.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Inspektor Gadget authors +// Copyright 2023-2024 The Inspektor Gadget authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import ( "gopkg.in/yaml.v3" k8syaml "sigs.k8s.io/yaml" + "github.com/inspektor-gadget/inspektor-gadget/pkg/btfhelpers" "github.com/inspektor-gadget/inspektor-gadget/pkg/columns" "github.com/inspektor-gadget/inspektor-gadget/pkg/columns/ellipsis" columns_json "github.com/inspektor-gadget/inspektor-gadget/pkg/columns/formatter/json" @@ -243,8 +244,8 @@ func getTypeHint(typ btf.Type) params.TypeHint { return params.TypeFloat64 } case *btf.Typedef: - typ, err := getUnderlyingType(typedMember) - if err != nil { + typ := btfhelpers.GetUnderlyingType(typedMember) + if typ == nil { return params.TypeUnknown } return getTypeHint(typ) @@ -295,15 +296,6 @@ func (g *GadgetDesc) GetGadgetInfo(params *params.Params, args []string) (*types return getGadgetInfo(params, args, secretBytes, log.StandardLogger()) } -func getUnderlyingType(tf *btf.Typedef) (btf.Type, error) { - switch typedMember := tf.Type.(type) { - case *btf.Typedef: - return getUnderlyingType(typedMember) - default: - return typedMember, nil - } -} - func loadSpec(progContent []byte) (*ebpf.CollectionSpec, error) { progReader := bytes.NewReader(progContent) spec, err := ebpf.LoadCollectionSpecFromReader(progReader) @@ -753,7 +745,7 @@ func simpleTypeFromBTF(typ btf.Type) *types.Type { return &types.Type{Kind: types.KindFloat64} } case *btf.Typedef: - typ, _ := getUnderlyingType(typedMember) + typ := btfhelpers.GetUnderlyingType(typedMember) return simpleTypeFromBTF(typ) case *btf.Enum: if typedMember.Signed { diff --git a/pkg/gadgets/run/tracer/tracer.go b/pkg/gadgets/run/tracer/tracer.go index 1dfa9074f5c..a6239c7cfdd 100644 --- a/pkg/gadgets/run/tracer/tracer.go +++ b/pkg/gadgets/run/tracer/tracer.go @@ -38,6 +38,7 @@ import ( log "github.com/sirupsen/logrus" + "github.com/inspektor-gadget/inspektor-gadget/pkg/btfhelpers" containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" gadgetcontext "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-context" "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets" @@ -563,9 +564,9 @@ func verifyGadgetUint64Typedef(t btf.Type) error { return fmt.Errorf("not a typedef") } - underlying, err := getUnderlyingType(typDef) - if err != nil { - return err + underlying := btfhelpers.GetUnderlyingType(typDef) + if underlying == nil { + return errors.New("unknown type") } intM, ok := underlying.(*btf.Int) diff --git a/pkg/gadgets/run/types/metadata.go b/pkg/gadgets/run/types/metadata.go index 53495fe8086..28823c3c123 100644 --- a/pkg/gadgets/run/types/metadata.go +++ b/pkg/gadgets/run/types/metadata.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Inspektor Gadget authors +// Copyright 2023-2024 The Inspektor Gadget authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import ( "github.com/hashicorp/go-multierror" log "github.com/sirupsen/logrus" + "github.com/inspektor-gadget/inspektor-gadget/pkg/btfhelpers" "github.com/inspektor-gadget/inspektor-gadget/pkg/columns" "github.com/inspektor-gadget/inspektor-gadget/pkg/params" ) @@ -475,15 +476,6 @@ func (m *GadgetMetadata) Populate(spec *ebpf.CollectionSpec) error { return nil } -func getUnderlyingType(tf *btf.Typedef) (btf.Type, error) { - switch typedMember := tf.Type.(type) { - case *btf.Typedef: - return getUnderlyingType(typedMember) - default: - return typedMember, nil - } -} - func getColumnSize(typ btf.Type) uint { switch typedMember := typ.(type) { case *btf.Int: @@ -517,7 +509,7 @@ func getColumnSize(typ btf.Type) uint { return columns.MaxCharsChar } case *btf.Typedef: - typ, _ := getUnderlyingType(typedMember) + typ := btfhelpers.GetUnderlyingType(typedMember) return getColumnSize(typ) }