Skip to content

Commit

Permalink
info: Add metadata to instructions returned by program info
Browse files Browse the repository at this point in the history
The instructions returned by program info gotten from the kernel did
not contain any metadata such as line info or function info. This
makes it harder to read the instructions when comparing the xlated
instruction in the kernel with the spec.

This commit uses the line info and function info that is available via
the program info and adds it to the returned instructions.

Signed-off-by: Dylan Reimerink <dylan.reimerink@isovalent.com>
  • Loading branch information
dylandreimerink committed Sep 13, 2023
1 parent 488ec91 commit ea5a274
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 4 deletions.
88 changes: 84 additions & 4 deletions info.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ type ProgramInfo struct {

maps []MapID
insns []byte

lineInfos []byte
numLineInfos uint32
funcInfos []byte
numFuncInfos uint32
}

func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
Expand Down Expand Up @@ -128,10 +133,13 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
// Start with a clean struct for the second call, otherwise we may get EFAULT.
var info2 sys.ProgInfo

makeSecondCall := false

if info.NrMapIds > 0 {
pi.maps = make([]MapID, info.NrMapIds)
info2.NrMapIds = info.NrMapIds
info2.MapIds = sys.NewPointer(unsafe.Pointer(&pi.maps[0]))
makeSecondCall = true
} else if haveProgramInfoMapIDs() == nil {
// This program really has no associated maps.
pi.maps = make([]MapID, 0)
Expand All @@ -150,9 +158,28 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
pi.insns = make([]byte, info.XlatedProgLen)
info2.XlatedProgLen = info.XlatedProgLen
info2.XlatedProgInsns = sys.NewSlicePointer(pi.insns)
makeSecondCall = true
}

if info.NrLineInfo > 0 {
pi.lineInfos = make([]byte, btf.LineInfoSize*info.NrLineInfo)
info2.LineInfo = sys.NewSlicePointer(pi.lineInfos)
info2.LineInfoRecSize = btf.LineInfoSize
info2.NrLineInfo = info.NrLineInfo
pi.numLineInfos = info.NrLineInfo
makeSecondCall = true
}

if info.NrMapIds > 0 || info.XlatedProgLen > 0 {
if info.NrFuncInfo > 0 {
pi.funcInfos = make([]byte, btf.FuncInfoSize*info.NrFuncInfo)
info2.FuncInfo = sys.NewSlicePointer(pi.funcInfos)
info2.FuncInfoRecSize = btf.FuncInfoSize
info2.NrFuncInfo = info.NrFuncInfo
pi.numFuncInfos = info.NrFuncInfo
makeSecondCall = true
}

if makeSecondCall {
if err := sys.ObjInfo(fd, &info2); err != nil {
return nil, err
}
Expand Down Expand Up @@ -245,7 +272,13 @@ func (pi *ProgramInfo) Runtime() (time.Duration, bool) {
//
// The first instruction is marked as a symbol using the Program's name.
//
// Available from 4.13. Requires CAP_BPF or equivalent.
// If available, the instructions will be annotated with metadata from the
// BTF. This includes line information and function information. Reading
// this metadata requires CAP_SYS_ADMIN or equivalent. If capability is
// unavailable, the instructions will be returned without metadata.
//
// Available from 4.13. Requires CAP_BPF or equivalent for plain instructions.
// Requires CAP_SYS_ADMIN for instructions with metadata.
func (pi *ProgramInfo) Instructions() (asm.Instructions, error) {
// If the calling process is not BPF-capable or if the kernel doesn't
// support getting xlated instructions, the field will be zero.
Expand All @@ -259,8 +292,55 @@ func (pi *ProgramInfo) Instructions() (asm.Instructions, error) {
return nil, fmt.Errorf("unmarshaling instructions: %w", err)
}

// Tag the first instruction with the name of the program, if available.
insns[0] = insns[0].WithSymbol(pi.Name)
if pi.btf != 0 {
btfh, err := btf.NewHandleFromID(pi.btf)
if err != nil {
// Getting a BTF handle requires CAP_SYS_ADMIN, if not available we get an -EPERM.
// Ignore it and fall back to instructions without metadata.
if !errors.Is(err, unix.EPERM) {
return nil, fmt.Errorf("unable to get BTF handle: %w", err)
}
}

// If we have a BTF handle, we can use it to assign metadata to the instructions.
if btfh != nil {
defer btfh.Close()

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

lineInfos, err := btf.LoadLineInfos(
bytes.NewReader(pi.lineInfos),
internal.NativeEndian,
pi.numLineInfos,
spec,
)
if err != nil {
return nil, fmt.Errorf("parse line info: %w", err)
}

funcInfos, err := btf.LoadFuncInfos(
bytes.NewReader(pi.funcInfos),
internal.NativeEndian,
pi.numFuncInfos,
spec,
)
if err != nil {
return nil, fmt.Errorf("parse func info: %w", err)
}

btf.AssignMetadataToInstructions(insns, funcInfos, lineInfos, btf.CORERelocationInfos{})
}
}

fn := btf.FuncMetadata(&insns[0])
name := pi.Name
if fn != nil {
name = fn.Name
}
insns[0] = insns[0].WithSymbol(name)

return insns, nil
}
Expand Down
40 changes: 40 additions & 0 deletions info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"

"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/testutils"
Expand Down Expand Up @@ -365,3 +366,42 @@ func testStats(prog *Program) error {
func TestHaveProgramInfoMapIDs(t *testing.T) {
testutils.CheckFeatureTest(t, haveProgramInfoMapIDs)
}

func TestProgInfoExtBTF(t *testing.T) {
testutils.SkipOnOldKernel(t, "5.0", "Program BTF (func/line_info)")

spec, err := LoadCollectionSpec("testdata/raw_tracepoint-el.elf")
if err != nil {
panic(err)
}

coll, err := NewCollection(spec)
if err != nil {
panic(err)
}
defer coll.Close()

info, err := coll.Programs["sched_process_exec"].Info()
if err != nil {
panic(err)
}

inst, err := info.Instructions()
if err != nil {
panic(err)
}

const expectedSource = "\treturn 0;"
if inst[0].Source().String() != expectedSource {
t.Fatalf("Source of first instruction incorrect. Got '%s', expected: '%s'", inst[0].Source().String(), expectedSource)
}

fn := btf.FuncMetadata(&inst[0])
if fn == nil {
t.Fatal("Func metadata missing")
}

if fn.Name != "sched_process_exec" {
t.Fatalf("Func metadata incorrect. Got '%s', expected: 'sched_process_exec'", fn.Name)
}
}

0 comments on commit ea5a274

Please sign in to comment.