Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 47 additions & 42 deletions pkg/btf/btf.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package btf

import (
"errors"
"fmt"
"math"
"os"
Expand Down Expand Up @@ -262,6 +263,7 @@ func ResolveBTFPath(
pathToFound []string,
i int,
) (*btf.Type, error) {
currentType = ResolveNestedTypes(currentType)
switch t := currentType.(type) {
case *btf.Struct:
return processMembers(btfArgs, currentType, t.Members, pathToFound, i)
Expand All @@ -276,9 +278,9 @@ func ResolveBTFPath(
if idx, err := parseArrayIdxStr(pathToFound[i]); err == nil {
// To stay ahead on the dereferecing, we mark the current btfArg as pointer
btfArgs[i].IsPointer = uint16(1)
return processArray(btfArgs, ResolveNestedTypes(t.Target), pathToFound, i, idx)
return processArray(btfArgs, t.Target, pathToFound, i, idx)
}
return ResolveBTFPath(btfArgs, ResolveNestedTypes(t.Target), pathToFound, i)
return ResolveBTFPath(btfArgs, t.Target, pathToFound, i)
case *btf.Array:
idx, err := parseArrayIdxStr(pathToFound[i])
if err != nil {
Expand All @@ -287,7 +289,7 @@ func ResolveBTFPath(
if idx >= t.Nelems {
return nil, fmt.Errorf("array index out of bound. Nelems=%d, got=%d", t.Nelems, idx)
}
return processArray(btfArgs, ResolveNestedTypes(t.Type), pathToFound, i, idx)
return processArray(btfArgs, t.Type, pathToFound, i, idx)
default:
ty := currentType.TypeName()
if len(ty) == 0 {
Expand All @@ -301,64 +303,66 @@ func ResolveBTFPath(
}
}

type resolveError struct {
idx int
str string
}

func (e *resolveError) Error() string {
return e.str
}

func processMembers(
btfArgs *[api.MaxBTFArgDepth]api.ConfigBTFArg,
currentType btf.Type,
members []btf.Member,
pathToFound []string,
i int,
) (*btf.Type, error) {
var lastError error
memberWasFound := false
var lastError *resolveError
for _, member := range members {
if len(member.Name) == 0 { // If anonymous struct, fallthrough
btfArgs[i].Offset = member.Offset.Bytes()
btfArgs[i].IsInitialized = uint16(1)
lastTy, err := ResolveBTFPath(btfArgs, ResolveNestedTypes(member.Type), pathToFound, i)
if len(member.Name) == 0 { // anonymous struct/union, fallthrough
lastTy, err := ResolveBTFPath(btfArgs, member.Type, pathToFound, i)
if err != nil {
if lastError != nil {
idx := i + 1
// If the error raised originates from a depth greater than the current one, we stop the search.
if idx < len(pathToFound) && strings.Contains(lastError.Error(), pathToFound[idx]) {
break
// Propagate the deepest error for both resolve and non-resolve error.
if err2, ok := errors.AsType[*resolveError](err); ok {
if lastError == nil || lastError.idx < err2.idx {
lastError = err2
}
} else if lastError == nil || lastError.idx <= i {
lastError = &resolveError{i, err.Error()}
}
lastError = err
continue
}
btfArgs[i].Offset += member.Offset.Bytes()
return lastTy, nil
}
if member.Name == pathToFound[i] {
memberWasFound = true
btfArgs[i].Offset = member.Offset.Bytes()
btfArgs[i].IsInitialized = uint16(1)
isNotLastChild := i < len(pathToFound)-1 && i < api.MaxBTFArgDepth
if isNotLastChild {
return ResolveBTFPath(btfArgs, ResolveNestedTypes(member.Type), pathToFound, i+1)
}
currentType = ResolveNestedTypes(member.Type)
break
if member.Name != pathToFound[i] {
continue
}
}
if !memberWasFound {
if lastError != nil {
return nil, lastError
btfArgs[i].Offset = member.Offset.Bytes()
btfArgs[i].IsInitialized = uint16(1)
if i < len(pathToFound)-1 && i < api.MaxBTFArgDepth {
return ResolveBTFPath(btfArgs, member.Type, pathToFound, i+1)
}
memberType := ResolveNestedTypes(member.Type)
switch t := memberType.(type) {
case *btf.Pointer:
btfArgs[i].IsPointer = uint16(1)
memberType = t.Target
case *btf.Int, *btf.Enum:
btfArgs[i].IsPointer = uint16(1)
}
return nil, fmt.Errorf(
"attribute %q not found in structure %q found %v",
pathToFound[i],
currentType.TypeName(),
members,
)
return &memberType, nil
}
switch t := currentType.(type) {
case *btf.Pointer:
btfArgs[i].IsPointer = uint16(1)
currentType = t.Target
case *btf.Int, *btf.Enum:
btfArgs[i].IsPointer = uint16(1)
if lastError != nil {
return nil, lastError
}
return &currentType, nil
return nil, &resolveError{i, fmt.Sprintf(
"attribute %q not found in structure %q",
pathToFound[i],
currentType.TypeName(),
)}
}

func processArray(
Expand All @@ -368,6 +372,7 @@ func processArray(
i int,
idx uint32,
) (*btf.Type, error) {
targetType = ResolveNestedTypes(targetType)
btfArgs[i].IsInitialized = uint16(1)
btfArgs[i].Offset = getSizeofType(targetType) * idx
if len(pathToFound) > i+1 {
Expand Down
176 changes: 166 additions & 10 deletions pkg/btf/btf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,29 +290,29 @@ func getBTFPointer(ty btf.Type) (*btf.Pointer, error) {
return nil, fmt.Errorf("Invalid type for \"%v\", expected \"*btf.Pointer\", got %q", t, reflect.TypeOf(ty).String())
}

func findMemberInBTFStruct(structTy *btf.Struct, memberName string) (*btf.Member, error) {
func findMemberInBTFStruct(structTy *btf.Struct, memberName string) (*btf.Member, uint32, error) {
for _, member := range structTy.Members {
if member.Name == memberName {
return &member, nil
return &member, 0, nil
}

if anonymousStructTy, ok := member.Type.(*btf.Struct); ok && len(member.Name) == 0 {
for _, m := range anonymousStructTy.Members {
if m.Name == memberName {
return &m, nil
return &m, uint32(member.Offset.Bytes()), nil
}
}
}

if unionTy, ok := member.Type.(*btf.Union); ok {
if unionTy, ok := member.Type.(*btf.Union); ok && len(member.Name) == 0 {
for _, m := range unionTy.Members {
if m.Name == memberName {
return &m, nil
return &m, uint32(member.Offset.Bytes()), nil
}
}
}
}
return nil, fmt.Errorf("Member %q not found in struct %v", memberName, structTy)
return nil, 0, fmt.Errorf("Member %q not found in struct %v", memberName, structTy)
}

func getBTFPointerAndSetConfig(ty btf.Type, btfConfig *api.ConfigBTFArg) (*btf.Pointer, error) {
Expand All @@ -329,12 +329,12 @@ func getBTFPointerAndSetConfig(ty btf.Type, btfConfig *api.ConfigBTFArg) (*btf.P
func getConfigAndNextType(structTy *btf.Struct, memberName string) (*btf.Type, *api.ConfigBTFArg, error) {
btfConfig := api.ConfigBTFArg{}

member, err := findMemberInBTFStruct(structTy, memberName)
member, parentOffset, err := findMemberInBTFStruct(structTy, memberName)
if err != nil {
return nil, nil, err
}

btfConfig.Offset = uint32(member.Offset.Bytes())
btfConfig.Offset = uint32(member.Offset.Bytes()) + parentOffset
btfConfig.IsInitialized = uint16(1)

ty := ResolveNestedTypes(member.Type)
Expand All @@ -352,12 +352,12 @@ func getConfigAndNextType(structTy *btf.Struct, memberName string) (*btf.Type, *
func getConfigAndNextStruct(structTy *btf.Struct, memberName string) (*btf.Struct, *api.ConfigBTFArg, error) {
btfConfig := api.ConfigBTFArg{}

member, err := findMemberInBTFStruct(structTy, memberName)
member, parentOffset, err := findMemberInBTFStruct(structTy, memberName)
if err != nil {
return nil, nil, err
}

btfConfig.Offset = uint32(member.Offset.Bytes())
btfConfig.Offset = uint32(member.Offset.Bytes()) + parentOffset
btfConfig.IsInitialized = uint16(1)

ty := ResolveNestedTypes(member.Type)
Expand Down Expand Up @@ -680,3 +680,159 @@ func TestParseArrayIdxStr(t *testing.T) {
})
}
}

func TestProcessMembersErrorPrecedence(t *testing.T) {
intTy := &btf.Int{Name: "int", Size: 4}

// deeper resolveError beats shallower resolveError
t.Run("deepest_resolve_error_wins", func(t *testing.T) {
// struct outer {
// struct { int a; }; // anon1: "x" not found → resolveError{depth=0}
// struct { struct inner { int b; } x; }; // anon2: "x" found, "y" not found → resolveError{depth=1}
// };
inner := &btf.Struct{
Name: "inner",
Size: 4,
Members: []btf.Member{
{Name: "b", Type: intTy, Offset: 0},
},
}
anon1 := &btf.Struct{
Size: 4,
Members: []btf.Member{
{Name: "a", Type: intTy, Offset: 0},
},
}
anon2 := &btf.Struct{
Size: 4,
Members: []btf.Member{
{Name: "x", Type: inner, Offset: 0},
},
}
outer := &btf.Struct{
Name: "outer",
Size: 8,
Members: []btf.Member{
{Name: "", Type: anon1, Offset: 0},
{Name: "", Type: anon2, Offset: btf.Bits(32)},
},
}

var btfArgs [api.MaxBTFArgDepth]api.ConfigBTFArg
_, err := ResolveBTFPath(&btfArgs, outer, []string{"x", "y"}, 0)
require.Error(t, err)
assert.ErrorContains(t, err, `attribute "y" not found in structure "inner"`)
})

// non-resolveError (attribute found, wrong type) beats resolveError at the same depth
t.Run("non_resolve_error_beats_resolve_error_at_same_depth", func(t *testing.T) {
// struct outer {
// struct { int q; }; // anon1: "x" not found → resolveError{depth=0}
// struct { int x; }; // anon2: "x" found, int has no subfields → "unexpected type" (non-resolveError at depth=0)
// };
//
// anon2 got further (found "x"), so its error should win
anon1 := &btf.Struct{
Size: 4,
Members: []btf.Member{
{Name: "q", Type: intTy, Offset: 0},
},
}
anon2 := &btf.Struct{
Size: 4,
Members: []btf.Member{
{Name: "x", Type: intTy, Offset: 0},
},
}
outer := &btf.Struct{
Name: "outer",
Size: 8,
Members: []btf.Member{
{Name: "", Type: anon1, Offset: 0},
{Name: "", Type: anon2, Offset: btf.Bits(32)},
},
}

var btfArgs [api.MaxBTFArgDepth]api.ConfigBTFArg
// Two-element path: "x" is not the last child, so finding it in anon2 triggers
// ResolveBTFPath(int, path, 1) which returns a non-resolveError.
_, err := ResolveBTFPath(&btfArgs, outer, []string{"x", "y"}, 0)
require.Error(t, err)
assert.ErrorContains(t, err, `unexpected type : "x" has type "int"`)
})

// deeper resolveError from a later branch beats a non-resolveError from an earlier branch
t.Run("deep_resolve_error_beats_non_resolve_error", func(t *testing.T) {
// struct outer {
// struct { int x; }; // anon1: "x" found, int type → non-resolveError stored at depth=0
// struct { struct inner { int b; } x; }; // anon2: "x" found, "y" not in inner → resolveError{depth=1}
// };
//
// anon2's resolveError at depth 1 is deeper than anon1's non-resolveError at depth 0.
inner := &btf.Struct{
Name: "inner",
Size: 4,
Members: []btf.Member{
{Name: "b", Type: intTy, Offset: 0},
},
}
anon1 := &btf.Struct{
Size: 4,
Members: []btf.Member{
{Name: "x", Type: intTy, Offset: 0},
},
}
anon2 := &btf.Struct{
Size: 4,
Members: []btf.Member{
{Name: "x", Type: inner, Offset: 0},
},
}
outer := &btf.Struct{
Name: "outer",
Size: 8,
Members: []btf.Member{
{Name: "", Type: anon1, Offset: 0},
{Name: "", Type: anon2, Offset: btf.Bits(32)},
},
}

var btfArgs [api.MaxBTFArgDepth]api.ConfigBTFArg
_, err := ResolveBTFPath(&btfArgs, outer, []string{"x", "y"}, 0)
require.Error(t, err)
assert.ErrorContains(t, err, `attribute "y" not found in structure "inner"`)
})

// success through one anonymous branch when another fails
t.Run("success_through_anonymous_branch", func(t *testing.T) {
// struct outer {
// struct { int q; }; // anon1: "x" not found, int type
// struct { int x; }; // anon2: "x" found
// };
anon1 := &btf.Struct{
Size: 4,
Members: []btf.Member{
{Name: "q", Type: intTy, Offset: 0},
},
}
anon2 := &btf.Struct{
Size: 4,
Members: []btf.Member{
{Name: "x", Type: intTy, Offset: 0},
},
}
outer := &btf.Struct{
Name: "outer",
Size: 8,
Members: []btf.Member{
{Name: "", Type: anon1, Offset: 0},
{Name: "", Type: anon2, Offset: btf.Bits(32)},
},
}

var btfArgs [api.MaxBTFArgDepth]api.ConfigBTFArg
ty, err := ResolveBTFPath(&btfArgs, outer, []string{"x"}, 0)
require.NoError(t, err)
require.NotNil(t, ty)
})
}
4 changes: 2 additions & 2 deletions pkg/sensors/tracing/args_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ func getArgStatus(r *bytes.Reader) (*api.MsgGenericKprobeArgError, error) {

if status != 0 {
if status == ^uint32(0) {
arg.Message = "Bad address"
arg.Message = "Bad address for basic type"
} else {
arg.Message = strconv.FormatUint(uint64(status), 10)
arg.Message = "Bad address encountered during resolve dereference at depth " + strconv.FormatUint(uint64(status-1), 10)
}
return &arg, nil
}
Expand Down
Loading
Loading