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  }