Skip to content

Commit d46bb3c

Browse files
lmbti-mo
authored andcommitted
btf: fix CO-RE relocations for local type id
At some point the kernel gained the ability to dynamically allocated typed memory via a bpf_obj_new helper (see [1]). This helper receives the ID of the type to allocate as an argument. Trying to use this helper with the library will currently fail, since we took a short-cut when implementing on the fly BTF generation. At that point in time there was no use of the local_type_id relocation in the kernel, so we just stuck with the value present in ext_infos. This is of course not correct when building a BTF blob from scratch: type IDs are reallocated, and only used types are passed to the kernel in the first place. Fix this by allocating an ID for any type that is the target of a local_type_id relocation. 1: https://lwn.net/Articles/915404/ Signed-off-by: Lorenz Bauer <[email protected]>
1 parent 1a65b78 commit d46bb3c

12 files changed

+112
-58
lines changed

btf/core.go

+14-8
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,17 @@ func (k coreKind) String() string {
159159
// CORERelocate calculates changes needed to adjust eBPF instructions for differences
160160
// in types.
161161
//
162+
// resolveLocalTypeID is called for each local type which requires a stable TypeID.
163+
// Calling the function with the same type multiple times must produce the same
164+
// result. It is the callers responsibility to ensure that the relocated instructions
165+
// are loaded with matching BTF.
166+
//
162167
// Returns a list of fixups which can be applied to instructions to make them
163168
// match the target type(s).
164169
//
165170
// Fixups are returned in the order of relos, e.g. fixup[i] is the solution
166171
// for relos[i].
167-
func CORERelocate(relos []*CORERelocation, target *Spec, bo binary.ByteOrder) ([]COREFixup, error) {
172+
func CORERelocate(relos []*CORERelocation, target *Spec, bo binary.ByteOrder, resolveLocalTypeID func(Type) (TypeID, error)) ([]COREFixup, error) {
168173
if target == nil {
169174
var err error
170175
target, _, err = kernelSpec()
@@ -194,14 +199,15 @@ func CORERelocate(relos []*CORERelocation, target *Spec, bo binary.ByteOrder) ([
194199
return nil, fmt.Errorf("%s: unexpected accessor %v", relo.kind, relo.accessor)
195200
}
196201

202+
id, err := resolveLocalTypeID(relo.typ)
203+
if err != nil {
204+
return nil, fmt.Errorf("%s: get type id: %w", relo.kind, err)
205+
}
206+
197207
result[i] = COREFixup{
198-
kind: relo.kind,
199-
local: uint64(relo.id),
200-
// NB: Using relo.id as the target here is incorrect, since
201-
// it doesn't match the BTF we generate on the fly. This isn't
202-
// too bad for now since there are no uses of the local type ID
203-
// in the kernel, yet.
204-
target: uint64(relo.id),
208+
kind: relo.kind,
209+
local: uint64(relo.id),
210+
target: uint64(id),
205211
}
206212
continue
207213
}

btf/core_reloc_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ func TestCORERelocationLoad(t *testing.T) {
3939
prog, err := ebpf.NewProgramWithOptions(progSpec, ebpf.ProgramOptions{
4040
KernelTypes: spec.Types,
4141
})
42+
testutils.SkipIfNotSupported(t, err)
4243

4344
if strings.HasPrefix(progSpec.Name, "err_") {
4445
if err == nil {

btf/core_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ func TestCORERelocation(t *testing.T) {
590590
relos = append(relos, reloInfo.relo)
591591
}
592592

593-
fixups, err := CORERelocate(relos, spec, spec.byteOrder)
593+
fixups, err := CORERelocate(relos, spec, spec.byteOrder, spec.TypeID)
594594
if want := errs[name]; want != nil {
595595
if !errors.Is(err, want) {
596596
t.Fatal("Expected", want, "got", err)
@@ -737,7 +737,7 @@ func BenchmarkCORESkBuff(b *testing.B) {
737737
b.ReportAllocs()
738738

739739
for i := 0; i < b.N; i++ {
740-
_, err = CORERelocate([]*CORERelocation{relo}, spec, spec.byteOrder)
740+
_, err = CORERelocate([]*CORERelocation{relo}, spec, spec.byteOrder, spec.TypeID)
741741
if err != nil {
742742
b.Fatal(err)
743743
}

btf/ext_info.go

+7-17
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,7 @@ func AssignMetadataToInstructions(
142142

143143
// MarshalExtInfos encodes function and line info embedded in insns into kernel
144144
// wire format.
145-
//
146-
// Returns ErrNotSupported if the kernel doesn't support BTF-associated programs.
147-
func MarshalExtInfos(insns asm.Instructions) (_ *Handle, funcInfos, lineInfos []byte, _ error) {
148-
// Bail out early if the kernel doesn't support Func(Proto). If this is the
149-
// case, func_info will also be unsupported.
150-
if err := haveProgBTF(); err != nil {
151-
return nil, nil, nil, err
152-
}
153-
145+
func MarshalExtInfos(insns asm.Instructions, b *Builder) (funcInfos, lineInfos []byte, _ error) {
154146
iter := insns.Iterate()
155147
for iter.Next() {
156148
_, ok := iter.Ins.Source().(*Line)
@@ -160,19 +152,18 @@ func MarshalExtInfos(insns asm.Instructions) (_ *Handle, funcInfos, lineInfos []
160152
}
161153
}
162154

163-
return nil, nil, nil, nil
155+
return nil, nil, nil
164156

165157
marshal:
166-
var b Builder
167158
var fiBuf, liBuf bytes.Buffer
168159
for {
169160
if fn := FuncMetadata(iter.Ins); fn != nil {
170161
fi := &funcInfo{
171162
fn: fn,
172163
offset: iter.Offset,
173164
}
174-
if err := fi.marshal(&fiBuf, &b); err != nil {
175-
return nil, nil, nil, fmt.Errorf("write func info: %w", err)
165+
if err := fi.marshal(&fiBuf, b); err != nil {
166+
return nil, nil, fmt.Errorf("write func info: %w", err)
176167
}
177168
}
178169

@@ -181,8 +172,8 @@ marshal:
181172
line: line,
182173
offset: iter.Offset,
183174
}
184-
if err := li.marshal(&liBuf, &b); err != nil {
185-
return nil, nil, nil, fmt.Errorf("write line info: %w", err)
175+
if err := li.marshal(&liBuf, b); err != nil {
176+
return nil, nil, fmt.Errorf("write line info: %w", err)
186177
}
187178
}
188179

@@ -191,8 +182,7 @@ marshal:
191182
}
192183
}
193184

194-
handle, err := NewHandle(&b)
195-
return handle, fiBuf.Bytes(), liBuf.Bytes(), err
185+
return fiBuf.Bytes(), liBuf.Bytes(), nil
196186
}
197187

198188
// btfExtHeader is found at the start of the .BTF.ext section.

btf/marshal.go

+5
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ func NewBuilder(types []Type) (*Builder, error) {
9393
return b, nil
9494
}
9595

96+
// Empty returns true if [Add] has not been invoked on the builder.
97+
func (b *Builder) Empty() bool {
98+
return len(b.types) == 0
99+
}
100+
96101
// Add a Type and allocate a stable ID for it.
97102
//
98103
// Adding the identical Type multiple times is valid and will return the same ID.

btf/testdata/relocs-eb.elf

24 Bytes
Binary file not shown.

btf/testdata/relocs-el.elf

24 Bytes
Binary file not shown.

btf/testdata/relocs.c

+18-21
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,16 @@ union u {
2929

3030
typedef union u u_t;
3131

32-
#define local_id_zero(expr) \
33-
({ \
34-
if (bpf_core_type_id_local(expr) != 0) { \
35-
return __LINE__; \
36-
} \
37-
})
38-
3932
#define local_id_not_zero(expr) \
4033
({ \
4134
if (bpf_core_type_id_local(expr) == 0) { \
4235
return __LINE__; \
4336
} \
4437
})
4538

46-
#define target_and_local_id_match(expr) \
39+
#define target_and_local_id_dont_match(expr) \
4740
({ \
48-
if (bpf_core_type_id_kernel(expr) != bpf_core_type_id_local(expr)) { \
41+
if (bpf_core_type_id_kernel(expr) == bpf_core_type_id_local(expr)) { \
4942
return __LINE__; \
5043
} \
5144
})
@@ -69,19 +62,23 @@ __section("socket_filter/type_ids") int type_ids() {
6962
local_id_not_zero(const u_t);
7063
local_id_not_zero(volatile u_t);
7164

65+
// In this context, target is the BTF generated by clang. local is
66+
// generated on the fly by the library. There is a low chance that
67+
// the order on both is the same, so we assert this to make sure that
68+
// CO-RE uses the IDs from the dynamic BTF.
7269
// Qualifiers on types crash clang.
73-
target_and_local_id_match(struct s);
74-
target_and_local_id_match(s_t);
75-
// target_and_local_id_match(const s_t);
76-
// target_and_local_id_match(volatile s_t);
77-
target_and_local_id_match(enum e);
78-
target_and_local_id_match(e_t);
79-
// target_and_local_id_match(const e_t);
80-
// target_and_local_id_match(volatile e_t);
81-
target_and_local_id_match(union u);
82-
target_and_local_id_match(u_t);
83-
// target_and_local_id_match(const u_t);
84-
// target_and_local_id_match(volatile u_t);
70+
target_and_local_id_dont_match(struct s);
71+
target_and_local_id_dont_match(s_t);
72+
// target_and_local_id_dont_match(const s_t);
73+
// target_and_local_id_dont_match(volatile s_t);
74+
target_and_local_id_dont_match(enum e);
75+
target_and_local_id_dont_match(e_t);
76+
// target_and_local_id_dont_match(const e_t);
77+
// target_and_local_id_dont_match(volatile e_t);
78+
target_and_local_id_dont_match(union u);
79+
target_and_local_id_dont_match(u_t);
80+
// target_and_local_id_dont_match(const u_t);
81+
// target_and_local_id_dont_match(volatile u_t);
8582

8683
return 0;
8784
}

linker.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func hasFunctionReferences(insns asm.Instructions) bool {
120120
//
121121
// Passing a nil target will relocate against the running kernel. insns are
122122
// modified in place.
123-
func applyRelocations(insns asm.Instructions, target *btf.Spec, bo binary.ByteOrder) error {
123+
func applyRelocations(insns asm.Instructions, target *btf.Spec, bo binary.ByteOrder, b *btf.Builder) error {
124124
var relos []*btf.CORERelocation
125125
var reloInsns []*asm.Instruction
126126
iter := insns.Iterate()
@@ -139,7 +139,7 @@ func applyRelocations(insns asm.Instructions, target *btf.Spec, bo binary.ByteOr
139139
bo = internal.NativeEndian
140140
}
141141

142-
fixups, err := btf.CORERelocate(relos, target, bo)
142+
fixups, err := btf.CORERelocate(relos, target, bo, b.Add)
143143
if err != nil {
144144
return err
145145
}

prog.go

+26-8
Original file line numberDiff line numberDiff line change
@@ -242,14 +242,26 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
242242
insns := make(asm.Instructions, len(spec.Instructions))
243243
copy(insns, spec.Instructions)
244244

245-
handle, fib, lib, err := btf.MarshalExtInfos(insns)
246-
if err != nil && !errors.Is(err, btf.ErrNotSupported) {
247-
return nil, fmt.Errorf("load ext_infos: %w", err)
245+
var b btf.Builder
246+
if err := applyRelocations(insns, opts.KernelTypes, spec.ByteOrder, &b); err != nil {
247+
return nil, fmt.Errorf("apply CO-RE relocations: %w", err)
248248
}
249-
if handle != nil {
250-
defer handle.Close()
251249

252-
attr.ProgBtfFd = uint32(handle.FD())
250+
errExtInfos := haveProgramExtInfos()
251+
if !b.Empty() && errors.Is(errExtInfos, ErrNotSupported) {
252+
// There is at least one CO-RE relocation which relies on a stable local
253+
// type ID.
254+
// Return ErrNotSupported instead of E2BIG if there is no BTF support.
255+
return nil, errExtInfos
256+
}
257+
258+
if errExtInfos == nil {
259+
// Only add func and line info if the kernel supports it. This allows
260+
// BPF compiled with modern toolchains to work on old kernels.
261+
fib, lib, err := btf.MarshalExtInfos(insns, &b)
262+
if err != nil {
263+
return nil, fmt.Errorf("marshal ext_infos: %w", err)
264+
}
253265

254266
attr.FuncInfoRecSize = btf.FuncInfoSize
255267
attr.FuncInfoCnt = uint32(len(fib)) / btf.FuncInfoSize
@@ -260,8 +272,14 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
260272
attr.LineInfo = sys.NewSlicePointer(lib)
261273
}
262274

263-
if err := applyRelocations(insns, opts.KernelTypes, spec.ByteOrder); err != nil {
264-
return nil, fmt.Errorf("apply CO-RE relocations: %w", err)
275+
if !b.Empty() {
276+
handle, err := btf.NewHandle(&b)
277+
if err != nil {
278+
return nil, fmt.Errorf("load BTF: %w", err)
279+
}
280+
defer handle.Close()
281+
282+
attr.ProgBtfFd = uint32(handle.FD())
265283
}
266284

267285
kconfig, err := resolveKconfigReferences(insns)

syscalls.go

+33
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"errors"
66
"fmt"
7+
"math"
78
"os"
89
"runtime"
910

@@ -302,3 +303,35 @@ var haveSyscallWrapper = internal.NewFeatureTest("syscall wrapper", "4.17", func
302303

303304
return evt.Close()
304305
})
306+
307+
var haveProgramExtInfos = internal.NewFeatureTest("program ext_infos", "5.0", func() error {
308+
insns := asm.Instructions{
309+
asm.Mov.Imm(asm.R0, 0),
310+
asm.Return(),
311+
}
312+
313+
buf := bytes.NewBuffer(make([]byte, 0, insns.Size()))
314+
if err := insns.Marshal(buf, internal.NativeEndian); err != nil {
315+
return err
316+
}
317+
bytecode := buf.Bytes()
318+
319+
_, err := sys.ProgLoad(&sys.ProgLoadAttr{
320+
ProgType: sys.ProgType(SocketFilter),
321+
License: sys.NewStringPointer("MIT"),
322+
Insns: sys.NewSlicePointer(bytecode),
323+
InsnCnt: uint32(len(bytecode) / asm.InstructionSize),
324+
FuncInfoCnt: 1,
325+
ProgBtfFd: math.MaxUint32,
326+
})
327+
328+
if errors.Is(err, unix.EBADF) {
329+
return nil
330+
}
331+
332+
if errors.Is(err, unix.E2BIG) {
333+
return ErrNotSupported
334+
}
335+
336+
return err
337+
})

syscalls_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,7 @@ func TestHaveBPFToBPFCalls(t *testing.T) {
6262
func TestHaveSyscallWrapper(t *testing.T) {
6363
testutils.CheckFeatureTest(t, haveSyscallWrapper)
6464
}
65+
66+
func TestHaveProgramExtInfos(t *testing.T) {
67+
testutils.CheckFeatureTest(t, haveProgramExtInfos)
68+
}

0 commit comments

Comments
 (0)