1 package btf
2
3 import (
4 "bytes"
5 "encoding/binary"
6 "errors"
7 "fmt"
8 "io"
9 "math"
10 "sort"
11
12 "github.com/cilium/ebpf/asm"
13 "github.com/cilium/ebpf/internal"
14 )
15
16
17 type ExtInfos struct {
18
19 funcInfos map[string][]funcInfo
20 lineInfos map[string][]lineInfo
21 relocationInfos map[string][]coreRelocationInfo
22 }
23
24
25
26
27 func loadExtInfosFromELF(file *internal.SafeELFFile, ts types, strings *stringTable) (*ExtInfos, error) {
28 section := file.Section(".BTF.ext")
29 if section == nil {
30 return nil, fmt.Errorf("btf ext infos: %w", ErrNotFound)
31 }
32
33 if section.ReaderAt == nil {
34 return nil, fmt.Errorf("compressed ext_info is not supported")
35 }
36
37 return loadExtInfos(section.ReaderAt, file.ByteOrder, ts, strings)
38 }
39
40
41 func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, ts types, strings *stringTable) (*ExtInfos, error) {
42
43
44 headerRd := io.NewSectionReader(r, 0, math.MaxInt64)
45 extHeader, err := parseBTFExtHeader(headerRd, bo)
46 if err != nil {
47 return nil, fmt.Errorf("parsing BTF extension header: %w", err)
48 }
49
50 coreHeader, err := parseBTFExtCOREHeader(headerRd, bo, extHeader)
51 if err != nil {
52 return nil, fmt.Errorf("parsing BTF CO-RE header: %w", err)
53 }
54
55 buf := internal.NewBufferedSectionReader(r, extHeader.funcInfoStart(), int64(extHeader.FuncInfoLen))
56 btfFuncInfos, err := parseFuncInfos(buf, bo, strings)
57 if err != nil {
58 return nil, fmt.Errorf("parsing BTF function info: %w", err)
59 }
60
61 funcInfos := make(map[string][]funcInfo, len(btfFuncInfos))
62 for section, bfis := range btfFuncInfos {
63 funcInfos[section], err = newFuncInfos(bfis, ts)
64 if err != nil {
65 return nil, fmt.Errorf("section %s: func infos: %w", section, err)
66 }
67 }
68
69 buf = internal.NewBufferedSectionReader(r, extHeader.lineInfoStart(), int64(extHeader.LineInfoLen))
70 btfLineInfos, err := parseLineInfos(buf, bo, strings)
71 if err != nil {
72 return nil, fmt.Errorf("parsing BTF line info: %w", err)
73 }
74
75 lineInfos := make(map[string][]lineInfo, len(btfLineInfos))
76 for section, blis := range btfLineInfos {
77 lineInfos[section], err = newLineInfos(blis, strings)
78 if err != nil {
79 return nil, fmt.Errorf("section %s: line infos: %w", section, err)
80 }
81 }
82
83 if coreHeader == nil || coreHeader.COREReloLen == 0 {
84 return &ExtInfos{funcInfos, lineInfos, nil}, nil
85 }
86
87 var btfCORERelos map[string][]bpfCORERelo
88 buf = internal.NewBufferedSectionReader(r, extHeader.coreReloStart(coreHeader), int64(coreHeader.COREReloLen))
89 btfCORERelos, err = parseCORERelos(buf, bo, strings)
90 if err != nil {
91 return nil, fmt.Errorf("parsing CO-RE relocation info: %w", err)
92 }
93
94 coreRelos := make(map[string][]coreRelocationInfo, len(btfCORERelos))
95 for section, brs := range btfCORERelos {
96 coreRelos[section], err = newRelocationInfos(brs, ts, strings)
97 if err != nil {
98 return nil, fmt.Errorf("section %s: CO-RE relocations: %w", section, err)
99 }
100 }
101
102 return &ExtInfos{funcInfos, lineInfos, coreRelos}, nil
103 }
104
105 type funcInfoMeta struct{}
106 type coreRelocationMeta struct{}
107
108
109 func (ei *ExtInfos) Assign(insns asm.Instructions, section string) {
110 funcInfos := ei.funcInfos[section]
111 lineInfos := ei.lineInfos[section]
112 reloInfos := ei.relocationInfos[section]
113
114 iter := insns.Iterate()
115 for iter.Next() {
116 if len(funcInfos) > 0 && funcInfos[0].offset == iter.Offset {
117 iter.Ins.Metadata.Set(funcInfoMeta{}, funcInfos[0].fn)
118 funcInfos = funcInfos[1:]
119 }
120
121 if len(lineInfos) > 0 && lineInfos[0].offset == iter.Offset {
122 *iter.Ins = iter.Ins.WithSource(lineInfos[0].line)
123 lineInfos = lineInfos[1:]
124 }
125
126 if len(reloInfos) > 0 && reloInfos[0].offset == iter.Offset {
127 iter.Ins.Metadata.Set(coreRelocationMeta{}, reloInfos[0].relo)
128 reloInfos = reloInfos[1:]
129 }
130 }
131 }
132
133
134
135 func MarshalExtInfos(insns asm.Instructions, typeID func(Type) (TypeID, error)) (funcInfos, lineInfos []byte, _ error) {
136 iter := insns.Iterate()
137 var fiBuf, liBuf bytes.Buffer
138 for iter.Next() {
139 if fn := FuncMetadata(iter.Ins); fn != nil {
140 fi := &funcInfo{
141 fn: fn,
142 offset: iter.Offset,
143 }
144 if err := fi.marshal(&fiBuf, typeID); err != nil {
145 return nil, nil, fmt.Errorf("write func info: %w", err)
146 }
147 }
148
149 if line, ok := iter.Ins.Source().(*Line); ok {
150 li := &lineInfo{
151 line: line,
152 offset: iter.Offset,
153 }
154 if err := li.marshal(&liBuf); err != nil {
155 return nil, nil, fmt.Errorf("write line info: %w", err)
156 }
157 }
158 }
159 return fiBuf.Bytes(), liBuf.Bytes(), nil
160 }
161
162
163 type btfExtHeader struct {
164 Magic uint16
165 Version uint8
166 Flags uint8
167
168
169
170 HdrLen uint32
171
172 FuncInfoOff uint32
173 FuncInfoLen uint32
174 LineInfoOff uint32
175 LineInfoLen uint32
176 }
177
178
179 func parseBTFExtHeader(r io.Reader, bo binary.ByteOrder) (*btfExtHeader, error) {
180 var header btfExtHeader
181 if err := binary.Read(r, bo, &header); err != nil {
182 return nil, fmt.Errorf("can't read header: %v", err)
183 }
184
185 if header.Magic != btfMagic {
186 return nil, fmt.Errorf("incorrect magic value %v", header.Magic)
187 }
188
189 if header.Version != 1 {
190 return nil, fmt.Errorf("unexpected version %v", header.Version)
191 }
192
193 if header.Flags != 0 {
194 return nil, fmt.Errorf("unsupported flags %v", header.Flags)
195 }
196
197 if int64(header.HdrLen) < int64(binary.Size(&header)) {
198 return nil, fmt.Errorf("header length shorter than btfExtHeader size")
199 }
200
201 return &header, nil
202 }
203
204
205
206 func (h *btfExtHeader) funcInfoStart() int64 {
207 return int64(h.HdrLen + h.FuncInfoOff)
208 }
209
210
211
212 func (h *btfExtHeader) lineInfoStart() int64 {
213 return int64(h.HdrLen + h.LineInfoOff)
214 }
215
216
217
218 func (h *btfExtHeader) coreReloStart(ch *btfExtCOREHeader) int64 {
219 return int64(h.HdrLen + ch.COREReloOff)
220 }
221
222
223
224 type btfExtCOREHeader struct {
225 COREReloOff uint32
226 COREReloLen uint32
227 }
228
229
230
231
232 func parseBTFExtCOREHeader(r io.Reader, bo binary.ByteOrder, extHeader *btfExtHeader) (*btfExtCOREHeader, error) {
233 extHdrSize := int64(binary.Size(&extHeader))
234 remainder := int64(extHeader.HdrLen) - extHdrSize
235
236 if remainder == 0 {
237 return nil, nil
238 }
239
240 var coreHeader btfExtCOREHeader
241 if err := binary.Read(r, bo, &coreHeader); err != nil {
242 return nil, fmt.Errorf("can't read header: %v", err)
243 }
244
245 return &coreHeader, nil
246 }
247
248 type btfExtInfoSec struct {
249 SecNameOff uint32
250 NumInfo uint32
251 }
252
253
254
255
256
257 func parseExtInfoSec(r io.Reader, bo binary.ByteOrder, strings *stringTable) (string, *btfExtInfoSec, error) {
258 var infoHeader btfExtInfoSec
259 if err := binary.Read(r, bo, &infoHeader); err != nil {
260 return "", nil, fmt.Errorf("read ext info header: %w", err)
261 }
262
263 secName, err := strings.Lookup(infoHeader.SecNameOff)
264 if err != nil {
265 return "", nil, fmt.Errorf("get section name: %w", err)
266 }
267 if secName == "" {
268 return "", nil, fmt.Errorf("extinfo header refers to empty section name")
269 }
270
271 if infoHeader.NumInfo == 0 {
272 return "", nil, fmt.Errorf("section %s has zero records", secName)
273 }
274
275 return secName, &infoHeader, nil
276 }
277
278
279
280
281 func parseExtInfoRecordSize(r io.Reader, bo binary.ByteOrder) (uint32, error) {
282 const maxRecordSize = 256
283
284 var recordSize uint32
285 if err := binary.Read(r, bo, &recordSize); err != nil {
286 return 0, fmt.Errorf("can't read record size: %v", err)
287 }
288
289 if recordSize < 4 {
290
291 return 0, errors.New("record size too short")
292 }
293 if recordSize > maxRecordSize {
294 return 0, fmt.Errorf("record size %v exceeds %v", recordSize, maxRecordSize)
295 }
296
297 return recordSize, nil
298 }
299
300
301 var FuncInfoSize = uint32(binary.Size(bpfFuncInfo{}))
302
303 type funcInfo struct {
304 fn *Func
305 offset asm.RawInstructionOffset
306 }
307
308 type bpfFuncInfo struct {
309
310 InsnOff uint32
311 TypeID TypeID
312 }
313
314 func newFuncInfo(fi bpfFuncInfo, ts types) (*funcInfo, error) {
315 typ, err := ts.ByID(fi.TypeID)
316 if err != nil {
317 return nil, err
318 }
319
320 fn, ok := typ.(*Func)
321 if !ok {
322 return nil, fmt.Errorf("type ID %d is a %T, but expected a Func", fi.TypeID, typ)
323 }
324
325
326 if fn.Name == "" {
327 return nil, fmt.Errorf("func with type ID %d doesn't have a name", fi.TypeID)
328 }
329
330 return &funcInfo{
331 fn,
332 asm.RawInstructionOffset(fi.InsnOff),
333 }, nil
334 }
335
336 func newFuncInfos(bfis []bpfFuncInfo, ts types) ([]funcInfo, error) {
337 fis := make([]funcInfo, 0, len(bfis))
338 for _, bfi := range bfis {
339 fi, err := newFuncInfo(bfi, ts)
340 if err != nil {
341 return nil, fmt.Errorf("offset %d: %w", bfi.InsnOff, err)
342 }
343 fis = append(fis, *fi)
344 }
345 sort.Slice(fis, func(i, j int) bool {
346 return fis[i].offset <= fis[j].offset
347 })
348 return fis, nil
349 }
350
351
352 func (fi *funcInfo) marshal(w io.Writer, typeID func(Type) (TypeID, error)) error {
353 id, err := typeID(fi.fn)
354 if err != nil {
355 return err
356 }
357 bfi := bpfFuncInfo{
358 InsnOff: uint32(fi.offset),
359 TypeID: id,
360 }
361 return binary.Write(w, internal.NativeEndian, &bfi)
362 }
363
364
365
366 func parseFuncInfos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map[string][]bpfFuncInfo, error) {
367 recordSize, err := parseExtInfoRecordSize(r, bo)
368 if err != nil {
369 return nil, err
370 }
371
372 result := make(map[string][]bpfFuncInfo)
373 for {
374 secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
375 if errors.Is(err, io.EOF) {
376 return result, nil
377 }
378 if err != nil {
379 return nil, err
380 }
381
382 records, err := parseFuncInfoRecords(r, bo, recordSize, infoHeader.NumInfo)
383 if err != nil {
384 return nil, fmt.Errorf("section %v: %w", secName, err)
385 }
386
387 result[secName] = records
388 }
389 }
390
391
392
393
394 func parseFuncInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) ([]bpfFuncInfo, error) {
395 var out []bpfFuncInfo
396 var fi bpfFuncInfo
397
398 if exp, got := FuncInfoSize, recordSize; exp != got {
399
400 return nil, fmt.Errorf("expected FuncInfo record size %d, but BTF blob contains %d", exp, got)
401 }
402
403 for i := uint32(0); i < recordNum; i++ {
404 if err := binary.Read(r, bo, &fi); err != nil {
405 return nil, fmt.Errorf("can't read function info: %v", err)
406 }
407
408 if fi.InsnOff%asm.InstructionSize != 0 {
409 return nil, fmt.Errorf("offset %v is not aligned with instruction size", fi.InsnOff)
410 }
411
412
413
414 fi.InsnOff /= asm.InstructionSize
415
416 out = append(out, fi)
417 }
418
419 return out, nil
420 }
421
422 var LineInfoSize = uint32(binary.Size(bpfLineInfo{}))
423
424
425
426 type Line struct {
427 fileName string
428 line string
429 lineNumber uint32
430 lineColumn uint32
431
432
433
434
435 fileNameOff uint32
436 lineOff uint32
437 }
438
439 func (li *Line) FileName() string {
440 return li.fileName
441 }
442
443 func (li *Line) Line() string {
444 return li.line
445 }
446
447 func (li *Line) LineNumber() uint32 {
448 return li.lineNumber
449 }
450
451 func (li *Line) LineColumn() uint32 {
452 return li.lineColumn
453 }
454
455 func (li *Line) String() string {
456 return li.line
457 }
458
459 type lineInfo struct {
460 line *Line
461 offset asm.RawInstructionOffset
462 }
463
464
465 const (
466 bpfLineShift = 10
467 bpfLineMax = (1 << (32 - bpfLineShift)) - 1
468 bpfColumnMax = (1 << bpfLineShift) - 1
469 )
470
471 type bpfLineInfo struct {
472
473 InsnOff uint32
474 FileNameOff uint32
475 LineOff uint32
476 LineCol uint32
477 }
478
479 func newLineInfo(li bpfLineInfo, strings *stringTable) (*lineInfo, error) {
480 line, err := strings.Lookup(li.LineOff)
481 if err != nil {
482 return nil, fmt.Errorf("lookup of line: %w", err)
483 }
484
485 fileName, err := strings.Lookup(li.FileNameOff)
486 if err != nil {
487 return nil, fmt.Errorf("lookup of filename: %w", err)
488 }
489
490 lineNumber := li.LineCol >> bpfLineShift
491 lineColumn := li.LineCol & bpfColumnMax
492
493 return &lineInfo{
494 &Line{
495 fileName,
496 line,
497 lineNumber,
498 lineColumn,
499 li.FileNameOff,
500 li.LineOff,
501 },
502 asm.RawInstructionOffset(li.InsnOff),
503 }, nil
504 }
505
506 func newLineInfos(blis []bpfLineInfo, strings *stringTable) ([]lineInfo, error) {
507 lis := make([]lineInfo, 0, len(blis))
508 for _, bli := range blis {
509 li, err := newLineInfo(bli, strings)
510 if err != nil {
511 return nil, fmt.Errorf("offset %d: %w", bli.InsnOff, err)
512 }
513 lis = append(lis, *li)
514 }
515 sort.Slice(lis, func(i, j int) bool {
516 return lis[i].offset <= lis[j].offset
517 })
518 return lis, nil
519 }
520
521
522 func (li *lineInfo) marshal(w io.Writer) error {
523 line := li.line
524 if line.lineNumber > bpfLineMax {
525 return fmt.Errorf("line %d exceeds %d", line.lineNumber, bpfLineMax)
526 }
527
528 if line.lineColumn > bpfColumnMax {
529 return fmt.Errorf("column %d exceeds %d", line.lineColumn, bpfColumnMax)
530 }
531
532 bli := bpfLineInfo{
533 uint32(li.offset),
534 line.fileNameOff,
535 line.lineOff,
536 (line.lineNumber << bpfLineShift) | line.lineColumn,
537 }
538 return binary.Write(w, internal.NativeEndian, &bli)
539 }
540
541
542
543 func parseLineInfos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map[string][]bpfLineInfo, error) {
544 recordSize, err := parseExtInfoRecordSize(r, bo)
545 if err != nil {
546 return nil, err
547 }
548
549 result := make(map[string][]bpfLineInfo)
550 for {
551 secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
552 if errors.Is(err, io.EOF) {
553 return result, nil
554 }
555 if err != nil {
556 return nil, err
557 }
558
559 records, err := parseLineInfoRecords(r, bo, recordSize, infoHeader.NumInfo)
560 if err != nil {
561 return nil, fmt.Errorf("section %v: %w", secName, err)
562 }
563
564 result[secName] = records
565 }
566 }
567
568
569
570
571 func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) ([]bpfLineInfo, error) {
572 var out []bpfLineInfo
573 var li bpfLineInfo
574
575 if exp, got := uint32(binary.Size(li)), recordSize; exp != got {
576
577 return nil, fmt.Errorf("expected LineInfo record size %d, but BTF blob contains %d", exp, got)
578 }
579
580 for i := uint32(0); i < recordNum; i++ {
581 if err := binary.Read(r, bo, &li); err != nil {
582 return nil, fmt.Errorf("can't read line info: %v", err)
583 }
584
585 if li.InsnOff%asm.InstructionSize != 0 {
586 return nil, fmt.Errorf("offset %v is not aligned with instruction size", li.InsnOff)
587 }
588
589
590
591 li.InsnOff /= asm.InstructionSize
592
593 out = append(out, li)
594 }
595
596 return out, nil
597 }
598
599
600 type bpfCORERelo struct {
601 InsnOff uint32
602 TypeID TypeID
603 AccessStrOff uint32
604 Kind coreKind
605 }
606
607 type CORERelocation struct {
608 typ Type
609 accessor coreAccessor
610 kind coreKind
611 }
612
613 func CORERelocationMetadata(ins *asm.Instruction) *CORERelocation {
614 relo, _ := ins.Metadata.Get(coreRelocationMeta{}).(*CORERelocation)
615 return relo
616 }
617
618 type coreRelocationInfo struct {
619 relo *CORERelocation
620 offset asm.RawInstructionOffset
621 }
622
623 func newRelocationInfo(relo bpfCORERelo, ts types, strings *stringTable) (*coreRelocationInfo, error) {
624 typ, err := ts.ByID(relo.TypeID)
625 if err != nil {
626 return nil, err
627 }
628
629 accessorStr, err := strings.Lookup(relo.AccessStrOff)
630 if err != nil {
631 return nil, err
632 }
633
634 accessor, err := parseCOREAccessor(accessorStr)
635 if err != nil {
636 return nil, fmt.Errorf("accessor %q: %s", accessorStr, err)
637 }
638
639 return &coreRelocationInfo{
640 &CORERelocation{
641 typ,
642 accessor,
643 relo.Kind,
644 },
645 asm.RawInstructionOffset(relo.InsnOff),
646 }, nil
647 }
648
649 func newRelocationInfos(brs []bpfCORERelo, ts types, strings *stringTable) ([]coreRelocationInfo, error) {
650 rs := make([]coreRelocationInfo, 0, len(brs))
651 for _, br := range brs {
652 relo, err := newRelocationInfo(br, ts, strings)
653 if err != nil {
654 return nil, fmt.Errorf("offset %d: %w", br.InsnOff, err)
655 }
656 rs = append(rs, *relo)
657 }
658 sort.Slice(rs, func(i, j int) bool {
659 return rs[i].offset < rs[j].offset
660 })
661 return rs, nil
662 }
663
664 var extInfoReloSize = binary.Size(bpfCORERelo{})
665
666
667
668 func parseCORERelos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map[string][]bpfCORERelo, error) {
669 recordSize, err := parseExtInfoRecordSize(r, bo)
670 if err != nil {
671 return nil, err
672 }
673
674 if recordSize != uint32(extInfoReloSize) {
675 return nil, fmt.Errorf("expected record size %d, got %d", extInfoReloSize, recordSize)
676 }
677
678 result := make(map[string][]bpfCORERelo)
679 for {
680 secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
681 if errors.Is(err, io.EOF) {
682 return result, nil
683 }
684 if err != nil {
685 return nil, err
686 }
687
688 records, err := parseCOREReloRecords(r, bo, recordSize, infoHeader.NumInfo)
689 if err != nil {
690 return nil, fmt.Errorf("section %v: %w", secName, err)
691 }
692
693 result[secName] = records
694 }
695 }
696
697
698
699
700 func parseCOREReloRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) ([]bpfCORERelo, error) {
701 var out []bpfCORERelo
702
703 var relo bpfCORERelo
704 for i := uint32(0); i < recordNum; i++ {
705 if err := binary.Read(r, bo, &relo); err != nil {
706 return nil, fmt.Errorf("can't read CO-RE relocation: %v", err)
707 }
708
709 if relo.InsnOff%asm.InstructionSize != 0 {
710 return nil, fmt.Errorf("offset %v is not aligned with instruction size", relo.InsnOff)
711 }
712
713
714
715 relo.InsnOff /= asm.InstructionSize
716
717 out = append(out, relo)
718 }
719
720 return out, nil
721 }
722
View as plain text