194 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			194 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
| package ebpf
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"encoding/binary"
 | |
| 	"fmt"
 | |
| 
 | |
| 	"github.com/cilium/ebpf/asm"
 | |
| 	"github.com/cilium/ebpf/internal/btf"
 | |
| )
 | |
| 
 | |
| // The linker is responsible for resolving bpf-to-bpf calls between programs
 | |
| // within an ELF. Each BPF program must be a self-contained binary blob,
 | |
| // so when an instruction in one ELF program section wants to jump to
 | |
| // a function in another, the linker needs to pull in the bytecode
 | |
| // (and BTF info) of the target function and concatenate the instruction
 | |
| // streams.
 | |
| //
 | |
| // Later on in the pipeline, all call sites are fixed up with relative jumps
 | |
| // within this newly-created instruction stream to then finally hand off to
 | |
| // the kernel with BPF_PROG_LOAD.
 | |
| //
 | |
| // Each function is denoted by an ELF symbol and the compiler takes care of
 | |
| // register setup before each jump instruction.
 | |
| 
 | |
| // populateReferences populates all of progs' Instructions and references
 | |
| // with their full dependency chains including transient dependencies.
 | |
| func populateReferences(progs map[string]*ProgramSpec) error {
 | |
| 	type props struct {
 | |
| 		insns asm.Instructions
 | |
| 		refs  map[string]*ProgramSpec
 | |
| 	}
 | |
| 
 | |
| 	out := make(map[string]props)
 | |
| 
 | |
| 	// Resolve and store direct references between all progs.
 | |
| 	if err := findReferences(progs); err != nil {
 | |
| 		return fmt.Errorf("finding references: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// Flatten all progs' instruction streams.
 | |
| 	for name, prog := range progs {
 | |
| 		insns, refs := prog.flatten(nil)
 | |
| 
 | |
| 		prop := props{
 | |
| 			insns: insns,
 | |
| 			refs:  refs,
 | |
| 		}
 | |
| 
 | |
| 		out[name] = prop
 | |
| 	}
 | |
| 
 | |
| 	// Replace all progs' instructions and references
 | |
| 	for name, props := range out {
 | |
| 		progs[name].Instructions = props.insns
 | |
| 		progs[name].references = props.refs
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // findReferences finds bpf-to-bpf calls between progs and populates each
 | |
| // prog's references field with its direct neighbours.
 | |
| func findReferences(progs map[string]*ProgramSpec) error {
 | |
| 	// Check all ProgramSpecs in the collection against each other.
 | |
| 	for _, prog := range progs {
 | |
| 		prog.references = make(map[string]*ProgramSpec)
 | |
| 
 | |
| 		// Look up call targets in progs and store pointers to their corresponding
 | |
| 		// ProgramSpecs as direct references.
 | |
| 		for refname := range prog.Instructions.FunctionReferences() {
 | |
| 			ref := progs[refname]
 | |
| 			// Call targets are allowed to be missing from an ELF. This occurs when
 | |
| 			// a program calls into a forward function declaration that is left
 | |
| 			// unimplemented. This is caught at load time during fixups.
 | |
| 			if ref != nil {
 | |
| 				prog.references[refname] = ref
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // marshalFuncInfos returns the BTF func infos of all progs in order.
 | |
| func marshalFuncInfos(layout []reference) ([]byte, error) {
 | |
| 	if len(layout) == 0 {
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 
 | |
| 	buf := bytes.NewBuffer(make([]byte, 0, binary.Size(&btf.FuncInfo{})*len(layout)))
 | |
| 	for _, sym := range layout {
 | |
| 		if err := sym.spec.BTF.FuncInfo.Marshal(buf, sym.offset); err != nil {
 | |
| 			return nil, fmt.Errorf("marshaling prog %s func info: %w", sym.spec.Name, err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return buf.Bytes(), nil
 | |
| }
 | |
| 
 | |
| // marshalLineInfos returns the BTF line infos of all progs in order.
 | |
| func marshalLineInfos(layout []reference) ([]byte, error) {
 | |
| 	if len(layout) == 0 {
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 
 | |
| 	buf := bytes.NewBuffer(make([]byte, 0, binary.Size(&btf.LineInfo{})*len(layout)))
 | |
| 	for _, sym := range layout {
 | |
| 		if err := sym.spec.BTF.LineInfos.Marshal(buf, sym.offset); err != nil {
 | |
| 			return nil, fmt.Errorf("marshaling prog %s line infos: %w", sym.spec.Name, err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return buf.Bytes(), nil
 | |
| }
 | |
| 
 | |
| func fixupJumpsAndCalls(insns asm.Instructions) error {
 | |
| 	symbolOffsets := make(map[string]asm.RawInstructionOffset)
 | |
| 	iter := insns.Iterate()
 | |
| 	for iter.Next() {
 | |
| 		ins := iter.Ins
 | |
| 
 | |
| 		if ins.Symbol == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if _, ok := symbolOffsets[ins.Symbol]; ok {
 | |
| 			return fmt.Errorf("duplicate symbol %s", ins.Symbol)
 | |
| 		}
 | |
| 
 | |
| 		symbolOffsets[ins.Symbol] = iter.Offset
 | |
| 	}
 | |
| 
 | |
| 	iter = insns.Iterate()
 | |
| 	for iter.Next() {
 | |
| 		i := iter.Index
 | |
| 		offset := iter.Offset
 | |
| 		ins := iter.Ins
 | |
| 
 | |
| 		if ins.Reference == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		symOffset, ok := symbolOffsets[ins.Reference]
 | |
| 		switch {
 | |
| 		case ins.IsFunctionReference() && ins.Constant == -1:
 | |
| 			if !ok {
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			ins.Constant = int64(symOffset - offset - 1)
 | |
| 			continue
 | |
| 
 | |
| 		case ins.OpCode.Class().IsJump() && ins.Offset == -1:
 | |
| 			if !ok {
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			ins.Offset = int16(symOffset - offset - 1)
 | |
| 			continue
 | |
| 
 | |
| 		case ins.IsLoadFromMap() && ins.MapPtr() == -1:
 | |
| 			return fmt.Errorf("map %s: %w", ins.Reference, errUnsatisfiedMap)
 | |
| 		default:
 | |
| 			// no fixup needed
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		return fmt.Errorf("%s at insn %d: symbol %q: %w", ins.OpCode, i, ins.Reference, errUnsatisfiedProgram)
 | |
| 	}
 | |
| 
 | |
| 	// fixupBPFCalls replaces bpf_probe_read_{kernel,user}[_str] with bpf_probe_read[_str] on older kernels
 | |
| 	// https://github.com/libbpf/libbpf/blob/master/src/libbpf.c#L6009
 | |
| 	iter = insns.Iterate()
 | |
| 	for iter.Next() {
 | |
| 		ins := iter.Ins
 | |
| 		if !ins.IsBuiltinCall() {
 | |
| 			continue
 | |
| 		}
 | |
| 		switch asm.BuiltinFunc(ins.Constant) {
 | |
| 		case asm.FnProbeReadKernel, asm.FnProbeReadUser:
 | |
| 			if err := haveProbeReadKernel(); err != nil {
 | |
| 				ins.Constant = int64(asm.FnProbeRead)
 | |
| 			}
 | |
| 		case asm.FnProbeReadKernelStr, asm.FnProbeReadUserStr:
 | |
| 			if err := haveProbeReadKernel(); err != nil {
 | |
| 				ins.Constant = int64(asm.FnProbeReadStr)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 |