From ab9722b2053d52ced47cb4f6f7940a737a272b70 Mon Sep 17 00:00:00 2001 From: Leon Hwang Date: Mon, 25 Nov 2024 20:47:33 +0800 Subject: [PATCH 1/5] btf: Refactor LineInfos to LineOffsets It's to export the raw line infos alongside with prog's jited infos. Signed-off-by: Leon Hwang --- btf/ext_info.go | 71 ++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/btf/ext_info.go b/btf/ext_info.go index 1a494488d..2c684fe2a 100644 --- a/btf/ext_info.go +++ b/btf/ext_info.go @@ -17,7 +17,7 @@ import ( type ExtInfos struct { // The slices are sorted by offset in ascending order. funcInfos map[string]FuncOffsets - lineInfos map[string]LineInfos + lineInfos map[string]LineOffsets relocationInfos map[string]CORERelocationInfos } @@ -72,7 +72,7 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec) (*ExtInfos, er return nil, fmt.Errorf("parsing BTF line info: %w", err) } - lineInfos := make(map[string]LineInfos, len(btfLineInfos)) + lineInfos := make(map[string]LineOffsets, len(btfLineInfos)) for section, blis := range btfLineInfos { lineInfos[section], err = newLineInfos(blis, spec.strings) if err != nil { @@ -102,8 +102,10 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec) (*ExtInfos, er return &ExtInfos{funcInfos, lineInfos, coreRelos}, nil } -type funcInfoMeta struct{} -type coreRelocationMeta struct{} +type ( + funcInfoMeta struct{} + coreRelocationMeta struct{} +) // Assign per-section metadata from BTF to a section's instructions. func (ei *ExtInfos) Assign(insns asm.Instructions, section string) { @@ -118,7 +120,7 @@ func (ei *ExtInfos) Assign(insns asm.Instructions, section string) { func AssignMetadataToInstructions( insns asm.Instructions, funcInfos FuncOffsets, - lineInfos LineInfos, + lineInfos LineOffsets, reloInfos CORERelocationInfos, ) { iter := insns.Iterate() @@ -128,9 +130,9 @@ func AssignMetadataToInstructions( funcInfos = funcInfos[1:] } - if len(lineInfos.infos) > 0 && lineInfos.infos[0].offset == iter.Offset { - *iter.Ins = iter.Ins.WithSource(lineInfos.infos[0].line) - lineInfos.infos = lineInfos.infos[1:] + if len(lineInfos) > 0 && lineInfos[0].Offset == iter.Offset { + *iter.Ins = iter.Ins.WithSource(lineInfos[0].Line) + lineInfos = lineInfos[1:] } if len(reloInfos.infos) > 0 && reloInfos.infos[0].offset == iter.Offset { @@ -178,9 +180,9 @@ marshal: } } - li := &lineInfo{ - line: line, - offset: iter.Offset, + li := &LineOffset{ + Offset: iter.Offset, + Line: line, } if err := li.marshal(&liBuf, b); err != nil { return nil, nil, fmt.Errorf("write line info: %w", err) @@ -516,14 +518,13 @@ func (li *Line) String() string { return li.line } -// LineInfos contains a sorted list of line infos. -type LineInfos struct { - infos []lineInfo -} +// LineOffsets contains a sorted list of line infos. +type LineOffsets []LineOffset -type lineInfo struct { - line *Line - offset asm.RawInstructionOffset +// LineOffset represents a line info and its raw instruction offset. +type LineOffset struct { + Offset asm.RawInstructionOffset + Line *Line } // Constants for the format of bpfLineInfo.LineCol. @@ -542,7 +543,7 @@ type bpfLineInfo struct { } // LoadLineInfos parses BTF line info in kernel wire format. -func LoadLineInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec *Spec) (LineInfos, error) { +func LoadLineInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec *Spec) (LineOffsets, error) { lis, err := parseLineInfoRecords( reader, bo, @@ -551,57 +552,55 @@ func LoadLineInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec false, ) if err != nil { - return LineInfos{}, fmt.Errorf("parsing BTF line info: %w", err) + return LineOffsets{}, fmt.Errorf("parsing BTF line info: %w", err) } return newLineInfos(lis, spec.strings) } -func newLineInfo(li bpfLineInfo, strings *stringTable) (lineInfo, error) { +func newLineInfo(li bpfLineInfo, strings *stringTable) (LineOffset, error) { line, err := strings.Lookup(li.LineOff) if err != nil { - return lineInfo{}, fmt.Errorf("lookup of line: %w", err) + return LineOffset{}, fmt.Errorf("lookup of line: %w", err) } fileName, err := strings.Lookup(li.FileNameOff) if err != nil { - return lineInfo{}, fmt.Errorf("lookup of filename: %w", err) + return LineOffset{}, fmt.Errorf("lookup of filename: %w", err) } lineNumber := li.LineCol >> bpfLineShift lineColumn := li.LineCol & bpfColumnMax - return lineInfo{ + return LineOffset{ + asm.RawInstructionOffset(li.InsnOff), &Line{ fileName, line, lineNumber, lineColumn, }, - asm.RawInstructionOffset(li.InsnOff), }, nil } -func newLineInfos(blis []bpfLineInfo, strings *stringTable) (LineInfos, error) { - lis := LineInfos{ - infos: make([]lineInfo, 0, len(blis)), - } +func newLineInfos(blis []bpfLineInfo, strings *stringTable) (LineOffsets, error) { + lis := make([]LineOffset, 0, len(blis)) for _, bli := range blis { li, err := newLineInfo(bli, strings) if err != nil { - return LineInfos{}, fmt.Errorf("offset %d: %w", bli.InsnOff, err) + return LineOffsets{}, fmt.Errorf("offset %d: %w", bli.InsnOff, err) } - lis.infos = append(lis.infos, li) + lis = append(lis, li) } - sort.Slice(lis.infos, func(i, j int) bool { - return lis.infos[i].offset <= lis.infos[j].offset + sort.Slice(lis, func(i, j int) bool { + return lis[i].Offset <= lis[j].Offset }) return lis, nil } // marshal writes the binary representation of the LineInfo to w. -func (li *lineInfo) marshal(w *bytes.Buffer, b *Builder) error { - line := li.line +func (li *LineOffset) marshal(w *bytes.Buffer, b *Builder) error { + line := li.Line if line.lineNumber > bpfLineMax { return fmt.Errorf("line %d exceeds %d", line.lineNumber, bpfLineMax) } @@ -621,7 +620,7 @@ func (li *lineInfo) marshal(w *bytes.Buffer, b *Builder) error { } bli := bpfLineInfo{ - uint32(li.offset), + uint32(li.Offset), fileNameOff, lineOff, (line.lineNumber << bpfLineShift) | line.lineColumn, From a34ec466d2729bff54cf47182bd20e7470e3190d Mon Sep 17 00:00:00 2001 From: Leon Hwang Date: Mon, 25 Nov 2024 21:01:22 +0800 Subject: [PATCH 2/5] info: expose more prog jited info Expose these prog jited info: 1. JITed machine native instructions torvalds/linux@1e2709769086 ("bpf: Add BPF_OBJ_GET_INFO_BY_FD") 2. JITed line info torvalds/linux@c454a46b5efd ("bpf: Add bpf_line_info support") 3. JITed image lengths torvalds/linux@815581c11cc2 ("bpf: get JITed image lengths of functions via syscall") Signed-off-by: Leon Hwang --- info.go | 164 +++++++++++++++++++++++++++++----- internal/cmd/gentypes/main.go | 3 + internal/sys/types.go | 6 +- 3 files changed, 149 insertions(+), 24 deletions(-) diff --git a/info.go b/info.go index 3652fd779..4458434bd 100644 --- a/info.go +++ b/info.go @@ -179,6 +179,40 @@ type programStats struct { recursionMisses uint64 } +// programJitedInfo holds information about JITed info of a program. +type programJitedInfo struct { + // ksyms holds the ksym addresses of the BPF program, including those of its + // subprograms. + // + // Available from 4.18. + ksyms []uintptr + numKsyms uint32 + + // insns holds the JITed machine native instructions of the program, + // including those of its subprograms. + // + // Available from 4.13. + insns []byte + numInsns uint32 + + // lineInfos holds the JITed line infos, which are kernel addresses. + // + // Available from 5.0. + lineInfos []uint64 + numLineInfos uint32 + + // lineInfoRecSize is the size of a single line info record. + // + // Available from 5.0. + lineInfoRecSize uint32 + + // funcLens holds the insns length of each function. + // + // Available from 4.18. + funcLens []uint32 + numFuncLens uint32 +} + // ProgramInfo describes a program. type ProgramInfo struct { Type ProgramType @@ -199,12 +233,12 @@ type ProgramInfo struct { jitedSize uint32 verifiedInstructions uint32 + jitedInfo programJitedInfo + lineInfos []byte numLineInfos uint32 funcInfos []byte numFuncInfos uint32 - ksymInfos []uint64 - numKsymInfos uint32 } func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { @@ -282,11 +316,37 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { makeSecondCall = true } + pi.jitedInfo.lineInfoRecSize = info.JitedLineInfoRecSize + if info.JitedProgLen > 0 { + pi.jitedInfo.numInsns = info.JitedProgLen + pi.jitedInfo.insns = make([]byte, info.JitedProgLen) + info2.JitedProgLen = info.JitedProgLen + info2.JitedProgInsns = sys.NewSlicePointer(pi.jitedInfo.insns) + makeSecondCall = true + } + + if info.NrJitedFuncLens > 0 { + pi.jitedInfo.numFuncLens = info.NrJitedFuncLens + pi.jitedInfo.funcLens = make([]uint32, info.NrJitedFuncLens) + info2.NrJitedFuncLens = info.NrJitedFuncLens + info2.JitedFuncLens = sys.NewSlicePointer(pi.jitedInfo.funcLens) + makeSecondCall = true + } + + if info.NrJitedLineInfo > 0 { + pi.jitedInfo.numLineInfos = info.NrJitedLineInfo + pi.jitedInfo.lineInfos = make([]uint64, info.NrJitedLineInfo) + info2.NrJitedLineInfo = info.NrJitedLineInfo + info2.JitedLineInfo = sys.NewSlicePointer(pi.jitedInfo.lineInfos) + info2.JitedLineInfoRecSize = info.JitedLineInfoRecSize + makeSecondCall = true + } + if info.NrJitedKsyms > 0 { - pi.ksymInfos = make([]uint64, info.NrJitedKsyms) - info2.JitedKsyms = sys.NewSlicePointer(pi.ksymInfos) + pi.jitedInfo.numKsyms = info.NrJitedKsyms + pi.jitedInfo.ksyms = make([]uintptr, info.NrJitedKsyms) + info2.JitedKsyms = sys.NewSlicePointer(pi.jitedInfo.ksyms) info2.NrJitedKsyms = info.NrJitedKsyms - pi.numKsymInfos = info.NrJitedKsyms makeSecondCall = true } @@ -380,6 +440,52 @@ func (pi *ProgramInfo) RecursionMisses() (uint64, bool) { return 0, false } +// btfSpec returns the BTF spec associated with the program. +func (pi *ProgramInfo) btfSpec() (*btf.Spec, error) { + id, ok := pi.BTFID() + if !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 spec, nil +} + +// LineInfos returns the BTF line information of the 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) LineInfos() (btf.LineOffsets, error) { + if len(pi.lineInfos) == 0 { + return nil, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported) + } + + spec, err := pi.btfSpec() + if err != nil { + return nil, err + } + + return btf.LoadLineInfos( + bytes.NewReader(pi.lineInfos), + internal.NativeEndian, + pi.numLineInfos, + spec, + ) +} + // Instructions returns the 'xlated' instruction stream of the program // after it has been verified and rewritten by the kernel. These instructions // cannot be loaded back into the kernel as-is, this is mainly used for @@ -524,11 +630,34 @@ func (pi *ProgramInfo) VerifiedInstructions() (uint32, bool) { // // 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 + return pi.jitedInfo.ksyms, len(pi.jitedInfo.ksyms) > 0 +} + +// JitedInsns returns the JITed machine native instructions of the program. +// +// Available from 4.13. +// +// The bool return value indicates whether this optional field is available. +func (pi *ProgramInfo) JitedInsns() ([]byte, bool) { + return pi.jitedInfo.insns, len(pi.jitedInfo.insns) > 0 +} + +// JitedLineInfos returns the JITed line infos of the program. +// +// Available from 5.0. +// +// The bool return value indicates whether this optional field is available. +func (pi *ProgramInfo) JitedLineInfos() ([]uint64, bool) { + return pi.jitedInfo.lineInfos, len(pi.jitedInfo.lineInfos) > 0 +} + +// JitedFuncLens returns the insns length of each function in the JITed program. +// +// Available from 4.18. +// +// The bool return value indicates whether this optional field is available. +func (pi *ProgramInfo) JitedFuncLens() ([]uint32, bool) { + return pi.jitedInfo.funcLens, len(pi.jitedInfo.funcLens) > 0 } // FuncInfos returns the offset and function information of all (sub)programs in @@ -540,20 +669,13 @@ func (pi *ProgramInfo) KsymAddrs() ([]uintptr, bool) { // 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) + if len(pi.funcInfos) == 0 { + return nil, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported) } - defer h.Close() - spec, err := h.Spec(nil) + spec, err := pi.btfSpec() if err != nil { - return nil, fmt.Errorf("get BTF spec: %w", err) + return nil, err } return btf.LoadFuncInfos( diff --git a/internal/cmd/gentypes/main.go b/internal/cmd/gentypes/main.go index 11f285943..c0225d3b2 100644 --- a/internal/cmd/gentypes/main.go +++ b/internal/cmd/gentypes/main.go @@ -197,10 +197,13 @@ import ( "ProgInfo", "bpf_prog_info", []patch{ replace(objName, "name"), + replace(pointer, "jited_prog_insns"), replace(pointer, "xlated_prog_insns"), replace(pointer, "map_ids"), replace(pointer, "line_info"), + replace(pointer, "jited_line_info"), replace(pointer, "jited_ksyms"), + replace(pointer, "jited_func_lens"), replace(pointer, "func_info"), replace(btfID, "btf_id", "attach_btf_obj_id"), replace(typeID, "attach_btf_id"), diff --git a/internal/sys/types.go b/internal/sys/types.go index 19b287a5c..f8792da05 100644 --- a/internal/sys/types.go +++ b/internal/sys/types.go @@ -720,7 +720,7 @@ type ProgInfo struct { Tag [8]uint8 JitedProgLen uint32 XlatedProgLen uint32 - JitedProgInsns uint64 + JitedProgInsns Pointer XlatedProgInsns Pointer LoadTime uint64 CreatedByUid uint32 @@ -734,14 +734,14 @@ type ProgInfo struct { NrJitedKsyms uint32 NrJitedFuncLens uint32 JitedKsyms Pointer - JitedFuncLens uint64 + JitedFuncLens Pointer BtfId BTFID FuncInfoRecSize uint32 FuncInfo Pointer NrFuncInfo uint32 NrLineInfo uint32 LineInfo Pointer - JitedLineInfo uint64 + JitedLineInfo Pointer NrJitedLineInfo uint32 LineInfoRecSize uint32 JitedLineInfoRecSize uint32 From 3ab546b081e065102268c66768036cbabbba1a55 Mon Sep 17 00:00:00 2001 From: Timo Beckers Date: Wed, 4 Dec 2024 11:47:38 +0100 Subject: [PATCH 3/5] info: rename KsymAddrs to JitedKsymAddrs Since the original field is called jited_ksyms, reflect that in the Go API as well. Signed-off-by: Timo Beckers --- info.go | 7 ++++--- info_test.go | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/info.go b/info.go index 4458434bd..56a1f1e9a 100644 --- a/info.go +++ b/info.go @@ -623,13 +623,14 @@ func (pi *ProgramInfo) VerifiedInstructions() (uint32, bool) { return pi.verifiedInstructions, pi.verifiedInstructions > 0 } -// KsymAddrs returns the ksym addresses of the BPF program, including its +// JitedKsymAddrs returns the ksym addresses of the BPF program, including its // subprograms. The addresses correspond to their symbols in /proc/kallsyms. // -// Available from 4.18. +// Available from 4.18. Note that before 5.x, this field can be empty for +// programs without subprograms (bpf2bpf calls). // // The bool return value indicates whether this optional field is available. -func (pi *ProgramInfo) KsymAddrs() ([]uintptr, bool) { +func (pi *ProgramInfo) JitedKsymAddrs() ([]uintptr, bool) { return pi.jitedInfo.ksyms, len(pi.jitedInfo.ksyms) > 0 } diff --git a/info_test.go b/info_test.go index ede2a1e83..607159e78 100644 --- a/info_test.go +++ b/info_test.go @@ -528,7 +528,7 @@ func TestProgInfoKsym(t *testing.T) { info, err := obj.Prog.Info() qt.Assert(t, qt.IsNil(err)) - addrs, ok := info.KsymAddrs() + addrs, ok := info.JitedKsymAddrs() qt.Assert(t, qt.IsTrue(ok)) qt.Assert(t, qt.HasLen(addrs, 5)) for _, addr := range addrs { From f38bc38ab3dd50ac9360a4d8e0f7e75e99111b6a Mon Sep 17 00:00:00 2001 From: Timo Beckers Date: Wed, 4 Dec 2024 10:13:25 +0100 Subject: [PATCH 4/5] info: split TestProgramInfo into fd and proc tests This was getting hard to follow with the 'name == "proc"' statements sprinkled around. These are actually separate tests, proc only populates type and tag. Remove negative tests from TestProgramInfoProc as newProgramInfoFromProc only sets those two fields. Signed-off-by: Timo Beckers --- info_test.go | 137 +++++++++++++++++++++++---------------------------- 1 file changed, 61 insertions(+), 76 deletions(-) diff --git a/info_test.go b/info_test.go index 607159e78..224fe2ceb 100644 --- a/info_test.go +++ b/info_test.go @@ -70,93 +70,78 @@ func TestMapInfoFromProcOuterMap(t *testing.T) { qt.Assert(t, qt.Equals(info.MaxEntries, 2)) } +func validateProgInfo(t *testing.T, info *ProgramInfo) { + t.Helper() + + qt.Assert(t, qt.Equals(info.Type, SocketFilter)) + qt.Assert(t, qt.Equals(info.Tag, "d7edec644f05498d")) +} + func TestProgramInfo(t *testing.T) { prog := mustSocketFilter(t) - for name, fn := range map[string]func(*sys.FD) (*ProgramInfo, error){ - "generic": newProgramInfoFromFd, - "proc": newProgramInfoFromProc, - } { - t.Run(name, func(t *testing.T) { - info, err := fn(prog.fd) - testutils.SkipIfNotSupported(t, err) - if err != nil { - t.Fatal("Can't get program info:", err) - } + info, err := newProgramInfoFromFd(prog.fd) + testutils.SkipIfNotSupported(t, err) + qt.Assert(t, qt.IsNil(err)) - if info.Type != SocketFilter { - t.Error("Expected Type to be SocketFilter, got", info.Type) - } + validateProgInfo(t, info) - if info.Name != "" && info.Name != "test" { - t.Error("Expected Name to be test, got", info.Name) - } + id, ok := info.ID() + qt.Assert(t, qt.IsTrue(ok)) + qt.Assert(t, qt.Not(qt.Equals(id, 0))) - if want := "d7edec644f05498d"; info.Tag != want { - t.Errorf("Expected Tag to be %s, got %s", want, info.Tag) - } + if testutils.IsKernelLessThan(t, "4.15") { + qt.Assert(t, qt.Equals(info.Name, "")) + } else { + qt.Assert(t, qt.Equals(info.Name, "test")) + } - if id, ok := info.ID(); ok && id == 0 { - t.Error("Expected a valid ID:", id) - } else if name == "proc" && ok { - t.Error("Expected ID to not be available") - } + if jitedSize, err := info.JitedSize(); testutils.IsKernelLessThan(t, "4.13") { + qt.Assert(t, qt.IsNotNil(err)) + } else { + qt.Assert(t, qt.IsNil(err)) + qt.Assert(t, qt.IsTrue(jitedSize > 0)) + } - if name == "proc" { - _, err := info.JitedSize() - qt.Assert(t, qt.IsNotNil(err)) - - _, err = info.TranslatedSize() - qt.Assert(t, qt.IsNotNil(err)) - - _, ok := info.CreatedByUID() - qt.Assert(t, qt.IsFalse(ok)) - - _, ok = info.LoadTime() - qt.Assert(t, qt.IsFalse(ok)) - - _, ok = info.VerifiedInstructions() - qt.Assert(t, qt.IsFalse(ok)) - } else { - if jitedSize, err := info.JitedSize(); testutils.IsKernelLessThan(t, "4.13") { - qt.Assert(t, qt.IsNotNil(err)) - } else { - qt.Assert(t, qt.IsNil(err)) - qt.Assert(t, qt.IsTrue(jitedSize > 0)) - } - - if xlatedSize, err := info.TranslatedSize(); testutils.IsKernelLessThan(t, "4.13") { - qt.Assert(t, qt.IsNotNil(err)) - } else { - qt.Assert(t, qt.IsNil(err)) - qt.Assert(t, qt.IsTrue(xlatedSize > 0)) - } - - if uid, ok := info.CreatedByUID(); testutils.IsKernelLessThan(t, "4.15") { - qt.Assert(t, qt.IsFalse(ok)) - } else { - qt.Assert(t, qt.IsTrue(ok)) - qt.Assert(t, qt.Equals(uid, uint32(os.Getuid()))) - } - - if loadTime, ok := info.LoadTime(); testutils.IsKernelLessThan(t, "4.15") { - qt.Assert(t, qt.IsFalse(ok)) - } else { - qt.Assert(t, qt.IsTrue(ok)) - qt.Assert(t, qt.IsTrue(loadTime > 0)) - } - - if verifiedInsns, ok := info.VerifiedInstructions(); testutils.IsKernelLessThan(t, "5.16") { - qt.Assert(t, qt.IsFalse(ok)) - } else { - qt.Assert(t, qt.IsTrue(ok)) - qt.Assert(t, qt.IsTrue(verifiedInsns > 0)) - } - } - }) + if xlatedSize, err := info.TranslatedSize(); testutils.IsKernelLessThan(t, "4.13") { + qt.Assert(t, qt.IsNotNil(err)) + } else { + qt.Assert(t, qt.IsNil(err)) + qt.Assert(t, qt.IsTrue(xlatedSize > 0)) + } + + if uid, ok := info.CreatedByUID(); testutils.IsKernelLessThan(t, "4.15") { + qt.Assert(t, qt.IsFalse(ok)) + } else { + qt.Assert(t, qt.IsTrue(ok)) + qt.Assert(t, qt.Equals(uid, uint32(os.Getuid()))) + } + + if loadTime, ok := info.LoadTime(); testutils.IsKernelLessThan(t, "4.15") { + qt.Assert(t, qt.IsFalse(ok)) + } else { + qt.Assert(t, qt.IsTrue(ok)) + qt.Assert(t, qt.IsTrue(loadTime > 0)) + } + + if verifiedInsns, ok := info.VerifiedInstructions(); testutils.IsKernelLessThan(t, "5.16") { + qt.Assert(t, qt.IsFalse(ok)) + } else { + qt.Assert(t, qt.IsTrue(ok)) + qt.Assert(t, qt.IsTrue(verifiedInsns > 0)) } } +func TestProgramInfoProc(t *testing.T) { + prog := mustSocketFilter(t) + + info, err := newProgramInfoFromProc(prog.fd) + testutils.SkipIfNotSupported(t, err) + qt.Assert(t, qt.IsNil(err)) + + validateProgInfo(t, info) +} + func TestProgramInfoMapIDs(t *testing.T) { arr, err := NewMap(&MapSpec{ Type: Array, From 7514cdfe9abfd3cdea2c56f7f18d0a9b938760e7 Mon Sep 17 00:00:00 2001 From: Timo Beckers Date: Wed, 4 Dec 2024 10:42:25 +0100 Subject: [PATCH 5/5] info: add tests for recent ProgramInfo API additions Added tests for: - FuncInfos() - LineInfos() - JitedKsymAddrs() - JitedInsns() - JitedLineInfos() - JitedFuncLens() The existing test for JitedKsymAddrs was removed because it's not strictly necessary to load a CollectionSpec to exercise it. It obscured the fact that it seems like kernels before at least 5.4 need to have a subprog for jited addrs to be returned in prog info. TestProgInfoFuncInfos was removed for a similar reason. It's possible to craft a program using the asm package that meets the criteria for exercising FuncInfos() and LineInfos() in a deterministic way, no need to load a Collection. Signed-off-by: Timo Beckers --- info_test.go | 135 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 83 insertions(+), 52 deletions(-) diff --git a/info_test.go b/info_test.go index 224fe2ceb..1134b69c5 100644 --- a/info_test.go +++ b/info_test.go @@ -17,6 +17,30 @@ import ( "github.com/cilium/ebpf/internal/testutils" ) +var btfFn = &btf.Func{ + Name: "_", + Type: &btf.FuncProto{ + Return: &btf.Int{Size: 16}, + Params: []btf.FuncParam{}, + }, + Linkage: btf.StaticFunc, +} + +var multiprogSpec = &ProgramSpec{ + Name: "test", + Type: SocketFilter, + Instructions: asm.Instructions{ + btf.WithFuncMetadata(asm.LoadImm(asm.R0, 0, asm.DWord), btfFn). + WithSource(asm.Comment("line info")), + asm.Call.Label("fn"), + asm.Return(), + btf.WithFuncMetadata(asm.LoadImm(asm.R0, 0, asm.DWord), btfFn). + WithSource(asm.Comment("line info")).WithSymbol("fn"), + asm.Return(), + }, + License: "MIT", +} + func TestMapInfoFromProc(t *testing.T) { hash, err := NewMap(&MapSpec{ Type: Hash, @@ -130,6 +154,13 @@ func TestProgramInfo(t *testing.T) { qt.Assert(t, qt.IsTrue(ok)) qt.Assert(t, qt.IsTrue(verifiedInsns > 0)) } + + if insns, ok := info.JitedInsns(); testutils.IsKernelLessThan(t, "4.13") { + qt.Assert(t, qt.IsFalse(ok)) + } else { + qt.Assert(t, qt.IsTrue(ok)) + qt.Assert(t, qt.IsTrue(len(insns) > 0)) + } } func TestProgramInfoProc(t *testing.T) { @@ -142,6 +173,58 @@ func TestProgramInfoProc(t *testing.T) { validateProgInfo(t, info) } +func TestProgramInfoBTF(t *testing.T) { + prog, err := NewProgram(multiprogSpec) + testutils.SkipIfNotSupported(t, err) + qt.Assert(t, qt.IsNil(err)) + t.Cleanup(func() { prog.Close() }) + + info, err := prog.Info() + testutils.SkipIfNotSupported(t, err) + qt.Assert(t, qt.IsNil(err)) + + // On kernels before 5.x, nr_jited_ksyms is not set for programs without subprogs. + // It's included here since this test uses a bpf program with subprogs. + if addrs, ok := info.JitedKsymAddrs(); testutils.IsKernelLessThan(t, "4.18") { + qt.Assert(t, qt.IsFalse(ok)) + } else { + qt.Assert(t, qt.IsTrue(ok)) + qt.Assert(t, qt.IsTrue(len(addrs) > 0)) + } + + if lens, ok := info.JitedFuncLens(); testutils.IsKernelLessThan(t, "4.18") { + qt.Assert(t, qt.IsFalse(ok)) + } else { + qt.Assert(t, qt.IsTrue(ok)) + qt.Assert(t, qt.IsTrue(len(lens) > 0)) + } + + if infos, ok := info.JitedLineInfos(); testutils.IsKernelLessThan(t, "5.0") { + qt.Assert(t, qt.IsFalse(ok)) + } else { + qt.Assert(t, qt.IsTrue(ok)) + qt.Assert(t, qt.IsTrue(len(infos) > 0)) + } + + if funcs, err := info.FuncInfos(); testutils.IsKernelLessThan(t, "5.0") { + qt.Assert(t, qt.IsNotNil(err)) + } else { + qt.Assert(t, qt.IsNil(err)) + qt.Assert(t, qt.HasLen(funcs, 2)) + qt.Assert(t, qt.ContentEquals(funcs[0].Func, btfFn)) + qt.Assert(t, qt.ContentEquals(funcs[1].Func, btfFn)) + } + + if lines, err := info.LineInfos(); testutils.IsKernelLessThan(t, "5.0") { + qt.Assert(t, qt.IsNotNil(err)) + } else { + qt.Assert(t, qt.IsNil(err)) + qt.Assert(t, qt.HasLen(lines, 2)) + qt.Assert(t, qt.Equals(lines[0].Line.Line(), "line info")) + qt.Assert(t, qt.Equals(lines[1].Line.Line(), "line info")) + } +} + func TestProgramInfoMapIDs(t *testing.T) { arr, err := NewMap(&MapSpec{ Type: Array, @@ -495,55 +578,3 @@ 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.JitedKsymAddrs() - 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, ""))) - } -}