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