diff --git a/btf/ext_info.go b/btf/ext_info.go index 227189fcc..1a494488d 100644 --- a/btf/ext_info.go +++ b/btf/ext_info.go @@ -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 } @@ -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) } @@ -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 { @@ -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) @@ -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 { @@ -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 @@ -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, @@ -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) diff --git a/info.go b/info.go index 4318540a3..9bb67b1e9 100644 --- a/info.go +++ b/info.go @@ -12,7 +12,6 @@ import ( "strings" "syscall" "time" - "unsafe" "github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/btf" @@ -204,6 +203,8 @@ type ProgramInfo struct { numLineInfos uint32 funcInfos []byte numFuncInfos uint32 + ksymInfos []uint64 + numKsymInfos uint32 } func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { @@ -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. @@ -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 @@ -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 { diff --git a/info_test.go b/info_test.go index a3c31d3e2..ede2a1e83 100644 --- a/info_test.go +++ b/info_test.go @@ -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, ""))) + } +} diff --git a/internal/cmd/gentypes/main.go b/internal/cmd/gentypes/main.go index 7b693ca5d..11f285943 100644 --- a/internal/cmd/gentypes/main.go +++ b/internal/cmd/gentypes/main.go @@ -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"), diff --git a/internal/sys/ptr.go b/internal/sys/ptr.go index e9bb59059..af0c014e3 100644 --- a/internal/sys/ptr.go +++ b/internal/sys/ptr.go @@ -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. diff --git a/internal/sys/types.go b/internal/sys/types.go index d46164ae8..19b287a5c 100644 --- a/internal/sys/types.go +++ b/internal/sys/types.go @@ -733,7 +733,7 @@ type ProgInfo struct { NetnsIno uint64 NrJitedKsyms uint32 NrJitedFuncLens uint32 - JitedKsyms uint64 + JitedKsyms Pointer JitedFuncLens uint64 BtfId BTFID FuncInfoRecSize uint32