1package ebpf
2
3import (
4	"errors"
5	"fmt"
6	"math"
7
8	"github.com/cilium/ebpf/asm"
9	"github.com/cilium/ebpf/internal"
10	"github.com/cilium/ebpf/internal/btf"
11)
12
13// CollectionOptions control loading a collection into the kernel.
14type CollectionOptions struct {
15	Programs ProgramOptions
16}
17
18// CollectionSpec describes a collection.
19type CollectionSpec struct {
20	Maps     map[string]*MapSpec
21	Programs map[string]*ProgramSpec
22}
23
24// Copy returns a recursive copy of the spec.
25func (cs *CollectionSpec) Copy() *CollectionSpec {
26	if cs == nil {
27		return nil
28	}
29
30	cpy := CollectionSpec{
31		Maps:     make(map[string]*MapSpec, len(cs.Maps)),
32		Programs: make(map[string]*ProgramSpec, len(cs.Programs)),
33	}
34
35	for name, spec := range cs.Maps {
36		cpy.Maps[name] = spec.Copy()
37	}
38
39	for name, spec := range cs.Programs {
40		cpy.Programs[name] = spec.Copy()
41	}
42
43	return &cpy
44}
45
46// RewriteMaps replaces all references to specific maps.
47//
48// Use this function to use pre-existing maps instead of creating new ones
49// when calling NewCollection. Any named maps are removed from CollectionSpec.Maps.
50//
51// Returns an error if a named map isn't used in at least one program.
52func (cs *CollectionSpec) RewriteMaps(maps map[string]*Map) error {
53	for symbol, m := range maps {
54		// have we seen a program that uses this symbol / map
55		seen := false
56		fd := m.FD()
57		for progName, progSpec := range cs.Programs {
58			err := progSpec.Instructions.RewriteMapPtr(symbol, fd)
59
60			switch {
61			case err == nil:
62				seen = true
63
64			case asm.IsUnreferencedSymbol(err):
65				// Not all programs need to use the map
66
67			default:
68				return fmt.Errorf("program %s: %w", progName, err)
69			}
70		}
71
72		if !seen {
73			return fmt.Errorf("map %s not referenced by any programs", symbol)
74		}
75
76		// Prevent NewCollection from creating rewritten maps
77		delete(cs.Maps, symbol)
78	}
79
80	return nil
81}
82
83// RewriteConstants replaces the value of multiple constants.
84//
85// The constant must be defined like so in the C program:
86//
87//    static volatile const type foobar;
88//    static volatile const type foobar = default;
89//
90// Replacement values must be of the same length as the C sizeof(type).
91// If necessary, they are marshalled according to the same rules as
92// map values.
93//
94// From Linux 5.5 the verifier will use constants to eliminate dead code.
95//
96// Returns an error if a constant doesn't exist.
97func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error {
98	rodata := cs.Maps[".rodata"]
99	if rodata == nil {
100		return errors.New("missing .rodata section")
101	}
102
103	if rodata.BTF == nil {
104		return errors.New(".rodata section has no BTF")
105	}
106
107	if n := len(rodata.Contents); n != 1 {
108		return fmt.Errorf("expected one key in .rodata, found %d", n)
109	}
110
111	kv := rodata.Contents[0]
112	value, ok := kv.Value.([]byte)
113	if !ok {
114		return fmt.Errorf("first value in .rodata is %T not []byte", kv.Value)
115	}
116
117	buf := make([]byte, len(value))
118	copy(buf, value)
119
120	err := patchValue(buf, btf.MapValue(rodata.BTF), consts)
121	if err != nil {
122		return err
123	}
124
125	rodata.Contents[0] = MapKV{kv.Key, buf}
126	return nil
127}
128
129// Collection is a collection of Programs and Maps associated
130// with their symbols
131type Collection struct {
132	Programs map[string]*Program
133	Maps     map[string]*Map
134}
135
136// NewCollection creates a Collection from a specification.
137//
138// Only maps referenced by at least one of the programs are initialized.
139func NewCollection(spec *CollectionSpec) (*Collection, error) {
140	return NewCollectionWithOptions(spec, CollectionOptions{})
141}
142
143// NewCollectionWithOptions creates a Collection from a specification.
144//
145// Only maps referenced by at least one of the programs are initialized.
146func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (coll *Collection, err error) {
147	var (
148		maps  = make(map[string]*Map)
149		progs = make(map[string]*Program)
150		btfs  = make(map[*btf.Spec]*btf.Handle)
151	)
152
153	defer func() {
154		for _, btf := range btfs {
155			btf.Close()
156		}
157
158		if err == nil {
159			return
160		}
161
162		for _, m := range maps {
163			m.Close()
164		}
165
166		for _, p := range progs {
167			p.Close()
168		}
169	}()
170
171	loadBTF := func(spec *btf.Spec) (*btf.Handle, error) {
172		if btfs[spec] != nil {
173			return btfs[spec], nil
174		}
175
176		handle, err := btf.NewHandle(spec)
177		if err != nil {
178			return nil, err
179		}
180
181		btfs[spec] = handle
182		return handle, nil
183	}
184
185	for mapName, mapSpec := range spec.Maps {
186		var handle *btf.Handle
187		if mapSpec.BTF != nil {
188			handle, err = loadBTF(btf.MapSpec(mapSpec.BTF))
189			if err != nil && !errors.Is(err, btf.ErrNotSupported) {
190				return nil, err
191			}
192		}
193
194		m, err := newMapWithBTF(mapSpec, handle)
195		if err != nil {
196			return nil, fmt.Errorf("map %s: %w", mapName, err)
197		}
198		maps[mapName] = m
199	}
200
201	for progName, origProgSpec := range spec.Programs {
202		progSpec := origProgSpec.Copy()
203
204		// Rewrite any reference to a valid map.
205		for i := range progSpec.Instructions {
206			ins := &progSpec.Instructions[i]
207
208			if ins.OpCode != asm.LoadImmOp(asm.DWord) || ins.Reference == "" {
209				continue
210			}
211
212			if uint32(ins.Constant) != math.MaxUint32 {
213				// Don't overwrite maps already rewritten, users can
214				// rewrite programs in the spec themselves
215				continue
216			}
217
218			m := maps[ins.Reference]
219			if m == nil {
220				return nil, fmt.Errorf("program %s: missing map %s", progName, ins.Reference)
221			}
222
223			fd := m.FD()
224			if fd < 0 {
225				return nil, fmt.Errorf("map %s: %w", ins.Reference, internal.ErrClosedFd)
226			}
227			if err := ins.RewriteMapPtr(m.FD()); err != nil {
228				return nil, fmt.Errorf("progam %s: map %s: %w", progName, ins.Reference, err)
229			}
230		}
231
232		var handle *btf.Handle
233		if progSpec.BTF != nil {
234			handle, err = loadBTF(btf.ProgramSpec(progSpec.BTF))
235			if err != nil && !errors.Is(err, btf.ErrNotSupported) {
236				return nil, err
237			}
238		}
239
240		prog, err := newProgramWithBTF(progSpec, handle, opts.Programs)
241		if err != nil {
242			return nil, fmt.Errorf("program %s: %w", progName, err)
243		}
244		progs[progName] = prog
245	}
246
247	return &Collection{
248		progs,
249		maps,
250	}, nil
251}
252
253// LoadCollection parses an object file and converts it to a collection.
254func LoadCollection(file string) (*Collection, error) {
255	spec, err := LoadCollectionSpec(file)
256	if err != nil {
257		return nil, err
258	}
259	return NewCollection(spec)
260}
261
262// Close frees all maps and programs associated with the collection.
263//
264// The collection mustn't be used afterwards.
265func (coll *Collection) Close() {
266	for _, prog := range coll.Programs {
267		prog.Close()
268	}
269	for _, m := range coll.Maps {
270		m.Close()
271	}
272}
273
274// DetachMap removes the named map from the Collection.
275//
276// This means that a later call to Close() will not affect this map.
277//
278// Returns nil if no map of that name exists.
279func (coll *Collection) DetachMap(name string) *Map {
280	m := coll.Maps[name]
281	delete(coll.Maps, name)
282	return m
283}
284
285// DetachProgram removes the named program from the Collection.
286//
287// This means that a later call to Close() will not affect this program.
288//
289// Returns nil if no program of that name exists.
290func (coll *Collection) DetachProgram(name string) *Program {
291	p := coll.Programs[name]
292	delete(coll.Programs, name)
293	return p
294}
295