pkg/exporter/bpfutil/btf.go (143 lines of code) (raw):
package bpfutil
import (
"debug/elf"
"fmt"
"io"
"os"
"path/filepath"
"github.com/cilium/ebpf/btf"
"golang.org/x/exp/slog"
"golang.org/x/sys/unix"
)
const (
kernelBTFPath = "/sys/kernel/btf/vmlinux"
BTFPATH = "/etc/net-exporter/"
bpfSharePath = "/etc/net-exporter/btf/"
userCustomBtfPath = "/etc/net-exporter/custom_btf/"
)
var (
inspMapPath = "/sys/fs/bpf/inspector/"
)
func GetBtfFile() (string, error) {
v, err := KernelRelease()
if err != nil {
return "", err
}
// prefer to use self-hosted btf file
selfpath := fmt.Sprintf("%svmlinux-%s", BTFPATH, v)
if _, err := os.Stat(selfpath); os.IsNotExist(err) {
sharepath := fmt.Sprintf("%svmlinux-%s", bpfSharePath, v)
if _, err := os.Stat(sharepath); os.IsNotExist(err) {
return "", err
}
return sharepath, nil
}
slog.Default().Info("found btf file", "path", selfpath)
return selfpath, nil
}
func KernelRelease() (string, error) {
var uname unix.Utsname
if err := unix.Uname(&uname); err != nil {
return "", fmt.Errorf("uname failed: %w", err)
}
return unix.ByteSliceToString(uname.Release[:]), nil
}
func KernelArch() (string, error) {
var uname unix.Utsname
if err := unix.Uname(&uname); err != nil {
return "", fmt.Errorf("uname failed: %w", err)
}
return unix.ByteSliceToString(uname.Machine[:]), nil
}
// LoadBTFSpecOrNil once error occurs in load process, return nil and use system raw spec instead
func LoadBTFSpecOrNil() *btf.Spec {
var (
btffile string
err error
)
if _, err = os.Stat(kernelBTFPath); err == nil {
btffile = kernelBTFPath
} else if os.IsNotExist(err) {
for _, btfPath := range []string{BTFPATH, bpfSharePath, userCustomBtfPath} {
btffile, err = FindBTFFileWithPath(btfPath)
if err == nil {
break
}
slog.Default().Debug("cannot found expect btf file", "err", err)
}
} else {
slog.Default().Error("error stat /sys/kernel/btf/vmlinux path", err)
return nil
}
if btffile == "" || err != nil {
slog.Default().Warn("load btf file", "paths", []string{BTFPATH, bpfSharePath, userCustomBtfPath}, "err", err)
return nil
}
spec, err := loadBTFSpec(btffile)
if err != nil {
slog.Default().Info("btf load spec failed", "file", btffile, "err", err)
return nil
}
slog.Default().Info("btf file loaded", "file", btffile)
return spec
}
func FindBTFFileWithPath(path string) (string, error) {
path = filepath.Clean(path)
v, err := KernelRelease()
if err != nil {
return "", err
}
btffile := fmt.Sprintf("%s/vmlinux-%s", path, v)
if _, err := os.Stat(btffile); os.IsNotExist(err) {
return "", fmt.Errorf("btf %s not found", btffile)
}
return btffile, err
}
func LoadBTFFromFile(file string) (*btf.Spec, error) {
return loadBTFSpec(file)
}
func loadBTFSpec(file string) (*btf.Spec, error) {
fh, err := os.Open(file)
if err != nil {
return nil, err
}
defer fh.Close()
spec, err := btf.LoadSpecFromReader(fh)
if err == nil {
return spec, nil
}
// if load spec with err, try to extract btf section directly
btfelf, err := getELFFileFromReader(fh)
if err != nil {
return nil, fmt.Errorf("read bare elf:%s", err)
}
var (
btfSection *elf.Section
)
for _, sec := range btfelf.Sections {
switch sec.Name {
case ".BTF":
btfSection = sec
default:
}
}
if btfSection == nil {
return nil, fmt.Errorf("read bare elf: no .BTF section in %s", file)
}
if btfSection.ReaderAt == nil {
return nil, fmt.Errorf("compressed BTF is not supported")
}
return btf.LoadSpecFromReader(btfSection.ReaderAt)
}
func getELFFileFromReader(r io.ReaderAt) (safe *elf.File, err error) {
defer func() {
r := recover()
if r == nil {
return
}
safe = nil
err = fmt.Errorf("reading ELF file panicked: %s", r)
}()
file, err := elf.NewFile(r)
if err != nil {
return nil, err
}
return file, nil
}