Repository: cilium/ebpf
Pull request #415 (2021-09-10 12:21:02)

small btf fixes
btf: add LoadRawSpec

btf: explain that target is optional for Fixups

btf: remove duplicate byte order check
    
    coreRelocate already checks that the local and target byte order
    match, no need to check twice.

btf: push byte order checking into ext_info
    
    Make extInfo aware of its own byte order and move the check into
    extInfo.MarshalBinary().


Patch:
diff --git a/internal/btf/btf.go b/internal/btf/btf.go
index 8efebb8..91779f7 100644
--- a/internal/btf/btf.go
+++ b/internal/btf/btf.go
@@ -64,15 +64,6 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
 	}
 	defer file.Close()
 
-	btfSection, btfExtSection, sectionSizes, err := findBtfSections(file)
-	if err != nil {
-		return nil, err
-	}
-
-	if btfSection == nil {
-		return nil, fmt.Errorf("btf: %w", ErrNotFound)
-	}
-
 	symbols, err := file.Symbols()
 	if err != nil {
 		return nil, fmt.Errorf("can't read symbols: %v", err)
@@ -90,10 +81,6 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
 		}
 
 		secName := file.Sections[symbol.Section].Name
-		if _, ok := sectionSizes[secName]; !ok {
-			continue
-		}
-
 		if symbol.Value > math.MaxUint32 {
 			return nil, fmt.Errorf("section %s: symbol %s: size exceeds maximum", secName, symbol.Name)
 		}
@@ -101,24 +88,10 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
 		variableOffsets[variable{secName, symbol.Name}] = uint32(symbol.Value)
 	}
 
-	spec, err := loadNakedSpec(btfSection.Open(), file.ByteOrder, sectionSizes, variableOffsets)
-	if err != nil {
-		return nil, err
-	}
-
-	if btfExtSection == nil {
-		return spec, nil
-	}
-
-	spec.funcInfos, spec.lineInfos, spec.coreRelos, err = parseExtInfos(btfExtSection.Open(), file.ByteOrder, spec.strings)
-	if err != nil {
-		return nil, fmt.Errorf("can't read ext info: %w", err)
-	}
-
-	return spec, nil
+	return loadSpecFromELF(file, variableOffsets)
 }
 
-func findBtfSections(file *internal.SafeELFFile) (*elf.Section, *elf.Section, map[string]uint32, error) {
+func loadSpecFromELF(file *internal.SafeELFFile, variableOffsets map[variable]uint32) (*Spec, error) {
 	var (
 		btfSection    *elf.Section
 		btfExtSection *elf.Section
@@ -137,33 +110,45 @@ func findBtfSections(file *internal.SafeELFFile) (*elf.Section, *elf.Section, ma
 			}
 
 			if sec.Size > math.MaxUint32 {
-				return nil, nil, nil, fmt.Errorf("section %s exceeds maximum size", sec.Name)
+				return nil, fmt.Errorf("section %s exceeds maximum size", sec.Name)
 			}
 
 			sectionSizes[sec.Name] = uint32(sec.Size)
 		}
 	}
-	return btfSection, btfExtSection, sectionSizes, nil
-}
 
-func loadSpecFromVmlinux(rd io.ReaderAt) (*Spec, error) {
-	file, err := internal.NewSafeELFFile(rd)
+	if btfSection == nil {
+		return nil, fmt.Errorf("btf: %w", ErrNotFound)
+	}
+
+	spec, err := loadRawSpec(btfSection.Open(), file.ByteOrder, sectionSizes, variableOffsets)
 	if err != nil {
 		return nil, err
 	}
-	defer file.Close()
 
-	btfSection, _, _, err := findBtfSections(file)
-	if err != nil {
-		return nil, fmt.Errorf(".BTF ELF section: %s", err)
+	if btfExtSection == nil {
+		return spec, nil
 	}
-	if btfSection == nil {
-		return nil, fmt.Errorf("unable to find .BTF ELF section")
+
+	spec.funcInfos, spec.lineInfos, spec.coreRelos, err = parseExtInfos(btfExtSection.Open(), file.ByteOrder, spec.strings)
+	if err != nil {
+		return nil, fmt.Errorf("can't read ext info: %w", err)
 	}
-	return loadNakedSpec(btfSection.Open(), file.ByteOrder, nil, nil)
+
+	return spec, nil
 }
 
-func loadNakedSpec(btf io.ReadSeeker, bo binary.ByteOrder, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) (*Spec, error) {
+// LoadRawSpec reads a blob of BTF data that isn't wrapped in an ELF file.
+//
+// Prefer using LoadSpecFromReader, since this function only supports a subset
+// of BTF.
+func LoadRawSpec(btf io.Reader, bo binary.ByteOrder) (*Spec, error) {
+	// This will return an error if we encounter a Datasec, since we can't fix
+	// it up.
+	return loadRawSpec(btf, bo, nil, nil)
+}
+
+func loadRawSpec(btf io.Reader, bo binary.ByteOrder, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) (*Spec, error) {
 	rawTypes, rawStrings, err := parseBTF(btf, bo)
 	if err != nil {
 		return nil, err
@@ -220,7 +205,7 @@ func loadKernelSpec() (*Spec, error) {
 	if err == nil {
 		defer fh.Close()
 
-		return loadNakedSpec(fh, internal.NativeEndian, nil, nil)
+		return LoadRawSpec(fh, internal.NativeEndian)
 	}
 
 	// use same list of locations as libbpf
@@ -244,13 +229,19 @@ func loadKernelSpec() (*Spec, error) {
 		}
 		defer fh.Close()
 
-		return loadSpecFromVmlinux(fh)
+		file, err := internal.NewSafeELFFile(fh)
+		if err != nil {
+			return nil, err
+		}
+		defer file.Close()
+
+		return loadSpecFromELF(file, nil)
 	}
 
 	return nil, fmt.Errorf("no BTF for kernel version %s: %w", release, internal.ErrNotSupported)
 }
 
-func parseBTF(btf io.ReadSeeker, bo binary.ByteOrder) ([]rawType, stringTable, error) {
+func parseBTF(btf io.Reader, bo binary.ByteOrder) ([]rawType, stringTable, error) {
 	rawBTF, err := ioutil.ReadAll(btf)
 	if err != nil {
 		return nil, nil, fmt.Errorf("can't read BTF: %v", err)
@@ -646,13 +637,9 @@ func (p *Program) Append(other *Program) error {
 
 // FuncInfos returns the binary form of BTF function infos.
 func (p *Program) FuncInfos() (recordSize uint32, bytes []byte, err error) {
-	if p.spec.byteOrder != internal.NativeEndian {
-		return 0, nil, fmt.Errorf("can't marshal func infos different endianness")
-	}
-
 	bytes, err = p.funcInfos.MarshalBinary()
 	if err != nil {
-		return 0, nil, err
+		return 0, nil, fmt.Errorf("func infos: %w", err)
 	}
 
 	return p.funcInfos.recordSize, bytes, nil
@@ -660,19 +647,17 @@ func (p *Program) FuncInfos() (recordSize uint32, bytes []byte, err error) {
 
 // LineInfos returns the binary form of BTF line infos.
 func (p *Program) LineInfos() (recordSize uint32, bytes []byte, err error) {
-	if p.spec.byteOrder != internal.NativeEndian {
-		return 0, nil, fmt.Errorf("can't marshal line infos different endianness")
-	}
-
 	bytes, err = p.lineInfos.MarshalBinary()
 	if err != nil {
-		return 0, nil, err
+		return 0, nil, fmt.Errorf("line infos: %w", err)
 	}
 
 	return p.lineInfos.recordSize, bytes, nil
 }
 
 // Fixups returns the changes required to adjust the program to the target.
+//
+// Passing a nil target will relocate against the running kernel.
 func (p *Program) Fixups(target *Spec) (COREFixups, error) {
 	if len(p.coreRelos) == 0 {
 		return nil, nil
@@ -686,10 +671,6 @@ func (p *Program) Fixups(target *Spec) (COREFixups, error) {
 		}
 	}
 
-	if p.spec.byteOrder != target.byteOrder {
-		return nil, fmt.Errorf("can't calculate fixups for mixed endianness")
-	}
-
 	return coreRelocate(p.spec, target, p.coreRelos)
 }
 
diff --git a/internal/btf/ext_info.go b/internal/btf/ext_info.go
index beba1bc..8a5384d 100644
--- a/internal/btf/ext_info.go
+++ b/internal/btf/ext_info.go
@@ -114,11 +114,16 @@ type extInfoRecord struct {
 }
 
 type extInfo struct {
+	byteOrder  binary.ByteOrder
 	recordSize uint32
 	records    []extInfoRecord
 }
 
 func (ei extInfo) append(other extInfo, offset uint64) (extInfo, error) {
+	if other.byteOrder != ei.byteOrder {
+		return extInfo{}, fmt.Errorf("ext_info byte order mismatch, want %v (got %v)", ei.byteOrder, other.byteOrder)
+	}
+
 	if other.recordSize != ei.recordSize {
 		return extInfo{}, fmt.Errorf("ext_info record size mismatch, want %d (got %d)", ei.recordSize, other.recordSize)
 	}
@@ -131,10 +136,14 @@ func (ei extInfo) append(other extInfo, offset uint64) (extInfo, error) {
 			Opaque:  info.Opaque,
 		})
 	}
-	return extInfo{ei.recordSize, records}, nil
+	return extInfo{ei.byteOrder, ei.recordSize, records}, nil
 }
 
 func (ei extInfo) MarshalBinary() ([]byte, error) {
+	if ei.byteOrder != internal.NativeEndian {
+		return nil, fmt.Errorf("%s is not the native byte order", ei.byteOrder)
+	}
+
 	if len(ei.records) == 0 {
 		return nil, nil
 	}
@@ -197,6 +206,7 @@ func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[st
 		}
 
 		result[secName] = extInfo{
+			bo,
 			recordSize,
 			records,
 		}
diff --git a/internal/btf/info.go b/internal/btf/info.go
index e757f52..6a9b5d2 100644
--- a/internal/btf/info.go
+++ b/internal/btf/info.go
@@ -34,7 +34,7 @@ func newInfoFromFd(fd *internal.FD) (*info, error) {
 		return nil, err
 	}
 
-	spec, err := loadNakedSpec(bytes.NewReader(btfBuffer), internal.NativeEndian, nil, nil)
+	spec, err := loadRawSpec(bytes.NewReader(btfBuffer), internal.NativeEndian, nil, nil)
 	if err != nil {
 		return nil, err
 	}


Test patch:
diff --git a/internal/btf/btf_test.go b/internal/btf/btf_test.go
index c036352..ca391a9 100644
--- a/internal/btf/btf_test.go
+++ b/internal/btf/btf_test.go
@@ -1,12 +1,10 @@
 package btf
 
 import (
-	"bytes"
 	"compress/gzip"
 	"encoding/binary"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"testing"
 
@@ -26,12 +24,7 @@ func TestParseVmlinux(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	buf, err := ioutil.ReadAll(rd)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	spec, err := loadNakedSpec(bytes.NewReader(buf), binary.LittleEndian, nil, nil)
+	spec, err := loadRawSpec(rd, binary.LittleEndian, nil, nil)
 	if err != nil {
 		t.Fatal("Can't load BTF:", err)
 	}