File: linker.go

package info (click to toggle)
golang-github-cilium-ebpf 0.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, bullseye-backports
  • size: 1,716 kB
  • sloc: ansic: 148; sh: 66; makefile: 24
file content (133 lines) | stat: -rw-r--r-- 3,011 bytes parent folder | download
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
package ebpf

import (
	"fmt"

	"github.com/cilium/ebpf/asm"
	"github.com/cilium/ebpf/internal/btf"
)

// link resolves bpf-to-bpf calls.
//
// Each library may contain multiple functions / labels, and is only linked
// if prog references one of these functions.
//
// Libraries also linked.
func link(prog *ProgramSpec, libs []*ProgramSpec) error {
	var (
		linked  = make(map[*ProgramSpec]bool)
		pending = []asm.Instructions{prog.Instructions}
		insns   asm.Instructions
	)
	for len(pending) > 0 {
		insns, pending = pending[0], pending[1:]
		for _, lib := range libs {
			if linked[lib] {
				continue
			}

			needed, err := needSection(insns, lib.Instructions)
			if err != nil {
				return fmt.Errorf("linking %s: %w", lib.Name, err)
			}

			if !needed {
				continue
			}

			linked[lib] = true
			prog.Instructions = append(prog.Instructions, lib.Instructions...)
			pending = append(pending, lib.Instructions)

			if prog.BTF != nil && lib.BTF != nil {
				if err := btf.ProgramAppend(prog.BTF, lib.BTF); err != nil {
					return fmt.Errorf("linking BTF of %s: %w", lib.Name, err)
				}
			}
		}
	}

	return nil
}

func needSection(insns, section asm.Instructions) (bool, error) {
	// A map of symbols to the libraries which contain them.
	symbols, err := section.SymbolOffsets()
	if err != nil {
		return false, err
	}

	for _, ins := range insns {
		if ins.Reference == "" {
			continue
		}

		if ins.OpCode.JumpOp() != asm.Call || ins.Src != asm.PseudoCall {
			continue
		}

		if ins.Constant != -1 {
			// This is already a valid call, no need to link again.
			continue
		}

		if _, ok := symbols[ins.Reference]; !ok {
			// Symbol isn't available in this section
			continue
		}

		// At this point we know that at least one function in the
		// library is called from insns, so we have to link it.
		return true, nil
	}

	// None of the functions in the section are called.
	return false, 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

		switch {
		case ins.IsFunctionCall() && ins.Constant == -1:
			// Rewrite bpf to bpf call
			callOffset, ok := symbolOffsets[ins.Reference]
			if !ok {
				return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
			}

			ins.Constant = int64(callOffset - offset - 1)

		case ins.OpCode.Class() == asm.JumpClass && ins.Offset == -1:
			// Rewrite jump to label
			jumpOffset, ok := symbolOffsets[ins.Reference]
			if !ok {
				return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
			}

			ins.Offset = int16(jumpOffset - offset - 1)
		}
	}

	return nil
}