282 lines
7.7 KiB
Go
282 lines
7.7 KiB
Go
package btf
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
|
|
"github.com/cilium/ebpf/asm"
|
|
"github.com/cilium/ebpf/internal"
|
|
)
|
|
|
|
type btfExtHeader struct {
|
|
Magic uint16
|
|
Version uint8
|
|
Flags uint8
|
|
HdrLen uint32
|
|
|
|
FuncInfoOff uint32
|
|
FuncInfoLen uint32
|
|
LineInfoOff uint32
|
|
LineInfoLen uint32
|
|
}
|
|
|
|
type btfExtCoreHeader struct {
|
|
CoreReloOff uint32
|
|
CoreReloLen uint32
|
|
}
|
|
|
|
func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (funcInfo, lineInfo map[string]extInfo, coreRelos map[string]bpfCoreRelos, err error) {
|
|
var header btfExtHeader
|
|
var coreHeader btfExtCoreHeader
|
|
if err := binary.Read(r, bo, &header); err != nil {
|
|
return nil, nil, nil, fmt.Errorf("can't read header: %v", err)
|
|
}
|
|
|
|
if header.Magic != btfMagic {
|
|
return nil, nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic)
|
|
}
|
|
|
|
if header.Version != 1 {
|
|
return nil, nil, nil, fmt.Errorf("unexpected version %v", header.Version)
|
|
}
|
|
|
|
if header.Flags != 0 {
|
|
return nil, nil, nil, fmt.Errorf("unsupported flags %v", header.Flags)
|
|
}
|
|
|
|
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
|
|
if remainder < 0 {
|
|
return nil, nil, nil, errors.New("header is too short")
|
|
}
|
|
|
|
coreHdrSize := int64(binary.Size(&coreHeader))
|
|
if remainder >= coreHdrSize {
|
|
if err := binary.Read(r, bo, &coreHeader); err != nil {
|
|
return nil, nil, nil, fmt.Errorf("can't read CO-RE relocation header: %v", err)
|
|
}
|
|
remainder -= coreHdrSize
|
|
}
|
|
|
|
// Of course, the .BTF.ext header has different semantics than the
|
|
// .BTF ext header. We need to ignore non-null values.
|
|
_, err = io.CopyN(ioutil.Discard, r, remainder)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("header padding: %v", err)
|
|
}
|
|
|
|
if _, err := r.Seek(int64(header.HdrLen+header.FuncInfoOff), io.SeekStart); err != nil {
|
|
return nil, nil, nil, fmt.Errorf("can't seek to function info section: %v", err)
|
|
}
|
|
|
|
buf := bufio.NewReader(io.LimitReader(r, int64(header.FuncInfoLen)))
|
|
funcInfo, err = parseExtInfo(buf, bo, strings)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("function info: %w", err)
|
|
}
|
|
|
|
if _, err := r.Seek(int64(header.HdrLen+header.LineInfoOff), io.SeekStart); err != nil {
|
|
return nil, nil, nil, fmt.Errorf("can't seek to line info section: %v", err)
|
|
}
|
|
|
|
buf = bufio.NewReader(io.LimitReader(r, int64(header.LineInfoLen)))
|
|
lineInfo, err = parseExtInfo(buf, bo, strings)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("line info: %w", err)
|
|
}
|
|
|
|
if coreHeader.CoreReloOff > 0 && coreHeader.CoreReloLen > 0 {
|
|
if _, err := r.Seek(int64(header.HdrLen+coreHeader.CoreReloOff), io.SeekStart); err != nil {
|
|
return nil, nil, nil, fmt.Errorf("can't seek to CO-RE relocation section: %v", err)
|
|
}
|
|
|
|
coreRelos, err = parseExtInfoRelos(io.LimitReader(r, int64(coreHeader.CoreReloLen)), bo, strings)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("CO-RE relocation info: %w", err)
|
|
}
|
|
}
|
|
|
|
return funcInfo, lineInfo, coreRelos, nil
|
|
}
|
|
|
|
type btfExtInfoSec struct {
|
|
SecNameOff uint32
|
|
NumInfo uint32
|
|
}
|
|
|
|
type extInfoRecord struct {
|
|
InsnOff uint64
|
|
Opaque []byte
|
|
}
|
|
|
|
type extInfo struct {
|
|
recordSize uint32
|
|
records []extInfoRecord
|
|
}
|
|
|
|
func (ei extInfo) append(other extInfo, offset uint64) (extInfo, error) {
|
|
if other.recordSize != ei.recordSize {
|
|
return extInfo{}, fmt.Errorf("ext_info record size mismatch, want %d (got %d)", ei.recordSize, other.recordSize)
|
|
}
|
|
|
|
records := make([]extInfoRecord, 0, len(ei.records)+len(other.records))
|
|
records = append(records, ei.records...)
|
|
for _, info := range other.records {
|
|
records = append(records, extInfoRecord{
|
|
InsnOff: info.InsnOff + offset,
|
|
Opaque: info.Opaque,
|
|
})
|
|
}
|
|
return extInfo{ei.recordSize, records}, nil
|
|
}
|
|
|
|
func (ei extInfo) MarshalBinary() ([]byte, error) {
|
|
if len(ei.records) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
buf := bytes.NewBuffer(make([]byte, 0, int(ei.recordSize)*len(ei.records)))
|
|
for _, info := range ei.records {
|
|
// The kernel expects offsets in number of raw bpf instructions,
|
|
// while the ELF tracks it in bytes.
|
|
insnOff := uint32(info.InsnOff / asm.InstructionSize)
|
|
if err := binary.Write(buf, internal.NativeEndian, insnOff); err != nil {
|
|
return nil, fmt.Errorf("can't write instruction offset: %v", err)
|
|
}
|
|
|
|
buf.Write(info.Opaque)
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]extInfo, error) {
|
|
const maxRecordSize = 256
|
|
|
|
var recordSize uint32
|
|
if err := binary.Read(r, bo, &recordSize); err != nil {
|
|
return nil, fmt.Errorf("can't read record size: %v", err)
|
|
}
|
|
|
|
if recordSize < 4 {
|
|
// Need at least insnOff
|
|
return nil, errors.New("record size too short")
|
|
}
|
|
if recordSize > maxRecordSize {
|
|
return nil, fmt.Errorf("record size %v exceeds %v", recordSize, maxRecordSize)
|
|
}
|
|
|
|
result := make(map[string]extInfo)
|
|
for {
|
|
secName, infoHeader, err := parseExtInfoHeader(r, bo, strings)
|
|
if errors.Is(err, io.EOF) {
|
|
return result, nil
|
|
}
|
|
|
|
var records []extInfoRecord
|
|
for i := uint32(0); i < infoHeader.NumInfo; i++ {
|
|
var byteOff uint32
|
|
if err := binary.Read(r, bo, &byteOff); err != nil {
|
|
return nil, fmt.Errorf("section %v: can't read extended info offset: %v", secName, err)
|
|
}
|
|
|
|
buf := make([]byte, int(recordSize-4))
|
|
if _, err := io.ReadFull(r, buf); err != nil {
|
|
return nil, fmt.Errorf("section %v: can't read record: %v", secName, err)
|
|
}
|
|
|
|
if byteOff%asm.InstructionSize != 0 {
|
|
return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, byteOff)
|
|
}
|
|
|
|
records = append(records, extInfoRecord{uint64(byteOff), buf})
|
|
}
|
|
|
|
result[secName] = extInfo{
|
|
recordSize,
|
|
records,
|
|
}
|
|
}
|
|
}
|
|
|
|
// bpfCoreRelo matches `struct bpf_core_relo` from the kernel
|
|
type bpfCoreRelo struct {
|
|
InsnOff uint32
|
|
TypeID TypeID
|
|
AccessStrOff uint32
|
|
ReloKind coreReloKind
|
|
}
|
|
|
|
type bpfCoreRelos []bpfCoreRelo
|
|
|
|
// append two slices of extInfoRelo to each other. The InsnOff of b are adjusted
|
|
// by offset.
|
|
func (r bpfCoreRelos) append(other bpfCoreRelos, offset uint64) bpfCoreRelos {
|
|
result := make([]bpfCoreRelo, 0, len(r)+len(other))
|
|
result = append(result, r...)
|
|
for _, relo := range other {
|
|
relo.InsnOff += uint32(offset)
|
|
result = append(result, relo)
|
|
}
|
|
return result
|
|
}
|
|
|
|
var extInfoReloSize = binary.Size(bpfCoreRelo{})
|
|
|
|
func parseExtInfoRelos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]bpfCoreRelos, error) {
|
|
var recordSize uint32
|
|
if err := binary.Read(r, bo, &recordSize); err != nil {
|
|
return nil, fmt.Errorf("read record size: %v", err)
|
|
}
|
|
|
|
if recordSize != uint32(extInfoReloSize) {
|
|
return nil, fmt.Errorf("expected record size %d, got %d", extInfoReloSize, recordSize)
|
|
}
|
|
|
|
result := make(map[string]bpfCoreRelos)
|
|
for {
|
|
secName, infoHeader, err := parseExtInfoHeader(r, bo, strings)
|
|
if errors.Is(err, io.EOF) {
|
|
return result, nil
|
|
}
|
|
|
|
var relos []bpfCoreRelo
|
|
for i := uint32(0); i < infoHeader.NumInfo; i++ {
|
|
var relo bpfCoreRelo
|
|
if err := binary.Read(r, bo, &relo); err != nil {
|
|
return nil, fmt.Errorf("section %v: read record: %v", secName, err)
|
|
}
|
|
|
|
if relo.InsnOff%asm.InstructionSize != 0 {
|
|
return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, relo.InsnOff)
|
|
}
|
|
|
|
relos = append(relos, relo)
|
|
}
|
|
|
|
result[secName] = relos
|
|
}
|
|
}
|
|
|
|
func parseExtInfoHeader(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 infoHeader.NumInfo == 0 {
|
|
return "", nil, fmt.Errorf("section %s has zero records", secName)
|
|
}
|
|
|
|
return secName, &infoHeader, nil
|
|
}
|