Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 38 additions & 37 deletions btf/ext_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
// ExtInfos contains ELF section metadata.
type ExtInfos struct {
// The slices are sorted by offset in ascending order.
funcInfos map[string]FuncInfos
funcInfos map[string]FuncOffsets
lineInfos map[string]LineInfos
relocationInfos map[string]CORERelocationInfos
}
Expand Down Expand Up @@ -58,9 +58,9 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec) (*ExtInfos, er
return nil, fmt.Errorf("parsing BTF function info: %w", err)
}

funcInfos := make(map[string]FuncInfos, len(btfFuncInfos))
funcInfos := make(map[string]FuncOffsets, len(btfFuncInfos))
for section, bfis := range btfFuncInfos {
funcInfos[section], err = newFuncInfos(bfis, spec)
funcInfos[section], err = newFuncOffsets(bfis, spec)
if err != nil {
return nil, fmt.Errorf("section %s: func infos: %w", section, err)
}
Expand Down Expand Up @@ -117,15 +117,15 @@ func (ei *ExtInfos) Assign(insns asm.Instructions, section string) {
// Assign per-instruction metadata to the instructions in insns.
func AssignMetadataToInstructions(
insns asm.Instructions,
funcInfos FuncInfos,
funcInfos FuncOffsets,
lineInfos LineInfos,
reloInfos CORERelocationInfos,
) {
iter := insns.Iterate()
for iter.Next() {
if len(funcInfos.infos) > 0 && funcInfos.infos[0].offset == iter.Offset {
*iter.Ins = WithFuncMetadata(*iter.Ins, funcInfos.infos[0].fn)
funcInfos.infos = funcInfos.infos[1:]
if len(funcInfos) > 0 && funcInfos[0].Offset == iter.Offset {
*iter.Ins = WithFuncMetadata(*iter.Ins, funcInfos[0].Func)
funcInfos = funcInfos[1:]
}

if len(lineInfos.infos) > 0 && lineInfos.infos[0].offset == iter.Offset {
Expand Down Expand Up @@ -159,9 +159,9 @@ marshal:
var fiBuf, liBuf bytes.Buffer
for {
if fn := FuncMetadata(iter.Ins); fn != nil {
fi := &funcInfo{
fn: fn,
offset: iter.Offset,
fi := &FuncOffset{
Func: fn,
Offset: iter.Offset,
}
if err := fi.marshal(&fiBuf, b); err != nil {
return nil, nil, fmt.Errorf("write func info: %w", err)
Expand Down Expand Up @@ -333,17 +333,17 @@ func parseExtInfoRecordSize(r io.Reader, bo binary.ByteOrder) (uint32, error) {
return recordSize, nil
}

// FuncInfos contains a sorted list of func infos.
type FuncInfos struct {
infos []funcInfo
}
// FuncOffsets is a sorted slice of FuncOffset.
type FuncOffsets []FuncOffset

// The size of a FuncInfo in BTF wire format.
var FuncInfoSize = uint32(binary.Size(bpfFuncInfo{}))

type funcInfo struct {
fn *Func
offset asm.RawInstructionOffset
// FuncOffset represents a [btf.Func] and its raw instruction offset within a
// BPF program.
type FuncOffset struct {
Offset asm.RawInstructionOffset
Func *Func
}

type bpfFuncInfo struct {
Expand All @@ -352,7 +352,7 @@ type bpfFuncInfo struct {
TypeID TypeID
}

func newFuncInfo(fi bpfFuncInfo, spec *Spec) (*funcInfo, error) {
func newFuncOffset(fi bpfFuncInfo, spec *Spec) (*FuncOffset, error) {
typ, err := spec.TypeByID(fi.TypeID)
if err != nil {
return nil, err
Expand All @@ -368,31 +368,32 @@ func newFuncInfo(fi bpfFuncInfo, spec *Spec) (*funcInfo, error) {
return nil, fmt.Errorf("func with type ID %d doesn't have a name", fi.TypeID)
}

return &funcInfo{
fn,
return &FuncOffset{
asm.RawInstructionOffset(fi.InsnOff),
fn,
}, nil
}

func newFuncInfos(bfis []bpfFuncInfo, spec *Spec) (FuncInfos, error) {
fis := FuncInfos{
infos: make([]funcInfo, 0, len(bfis)),
}
func newFuncOffsets(bfis []bpfFuncInfo, spec *Spec) (FuncOffsets, error) {
fos := make(FuncOffsets, 0, len(bfis))

for _, bfi := range bfis {
fi, err := newFuncInfo(bfi, spec)
fi, err := newFuncOffset(bfi, spec)
if err != nil {
return FuncInfos{}, fmt.Errorf("offset %d: %w", bfi.InsnOff, err)
return FuncOffsets{}, fmt.Errorf("offset %d: %w", bfi.InsnOff, err)
}
fis.infos = append(fis.infos, *fi)
fos = append(fos, *fi)
}
sort.Slice(fis.infos, func(i, j int) bool {
return fis.infos[i].offset <= fis.infos[j].offset
sort.Slice(fos, func(i, j int) bool {
return fos[i].Offset <= fos[j].Offset
})
return fis, nil
return fos, nil
}

// LoadFuncInfos parses BTF func info in kernel wire format.
func LoadFuncInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec *Spec) (FuncInfos, error) {
// LoadFuncInfos parses BTF func info from kernel wire format into a
// [FuncOffsets], a sorted slice of [btf.Func]s of (sub)programs within a BPF
// program with their corresponding raw instruction offsets.
func LoadFuncInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec *Spec) (FuncOffsets, error) {
fis, err := parseFuncInfoRecords(
reader,
bo,
Expand All @@ -401,20 +402,20 @@ func LoadFuncInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec
false,
)
if err != nil {
return FuncInfos{}, fmt.Errorf("parsing BTF func info: %w", err)
return FuncOffsets{}, fmt.Errorf("parsing BTF func info: %w", err)
}

return newFuncInfos(fis, spec)
return newFuncOffsets(fis, spec)
}

// marshal into the BTF wire format.
func (fi *funcInfo) marshal(w *bytes.Buffer, b *Builder) error {
id, err := b.Add(fi.fn)
func (fi *FuncOffset) marshal(w *bytes.Buffer, b *Builder) error {
id, err := b.Add(fi.Func)
if err != nil {
return err
}
bfi := bpfFuncInfo{
InsnOff: uint32(fi.offset),
InsnOff: uint32(fi.Offset),
TypeID: id,
}
buf := make([]byte, FuncInfoSize)
Expand Down
60 changes: 58 additions & 2 deletions info.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"strings"
"syscall"
"time"
"unsafe"

"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/btf"
Expand Down Expand Up @@ -204,6 +203,8 @@ type ProgramInfo struct {
numLineInfos uint32
funcInfos []byte
numFuncInfos uint32
ksymInfos []uint64
numKsymInfos uint32
}

func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
Expand Down Expand Up @@ -240,7 +241,7 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
if info.NrMapIds > 0 {
pi.maps = make([]MapID, info.NrMapIds)
info2.NrMapIds = info.NrMapIds
info2.MapIds = sys.NewPointer(unsafe.Pointer(&pi.maps[0]))
info2.MapIds = sys.NewSlicePointer(pi.maps)
makeSecondCall = true
} else if haveProgramInfoMapIDs() == nil {
// This program really has no associated maps.
Expand Down Expand Up @@ -281,6 +282,14 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
makeSecondCall = true
}

if info.NrJitedKsyms > 0 {
pi.ksymInfos = make([]uint64, info.NrJitedKsyms)
info2.JitedKsyms = sys.NewSlicePointer(pi.ksymInfos)
info2.NrJitedKsyms = info.NrJitedKsyms
pi.numKsymInfos = info.NrJitedKsyms
makeSecondCall = true
}

if makeSecondCall {
if err := sys.ObjInfo(fd, &info2); err != nil {
return nil, err
Expand Down Expand Up @@ -508,6 +517,53 @@ func (pi *ProgramInfo) VerifiedInstructions() (uint32, bool) {
return pi.verifiedInstructions, pi.verifiedInstructions > 0
}

// KsymAddrs returns the ksym addresses of the BPF program, including its
// subprograms. The addresses correspond to their symbols in /proc/kallsyms.
//
// Available from 4.18.
//
// The bool return value indicates whether this optional field is available.
func (pi *ProgramInfo) KsymAddrs() ([]uintptr, bool) {
addrs := make([]uintptr, 0, len(pi.ksymInfos))
for _, addr := range pi.ksymInfos {
addrs = append(addrs, uintptr(addr))
}
return addrs, pi.numKsymInfos > 0
}

// FuncInfos returns the offset and function information of all (sub)programs in
// a BPF program.
//
// Available from 5.0.
//
// Requires CAP_SYS_ADMIN or equivalent for reading BTF information. Returns
// ErrNotSupported if the program was created without BTF or if the kernel
// doesn't support the field.
func (pi *ProgramInfo) FuncInfos() (btf.FuncOffsets, error) {
id, ok := pi.BTFID()
if pi.numFuncInfos == 0 || !ok {
return nil, fmt.Errorf("program created without BTF or unsupported kernel: %w", ErrNotSupported)
}

h, err := btf.NewHandleFromID(id)
if err != nil {
return nil, fmt.Errorf("get BTF handle: %w", err)
}
defer h.Close()

spec, err := h.Spec(nil)
if err != nil {
return nil, fmt.Errorf("get BTF spec: %w", err)
}

return btf.LoadFuncInfos(
bytes.NewReader(pi.funcInfos),
internal.NativeEndian,
pi.numFuncInfos,
spec,
)
}

func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error {
fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", fd.Int()))
if err != nil {
Expand Down
52 changes: 52 additions & 0 deletions info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,3 +510,55 @@ func TestZero(t *testing.T) {
qt.Assert(t, qt.IsTrue(zero(&inul)))
qt.Assert(t, qt.IsFalse(zero(&ione)))
}

func TestProgInfoKsym(t *testing.T) {
testutils.SkipOnOldKernel(t, "4.18", "Program ksym addresses")

spec, err := LoadCollectionSpec(testutils.NativeFile(t, "testdata/loader-%s.elf"))
qt.Assert(t, qt.IsNil(err))

var obj struct {
Prog *Program `ebpf:"xdp_prog"`
}
err = spec.LoadAndAssign(&obj, nil)
testutils.SkipIfNotSupported(t, err)
qt.Assert(t, qt.IsNil(err))
defer obj.Prog.Close()

info, err := obj.Prog.Info()
qt.Assert(t, qt.IsNil(err))

addrs, ok := info.KsymAddrs()
qt.Assert(t, qt.IsTrue(ok))
qt.Assert(t, qt.HasLen(addrs, 5))
for _, addr := range addrs {
qt.Assert(t, qt.Not(qt.Equals(addr, 0)))
}
}

func TestProgInfoFuncInfos(t *testing.T) {
testutils.SkipOnOldKernel(t, "5.0", "Program func info")

spec, err := LoadCollectionSpec(testutils.NativeFile(t, "testdata/loader-%s.elf"))
qt.Assert(t, qt.IsNil(err))

var obj struct {
Prog *Program `ebpf:"xdp_prog"`
}
err = spec.LoadAndAssign(&obj, nil)
testutils.SkipIfNotSupported(t, err)
qt.Assert(t, qt.IsNil(err))
defer obj.Prog.Close()

info, err := obj.Prog.Info()
qt.Assert(t, qt.IsNil(err))

funcs, err := info.FuncInfos()
qt.Assert(t, qt.IsNil(err))

qt.Assert(t, qt.HasLen(funcs, 5))
for _, fo := range funcs {
qt.Assert(t, qt.IsNotNil(fo.Func))
qt.Assert(t, qt.Not(qt.Equals(fo.Func.Name, "")))
}
}
1 change: 1 addition & 0 deletions internal/cmd/gentypes/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ import (
replace(pointer, "xlated_prog_insns"),
replace(pointer, "map_ids"),
replace(pointer, "line_info"),
replace(pointer, "jited_ksyms"),
replace(pointer, "func_info"),
replace(btfID, "btf_id", "attach_btf_obj_id"),
replace(typeID, "attach_btf_id"),
Expand Down
6 changes: 3 additions & 3 deletions internal/sys/ptr.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ func NewPointer(ptr unsafe.Pointer) Pointer {
return Pointer{ptr: ptr}
}

// NewSlicePointer creates a 64-bit pointer from a byte slice.
func NewSlicePointer(buf []byte) Pointer {
// NewSlicePointer creates a 64-bit pointer from a slice.
func NewSlicePointer[T comparable](buf []T) Pointer {
if len(buf) == 0 {
return Pointer{}
}

return Pointer{ptr: unsafe.Pointer(&buf[0])}
return Pointer{ptr: unsafe.Pointer(unsafe.SliceData(buf))}
}

// NewSlicePointerLen creates a 64-bit pointer from a byte slice.
Expand Down
2 changes: 1 addition & 1 deletion internal/sys/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,7 @@ type ProgInfo struct {
NetnsIno uint64
NrJitedKsyms uint32
NrJitedFuncLens uint32
JitedKsyms uint64
JitedKsyms Pointer
JitedFuncLens uint64
BtfId BTFID
FuncInfoRecSize uint32
Expand Down