123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972 |
- package btf
- import (
- "encoding/binary"
- "errors"
- "fmt"
- "math"
- "reflect"
- "strconv"
- "strings"
- "github.com/cilium/ebpf/asm"
- )
- // Code in this file is derived from libbpf, which is available under a BSD
- // 2-Clause license.
- // COREFixup is the result of computing a CO-RE relocation for a target.
- type COREFixup struct {
- kind coreKind
- local uint32
- target uint32
- // True if there is no valid fixup. The instruction is replaced with an
- // invalid dummy.
- poison bool
- // True if the validation of the local value should be skipped. Used by
- // some kinds of bitfield relocations.
- skipLocalValidation bool
- }
- func (f *COREFixup) equal(other COREFixup) bool {
- return f.local == other.local && f.target == other.target
- }
- func (f *COREFixup) String() string {
- if f.poison {
- return fmt.Sprintf("%s=poison", f.kind)
- }
- return fmt.Sprintf("%s=%d->%d", f.kind, f.local, f.target)
- }
- func (f *COREFixup) Apply(ins *asm.Instruction) error {
- if f.poison {
- const badRelo = 0xbad2310
- *ins = asm.BuiltinFunc(badRelo).Call()
- return nil
- }
- switch class := ins.OpCode.Class(); class {
- case asm.LdXClass, asm.StClass, asm.StXClass:
- if want := int16(f.local); !f.skipLocalValidation && want != ins.Offset {
- return fmt.Errorf("invalid offset %d, expected %d", ins.Offset, f.local)
- }
- if f.target > math.MaxInt16 {
- return fmt.Errorf("offset %d exceeds MaxInt16", f.target)
- }
- ins.Offset = int16(f.target)
- case asm.LdClass:
- if !ins.IsConstantLoad(asm.DWord) {
- return fmt.Errorf("not a dword-sized immediate load")
- }
- if want := int64(f.local); !f.skipLocalValidation && want != ins.Constant {
- return fmt.Errorf("invalid immediate %d, expected %d (fixup: %v)", ins.Constant, want, f)
- }
- ins.Constant = int64(f.target)
- case asm.ALUClass:
- if ins.OpCode.ALUOp() == asm.Swap {
- return fmt.Errorf("relocation against swap")
- }
- fallthrough
- case asm.ALU64Class:
- if src := ins.OpCode.Source(); src != asm.ImmSource {
- return fmt.Errorf("invalid source %s", src)
- }
- if want := int64(f.local); !f.skipLocalValidation && want != ins.Constant {
- return fmt.Errorf("invalid immediate %d, expected %d (fixup: %v, kind: %v, ins: %v)", ins.Constant, want, f, f.kind, ins)
- }
- if f.target > math.MaxInt32 {
- return fmt.Errorf("immediate %d exceeds MaxInt32", f.target)
- }
- ins.Constant = int64(f.target)
- default:
- return fmt.Errorf("invalid class %s", class)
- }
- return nil
- }
- func (f COREFixup) isNonExistant() bool {
- return f.kind.checksForExistence() && f.target == 0
- }
- // coreKind is the type of CO-RE relocation as specified in BPF source code.
- type coreKind uint32
- const (
- reloFieldByteOffset coreKind = iota /* field byte offset */
- reloFieldByteSize /* field size in bytes */
- reloFieldExists /* field existence in target kernel */
- reloFieldSigned /* field signedness (0 - unsigned, 1 - signed) */
- reloFieldLShiftU64 /* bitfield-specific left bitshift */
- reloFieldRShiftU64 /* bitfield-specific right bitshift */
- reloTypeIDLocal /* type ID in local BPF object */
- reloTypeIDTarget /* type ID in target kernel */
- reloTypeExists /* type existence in target kernel */
- reloTypeSize /* type size in bytes */
- reloEnumvalExists /* enum value existence in target kernel */
- reloEnumvalValue /* enum value integer value */
- )
- func (k coreKind) checksForExistence() bool {
- return k == reloEnumvalExists || k == reloTypeExists || k == reloFieldExists
- }
- func (k coreKind) String() string {
- switch k {
- case reloFieldByteOffset:
- return "byte_off"
- case reloFieldByteSize:
- return "byte_sz"
- case reloFieldExists:
- return "field_exists"
- case reloFieldSigned:
- return "signed"
- case reloFieldLShiftU64:
- return "lshift_u64"
- case reloFieldRShiftU64:
- return "rshift_u64"
- case reloTypeIDLocal:
- return "local_type_id"
- case reloTypeIDTarget:
- return "target_type_id"
- case reloTypeExists:
- return "type_exists"
- case reloTypeSize:
- return "type_size"
- case reloEnumvalExists:
- return "enumval_exists"
- case reloEnumvalValue:
- return "enumval_value"
- default:
- return "unknown"
- }
- }
- // CORERelocate calculates the difference in types between local and target.
- //
- // Returns a list of fixups which can be applied to instructions to make them
- // match the target type(s).
- //
- // Fixups are returned in the order of relos, e.g. fixup[i] is the solution
- // for relos[i].
- func CORERelocate(local, target *Spec, relos []*CORERelocation) ([]COREFixup, error) {
- if local.byteOrder != target.byteOrder {
- return nil, fmt.Errorf("can't relocate %s against %s", local.byteOrder, target.byteOrder)
- }
- type reloGroup struct {
- relos []*CORERelocation
- // Position of each relocation in relos.
- indices []int
- }
- // Split relocations into per Type lists.
- relosByType := make(map[Type]*reloGroup)
- result := make([]COREFixup, len(relos))
- for i, relo := range relos {
- if relo.kind == reloTypeIDLocal {
- // Filtering out reloTypeIDLocal here makes our lives a lot easier
- // down the line, since it doesn't have a target at all.
- if len(relo.accessor) > 1 || relo.accessor[0] != 0 {
- return nil, fmt.Errorf("%s: unexpected accessor %v", relo.kind, relo.accessor)
- }
- id, err := local.TypeID(relo.typ)
- if err != nil {
- return nil, fmt.Errorf("%s: %w", relo.kind, err)
- }
- result[i] = COREFixup{
- kind: relo.kind,
- local: uint32(id),
- target: uint32(id),
- }
- continue
- }
- group, ok := relosByType[relo.typ]
- if !ok {
- group = &reloGroup{}
- relosByType[relo.typ] = group
- }
- group.relos = append(group.relos, relo)
- group.indices = append(group.indices, i)
- }
- for localType, group := range relosByType {
- localTypeName := localType.TypeName()
- if localTypeName == "" {
- return nil, fmt.Errorf("relocate unnamed or anonymous type %s: %w", localType, ErrNotSupported)
- }
- targets := target.namedTypes[newEssentialName(localTypeName)]
- fixups, err := coreCalculateFixups(local, target, localType, targets, group.relos)
- if err != nil {
- return nil, fmt.Errorf("relocate %s: %w", localType, err)
- }
- for j, index := range group.indices {
- result[index] = fixups[j]
- }
- }
- return result, nil
- }
- var errAmbiguousRelocation = errors.New("ambiguous relocation")
- var errImpossibleRelocation = errors.New("impossible relocation")
- // coreCalculateFixups calculates the fixups for the given relocations using
- // the "best" target.
- //
- // The best target is determined by scoring: the less poisoning we have to do
- // the better the target is.
- func coreCalculateFixups(localSpec, targetSpec *Spec, local Type, targets []Type, relos []*CORERelocation) ([]COREFixup, error) {
- localID, err := localSpec.TypeID(local)
- if err != nil {
- return nil, fmt.Errorf("local type ID: %w", err)
- }
- local = Copy(local, UnderlyingType)
- bestScore := len(relos)
- var bestFixups []COREFixup
- for i := range targets {
- targetID, err := targetSpec.TypeID(targets[i])
- if err != nil {
- return nil, fmt.Errorf("target type ID: %w", err)
- }
- target := Copy(targets[i], UnderlyingType)
- score := 0 // lower is better
- fixups := make([]COREFixup, 0, len(relos))
- for _, relo := range relos {
- fixup, err := coreCalculateFixup(localSpec.byteOrder, local, localID, target, targetID, relo)
- if err != nil {
- return nil, fmt.Errorf("target %s: %w", target, err)
- }
- if fixup.poison || fixup.isNonExistant() {
- score++
- }
- fixups = append(fixups, fixup)
- }
- if score > bestScore {
- // We have a better target already, ignore this one.
- continue
- }
- if score < bestScore {
- // This is the best target yet, use it.
- bestScore = score
- bestFixups = fixups
- continue
- }
- // Some other target has the same score as the current one. Make sure
- // the fixups agree with each other.
- for i, fixup := range bestFixups {
- if !fixup.equal(fixups[i]) {
- return nil, fmt.Errorf("%s: multiple types match: %w", fixup.kind, errAmbiguousRelocation)
- }
- }
- }
- if bestFixups == nil {
- // Nothing at all matched, probably because there are no suitable
- // targets at all.
- //
- // Poison everything except checksForExistence.
- bestFixups = make([]COREFixup, len(relos))
- for i, relo := range relos {
- if relo.kind.checksForExistence() {
- bestFixups[i] = COREFixup{kind: relo.kind, local: 1, target: 0}
- } else {
- bestFixups[i] = COREFixup{kind: relo.kind, poison: true}
- }
- }
- }
- return bestFixups, nil
- }
- // coreCalculateFixup calculates the fixup for a single local type, target type
- // and relocation.
- func coreCalculateFixup(byteOrder binary.ByteOrder, local Type, localID TypeID, target Type, targetID TypeID, relo *CORERelocation) (COREFixup, error) {
- fixup := func(local, target uint32) (COREFixup, error) {
- return COREFixup{kind: relo.kind, local: local, target: target}, nil
- }
- fixupWithoutValidation := func(local, target uint32) (COREFixup, error) {
- return COREFixup{kind: relo.kind, local: local, target: target, skipLocalValidation: true}, nil
- }
- poison := func() (COREFixup, error) {
- if relo.kind.checksForExistence() {
- return fixup(1, 0)
- }
- return COREFixup{kind: relo.kind, poison: true}, nil
- }
- zero := COREFixup{}
- switch relo.kind {
- case reloTypeIDTarget, reloTypeSize, reloTypeExists:
- if len(relo.accessor) > 1 || relo.accessor[0] != 0 {
- return zero, fmt.Errorf("%s: unexpected accessor %v", relo.kind, relo.accessor)
- }
- err := coreAreTypesCompatible(local, target)
- if errors.Is(err, errImpossibleRelocation) {
- return poison()
- }
- if err != nil {
- return zero, fmt.Errorf("relocation %s: %w", relo.kind, err)
- }
- switch relo.kind {
- case reloTypeExists:
- return fixup(1, 1)
- case reloTypeIDTarget:
- return fixup(uint32(localID), uint32(targetID))
- case reloTypeSize:
- localSize, err := Sizeof(local)
- if err != nil {
- return zero, err
- }
- targetSize, err := Sizeof(target)
- if err != nil {
- return zero, err
- }
- return fixup(uint32(localSize), uint32(targetSize))
- }
- case reloEnumvalValue, reloEnumvalExists:
- localValue, targetValue, err := coreFindEnumValue(local, relo.accessor, target)
- if errors.Is(err, errImpossibleRelocation) {
- return poison()
- }
- if err != nil {
- return zero, fmt.Errorf("relocation %s: %w", relo.kind, err)
- }
- switch relo.kind {
- case reloEnumvalExists:
- return fixup(1, 1)
- case reloEnumvalValue:
- return fixup(uint32(localValue.Value), uint32(targetValue.Value))
- }
- case reloFieldSigned:
- switch local.(type) {
- case *Enum:
- return fixup(1, 1)
- case *Int:
- return fixup(
- uint32(local.(*Int).Encoding&Signed),
- uint32(target.(*Int).Encoding&Signed),
- )
- default:
- return fixupWithoutValidation(0, 0)
- }
- case reloFieldByteOffset, reloFieldByteSize, reloFieldExists, reloFieldLShiftU64, reloFieldRShiftU64:
- if _, ok := target.(*Fwd); ok {
- // We can't relocate fields using a forward declaration, so
- // skip it. If a non-forward declaration is present in the BTF
- // we'll find it in one of the other iterations.
- return poison()
- }
- localField, targetField, err := coreFindField(local, relo.accessor, target)
- if errors.Is(err, errImpossibleRelocation) {
- return poison()
- }
- if err != nil {
- return zero, fmt.Errorf("target %s: %w", target, err)
- }
- maybeSkipValidation := func(f COREFixup, err error) (COREFixup, error) {
- f.skipLocalValidation = localField.bitfieldSize > 0
- return f, err
- }
- switch relo.kind {
- case reloFieldExists:
- return fixup(1, 1)
- case reloFieldByteOffset:
- return maybeSkipValidation(fixup(localField.offset, targetField.offset))
- case reloFieldByteSize:
- localSize, err := Sizeof(localField.Type)
- if err != nil {
- return zero, err
- }
- targetSize, err := Sizeof(targetField.Type)
- if err != nil {
- return zero, err
- }
- return maybeSkipValidation(fixup(uint32(localSize), uint32(targetSize)))
- case reloFieldLShiftU64:
- var target uint32
- if byteOrder == binary.LittleEndian {
- targetSize, err := targetField.sizeBits()
- if err != nil {
- return zero, err
- }
- target = uint32(64 - targetField.bitfieldOffset - targetSize)
- } else {
- loadWidth, err := Sizeof(targetField.Type)
- if err != nil {
- return zero, err
- }
- target = uint32(64 - Bits(loadWidth*8) + targetField.bitfieldOffset)
- }
- return fixupWithoutValidation(0, target)
- case reloFieldRShiftU64:
- targetSize, err := targetField.sizeBits()
- if err != nil {
- return zero, err
- }
- return fixupWithoutValidation(0, uint32(64-targetSize))
- }
- }
- return zero, fmt.Errorf("relocation %s: %w", relo.kind, ErrNotSupported)
- }
- /* coreAccessor contains a path through a struct. It contains at least one index.
- *
- * The interpretation depends on the kind of the relocation. The following is
- * taken from struct bpf_core_relo in libbpf_internal.h:
- *
- * - for field-based relocations, string encodes an accessed field using
- * a sequence of field and array indices, separated by colon (:). It's
- * conceptually very close to LLVM's getelementptr ([0]) instruction's
- * arguments for identifying offset to a field.
- * - for type-based relocations, strings is expected to be just "0";
- * - for enum value-based relocations, string contains an index of enum
- * value within its enum type;
- *
- * Example to provide a better feel.
- *
- * struct sample {
- * int a;
- * struct {
- * int b[10];
- * };
- * };
- *
- * struct sample s = ...;
- * int x = &s->a; // encoded as "0:0" (a is field #0)
- * int y = &s->b[5]; // encoded as "0:1:0:5" (anon struct is field #1,
- * // b is field #0 inside anon struct, accessing elem #5)
- * int z = &s[10]->b; // encoded as "10:1" (ptr is used as an array)
- */
- type coreAccessor []int
- func parseCOREAccessor(accessor string) (coreAccessor, error) {
- if accessor == "" {
- return nil, fmt.Errorf("empty accessor")
- }
- parts := strings.Split(accessor, ":")
- result := make(coreAccessor, 0, len(parts))
- for _, part := range parts {
- // 31 bits to avoid overflowing int on 32 bit platforms.
- index, err := strconv.ParseUint(part, 10, 31)
- if err != nil {
- return nil, fmt.Errorf("accessor index %q: %s", part, err)
- }
- result = append(result, int(index))
- }
- return result, nil
- }
- func (ca coreAccessor) String() string {
- strs := make([]string, 0, len(ca))
- for _, i := range ca {
- strs = append(strs, strconv.Itoa(i))
- }
- return strings.Join(strs, ":")
- }
- func (ca coreAccessor) enumValue(t Type) (*EnumValue, error) {
- e, ok := t.(*Enum)
- if !ok {
- return nil, fmt.Errorf("not an enum: %s", t)
- }
- if len(ca) > 1 {
- return nil, fmt.Errorf("invalid accessor %s for enum", ca)
- }
- i := ca[0]
- if i >= len(e.Values) {
- return nil, fmt.Errorf("invalid index %d for %s", i, e)
- }
- return &e.Values[i], nil
- }
- // coreField represents the position of a "child" of a composite type from the
- // start of that type.
- //
- // /- start of composite
- // | offset * 8 | bitfieldOffset | bitfieldSize | ... |
- // \- start of field end of field -/
- type coreField struct {
- Type Type
- // The position of the field from the start of the composite type in bytes.
- offset uint32
- // The offset of the bitfield in bits from the start of the field.
- bitfieldOffset Bits
- // The size of the bitfield in bits.
- //
- // Zero if the field is not a bitfield.
- bitfieldSize Bits
- }
- func (cf *coreField) adjustOffsetToNthElement(n int) error {
- size, err := Sizeof(cf.Type)
- if err != nil {
- return err
- }
- cf.offset += uint32(n) * uint32(size)
- return nil
- }
- func (cf *coreField) adjustOffsetBits(offset Bits) error {
- align, err := alignof(cf.Type)
- if err != nil {
- return err
- }
- // We can compute the load offset by:
- // 1) converting the bit offset to bytes with a flooring division.
- // 2) dividing and multiplying that offset by the alignment, yielding the
- // load size aligned offset.
- offsetBytes := uint32(offset/8) / uint32(align) * uint32(align)
- // The number of bits remaining is the bit offset less the number of bits
- // we can "skip" with the aligned offset.
- cf.bitfieldOffset = offset - Bits(offsetBytes*8)
- // We know that cf.offset is aligned at to at least align since we get it
- // from the compiler via BTF. Adding an aligned offsetBytes preserves the
- // alignment.
- cf.offset += offsetBytes
- return nil
- }
- func (cf *coreField) sizeBits() (Bits, error) {
- if cf.bitfieldSize > 0 {
- return cf.bitfieldSize, nil
- }
- // Someone is trying to access a non-bitfield via a bit shift relocation.
- // This happens when a field changes from a bitfield to a regular field
- // between kernel versions. Synthesise the size to make the shifts work.
- size, err := Sizeof(cf.Type)
- if err != nil {
- return 0, nil
- }
- return Bits(size * 8), nil
- }
- // coreFindField descends into the local type using the accessor and tries to
- // find an equivalent field in target at each step.
- //
- // Returns the field and the offset of the field from the start of
- // target in bits.
- func coreFindField(localT Type, localAcc coreAccessor, targetT Type) (coreField, coreField, error) {
- local := coreField{Type: localT}
- target := coreField{Type: targetT}
- // The first index is used to offset a pointer of the base type like
- // when accessing an array.
- if err := local.adjustOffsetToNthElement(localAcc[0]); err != nil {
- return coreField{}, coreField{}, err
- }
- if err := target.adjustOffsetToNthElement(localAcc[0]); err != nil {
- return coreField{}, coreField{}, err
- }
- if err := coreAreMembersCompatible(local.Type, target.Type); err != nil {
- return coreField{}, coreField{}, fmt.Errorf("fields: %w", err)
- }
- var localMaybeFlex, targetMaybeFlex bool
- for i, acc := range localAcc[1:] {
- switch localType := local.Type.(type) {
- case composite:
- // For composite types acc is used to find the field in the local type,
- // and then we try to find a field in target with the same name.
- localMembers := localType.members()
- if acc >= len(localMembers) {
- return coreField{}, coreField{}, fmt.Errorf("invalid accessor %d for %s", acc, localType)
- }
- localMember := localMembers[acc]
- if localMember.Name == "" {
- _, ok := localMember.Type.(composite)
- if !ok {
- return coreField{}, coreField{}, fmt.Errorf("unnamed field with type %s: %s", localMember.Type, ErrNotSupported)
- }
- // This is an anonymous struct or union, ignore it.
- local = coreField{
- Type: localMember.Type,
- offset: local.offset + localMember.Offset.Bytes(),
- }
- localMaybeFlex = false
- continue
- }
- targetType, ok := target.Type.(composite)
- if !ok {
- return coreField{}, coreField{}, fmt.Errorf("target not composite: %w", errImpossibleRelocation)
- }
- targetMember, last, err := coreFindMember(targetType, localMember.Name)
- if err != nil {
- return coreField{}, coreField{}, err
- }
- local = coreField{
- Type: localMember.Type,
- offset: local.offset,
- bitfieldSize: localMember.BitfieldSize,
- }
- localMaybeFlex = acc == len(localMembers)-1
- target = coreField{
- Type: targetMember.Type,
- offset: target.offset,
- bitfieldSize: targetMember.BitfieldSize,
- }
- targetMaybeFlex = last
- if local.bitfieldSize == 0 && target.bitfieldSize == 0 {
- local.offset += localMember.Offset.Bytes()
- target.offset += targetMember.Offset.Bytes()
- break
- }
- // Either of the members is a bitfield. Make sure we're at the
- // end of the accessor.
- if next := i + 1; next < len(localAcc[1:]) {
- return coreField{}, coreField{}, fmt.Errorf("can't descend into bitfield")
- }
- if err := local.adjustOffsetBits(localMember.Offset); err != nil {
- return coreField{}, coreField{}, err
- }
- if err := target.adjustOffsetBits(targetMember.Offset); err != nil {
- return coreField{}, coreField{}, err
- }
- case *Array:
- // For arrays, acc is the index in the target.
- targetType, ok := target.Type.(*Array)
- if !ok {
- return coreField{}, coreField{}, fmt.Errorf("target not array: %w", errImpossibleRelocation)
- }
- if localType.Nelems == 0 && !localMaybeFlex {
- return coreField{}, coreField{}, fmt.Errorf("local type has invalid flexible array")
- }
- if targetType.Nelems == 0 && !targetMaybeFlex {
- return coreField{}, coreField{}, fmt.Errorf("target type has invalid flexible array")
- }
- if localType.Nelems > 0 && acc >= int(localType.Nelems) {
- return coreField{}, coreField{}, fmt.Errorf("invalid access of %s at index %d", localType, acc)
- }
- if targetType.Nelems > 0 && acc >= int(targetType.Nelems) {
- return coreField{}, coreField{}, fmt.Errorf("out of bounds access of target: %w", errImpossibleRelocation)
- }
- local = coreField{
- Type: localType.Type,
- offset: local.offset,
- }
- localMaybeFlex = false
- if err := local.adjustOffsetToNthElement(acc); err != nil {
- return coreField{}, coreField{}, err
- }
- target = coreField{
- Type: targetType.Type,
- offset: target.offset,
- }
- targetMaybeFlex = false
- if err := target.adjustOffsetToNthElement(acc); err != nil {
- return coreField{}, coreField{}, err
- }
- default:
- return coreField{}, coreField{}, fmt.Errorf("relocate field of %T: %w", localType, ErrNotSupported)
- }
- if err := coreAreMembersCompatible(local.Type, target.Type); err != nil {
- return coreField{}, coreField{}, err
- }
- }
- return local, target, nil
- }
- // coreFindMember finds a member in a composite type while handling anonymous
- // structs and unions.
- func coreFindMember(typ composite, name string) (Member, bool, error) {
- if name == "" {
- return Member{}, false, errors.New("can't search for anonymous member")
- }
- type offsetTarget struct {
- composite
- offset Bits
- }
- targets := []offsetTarget{{typ, 0}}
- visited := make(map[composite]bool)
- for i := 0; i < len(targets); i++ {
- target := targets[i]
- // Only visit targets once to prevent infinite recursion.
- if visited[target] {
- continue
- }
- if len(visited) >= maxTypeDepth {
- // This check is different than libbpf, which restricts the entire
- // path to BPF_CORE_SPEC_MAX_LEN items.
- return Member{}, false, fmt.Errorf("type is nested too deep")
- }
- visited[target] = true
- members := target.members()
- for j, member := range members {
- if member.Name == name {
- // NB: This is safe because member is a copy.
- member.Offset += target.offset
- return member, j == len(members)-1, nil
- }
- // The names don't match, but this member could be an anonymous struct
- // or union.
- if member.Name != "" {
- continue
- }
- comp, ok := member.Type.(composite)
- if !ok {
- return Member{}, false, fmt.Errorf("anonymous non-composite type %T not allowed", member.Type)
- }
- targets = append(targets, offsetTarget{comp, target.offset + member.Offset})
- }
- }
- return Member{}, false, fmt.Errorf("no matching member: %w", errImpossibleRelocation)
- }
- // coreFindEnumValue follows localAcc to find the equivalent enum value in target.
- func coreFindEnumValue(local Type, localAcc coreAccessor, target Type) (localValue, targetValue *EnumValue, _ error) {
- localValue, err := localAcc.enumValue(local)
- if err != nil {
- return nil, nil, err
- }
- targetEnum, ok := target.(*Enum)
- if !ok {
- return nil, nil, errImpossibleRelocation
- }
- localName := newEssentialName(localValue.Name)
- for i, targetValue := range targetEnum.Values {
- if newEssentialName(targetValue.Name) != localName {
- continue
- }
- return localValue, &targetEnum.Values[i], nil
- }
- return nil, nil, errImpossibleRelocation
- }
- /* The comment below is from bpf_core_types_are_compat in libbpf.c:
- *
- * Check local and target types for compatibility. This check is used for
- * type-based CO-RE relocations and follow slightly different rules than
- * field-based relocations. This function assumes that root types were already
- * checked for name match. Beyond that initial root-level name check, names
- * are completely ignored. Compatibility rules are as follows:
- * - any two STRUCTs/UNIONs/FWDs/ENUMs/INTs are considered compatible, but
- * kind should match for local and target types (i.e., STRUCT is not
- * compatible with UNION);
- * - for ENUMs, the size is ignored;
- * - for INT, size and signedness are ignored;
- * - for ARRAY, dimensionality is ignored, element types are checked for
- * compatibility recursively;
- * - CONST/VOLATILE/RESTRICT modifiers are ignored;
- * - TYPEDEFs/PTRs are compatible if types they pointing to are compatible;
- * - FUNC_PROTOs are compatible if they have compatible signature: same
- * number of input args and compatible return and argument types.
- * These rules are not set in stone and probably will be adjusted as we get
- * more experience with using BPF CO-RE relocations.
- *
- * Returns errImpossibleRelocation if types are not compatible.
- */
- func coreAreTypesCompatible(localType Type, targetType Type) error {
- var (
- localTs, targetTs typeDeque
- l, t = &localType, &targetType
- depth = 0
- )
- for ; l != nil && t != nil; l, t = localTs.shift(), targetTs.shift() {
- if depth >= maxTypeDepth {
- return errors.New("types are nested too deep")
- }
- localType = *l
- targetType = *t
- if reflect.TypeOf(localType) != reflect.TypeOf(targetType) {
- return fmt.Errorf("type mismatch: %w", errImpossibleRelocation)
- }
- switch lv := (localType).(type) {
- case *Void, *Struct, *Union, *Enum, *Fwd, *Int:
- // Nothing to do here
- case *Pointer, *Array:
- depth++
- localType.walk(&localTs)
- targetType.walk(&targetTs)
- case *FuncProto:
- tv := targetType.(*FuncProto)
- if len(lv.Params) != len(tv.Params) {
- return fmt.Errorf("function param mismatch: %w", errImpossibleRelocation)
- }
- depth++
- localType.walk(&localTs)
- targetType.walk(&targetTs)
- default:
- return fmt.Errorf("unsupported type %T", localType)
- }
- }
- if l != nil {
- return fmt.Errorf("dangling local type %T", *l)
- }
- if t != nil {
- return fmt.Errorf("dangling target type %T", *t)
- }
- return nil
- }
- /* coreAreMembersCompatible checks two types for field-based relocation compatibility.
- *
- * The comment below is from bpf_core_fields_are_compat in libbpf.c:
- *
- * Check two types for compatibility for the purpose of field access
- * relocation. const/volatile/restrict and typedefs are skipped to ensure we
- * are relocating semantically compatible entities:
- * - any two STRUCTs/UNIONs are compatible and can be mixed;
- * - any two FWDs are compatible, if their names match (modulo flavor suffix);
- * - any two PTRs are always compatible;
- * - for ENUMs, names should be the same (ignoring flavor suffix) or at
- * least one of enums should be anonymous;
- * - for ENUMs, check sizes, names are ignored;
- * - for INT, size and signedness are ignored;
- * - any two FLOATs are always compatible;
- * - for ARRAY, dimensionality is ignored, element types are checked for
- * compatibility recursively;
- * [ NB: coreAreMembersCompatible doesn't recurse, this check is done
- * by coreFindField. ]
- * - everything else shouldn't be ever a target of relocation.
- * These rules are not set in stone and probably will be adjusted as we get
- * more experience with using BPF CO-RE relocations.
- *
- * Returns errImpossibleRelocation if the members are not compatible.
- */
- func coreAreMembersCompatible(localType Type, targetType Type) error {
- doNamesMatch := func(a, b string) error {
- if a == "" || b == "" {
- // allow anonymous and named type to match
- return nil
- }
- if newEssentialName(a) == newEssentialName(b) {
- return nil
- }
- return fmt.Errorf("names don't match: %w", errImpossibleRelocation)
- }
- _, lok := localType.(composite)
- _, tok := targetType.(composite)
- if lok && tok {
- return nil
- }
- if reflect.TypeOf(localType) != reflect.TypeOf(targetType) {
- return fmt.Errorf("type mismatch: %w", errImpossibleRelocation)
- }
- switch lv := localType.(type) {
- case *Array, *Pointer, *Float, *Int:
- return nil
- case *Enum:
- tv := targetType.(*Enum)
- return doNamesMatch(lv.Name, tv.Name)
- case *Fwd:
- tv := targetType.(*Fwd)
- return doNamesMatch(lv.Name, tv.Name)
- default:
- return fmt.Errorf("type %s: %w", localType, ErrNotSupported)
- }
- }
|