forked from cilium/ebpf
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlinker.go
191 lines (157 loc) · 5 KB
/
linker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
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]
if ref == nil {
return fmt.Errorf("symbol reference %s not present in progs", refname)
}
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, errUnsatisfiedReference)
default:
// no fixup needed
continue
}
return fmt.Errorf("%s at %d: reference to missing symbol %q", ins.OpCode, i, ins.Reference)
}
// 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
}