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  }