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