package ebpf import ( "bufio" "bytes" "debug/elf" "encoding/binary" "errors" "fmt" "io" "math" "os" "strings" "github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal/btf" "github.com/cilium/ebpf/internal/unix" ) // elfCode is a convenience to reduce the amount of arguments that have to // be passed around explicitly. You should treat it's contents as immutable. type elfCode struct { *internal.SafeELFFile sections map[elf.SectionIndex]*elfSection license string version uint32 btf *btf.Spec } // LoadCollectionSpec parses an ELF file into a CollectionSpec. func LoadCollectionSpec(file string) (*CollectionSpec, error) { f, err := os.Open(file) if err != nil { return nil, err } defer f.Close() spec, err := LoadCollectionSpecFromReader(f) if err != nil { return nil, fmt.Errorf("file %s: %w", file, err) } return spec, nil } // LoadCollectionSpecFromReader parses an ELF file into a CollectionSpec. func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { f, err := internal.NewSafeELFFile(rd) if err != nil { return nil, err } defer f.Close() var ( licenseSection *elf.Section versionSection *elf.Section sections = make(map[elf.SectionIndex]*elfSection) relSections = make(map[elf.SectionIndex]*elf.Section) ) // This is the target of relocations generated by inline assembly. sections[elf.SHN_UNDEF] = newElfSection(new(elf.Section), undefSection) // Collect all the sections we're interested in. This includes relocations // which we parse later. for i, sec := range f.Sections { idx := elf.SectionIndex(i) switch { case strings.HasPrefix(sec.Name, "license"): licenseSection = sec case strings.HasPrefix(sec.Name, "version"): versionSection = sec case strings.HasPrefix(sec.Name, "maps"): sections[idx] = newElfSection(sec, mapSection) case sec.Name == ".maps": sections[idx] = newElfSection(sec, btfMapSection) case sec.Name == ".bss" || sec.Name == ".data" || strings.HasPrefix(sec.Name, ".rodata"): sections[idx] = newElfSection(sec, dataSection) case sec.Type == elf.SHT_REL: // Store relocations under the section index of the target relSections[elf.SectionIndex(sec.Info)] = sec case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0: sections[idx] = newElfSection(sec, programSection) } } license, err := loadLicense(licenseSection) if err != nil { return nil, fmt.Errorf("load license: %w", err) } version, err := loadVersion(versionSection, f.ByteOrder) if err != nil { return nil, fmt.Errorf("load version: %w", err) } btfSpec, err := btf.LoadSpecFromReader(rd) if err != nil { return nil, fmt.Errorf("load BTF: %w", err) } // Assign symbols to all the sections we're interested in. symbols, err := f.Symbols() if err != nil { return nil, fmt.Errorf("load symbols: %v", err) } for _, symbol := range symbols { idx := symbol.Section symType := elf.ST_TYPE(symbol.Info) section := sections[idx] if section == nil { continue } // Older versions of LLVM don't tag symbols correctly, so keep // all NOTYPE ones. keep := symType == elf.STT_NOTYPE switch section.kind { case mapSection, btfMapSection, dataSection: keep = keep || symType == elf.STT_OBJECT case programSection: keep = keep || symType == elf.STT_FUNC } if !keep || symbol.Name == "" { continue } section.symbols[symbol.Value] = symbol } ec := &elfCode{ SafeELFFile: f, sections: sections, license: license, version: version, btf: btfSpec, } // Go through relocation sections, and parse the ones for sections we're // interested in. Make sure that relocations point at valid sections. for idx, relSection := range relSections { section := sections[idx] if section == nil { continue } rels, err := ec.loadRelocations(relSection, symbols) if err != nil { return nil, fmt.Errorf("relocation for section %q: %w", section.Name, err) } for _, rel := range rels { target := sections[rel.Section] if target == nil { return nil, fmt.Errorf("section %q: reference to %q in section %s: %w", section.Name, rel.Name, rel.Section, ErrNotSupported) } if target.Flags&elf.SHF_STRINGS > 0 { return nil, fmt.Errorf("section %q: string %q is not stack allocated: %w", section.Name, rel.Name, ErrNotSupported) } target.references++ } section.relocations = rels } // Collect all the various ways to define maps. maps := make(map[string]*MapSpec) if err := ec.loadMaps(maps); err != nil { return nil, fmt.Errorf("load maps: %w", err) } if err := ec.loadBTFMaps(maps); err != nil { return nil, fmt.Errorf("load BTF maps: %w", err) } if err := ec.loadDataSections(maps); err != nil { return nil, fmt.Errorf("load data sections: %w", err) } // Finally, collect programs and link them. progs, err := ec.loadPrograms() if err != nil { return nil, fmt.Errorf("load programs: %w", err) } return &CollectionSpec{maps, progs}, nil } func loadLicense(sec *elf.Section) (string, error) { if sec == nil { return "", nil } data, err := sec.Data() if err != nil { return "", fmt.Errorf("section %s: %v", sec.Name, err) } return string(bytes.TrimRight(data, "\000")), nil } func loadVersion(sec *elf.Section, bo binary.ByteOrder) (uint32, error) { if sec == nil { return 0, nil } var version uint32 if err := binary.Read(sec.Open(), bo, &version); err != nil { return 0, fmt.Errorf("section %s: %v", sec.Name, err) } return version, nil } type elfSectionKind int const ( undefSection elfSectionKind = iota mapSection btfMapSection programSection dataSection ) type elfSection struct { *elf.Section kind elfSectionKind // Offset from the start of the section to a symbol symbols map[uint64]elf.Symbol // Offset from the start of the section to a relocation, which points at // a symbol in another section. relocations map[uint64]elf.Symbol // The number of relocations pointing at this section. references int } func newElfSection(section *elf.Section, kind elfSectionKind) *elfSection { return &elfSection{ section, kind, make(map[uint64]elf.Symbol), make(map[uint64]elf.Symbol), 0, } } func (ec *elfCode) loadPrograms() (map[string]*ProgramSpec, error) { var ( progs []*ProgramSpec libs []*ProgramSpec ) for _, sec := range ec.sections { if sec.kind != programSection { continue } if len(sec.symbols) == 0 { return nil, fmt.Errorf("section %v: missing symbols", sec.Name) } funcSym, ok := sec.symbols[0] if !ok { return nil, fmt.Errorf("section %v: no label at start", sec.Name) } insns, length, err := ec.loadInstructions(sec) if err != nil { return nil, fmt.Errorf("program %s: %w", funcSym.Name, err) } progType, attachType, attachTo := getProgType(sec.Name) spec := &ProgramSpec{ Name: funcSym.Name, Type: progType, AttachType: attachType, AttachTo: attachTo, License: ec.license, KernelVersion: ec.version, Instructions: insns, ByteOrder: ec.ByteOrder, } if ec.btf != nil { spec.BTF, err = ec.btf.Program(sec.Name, length) if err != nil && !errors.Is(err, btf.ErrNoExtendedInfo) { return nil, fmt.Errorf("program %s: %w", funcSym.Name, err) } } if spec.Type == UnspecifiedProgram { // There is no single name we can use for "library" sections, // since they may contain multiple functions. We'll decode the // labels they contain later on, and then link sections that way. libs = append(libs, spec) } else { progs = append(progs, spec) } } res := make(map[string]*ProgramSpec, len(progs)) for _, prog := range progs { err := link(prog, libs) if err != nil { return nil, fmt.Errorf("program %s: %w", prog.Name, err) } res[prog.Name] = prog } return res, nil } func (ec *elfCode) loadInstructions(section *elfSection) (asm.Instructions, uint64, error) { var ( r = bufio.NewReader(section.Open()) insns asm.Instructions offset uint64 ) for { var ins asm.Instruction n, err := ins.Unmarshal(r, ec.ByteOrder) if err == io.EOF { return insns, offset, nil } if err != nil { return nil, 0, fmt.Errorf("offset %d: %w", offset, err) } ins.Symbol = section.symbols[offset].Name if rel, ok := section.relocations[offset]; ok { if err = ec.relocateInstruction(&ins, rel); err != nil { return nil, 0, fmt.Errorf("offset %d: relocate instruction: %w", offset, err) } } insns = append(insns, ins) offset += n } } func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) error { var ( typ = elf.ST_TYPE(rel.Info) bind = elf.ST_BIND(rel.Info) name = rel.Name ) target := ec.sections[rel.Section] switch target.kind { case mapSection, btfMapSection: if bind != elf.STB_GLOBAL { return fmt.Errorf("possible erroneous static qualifier on map definition: found reference to %q", name) } if typ != elf.STT_OBJECT && typ != elf.STT_NOTYPE { // STT_NOTYPE is generated on clang < 8 which doesn't tag // relocations appropriately. return fmt.Errorf("map load: incorrect relocation type %v", typ) } ins.Src = asm.PseudoMapFD // Mark the instruction as needing an update when creating the // collection. if err := ins.RewriteMapPtr(-1); err != nil { return err } case dataSection: switch typ { case elf.STT_SECTION: if bind != elf.STB_LOCAL { return fmt.Errorf("direct load: %s: unsupported relocation %s", name, bind) } case elf.STT_OBJECT: if bind != elf.STB_GLOBAL { return fmt.Errorf("direct load: %s: unsupported relocation %s", name, bind) } default: return fmt.Errorf("incorrect relocation type %v for direct map load", typ) } // We rely on using the name of the data section as the reference. It // would be nicer to keep the real name in case of an STT_OBJECT, but // it's not clear how to encode that into Instruction. name = target.Name // For some reason, clang encodes the offset of the symbol its // section in the first basic BPF instruction, while the kernel // expects it in the second one. ins.Constant <<= 32 ins.Src = asm.PseudoMapValue // Mark the instruction as needing an update when creating the // collection. if err := ins.RewriteMapPtr(-1); err != nil { return err } case programSection: if ins.OpCode.JumpOp() != asm.Call { return fmt.Errorf("not a call instruction: %s", ins) } if ins.Src != asm.PseudoCall { return fmt.Errorf("call: %s: incorrect source register", name) } switch typ { case elf.STT_NOTYPE, elf.STT_FUNC: if bind != elf.STB_GLOBAL { return fmt.Errorf("call: %s: unsupported binding: %s", name, bind) } case elf.STT_SECTION: if bind != elf.STB_LOCAL { return fmt.Errorf("call: %s: unsupported binding: %s", name, bind) } // The function we want to call is in the indicated section, // at the offset encoded in the instruction itself. Reverse // the calculation to find the real function we're looking for. // A value of -1 references the first instruction in the section. offset := int64(int32(ins.Constant)+1) * asm.InstructionSize if offset < 0 { return fmt.Errorf("call: %s: invalid offset %d", name, offset) } sym, ok := target.symbols[uint64(offset)] if !ok { return fmt.Errorf("call: %s: no symbol at offset %d", name, offset) } ins.Constant = -1 name = sym.Name default: return fmt.Errorf("call: %s: invalid symbol type %s", name, typ) } case undefSection: if bind != elf.STB_GLOBAL { return fmt.Errorf("asm relocation: %s: unsupported binding: %s", name, bind) } if typ != elf.STT_NOTYPE { return fmt.Errorf("asm relocation: %s: unsupported type %s", name, typ) } // There is nothing to do here but set ins.Reference. default: return fmt.Errorf("relocation to %q: %w", target.Name, ErrNotSupported) } ins.Reference = name return nil } func (ec *elfCode) loadMaps(maps map[string]*MapSpec) error { for _, sec := range ec.sections { if sec.kind != mapSection { continue } nSym := len(sec.symbols) if nSym == 0 { return fmt.Errorf("section %v: no symbols", sec.Name) } if sec.Size%uint64(nSym) != 0 { return fmt.Errorf("section %v: map descriptors are not of equal size", sec.Name) } var ( r = bufio.NewReader(sec.Open()) size = sec.Size / uint64(nSym) ) for i, offset := 0, uint64(0); i < nSym; i, offset = i+1, offset+size { mapSym, ok := sec.symbols[offset] if !ok { return fmt.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset) } if maps[mapSym.Name] != nil { return fmt.Errorf("section %v: map %v already exists", sec.Name, mapSym) } lr := io.LimitReader(r, int64(size)) spec := MapSpec{ Name: SanitizeName(mapSym.Name, -1), } switch { case binary.Read(lr, ec.ByteOrder, &spec.Type) != nil: return fmt.Errorf("map %v: missing type", mapSym) case binary.Read(lr, ec.ByteOrder, &spec.KeySize) != nil: return fmt.Errorf("map %v: missing key size", mapSym) case binary.Read(lr, ec.ByteOrder, &spec.ValueSize) != nil: return fmt.Errorf("map %v: missing value size", mapSym) case binary.Read(lr, ec.ByteOrder, &spec.MaxEntries) != nil: return fmt.Errorf("map %v: missing max entries", mapSym) case binary.Read(lr, ec.ByteOrder, &spec.Flags) != nil: return fmt.Errorf("map %v: missing flags", mapSym) } if _, err := io.Copy(internal.DiscardZeroes{}, lr); err != nil { return fmt.Errorf("map %v: unknown and non-zero fields in definition", mapSym) } maps[mapSym.Name] = &spec } } return nil } func (ec *elfCode) loadBTFMaps(maps map[string]*MapSpec) error { for _, sec := range ec.sections { if sec.kind != btfMapSection { continue } if ec.btf == nil { return fmt.Errorf("missing BTF") } if len(sec.symbols) == 0 { return fmt.Errorf("section %v: no symbols", sec.Name) } _, err := io.Copy(internal.DiscardZeroes{}, bufio.NewReader(sec.Open())) if err != nil { return fmt.Errorf("section %v: initializing BTF map definitions: %w", sec.Name, internal.ErrNotSupported) } for _, sym := range sec.symbols { name := sym.Name if maps[name] != nil { return fmt.Errorf("section %v: map %v already exists", sec.Name, sym) } // A global Var is created by declaring a struct with a 'structure variable', // as is common in eBPF C to declare eBPF maps. For example, // `struct { ... } map_name ...;` emits a global variable `map_name` // with the type of said struct (which can be anonymous). var v btf.Var if err := ec.btf.FindType(name, &v); err != nil { return fmt.Errorf("cannot find global variable '%s' in BTF: %w", name, err) } mapStruct, ok := v.Type.(*btf.Struct) if !ok { return fmt.Errorf("expected struct, got %s", v.Type) } mapSpec, err := mapSpecFromBTF(name, mapStruct, false, ec.btf) if err != nil { return fmt.Errorf("map %v: %w", name, err) } maps[name] = mapSpec } } return nil } // mapSpecFromBTF produces a MapSpec based on a btf.Struct def representing // a BTF map definition. The name and spec arguments will be copied to the // resulting MapSpec, and inner must be true on any resursive invocations. func mapSpecFromBTF(name string, def *btf.Struct, inner bool, spec *btf.Spec) (*MapSpec, error) { var ( key, value btf.Type keySize, valueSize uint32 mapType, flags, maxEntries uint32 pinType PinType innerMapSpec *MapSpec err error ) for i, member := range def.Members { switch member.Name { case "type": mapType, err = uintFromBTF(member.Type) if err != nil { return nil, fmt.Errorf("can't get type: %w", err) } case "map_flags": flags, err = uintFromBTF(member.Type) if err != nil { return nil, fmt.Errorf("can't get BTF map flags: %w", err) } case "max_entries": maxEntries, err = uintFromBTF(member.Type) if err != nil { return nil, fmt.Errorf("can't get BTF map max entries: %w", err) } case "key": if keySize != 0 { return nil, errors.New("both key and key_size given") } pk, ok := member.Type.(*btf.Pointer) if !ok { return nil, fmt.Errorf("key type is not a pointer: %T", member.Type) } key = pk.Target size, err := btf.Sizeof(pk.Target) if err != nil { return nil, fmt.Errorf("can't get size of BTF key: %w", err) } keySize = uint32(size) case "value": if valueSize != 0 { return nil, errors.New("both value and value_size given") } vk, ok := member.Type.(*btf.Pointer) if !ok { return nil, fmt.Errorf("value type is not a pointer: %T", member.Type) } value = vk.Target size, err := btf.Sizeof(vk.Target) if err != nil { return nil, fmt.Errorf("can't get size of BTF value: %w", err) } valueSize = uint32(size) case "key_size": // Key needs to be nil and keySize needs to be 0 for key_size to be // considered a valid member. if key != nil || keySize != 0 { return nil, errors.New("both key and key_size given") } keySize, err = uintFromBTF(member.Type) if err != nil { return nil, fmt.Errorf("can't get BTF key size: %w", err) } case "value_size": // Value needs to be nil and valueSize needs to be 0 for value_size to be // considered a valid member. if value != nil || valueSize != 0 { return nil, errors.New("both value and value_size given") } valueSize, err = uintFromBTF(member.Type) if err != nil { return nil, fmt.Errorf("can't get BTF value size: %w", err) } case "pinning": if inner { return nil, errors.New("inner maps can't be pinned") } pinning, err := uintFromBTF(member.Type) if err != nil { return nil, fmt.Errorf("can't get pinning: %w", err) } pinType = PinType(pinning) case "values": // The 'values' field in BTF map definitions is used for declaring map // value types that are references to other BPF objects, like other maps // or programs. It is always expected to be an array of pointers. if i != len(def.Members)-1 { return nil, errors.New("'values' must be the last member in a BTF map definition") } if valueSize != 0 && valueSize != 4 { return nil, errors.New("value_size must be 0 or 4") } valueSize = 4 valueType, err := resolveBTFArrayMacro(member.Type) if err != nil { return nil, fmt.Errorf("can't resolve type of member 'values': %w", err) } switch t := valueType.(type) { case *btf.Struct: // The values member pointing to an array of structs means we're expecting // a map-in-map declaration. if MapType(mapType) != ArrayOfMaps && MapType(mapType) != HashOfMaps { return nil, errors.New("outer map needs to be an array or a hash of maps") } if inner { return nil, fmt.Errorf("nested inner maps are not supported") } // This inner map spec is used as a map template, but it needs to be // created as a traditional map before it can be used to do so. // libbpf names the inner map template '.inner', but we // opted for _inner to simplify validation logic. (dots only supported // on kernels 5.2 and up) // Pass the BTF spec from the parent object, since both parent and // child must be created from the same BTF blob (on kernels that support BTF). innerMapSpec, err = mapSpecFromBTF(name+"_inner", t, true, spec) if err != nil { return nil, fmt.Errorf("can't parse BTF map definition of inner map: %w", err) } default: return nil, fmt.Errorf("unsupported value type %q in 'values' field", t) } default: return nil, fmt.Errorf("unrecognized field %s in BTF map definition", member.Name) } } bm := btf.NewMap(spec, key, value) return &MapSpec{ Name: SanitizeName(name, -1), Type: MapType(mapType), KeySize: keySize, ValueSize: valueSize, MaxEntries: maxEntries, Flags: flags, BTF: &bm, Pinning: pinType, InnerMap: innerMapSpec, }, nil } // uintFromBTF resolves the __uint macro, which is a pointer to a sized // array, e.g. for int (*foo)[10], this function will return 10. func uintFromBTF(typ btf.Type) (uint32, error) { ptr, ok := typ.(*btf.Pointer) if !ok { return 0, fmt.Errorf("not a pointer: %v", typ) } arr, ok := ptr.Target.(*btf.Array) if !ok { return 0, fmt.Errorf("not a pointer to array: %v", typ) } return arr.Nelems, nil } // resolveBTFArrayMacro resolves the __array macro, which declares an array // of pointers to a given type. This function returns the target Type of // the pointers in the array. func resolveBTFArrayMacro(typ btf.Type) (btf.Type, error) { arr, ok := typ.(*btf.Array) if !ok { return nil, fmt.Errorf("not an array: %v", typ) } ptr, ok := arr.Type.(*btf.Pointer) if !ok { return nil, fmt.Errorf("not an array of pointers: %v", typ) } return ptr.Target, nil } func (ec *elfCode) loadDataSections(maps map[string]*MapSpec) error { for _, sec := range ec.sections { if sec.kind != dataSection { continue } if sec.references == 0 { // Prune data sections which are not referenced by any // instructions. continue } if ec.btf == nil { return errors.New("data sections require BTF, make sure all consts are marked as static") } btfMap, err := ec.btf.Datasec(sec.Name) if err != nil { return err } data, err := sec.Data() if err != nil { return fmt.Errorf("data section %s: can't get contents: %w", sec.Name, err) } if uint64(len(data)) > math.MaxUint32 { return fmt.Errorf("data section %s: contents exceed maximum size", sec.Name) } mapSpec := &MapSpec{ Name: SanitizeName(sec.Name, -1), Type: Array, KeySize: 4, ValueSize: uint32(len(data)), MaxEntries: 1, Contents: []MapKV{{uint32(0), data}}, BTF: btfMap, } switch sec.Name { case ".rodata": mapSpec.Flags = unix.BPF_F_RDONLY_PROG mapSpec.Freeze = true case ".bss": // The kernel already zero-initializes the map mapSpec.Contents = nil } maps[sec.Name] = mapSpec } return nil } func getProgType(sectionName string) (ProgramType, AttachType, string) { types := map[string]struct { progType ProgramType attachType AttachType }{ // From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c "socket": {SocketFilter, AttachNone}, "seccomp": {SocketFilter, AttachNone}, "kprobe/": {Kprobe, AttachNone}, "uprobe/": {Kprobe, AttachNone}, "kretprobe/": {Kprobe, AttachNone}, "uretprobe/": {Kprobe, AttachNone}, "tracepoint/": {TracePoint, AttachNone}, "raw_tracepoint/": {RawTracepoint, AttachNone}, "xdp": {XDP, AttachNone}, "perf_event": {PerfEvent, AttachNone}, "lwt_in": {LWTIn, AttachNone}, "lwt_out": {LWTOut, AttachNone}, "lwt_xmit": {LWTXmit, AttachNone}, "lwt_seg6local": {LWTSeg6Local, AttachNone}, "sockops": {SockOps, AttachCGroupSockOps}, "sk_skb/stream_parser": {SkSKB, AttachSkSKBStreamParser}, "sk_skb/stream_verdict": {SkSKB, AttachSkSKBStreamParser}, "sk_msg": {SkMsg, AttachSkSKBStreamVerdict}, "lirc_mode2": {LircMode2, AttachLircMode2}, "flow_dissector": {FlowDissector, AttachFlowDissector}, "iter/": {Tracing, AttachTraceIter}, "sk_lookup/": {SkLookup, AttachSkLookup}, "lsm/": {LSM, AttachLSMMac}, "cgroup_skb/ingress": {CGroupSKB, AttachCGroupInetIngress}, "cgroup_skb/egress": {CGroupSKB, AttachCGroupInetEgress}, "cgroup/dev": {CGroupDevice, AttachCGroupDevice}, "cgroup/skb": {CGroupSKB, AttachNone}, "cgroup/sock": {CGroupSock, AttachCGroupInetSockCreate}, "cgroup/post_bind4": {CGroupSock, AttachCGroupInet4PostBind}, "cgroup/post_bind6": {CGroupSock, AttachCGroupInet6PostBind}, "cgroup/bind4": {CGroupSockAddr, AttachCGroupInet4Bind}, "cgroup/bind6": {CGroupSockAddr, AttachCGroupInet6Bind}, "cgroup/connect4": {CGroupSockAddr, AttachCGroupInet4Connect}, "cgroup/connect6": {CGroupSockAddr, AttachCGroupInet6Connect}, "cgroup/sendmsg4": {CGroupSockAddr, AttachCGroupUDP4Sendmsg}, "cgroup/sendmsg6": {CGroupSockAddr, AttachCGroupUDP6Sendmsg}, "cgroup/recvmsg4": {CGroupSockAddr, AttachCGroupUDP4Recvmsg}, "cgroup/recvmsg6": {CGroupSockAddr, AttachCGroupUDP6Recvmsg}, "cgroup/sysctl": {CGroupSysctl, AttachCGroupSysctl}, "cgroup/getsockopt": {CGroupSockopt, AttachCGroupGetsockopt}, "cgroup/setsockopt": {CGroupSockopt, AttachCGroupSetsockopt}, "classifier": {SchedCLS, AttachNone}, "action": {SchedACT, AttachNone}, } for prefix, t := range types { if !strings.HasPrefix(sectionName, prefix) { continue } if !strings.HasSuffix(prefix, "/") { return t.progType, t.attachType, "" } return t.progType, t.attachType, sectionName[len(prefix):] } return UnspecifiedProgram, AttachNone, "" } func (ec *elfCode) loadRelocations(sec *elf.Section, symbols []elf.Symbol) (map[uint64]elf.Symbol, error) { rels := make(map[uint64]elf.Symbol) if sec.Entsize < 16 { return nil, fmt.Errorf("section %s: relocations are less than 16 bytes", sec.Name) } r := bufio.NewReader(sec.Open()) for off := uint64(0); off < sec.Size; off += sec.Entsize { ent := io.LimitReader(r, int64(sec.Entsize)) var rel elf.Rel64 if binary.Read(ent, ec.ByteOrder, &rel) != nil { return nil, fmt.Errorf("can't parse relocation at offset %v", off) } symNo := int(elf.R_SYM64(rel.Info) - 1) if symNo >= len(symbols) { return nil, fmt.Errorf("offset %d: symbol %d doesn't exist", off, symNo) } symbol := symbols[symNo] rels[rel.Off] = symbol } return rels, nil }