mirror of https://github.com/k3s-io/k3s
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
393 lines
11 KiB
393 lines
11 KiB
package ebpf
|
|
|
|
import (
|
|
"bytes"
|
|
"debug/elf"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/cilium/ebpf/asm"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type elfCode struct {
|
|
*elf.File
|
|
symbols []elf.Symbol
|
|
symbolsPerSection map[elf.SectionIndex]map[uint64]string
|
|
}
|
|
|
|
// 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)
|
|
return spec, errors.Wrapf(err, "file %s", file)
|
|
}
|
|
|
|
// LoadCollectionSpecFromReader parses an ELF file into a CollectionSpec.
|
|
func LoadCollectionSpecFromReader(code io.ReaderAt) (*CollectionSpec, error) {
|
|
f, err := elf.NewFile(code)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
symbols, err := f.Symbols()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "load symbols")
|
|
}
|
|
|
|
ec := &elfCode{f, symbols, symbolsPerSection(symbols)}
|
|
|
|
var licenseSection, versionSection *elf.Section
|
|
progSections := make(map[elf.SectionIndex]*elf.Section)
|
|
relSections := make(map[elf.SectionIndex]*elf.Section)
|
|
mapSections := make(map[elf.SectionIndex]*elf.Section)
|
|
for i, sec := range ec.Sections {
|
|
switch {
|
|
case strings.HasPrefix(sec.Name, "license"):
|
|
licenseSection = sec
|
|
case strings.HasPrefix(sec.Name, "version"):
|
|
versionSection = sec
|
|
case strings.HasPrefix(sec.Name, "maps"):
|
|
mapSections[elf.SectionIndex(i)] = sec
|
|
case sec.Type == elf.SHT_REL:
|
|
if int(sec.Info) >= len(ec.Sections) {
|
|
return nil, errors.Errorf("found relocation section %v for missing section %v", i, sec.Info)
|
|
}
|
|
|
|
// Store relocations under the section index of the target
|
|
idx := elf.SectionIndex(sec.Info)
|
|
if relSections[idx] != nil {
|
|
return nil, errors.Errorf("section %d has multiple relocation sections", idx)
|
|
}
|
|
relSections[idx] = sec
|
|
case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0:
|
|
progSections[elf.SectionIndex(i)] = sec
|
|
}
|
|
}
|
|
|
|
license, err := loadLicense(licenseSection)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "load license")
|
|
}
|
|
|
|
version, err := loadVersion(versionSection, ec.ByteOrder)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "load version")
|
|
}
|
|
|
|
maps, err := ec.loadMaps(mapSections)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "load maps")
|
|
}
|
|
|
|
progs, libs, err := ec.loadPrograms(progSections, relSections, license, version)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "load programs")
|
|
}
|
|
|
|
if len(libs) > 0 {
|
|
for name, prog := range progs {
|
|
prog.Instructions, err = link(prog.Instructions, libs...)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "program %s", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
return &CollectionSpec{maps, progs}, nil
|
|
}
|
|
|
|
func loadLicense(sec *elf.Section) (string, error) {
|
|
if sec == nil {
|
|
return "", errors.Errorf("missing license section")
|
|
}
|
|
data, err := sec.Data()
|
|
if err != nil {
|
|
return "", errors.Wrapf(err, "section %s", sec.Name)
|
|
}
|
|
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
|
|
err := binary.Read(sec.Open(), bo, &version)
|
|
return version, errors.Wrapf(err, "section %s", sec.Name)
|
|
}
|
|
|
|
func (ec *elfCode) loadPrograms(progSections, relSections map[elf.SectionIndex]*elf.Section, license string, version uint32) (map[string]*ProgramSpec, []asm.Instructions, error) {
|
|
var (
|
|
progs = make(map[string]*ProgramSpec)
|
|
libs []asm.Instructions
|
|
)
|
|
for idx, prog := range progSections {
|
|
syms := ec.symbolsPerSection[idx]
|
|
if len(syms) == 0 {
|
|
return nil, nil, errors.Errorf("section %v: missing symbols", prog.Name)
|
|
}
|
|
|
|
funcSym := syms[0]
|
|
if funcSym == "" {
|
|
return nil, nil, errors.Errorf("section %v: no label at start", prog.Name)
|
|
}
|
|
|
|
rels, err := ec.loadRelocations(relSections[idx])
|
|
if err != nil {
|
|
return nil, nil, errors.Wrapf(err, "program %s: can't load relocations", funcSym)
|
|
}
|
|
|
|
insns, err := ec.loadInstructions(prog, syms, rels)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrapf(err, "program %s: can't unmarshal instructions", funcSym)
|
|
}
|
|
|
|
if progType, attachType := getProgType(prog.Name); progType == 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, insns)
|
|
} else {
|
|
progs[funcSym] = &ProgramSpec{
|
|
Name: funcSym,
|
|
Type: progType,
|
|
AttachType: attachType,
|
|
License: license,
|
|
KernelVersion: version,
|
|
Instructions: insns,
|
|
}
|
|
}
|
|
}
|
|
return progs, libs, nil
|
|
}
|
|
|
|
func (ec *elfCode) loadInstructions(section *elf.Section, symbols, relocations map[uint64]string) (asm.Instructions, error) {
|
|
var (
|
|
r = section.Open()
|
|
insns asm.Instructions
|
|
ins asm.Instruction
|
|
offset uint64
|
|
)
|
|
for {
|
|
n, err := ins.Unmarshal(r, ec.ByteOrder)
|
|
if err == io.EOF {
|
|
return insns, nil
|
|
}
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "offset %d", offset)
|
|
}
|
|
|
|
ins.Symbol = symbols[offset]
|
|
ins.Reference = relocations[offset]
|
|
|
|
insns = append(insns, ins)
|
|
offset += n
|
|
}
|
|
}
|
|
|
|
func (ec *elfCode) loadMaps(mapSections map[elf.SectionIndex]*elf.Section) (map[string]*MapSpec, error) {
|
|
var (
|
|
maps = make(map[string]*MapSpec)
|
|
b = make([]byte, 1)
|
|
)
|
|
for idx, sec := range mapSections {
|
|
syms := ec.symbolsPerSection[idx]
|
|
if len(syms) == 0 {
|
|
return nil, errors.Errorf("section %v: no symbols", sec.Name)
|
|
}
|
|
|
|
if sec.Size%uint64(len(syms)) != 0 {
|
|
return nil, errors.Errorf("section %v: map descriptors are not of equal size", sec.Name)
|
|
}
|
|
|
|
var (
|
|
r = sec.Open()
|
|
size = sec.Size / uint64(len(syms))
|
|
)
|
|
for i, offset := 0, uint64(0); i < len(syms); i, offset = i+1, offset+size {
|
|
mapSym := syms[offset]
|
|
if mapSym == "" {
|
|
fmt.Println(syms)
|
|
return nil, errors.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset)
|
|
}
|
|
|
|
if maps[mapSym] != nil {
|
|
return nil, errors.Errorf("section %v: map %v already exists", sec.Name, mapSym)
|
|
}
|
|
|
|
lr := io.LimitReader(r, int64(size))
|
|
|
|
var spec MapSpec
|
|
switch {
|
|
case binary.Read(lr, ec.ByteOrder, &spec.Type) != nil:
|
|
return nil, errors.Errorf("map %v: missing type", mapSym)
|
|
case binary.Read(lr, ec.ByteOrder, &spec.KeySize) != nil:
|
|
return nil, errors.Errorf("map %v: missing key size", mapSym)
|
|
case binary.Read(lr, ec.ByteOrder, &spec.ValueSize) != nil:
|
|
return nil, errors.Errorf("map %v: missing value size", mapSym)
|
|
case binary.Read(lr, ec.ByteOrder, &spec.MaxEntries) != nil:
|
|
return nil, errors.Errorf("map %v: missing max entries", mapSym)
|
|
case binary.Read(lr, ec.ByteOrder, &spec.Flags) != nil:
|
|
return nil, errors.Errorf("map %v: missing flags", mapSym)
|
|
}
|
|
|
|
for {
|
|
_, err := lr.Read(b)
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if b[0] != 0 {
|
|
return nil, errors.Errorf("map %v: unknown and non-zero fields in definition", mapSym)
|
|
}
|
|
}
|
|
|
|
maps[mapSym] = &spec
|
|
}
|
|
}
|
|
return maps, nil
|
|
}
|
|
|
|
func getProgType(v string) (ProgramType, AttachType) {
|
|
types := map[string]ProgramType{
|
|
// From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c#n3568
|
|
"socket": SocketFilter,
|
|
"seccomp": SocketFilter,
|
|
"kprobe/": Kprobe,
|
|
"kretprobe/": Kprobe,
|
|
"tracepoint/": TracePoint,
|
|
"xdp": XDP,
|
|
"perf_event": PerfEvent,
|
|
"sockops": SockOps,
|
|
"sk_skb": SkSKB,
|
|
"sk_msg": SkMsg,
|
|
"lirc_mode2": LircMode2,
|
|
"flow_dissector": FlowDissector,
|
|
|
|
"cgroup_skb/": CGroupSKB,
|
|
"cgroup/dev": CGroupDevice,
|
|
"cgroup/skb": CGroupSKB,
|
|
"cgroup/sock": CGroupSock,
|
|
"cgroup/post_bind": CGroupSock,
|
|
"cgroup/bind": CGroupSockAddr,
|
|
"cgroup/connect": CGroupSockAddr,
|
|
"cgroup/sendmsg": CGroupSockAddr,
|
|
"cgroup/recvmsg": CGroupSockAddr,
|
|
"cgroup/sysctl": CGroupSysctl,
|
|
"cgroup/getsockopt": CGroupSockopt,
|
|
"cgroup/setsockopt": CGroupSockopt,
|
|
"classifier": SchedCLS,
|
|
"action": SchedACT,
|
|
}
|
|
attachTypes := map[string]AttachType{
|
|
"cgroup_skb/ingress": AttachCGroupInetIngress,
|
|
"cgroup_skb/egress": AttachCGroupInetEgress,
|
|
"cgroup/sock": AttachCGroupInetSockCreate,
|
|
"cgroup/post_bind4": AttachCGroupInet4PostBind,
|
|
"cgroup/post_bind6": AttachCGroupInet6PostBind,
|
|
"cgroup/dev": AttachCGroupDevice,
|
|
"sockops": AttachCGroupSockOps,
|
|
"sk_skb/stream_parser": AttachSkSKBStreamParser,
|
|
"sk_skb/stream_verdict": AttachSkSKBStreamVerdict,
|
|
"sk_msg": AttachSkSKBStreamVerdict,
|
|
"lirc_mode2": AttachLircMode2,
|
|
"flow_dissector": AttachFlowDissector,
|
|
"cgroup/bind4": AttachCGroupInet4Bind,
|
|
"cgroup/bind6": AttachCGroupInet6Bind,
|
|
"cgroup/connect4": AttachCGroupInet4Connect,
|
|
"cgroup/connect6": AttachCGroupInet6Connect,
|
|
"cgroup/sendmsg4": AttachCGroupUDP4Sendmsg,
|
|
"cgroup/sendmsg6": AttachCGroupUDP6Sendmsg,
|
|
"cgroup/recvmsg4": AttachCGroupUDP4Recvmsg,
|
|
"cgroup/recvmsg6": AttachCGroupUDP6Recvmsg,
|
|
"cgroup/sysctl": AttachCGroupSysctl,
|
|
"cgroup/getsockopt": AttachCGroupGetsockopt,
|
|
"cgroup/setsockopt": AttachCGroupSetsockopt,
|
|
}
|
|
attachType := AttachNone
|
|
for k, t := range attachTypes {
|
|
if strings.HasPrefix(v, k) {
|
|
attachType = t
|
|
}
|
|
}
|
|
|
|
for k, t := range types {
|
|
if strings.HasPrefix(v, k) {
|
|
return t, attachType
|
|
}
|
|
}
|
|
return UnspecifiedProgram, AttachNone
|
|
}
|
|
|
|
func (ec *elfCode) loadRelocations(sec *elf.Section) (map[uint64]string, error) {
|
|
rels := make(map[uint64]string)
|
|
if sec == nil {
|
|
return rels, nil
|
|
}
|
|
|
|
if sec.Entsize < 16 {
|
|
return nil, errors.New("rels are less than 16 bytes")
|
|
}
|
|
|
|
r := 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, errors.Errorf("can't parse relocation at offset %v", off)
|
|
}
|
|
|
|
symNo := int(elf.R_SYM64(rel.Info) - 1)
|
|
if symNo >= len(ec.symbols) {
|
|
return nil, errors.Errorf("relocation at offset %d: symbol %v doesnt exist", off, symNo)
|
|
}
|
|
|
|
rels[rel.Off] = ec.symbols[symNo].Name
|
|
}
|
|
return rels, nil
|
|
}
|
|
|
|
func symbolsPerSection(symbols []elf.Symbol) map[elf.SectionIndex]map[uint64]string {
|
|
result := make(map[elf.SectionIndex]map[uint64]string)
|
|
for i, sym := range symbols {
|
|
switch elf.ST_TYPE(sym.Info) {
|
|
case elf.STT_NOTYPE:
|
|
// Older versions of LLVM doesn't tag
|
|
// symbols correctly.
|
|
break
|
|
case elf.STT_OBJECT:
|
|
break
|
|
case elf.STT_FUNC:
|
|
break
|
|
default:
|
|
continue
|
|
}
|
|
|
|
if sym.Name == "" {
|
|
continue
|
|
}
|
|
|
|
idx := sym.Section
|
|
if _, ok := result[idx]; !ok {
|
|
result[idx] = make(map[uint64]string)
|
|
}
|
|
result[idx][sym.Value] = symbols[i].Name
|
|
}
|
|
return result
|
|
}
|