github.com/cilium/ebpf@v0.10.0/linker.go (about) 1 package ebpf 2 3 import ( 4 "encoding/binary" 5 "errors" 6 "fmt" 7 8 "github.com/cilium/ebpf/asm" 9 "github.com/cilium/ebpf/btf" 10 "github.com/cilium/ebpf/internal" 11 ) 12 13 // splitSymbols splits insns into subsections delimited by Symbol Instructions. 14 // insns cannot be empty and must start with a Symbol Instruction. 15 // 16 // The resulting map is indexed by Symbol name. 17 func splitSymbols(insns asm.Instructions) (map[string]asm.Instructions, error) { 18 if len(insns) == 0 { 19 return nil, errors.New("insns is empty") 20 } 21 22 if insns[0].Symbol() == "" { 23 return nil, errors.New("insns must start with a Symbol") 24 } 25 26 var name string 27 progs := make(map[string]asm.Instructions) 28 for _, ins := range insns { 29 if sym := ins.Symbol(); sym != "" { 30 if progs[sym] != nil { 31 return nil, fmt.Errorf("insns contains duplicate Symbol %s", sym) 32 } 33 name = sym 34 } 35 36 progs[name] = append(progs[name], ins) 37 } 38 39 return progs, nil 40 } 41 42 // The linker is responsible for resolving bpf-to-bpf calls between programs 43 // within an ELF. Each BPF program must be a self-contained binary blob, 44 // so when an instruction in one ELF program section wants to jump to 45 // a function in another, the linker needs to pull in the bytecode 46 // (and BTF info) of the target function and concatenate the instruction 47 // streams. 48 // 49 // Later on in the pipeline, all call sites are fixed up with relative jumps 50 // within this newly-created instruction stream to then finally hand off to 51 // the kernel with BPF_PROG_LOAD. 52 // 53 // Each function is denoted by an ELF symbol and the compiler takes care of 54 // register setup before each jump instruction. 55 56 // hasFunctionReferences returns true if insns contains one or more bpf2bpf 57 // function references. 58 func hasFunctionReferences(insns asm.Instructions) bool { 59 for _, i := range insns { 60 if i.IsFunctionReference() { 61 return true 62 } 63 } 64 return false 65 } 66 67 // applyRelocations collects and applies any CO-RE relocations in insns. 68 // 69 // Passing a nil target will relocate against the running kernel. insns are 70 // modified in place. 71 func applyRelocations(insns asm.Instructions, target *btf.Spec, bo binary.ByteOrder) error { 72 var relos []*btf.CORERelocation 73 var reloInsns []*asm.Instruction 74 iter := insns.Iterate() 75 for iter.Next() { 76 if relo := btf.CORERelocationMetadata(iter.Ins); relo != nil { 77 relos = append(relos, relo) 78 reloInsns = append(reloInsns, iter.Ins) 79 } 80 } 81 82 if len(relos) == 0 { 83 return nil 84 } 85 86 if bo == nil { 87 bo = internal.NativeEndian 88 } 89 90 if target == nil { 91 var err error 92 target, err = btf.LoadKernelSpec() 93 if err != nil { 94 return fmt.Errorf("load kernel spec: %w", err) 95 } 96 } 97 98 fixups, err := btf.CORERelocate(relos, target, bo) 99 if err != nil { 100 return err 101 } 102 103 for i, fixup := range fixups { 104 if err := fixup.Apply(reloInsns[i]); err != nil { 105 return fmt.Errorf("apply fixup %s: %w", &fixup, err) 106 } 107 } 108 109 return nil 110 } 111 112 // flattenPrograms resolves bpf-to-bpf calls for a set of programs. 113 // 114 // Links all programs in names by modifying their ProgramSpec in progs. 115 func flattenPrograms(progs map[string]*ProgramSpec, names []string) { 116 // Pre-calculate all function references. 117 refs := make(map[*ProgramSpec][]string) 118 for _, prog := range progs { 119 refs[prog] = prog.Instructions.FunctionReferences() 120 } 121 122 // Create a flattened instruction stream, but don't modify progs yet to 123 // avoid linking multiple times. 124 flattened := make([]asm.Instructions, 0, len(names)) 125 for _, name := range names { 126 flattened = append(flattened, flattenInstructions(name, progs, refs)) 127 } 128 129 // Finally, assign the flattened instructions. 130 for i, name := range names { 131 progs[name].Instructions = flattened[i] 132 } 133 } 134 135 // flattenInstructions resolves bpf-to-bpf calls for a single program. 136 // 137 // Flattens the instructions of prog by concatenating the instructions of all 138 // direct and indirect dependencies. 139 // 140 // progs contains all referenceable programs, while refs contain the direct 141 // dependencies of each program. 142 func flattenInstructions(name string, progs map[string]*ProgramSpec, refs map[*ProgramSpec][]string) asm.Instructions { 143 prog := progs[name] 144 145 insns := make(asm.Instructions, len(prog.Instructions)) 146 copy(insns, prog.Instructions) 147 148 // Add all direct references of prog to the list of to be linked programs. 149 pending := make([]string, len(refs[prog])) 150 copy(pending, refs[prog]) 151 152 // All references for which we've appended instructions. 153 linked := make(map[string]bool) 154 155 // Iterate all pending references. We can't use a range since pending is 156 // modified in the body below. 157 for len(pending) > 0 { 158 var ref string 159 ref, pending = pending[0], pending[1:] 160 161 if linked[ref] { 162 // We've already linked this ref, don't append instructions again. 163 continue 164 } 165 166 progRef := progs[ref] 167 if progRef == nil { 168 // We don't have instructions that go with this reference. This 169 // happens when calling extern functions. 170 continue 171 } 172 173 insns = append(insns, progRef.Instructions...) 174 linked[ref] = true 175 176 // Make sure we link indirect references. 177 pending = append(pending, refs[progRef]...) 178 } 179 180 return insns 181 } 182 183 // fixupAndValidate is called by the ELF reader right before marshaling the 184 // instruction stream. It performs last-minute adjustments to the program and 185 // runs some sanity checks before sending it off to the kernel. 186 func fixupAndValidate(insns asm.Instructions) error { 187 iter := insns.Iterate() 188 for iter.Next() { 189 ins := iter.Ins 190 191 // Map load was tagged with a Reference, but does not contain a Map pointer. 192 if ins.IsLoadFromMap() && ins.Reference() != "" && ins.Map() == nil { 193 return fmt.Errorf("instruction %d: map %s: %w", iter.Index, ins.Reference(), asm.ErrUnsatisfiedMapReference) 194 } 195 196 fixupProbeReadKernel(ins) 197 } 198 199 return nil 200 } 201 202 // fixupProbeReadKernel replaces calls to bpf_probe_read_{kernel,user}(_str) 203 // with bpf_probe_read(_str) on kernels that don't support it yet. 204 func fixupProbeReadKernel(ins *asm.Instruction) { 205 if !ins.IsBuiltinCall() { 206 return 207 } 208 209 // Kernel supports bpf_probe_read_kernel, nothing to do. 210 if haveProbeReadKernel() == nil { 211 return 212 } 213 214 switch asm.BuiltinFunc(ins.Constant) { 215 case asm.FnProbeReadKernel, asm.FnProbeReadUser: 216 ins.Constant = int64(asm.FnProbeRead) 217 case asm.FnProbeReadKernelStr, asm.FnProbeReadUserStr: 218 ins.Constant = int64(asm.FnProbeReadStr) 219 } 220 }