Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add metadata to instructions returned by program info #1118

Merged
merged 4 commits into from
Sep 13, 2023
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
2 changes: 1 addition & 1 deletion btf/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ func TestCORERelocation(t *testing.T) {
name := strings.TrimPrefix(section, "socket_filter/")
t.Run(name, func(t *testing.T) {
var relos []*CORERelocation
for _, reloInfo := range extInfos.relocationInfos[section] {
for _, reloInfo := range extInfos.relocationInfos[section].infos {
relos = append(relos, reloInfo.relo)
}

Expand Down
127 changes: 94 additions & 33 deletions btf/ext_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import (
// ExtInfos contains ELF section metadata.
type ExtInfos struct {
// The slices are sorted by offset in ascending order.
funcInfos map[string][]funcInfo
lineInfos map[string][]lineInfo
relocationInfos map[string][]coreRelocationInfo
funcInfos map[string]FuncInfos
lineInfos map[string]LineInfos
relocationInfos map[string]CORERelocationInfos
}

// loadExtInfosFromELF parses ext infos from the .BTF.ext section in an ELF.
Expand Down Expand Up @@ -58,7 +58,7 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec, strings *strin
return nil, fmt.Errorf("parsing BTF function info: %w", err)
}

funcInfos := make(map[string][]funcInfo, len(btfFuncInfos))
funcInfos := make(map[string]FuncInfos, len(btfFuncInfos))
for section, bfis := range btfFuncInfos {
funcInfos[section], err = newFuncInfos(bfis, spec)
if err != nil {
Expand All @@ -72,7 +72,7 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec, strings *strin
return nil, fmt.Errorf("parsing BTF line info: %w", err)
}

lineInfos := make(map[string][]lineInfo, len(btfLineInfos))
lineInfos := make(map[string]LineInfos, len(btfLineInfos))
for section, blis := range btfLineInfos {
lineInfos[section], err = newLineInfos(blis, strings)
if err != nil {
Expand All @@ -91,7 +91,7 @@ func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec, strings *strin
return nil, fmt.Errorf("parsing CO-RE relocation info: %w", err)
}

coreRelos := make(map[string][]coreRelocationInfo, len(btfCORERelos))
coreRelos := make(map[string]CORERelocationInfos, len(btfCORERelos))
for section, brs := range btfCORERelos {
coreRelos[section], err = newRelocationInfos(brs, spec, strings)
if err != nil {
Expand All @@ -111,21 +111,31 @@ func (ei *ExtInfos) Assign(insns asm.Instructions, section string) {
lineInfos := ei.lineInfos[section]
reloInfos := ei.relocationInfos[section]

AssignMetadataToInstructions(insns, funcInfos, lineInfos, reloInfos)
}

// Assign per-instruction metadata to the instructions in insns.
dylandreimerink marked this conversation as resolved.
Show resolved Hide resolved
func AssignMetadataToInstructions(
insns asm.Instructions,
funcInfos FuncInfos,
lineInfos LineInfos,
reloInfos CORERelocationInfos,
) {
iter := insns.Iterate()
for iter.Next() {
if len(funcInfos) > 0 && funcInfos[0].offset == iter.Offset {
*iter.Ins = WithFuncMetadata(*iter.Ins, funcInfos[0].fn)
funcInfos = funcInfos[1:]
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(lineInfos) > 0 && lineInfos[0].offset == iter.Offset {
*iter.Ins = iter.Ins.WithSource(lineInfos[0].line)
lineInfos = lineInfos[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(reloInfos) > 0 && reloInfos[0].offset == iter.Offset {
iter.Ins.Metadata.Set(coreRelocationMeta{}, reloInfos[0].relo)
reloInfos = reloInfos[1:]
if len(reloInfos.infos) > 0 && reloInfos.infos[0].offset == iter.Offset {
iter.Ins.Metadata.Set(coreRelocationMeta{}, reloInfos.infos[0].relo)
reloInfos.infos = reloInfos.infos[1:]
}
}
}
Expand Down Expand Up @@ -323,6 +333,11 @@ 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
}

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

Expand Down Expand Up @@ -359,21 +374,38 @@ func newFuncInfo(fi bpfFuncInfo, spec *Spec) (*funcInfo, error) {
}, nil
}

func newFuncInfos(bfis []bpfFuncInfo, spec *Spec) ([]funcInfo, error) {
fis := make([]funcInfo, 0, len(bfis))
func newFuncInfos(bfis []bpfFuncInfo, spec *Spec) (FuncInfos, error) {
fis := FuncInfos{
infos: make([]funcInfo, 0, len(bfis)),
}
for _, bfi := range bfis {
fi, err := newFuncInfo(bfi, spec)
if err != nil {
return nil, fmt.Errorf("offset %d: %w", bfi.InsnOff, err)
return FuncInfos{}, fmt.Errorf("offset %d: %w", bfi.InsnOff, err)
}
fis = append(fis, *fi)
fis.infos = append(fis.infos, *fi)
}
sort.Slice(fis, func(i, j int) bool {
return fis[i].offset <= fis[j].offset
sort.Slice(fis.infos, func(i, j int) bool {
return fis.infos[i].offset <= fis.infos[j].offset
})
return fis, nil
}

// LoadFuncInfos parses btf func info in wire format.
func LoadFuncInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec *Spec) (FuncInfos, error) {
fis, err := parseFuncInfoRecords(
reader,
bo,
FuncInfoSize,
recordNum,
)
if err != nil {
return FuncInfos{}, fmt.Errorf("parsing BTF func info: %w", err)
}

return newFuncInfos(fis, spec)
}

// marshal into the BTF wire format.
func (fi *funcInfo) marshal(w *bytes.Buffer, b *Builder) error {
id, err := b.Add(fi.fn)
Expand Down Expand Up @@ -480,6 +512,11 @@ func (li *Line) String() string {
return li.line
}

// LineInfos contains a sorted list of line infos.
type LineInfos struct {
infos []lineInfo
}

type lineInfo struct {
line *Line
offset asm.RawInstructionOffset
Expand All @@ -500,6 +537,21 @@ type bpfLineInfo struct {
LineCol uint32
}

// LoadLineInfos parses btf line info in wire format.
func LoadLineInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec *Spec) (LineInfos, error) {
lis, err := parseLineInfoRecords(
reader,
bo,
LineInfoSize,
recordNum,
)
if err != nil {
return LineInfos{}, fmt.Errorf("parsing BTF line info: %w", err)
}

return newLineInfos(lis, spec.strings)
}

func newLineInfo(li bpfLineInfo, strings *stringTable) (*lineInfo, error) {
line, err := strings.Lookup(li.LineOff)
if err != nil {
Expand All @@ -525,17 +577,19 @@ func newLineInfo(li bpfLineInfo, strings *stringTable) (*lineInfo, error) {
}, nil
}

func newLineInfos(blis []bpfLineInfo, strings *stringTable) ([]lineInfo, error) {
lis := make([]lineInfo, 0, len(blis))
func newLineInfos(blis []bpfLineInfo, strings *stringTable) (LineInfos, error) {
lis := LineInfos{
infos: make([]lineInfo, 0, len(blis)),
}
for _, bli := range blis {
li, err := newLineInfo(bli, strings)
if err != nil {
return nil, fmt.Errorf("offset %d: %w", bli.InsnOff, err)
return LineInfos{}, fmt.Errorf("offset %d: %w", bli.InsnOff, err)
}
lis = append(lis, *li)
lis.infos = append(lis.infos, *li)
}
sort.Slice(lis, func(i, j int) bool {
return lis[i].offset <= lis[j].offset
sort.Slice(lis.infos, func(i, j int) bool {
return lis.infos[i].offset <= lis.infos[j].offset
})
return lis, nil
}
Expand Down Expand Up @@ -661,6 +715,11 @@ func CORERelocationMetadata(ins *asm.Instruction) *CORERelocation {
return relo
}

// CORERelocationInfos contains a sorted list of co:re relocation infos.
type CORERelocationInfos struct {
infos []coreRelocationInfo
}

type coreRelocationInfo struct {
relo *CORERelocation
offset asm.RawInstructionOffset
Expand Down Expand Up @@ -693,17 +752,19 @@ func newRelocationInfo(relo bpfCORERelo, spec *Spec, strings *stringTable) (*cor
}, nil
}

func newRelocationInfos(brs []bpfCORERelo, spec *Spec, strings *stringTable) ([]coreRelocationInfo, error) {
rs := make([]coreRelocationInfo, 0, len(brs))
func newRelocationInfos(brs []bpfCORERelo, spec *Spec, strings *stringTable) (CORERelocationInfos, error) {
rs := CORERelocationInfos{
infos: make([]coreRelocationInfo, 0, len(brs)),
}
for _, br := range brs {
relo, err := newRelocationInfo(br, spec, strings)
if err != nil {
return nil, fmt.Errorf("offset %d: %w", br.InsnOff, err)
return CORERelocationInfos{}, fmt.Errorf("offset %d: %w", br.InsnOff, err)
}
rs = append(rs, *relo)
rs.infos = append(rs.infos, *relo)
}
sort.Slice(rs, func(i, j int) bool {
return rs[i].offset < rs[j].offset
sort.Slice(rs.infos, func(i, j int) bool {
return rs.infos[i].offset < rs.infos[j].offset
})
return rs, nil
}
Expand Down
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)
dylandreimerink marked this conversation as resolved.
Show resolved Hide resolved
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
Loading