722 lines
20 KiB
Go
722 lines
20 KiB
Go
package btf
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"sort"
|
|
|
|
"github.com/cilium/ebpf/asm"
|
|
"github.com/cilium/ebpf/internal"
|
|
)
|
|
|
|
// ExtInfos contains ELF section metadata.
|
|
type ExtInfos struct {
|
|
// The slices are sorted by offset in ascending order.
|
|
funcInfos map[string][]funcInfo
|
|
lineInfos map[string][]lineInfo
|
|
relocationInfos map[string][]coreRelocationInfo
|
|
}
|
|
|
|
// loadExtInfosFromELF parses ext infos from the .BTF.ext section in an ELF.
|
|
//
|
|
// Returns an error wrapping ErrNotFound if no ext infos are present.
|
|
func loadExtInfosFromELF(file *internal.SafeELFFile, ts types, strings *stringTable) (*ExtInfos, error) {
|
|
section := file.Section(".BTF.ext")
|
|
if section == nil {
|
|
return nil, fmt.Errorf("btf ext infos: %w", ErrNotFound)
|
|
}
|
|
|
|
if section.ReaderAt == nil {
|
|
return nil, fmt.Errorf("compressed ext_info is not supported")
|
|
}
|
|
|
|
return loadExtInfos(section.ReaderAt, file.ByteOrder, ts, strings)
|
|
}
|
|
|
|
// loadExtInfos parses bare ext infos.
|
|
func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, ts types, strings *stringTable) (*ExtInfos, error) {
|
|
// Open unbuffered section reader. binary.Read() calls io.ReadFull on
|
|
// the header structs, resulting in one syscall per header.
|
|
headerRd := io.NewSectionReader(r, 0, math.MaxInt64)
|
|
extHeader, err := parseBTFExtHeader(headerRd, bo)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing BTF extension header: %w", err)
|
|
}
|
|
|
|
coreHeader, err := parseBTFExtCOREHeader(headerRd, bo, extHeader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing BTF CO-RE header: %w", err)
|
|
}
|
|
|
|
buf := internal.NewBufferedSectionReader(r, extHeader.funcInfoStart(), int64(extHeader.FuncInfoLen))
|
|
btfFuncInfos, err := parseFuncInfos(buf, bo, strings)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing BTF function info: %w", err)
|
|
}
|
|
|
|
funcInfos := make(map[string][]funcInfo, len(btfFuncInfos))
|
|
for section, bfis := range btfFuncInfos {
|
|
funcInfos[section], err = newFuncInfos(bfis, ts)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("section %s: func infos: %w", section, err)
|
|
}
|
|
}
|
|
|
|
buf = internal.NewBufferedSectionReader(r, extHeader.lineInfoStart(), int64(extHeader.LineInfoLen))
|
|
btfLineInfos, err := parseLineInfos(buf, bo, strings)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing BTF line info: %w", err)
|
|
}
|
|
|
|
lineInfos := make(map[string][]lineInfo, len(btfLineInfos))
|
|
for section, blis := range btfLineInfos {
|
|
lineInfos[section], err = newLineInfos(blis, strings)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("section %s: line infos: %w", section, err)
|
|
}
|
|
}
|
|
|
|
if coreHeader == nil || coreHeader.COREReloLen == 0 {
|
|
return &ExtInfos{funcInfos, lineInfos, nil}, nil
|
|
}
|
|
|
|
var btfCORERelos map[string][]bpfCORERelo
|
|
buf = internal.NewBufferedSectionReader(r, extHeader.coreReloStart(coreHeader), int64(coreHeader.COREReloLen))
|
|
btfCORERelos, err = parseCORERelos(buf, bo, strings)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing CO-RE relocation info: %w", err)
|
|
}
|
|
|
|
coreRelos := make(map[string][]coreRelocationInfo, len(btfCORERelos))
|
|
for section, brs := range btfCORERelos {
|
|
coreRelos[section], err = newRelocationInfos(brs, ts, strings)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("section %s: CO-RE relocations: %w", section, err)
|
|
}
|
|
}
|
|
|
|
return &ExtInfos{funcInfos, lineInfos, coreRelos}, nil
|
|
}
|
|
|
|
type funcInfoMeta struct{}
|
|
type coreRelocationMeta struct{}
|
|
|
|
// Assign per-section metadata from BTF to a section's instructions.
|
|
func (ei *ExtInfos) Assign(insns asm.Instructions, section string) {
|
|
funcInfos := ei.funcInfos[section]
|
|
lineInfos := ei.lineInfos[section]
|
|
reloInfos := ei.relocationInfos[section]
|
|
|
|
iter := insns.Iterate()
|
|
for iter.Next() {
|
|
if len(funcInfos) > 0 && funcInfos[0].offset == iter.Offset {
|
|
iter.Ins.Metadata.Set(funcInfoMeta{}, funcInfos[0].fn)
|
|
funcInfos = funcInfos[1:]
|
|
}
|
|
|
|
if len(lineInfos) > 0 && lineInfos[0].offset == iter.Offset {
|
|
*iter.Ins = iter.Ins.WithSource(lineInfos[0].line)
|
|
lineInfos = lineInfos[1:]
|
|
}
|
|
|
|
if len(reloInfos) > 0 && reloInfos[0].offset == iter.Offset {
|
|
iter.Ins.Metadata.Set(coreRelocationMeta{}, reloInfos[0].relo)
|
|
reloInfos = reloInfos[1:]
|
|
}
|
|
}
|
|
}
|
|
|
|
// MarshalExtInfos encodes function and line info embedded in insns into kernel
|
|
// wire format.
|
|
func MarshalExtInfos(insns asm.Instructions, typeID func(Type) (TypeID, error)) (funcInfos, lineInfos []byte, _ error) {
|
|
iter := insns.Iterate()
|
|
var fiBuf, liBuf bytes.Buffer
|
|
for iter.Next() {
|
|
if fn := FuncMetadata(iter.Ins); fn != nil {
|
|
fi := &funcInfo{
|
|
fn: fn,
|
|
offset: iter.Offset,
|
|
}
|
|
if err := fi.marshal(&fiBuf, typeID); err != nil {
|
|
return nil, nil, fmt.Errorf("write func info: %w", err)
|
|
}
|
|
}
|
|
|
|
if line, ok := iter.Ins.Source().(*Line); ok {
|
|
li := &lineInfo{
|
|
line: line,
|
|
offset: iter.Offset,
|
|
}
|
|
if err := li.marshal(&liBuf); err != nil {
|
|
return nil, nil, fmt.Errorf("write line info: %w", err)
|
|
}
|
|
}
|
|
}
|
|
return fiBuf.Bytes(), liBuf.Bytes(), nil
|
|
}
|
|
|
|
// btfExtHeader is found at the start of the .BTF.ext section.
|
|
type btfExtHeader struct {
|
|
Magic uint16
|
|
Version uint8
|
|
Flags uint8
|
|
|
|
// HdrLen is larger than the size of struct btfExtHeader when it is
|
|
// immediately followed by a btfExtCOREHeader.
|
|
HdrLen uint32
|
|
|
|
FuncInfoOff uint32
|
|
FuncInfoLen uint32
|
|
LineInfoOff uint32
|
|
LineInfoLen uint32
|
|
}
|
|
|
|
// parseBTFExtHeader parses the header of the .BTF.ext section.
|
|
func parseBTFExtHeader(r io.Reader, bo binary.ByteOrder) (*btfExtHeader, error) {
|
|
var header btfExtHeader
|
|
if err := binary.Read(r, bo, &header); err != nil {
|
|
return nil, fmt.Errorf("can't read header: %v", err)
|
|
}
|
|
|
|
if header.Magic != btfMagic {
|
|
return nil, fmt.Errorf("incorrect magic value %v", header.Magic)
|
|
}
|
|
|
|
if header.Version != 1 {
|
|
return nil, fmt.Errorf("unexpected version %v", header.Version)
|
|
}
|
|
|
|
if header.Flags != 0 {
|
|
return nil, fmt.Errorf("unsupported flags %v", header.Flags)
|
|
}
|
|
|
|
if int64(header.HdrLen) < int64(binary.Size(&header)) {
|
|
return nil, fmt.Errorf("header length shorter than btfExtHeader size")
|
|
}
|
|
|
|
return &header, nil
|
|
}
|
|
|
|
// funcInfoStart returns the offset from the beginning of the .BTF.ext section
|
|
// to the start of its func_info entries.
|
|
func (h *btfExtHeader) funcInfoStart() int64 {
|
|
return int64(h.HdrLen + h.FuncInfoOff)
|
|
}
|
|
|
|
// lineInfoStart returns the offset from the beginning of the .BTF.ext section
|
|
// to the start of its line_info entries.
|
|
func (h *btfExtHeader) lineInfoStart() int64 {
|
|
return int64(h.HdrLen + h.LineInfoOff)
|
|
}
|
|
|
|
// coreReloStart returns the offset from the beginning of the .BTF.ext section
|
|
// to the start of its CO-RE relocation entries.
|
|
func (h *btfExtHeader) coreReloStart(ch *btfExtCOREHeader) int64 {
|
|
return int64(h.HdrLen + ch.COREReloOff)
|
|
}
|
|
|
|
// btfExtCOREHeader is found right after the btfExtHeader when its HdrLen
|
|
// field is larger than its size.
|
|
type btfExtCOREHeader struct {
|
|
COREReloOff uint32
|
|
COREReloLen uint32
|
|
}
|
|
|
|
// parseBTFExtCOREHeader parses the tail of the .BTF.ext header. If additional
|
|
// header bytes are present, extHeader.HdrLen will be larger than the struct,
|
|
// indicating the presence of a CO-RE extension header.
|
|
func parseBTFExtCOREHeader(r io.Reader, bo binary.ByteOrder, extHeader *btfExtHeader) (*btfExtCOREHeader, error) {
|
|
extHdrSize := int64(binary.Size(&extHeader))
|
|
remainder := int64(extHeader.HdrLen) - extHdrSize
|
|
|
|
if remainder == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
var coreHeader btfExtCOREHeader
|
|
if err := binary.Read(r, bo, &coreHeader); err != nil {
|
|
return nil, fmt.Errorf("can't read header: %v", err)
|
|
}
|
|
|
|
return &coreHeader, nil
|
|
}
|
|
|
|
type btfExtInfoSec struct {
|
|
SecNameOff uint32
|
|
NumInfo uint32
|
|
}
|
|
|
|
// parseExtInfoSec parses a btf_ext_info_sec header within .BTF.ext,
|
|
// appearing within func_info and line_info sub-sections.
|
|
// These headers appear once for each program section in the ELF and are
|
|
// followed by one or more func/line_info records for the section.
|
|
func parseExtInfoSec(r io.Reader, bo binary.ByteOrder, strings *stringTable) (string, *btfExtInfoSec, error) {
|
|
var infoHeader btfExtInfoSec
|
|
if err := binary.Read(r, bo, &infoHeader); err != nil {
|
|
return "", nil, fmt.Errorf("read ext info header: %w", err)
|
|
}
|
|
|
|
secName, err := strings.Lookup(infoHeader.SecNameOff)
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("get section name: %w", err)
|
|
}
|
|
if secName == "" {
|
|
return "", nil, fmt.Errorf("extinfo header refers to empty section name")
|
|
}
|
|
|
|
if infoHeader.NumInfo == 0 {
|
|
return "", nil, fmt.Errorf("section %s has zero records", secName)
|
|
}
|
|
|
|
return secName, &infoHeader, nil
|
|
}
|
|
|
|
// parseExtInfoRecordSize parses the uint32 at the beginning of a func_infos
|
|
// or line_infos segment that describes the length of all extInfoRecords in
|
|
// that segment.
|
|
func parseExtInfoRecordSize(r io.Reader, bo binary.ByteOrder) (uint32, error) {
|
|
const maxRecordSize = 256
|
|
|
|
var recordSize uint32
|
|
if err := binary.Read(r, bo, &recordSize); err != nil {
|
|
return 0, fmt.Errorf("can't read record size: %v", err)
|
|
}
|
|
|
|
if recordSize < 4 {
|
|
// Need at least InsnOff worth of bytes per record.
|
|
return 0, errors.New("record size too short")
|
|
}
|
|
if recordSize > maxRecordSize {
|
|
return 0, fmt.Errorf("record size %v exceeds %v", recordSize, maxRecordSize)
|
|
}
|
|
|
|
return recordSize, nil
|
|
}
|
|
|
|
// The size of a FuncInfo in BTF wire format.
|
|
var FuncInfoSize = uint32(binary.Size(bpfFuncInfo{}))
|
|
|
|
type funcInfo struct {
|
|
fn *Func
|
|
offset asm.RawInstructionOffset
|
|
}
|
|
|
|
type bpfFuncInfo struct {
|
|
// Instruction offset of the function within an ELF section.
|
|
InsnOff uint32
|
|
TypeID TypeID
|
|
}
|
|
|
|
func newFuncInfo(fi bpfFuncInfo, ts types) (*funcInfo, error) {
|
|
typ, err := ts.ByID(fi.TypeID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fn, ok := typ.(*Func)
|
|
if !ok {
|
|
return nil, fmt.Errorf("type ID %d is a %T, but expected a Func", fi.TypeID, typ)
|
|
}
|
|
|
|
// C doesn't have anonymous functions, but check just in case.
|
|
if fn.Name == "" {
|
|
return nil, fmt.Errorf("func with type ID %d doesn't have a name", fi.TypeID)
|
|
}
|
|
|
|
return &funcInfo{
|
|
fn,
|
|
asm.RawInstructionOffset(fi.InsnOff),
|
|
}, nil
|
|
}
|
|
|
|
func newFuncInfos(bfis []bpfFuncInfo, ts types) ([]funcInfo, error) {
|
|
fis := make([]funcInfo, 0, len(bfis))
|
|
for _, bfi := range bfis {
|
|
fi, err := newFuncInfo(bfi, ts)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("offset %d: %w", bfi.InsnOff, err)
|
|
}
|
|
fis = append(fis, *fi)
|
|
}
|
|
sort.Slice(fis, func(i, j int) bool {
|
|
return fis[i].offset <= fis[j].offset
|
|
})
|
|
return fis, nil
|
|
}
|
|
|
|
// marshal into the BTF wire format.
|
|
func (fi *funcInfo) marshal(w io.Writer, typeID func(Type) (TypeID, error)) error {
|
|
id, err := typeID(fi.fn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bfi := bpfFuncInfo{
|
|
InsnOff: uint32(fi.offset),
|
|
TypeID: id,
|
|
}
|
|
return binary.Write(w, internal.NativeEndian, &bfi)
|
|
}
|
|
|
|
// parseLineInfos parses a func_info sub-section within .BTF.ext ito a map of
|
|
// func infos indexed by section name.
|
|
func parseFuncInfos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map[string][]bpfFuncInfo, error) {
|
|
recordSize, err := parseExtInfoRecordSize(r, bo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := make(map[string][]bpfFuncInfo)
|
|
for {
|
|
secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
|
|
if errors.Is(err, io.EOF) {
|
|
return result, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
records, err := parseFuncInfoRecords(r, bo, recordSize, infoHeader.NumInfo)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("section %v: %w", secName, err)
|
|
}
|
|
|
|
result[secName] = records
|
|
}
|
|
}
|
|
|
|
// parseFuncInfoRecords parses a stream of func_infos into a funcInfos.
|
|
// These records appear after a btf_ext_info_sec header in the func_info
|
|
// sub-section of .BTF.ext.
|
|
func parseFuncInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) ([]bpfFuncInfo, error) {
|
|
var out []bpfFuncInfo
|
|
var fi bpfFuncInfo
|
|
|
|
if exp, got := FuncInfoSize, recordSize; exp != got {
|
|
// BTF blob's record size is longer than we know how to parse.
|
|
return nil, fmt.Errorf("expected FuncInfo record size %d, but BTF blob contains %d", exp, got)
|
|
}
|
|
|
|
for i := uint32(0); i < recordNum; i++ {
|
|
if err := binary.Read(r, bo, &fi); err != nil {
|
|
return nil, fmt.Errorf("can't read function info: %v", err)
|
|
}
|
|
|
|
if fi.InsnOff%asm.InstructionSize != 0 {
|
|
return nil, fmt.Errorf("offset %v is not aligned with instruction size", fi.InsnOff)
|
|
}
|
|
|
|
// ELF tracks offset in bytes, the kernel expects raw BPF instructions.
|
|
// Convert as early as possible.
|
|
fi.InsnOff /= asm.InstructionSize
|
|
|
|
out = append(out, fi)
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
var LineInfoSize = uint32(binary.Size(bpfLineInfo{}))
|
|
|
|
// Line represents the location and contents of a single line of source
|
|
// code a BPF ELF was compiled from.
|
|
type Line struct {
|
|
fileName string
|
|
line string
|
|
lineNumber uint32
|
|
lineColumn uint32
|
|
|
|
// TODO: We should get rid of the fields below, but for that we need to be
|
|
// able to write BTF.
|
|
|
|
fileNameOff uint32
|
|
lineOff uint32
|
|
}
|
|
|
|
func (li *Line) FileName() string {
|
|
return li.fileName
|
|
}
|
|
|
|
func (li *Line) Line() string {
|
|
return li.line
|
|
}
|
|
|
|
func (li *Line) LineNumber() uint32 {
|
|
return li.lineNumber
|
|
}
|
|
|
|
func (li *Line) LineColumn() uint32 {
|
|
return li.lineColumn
|
|
}
|
|
|
|
func (li *Line) String() string {
|
|
return li.line
|
|
}
|
|
|
|
type lineInfo struct {
|
|
line *Line
|
|
offset asm.RawInstructionOffset
|
|
}
|
|
|
|
// Constants for the format of bpfLineInfo.LineCol.
|
|
const (
|
|
bpfLineShift = 10
|
|
bpfLineMax = (1 << (32 - bpfLineShift)) - 1
|
|
bpfColumnMax = (1 << bpfLineShift) - 1
|
|
)
|
|
|
|
type bpfLineInfo struct {
|
|
// Instruction offset of the line within the whole instruction stream, in instructions.
|
|
InsnOff uint32
|
|
FileNameOff uint32
|
|
LineOff uint32
|
|
LineCol uint32
|
|
}
|
|
|
|
func newLineInfo(li bpfLineInfo, strings *stringTable) (*lineInfo, error) {
|
|
line, err := strings.Lookup(li.LineOff)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("lookup of line: %w", err)
|
|
}
|
|
|
|
fileName, err := strings.Lookup(li.FileNameOff)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("lookup of filename: %w", err)
|
|
}
|
|
|
|
lineNumber := li.LineCol >> bpfLineShift
|
|
lineColumn := li.LineCol & bpfColumnMax
|
|
|
|
return &lineInfo{
|
|
&Line{
|
|
fileName,
|
|
line,
|
|
lineNumber,
|
|
lineColumn,
|
|
li.FileNameOff,
|
|
li.LineOff,
|
|
},
|
|
asm.RawInstructionOffset(li.InsnOff),
|
|
}, nil
|
|
}
|
|
|
|
func newLineInfos(blis []bpfLineInfo, strings *stringTable) ([]lineInfo, error) {
|
|
lis := make([]lineInfo, 0, len(blis))
|
|
for _, bli := range blis {
|
|
li, err := newLineInfo(bli, strings)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("offset %d: %w", bli.InsnOff, err)
|
|
}
|
|
lis = append(lis, *li)
|
|
}
|
|
sort.Slice(lis, func(i, j int) bool {
|
|
return lis[i].offset <= lis[j].offset
|
|
})
|
|
return lis, nil
|
|
}
|
|
|
|
// marshal writes the binary representation of the LineInfo to w.
|
|
func (li *lineInfo) marshal(w io.Writer) error {
|
|
line := li.line
|
|
if line.lineNumber > bpfLineMax {
|
|
return fmt.Errorf("line %d exceeds %d", line.lineNumber, bpfLineMax)
|
|
}
|
|
|
|
if line.lineColumn > bpfColumnMax {
|
|
return fmt.Errorf("column %d exceeds %d", line.lineColumn, bpfColumnMax)
|
|
}
|
|
|
|
bli := bpfLineInfo{
|
|
uint32(li.offset),
|
|
line.fileNameOff,
|
|
line.lineOff,
|
|
(line.lineNumber << bpfLineShift) | line.lineColumn,
|
|
}
|
|
return binary.Write(w, internal.NativeEndian, &bli)
|
|
}
|
|
|
|
// parseLineInfos parses a line_info sub-section within .BTF.ext ito a map of
|
|
// line infos indexed by section name.
|
|
func parseLineInfos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map[string][]bpfLineInfo, error) {
|
|
recordSize, err := parseExtInfoRecordSize(r, bo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := make(map[string][]bpfLineInfo)
|
|
for {
|
|
secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
|
|
if errors.Is(err, io.EOF) {
|
|
return result, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
records, err := parseLineInfoRecords(r, bo, recordSize, infoHeader.NumInfo)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("section %v: %w", secName, err)
|
|
}
|
|
|
|
result[secName] = records
|
|
}
|
|
}
|
|
|
|
// parseLineInfoRecords parses a stream of line_infos into a lineInfos.
|
|
// These records appear after a btf_ext_info_sec header in the line_info
|
|
// sub-section of .BTF.ext.
|
|
func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) ([]bpfLineInfo, error) {
|
|
var out []bpfLineInfo
|
|
var li bpfLineInfo
|
|
|
|
if exp, got := uint32(binary.Size(li)), recordSize; exp != got {
|
|
// BTF blob's record size is longer than we know how to parse.
|
|
return nil, fmt.Errorf("expected LineInfo record size %d, but BTF blob contains %d", exp, got)
|
|
}
|
|
|
|
for i := uint32(0); i < recordNum; i++ {
|
|
if err := binary.Read(r, bo, &li); err != nil {
|
|
return nil, fmt.Errorf("can't read line info: %v", err)
|
|
}
|
|
|
|
if li.InsnOff%asm.InstructionSize != 0 {
|
|
return nil, fmt.Errorf("offset %v is not aligned with instruction size", li.InsnOff)
|
|
}
|
|
|
|
// ELF tracks offset in bytes, the kernel expects raw BPF instructions.
|
|
// Convert as early as possible.
|
|
li.InsnOff /= asm.InstructionSize
|
|
|
|
out = append(out, li)
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
// bpfCORERelo matches the kernel's struct bpf_core_relo.
|
|
type bpfCORERelo struct {
|
|
InsnOff uint32
|
|
TypeID TypeID
|
|
AccessStrOff uint32
|
|
Kind coreKind
|
|
}
|
|
|
|
type CORERelocation struct {
|
|
typ Type
|
|
accessor coreAccessor
|
|
kind coreKind
|
|
}
|
|
|
|
func CORERelocationMetadata(ins *asm.Instruction) *CORERelocation {
|
|
relo, _ := ins.Metadata.Get(coreRelocationMeta{}).(*CORERelocation)
|
|
return relo
|
|
}
|
|
|
|
type coreRelocationInfo struct {
|
|
relo *CORERelocation
|
|
offset asm.RawInstructionOffset
|
|
}
|
|
|
|
func newRelocationInfo(relo bpfCORERelo, ts types, strings *stringTable) (*coreRelocationInfo, error) {
|
|
typ, err := ts.ByID(relo.TypeID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
accessorStr, err := strings.Lookup(relo.AccessStrOff)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
accessor, err := parseCOREAccessor(accessorStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("accessor %q: %s", accessorStr, err)
|
|
}
|
|
|
|
return &coreRelocationInfo{
|
|
&CORERelocation{
|
|
typ,
|
|
accessor,
|
|
relo.Kind,
|
|
},
|
|
asm.RawInstructionOffset(relo.InsnOff),
|
|
}, nil
|
|
}
|
|
|
|
func newRelocationInfos(brs []bpfCORERelo, ts types, strings *stringTable) ([]coreRelocationInfo, error) {
|
|
rs := make([]coreRelocationInfo, 0, len(brs))
|
|
for _, br := range brs {
|
|
relo, err := newRelocationInfo(br, ts, strings)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("offset %d: %w", br.InsnOff, err)
|
|
}
|
|
rs = append(rs, *relo)
|
|
}
|
|
sort.Slice(rs, func(i, j int) bool {
|
|
return rs[i].offset < rs[j].offset
|
|
})
|
|
return rs, nil
|
|
}
|
|
|
|
var extInfoReloSize = binary.Size(bpfCORERelo{})
|
|
|
|
// parseCORERelos parses a core_relos sub-section within .BTF.ext ito a map of
|
|
// CO-RE relocations indexed by section name.
|
|
func parseCORERelos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map[string][]bpfCORERelo, error) {
|
|
recordSize, err := parseExtInfoRecordSize(r, bo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if recordSize != uint32(extInfoReloSize) {
|
|
return nil, fmt.Errorf("expected record size %d, got %d", extInfoReloSize, recordSize)
|
|
}
|
|
|
|
result := make(map[string][]bpfCORERelo)
|
|
for {
|
|
secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
|
|
if errors.Is(err, io.EOF) {
|
|
return result, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
records, err := parseCOREReloRecords(r, bo, recordSize, infoHeader.NumInfo)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("section %v: %w", secName, err)
|
|
}
|
|
|
|
result[secName] = records
|
|
}
|
|
}
|
|
|
|
// parseCOREReloRecords parses a stream of CO-RE relocation entries into a
|
|
// coreRelos. These records appear after a btf_ext_info_sec header in the
|
|
// core_relos sub-section of .BTF.ext.
|
|
func parseCOREReloRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) ([]bpfCORERelo, error) {
|
|
var out []bpfCORERelo
|
|
|
|
var relo bpfCORERelo
|
|
for i := uint32(0); i < recordNum; i++ {
|
|
if err := binary.Read(r, bo, &relo); err != nil {
|
|
return nil, fmt.Errorf("can't read CO-RE relocation: %v", err)
|
|
}
|
|
|
|
if relo.InsnOff%asm.InstructionSize != 0 {
|
|
return nil, fmt.Errorf("offset %v is not aligned with instruction size", relo.InsnOff)
|
|
}
|
|
|
|
// ELF tracks offset in bytes, the kernel expects raw BPF instructions.
|
|
// Convert as early as possible.
|
|
relo.InsnOff /= asm.InstructionSize
|
|
|
|
out = append(out, relo)
|
|
}
|
|
|
|
return out, nil
|
|
}
|