Skip to content

Commit

Permalink
btf: hide LineInfo wire format
Browse files Browse the repository at this point in the history
As with FuncInfo, transform LineInfo into something we can export
without tying us to the BPF wire format. In the future I'd like
to just make all fields of LineInfo exported, but that requires
being able to write out BTF on the fly.
  • Loading branch information
lmb authored and ti-mo committed Feb 23, 2022
1 parent 4ef1a98 commit 279bd96
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 32 deletions.
27 changes: 23 additions & 4 deletions internal/btf/btf.go
Expand Up @@ -247,11 +247,30 @@ func (spec *Spec) splitExtInfos(info *extInfo) error {
return fmt.Errorf("section %s: error looking up FuncInfo for LineInfo %v", secName, li)
}

// Offsets are ELF section-scoped, make them function-scoped by
// subtracting the function's start offset.
li.InsnOff -= fnOffset
line, err := spec.strings.Lookup(li.LineOff)
if err != nil {
return fmt.Errorf("lookup of line: %w", err)
}

fileName, err := spec.strings.Lookup(li.FileNameOff)
if err != nil {
return fmt.Errorf("lookup of filename: %w", err)
}

oli[fn.Name] = append(oli[fn.Name], li)
lineNumber := li.LineCol >> bpfLineShift
lineColumn := li.LineCol & bpfColumnMax

oli[fn.Name] = append(oli[fn.Name], LineInfo{
fileName,
line,
lineNumber,
lineColumn,
// Offsets are ELF section-scoped, make them function-scoped by
// subtracting the function's start offset.
li.InsnOff - fnOffset,
li.FileNameOff,
li.LineOff,
})
}
}

Expand Down
102 changes: 80 additions & 22 deletions internal/btf/ext_info.go
Expand Up @@ -16,7 +16,7 @@ import (
// It is indexed per section.
type extInfo struct {
funcInfos map[string][]bpfFuncInfo
lineInfos map[string]LineInfos
lineInfos map[string][]bpfLineInfo
relos map[string]CoreRelos
}

Expand Down Expand Up @@ -273,44 +273,98 @@ func parseFuncInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, r
return nil, fmt.Errorf("offset %v is not aligned with instruction size", fi.InsnOff)
}

// ELF tracks offset in bytes, the kernel expects raw BPF instructions.
// Convert as early as possible.
fi.InsnOff /= asm.InstructionSize

out = append(out, fi)
}

return out, nil
}

var LineInfoSize = uint32(binary.Size(bpfLineInfo{}))

// LineInfo represents the location and contents of a single line of source
// code a BPF ELF was compiled from.
type LineInfo struct {
// Instruction offset of the function within an ELF section.
// After parsing a LineInfo from an ELF, this offset is relative to
// the function body instead of an ELF section.
fileName string
line string
lineNumber uint32
lineColumn uint32

// TODO: We should get rid of the fields below, but for that we need to be
// able to write BTF.

// Instruction offset of the line within its enclosing function, in instructions.
insnOff uint32
fileNameOff uint32
lineOff uint32
}

// Constants for the format of bpfLineInfo.LineCol.
const (
bpfLineShift = 10
bpfLineMax = (1 << (32 - bpfLineShift)) - 1
bpfColumnMax = (1 << bpfLineShift) - 1
)

type bpfLineInfo struct {
// Instruction offset of the line within the whole instruction stream, in instructions.
InsnOff uint32
FileNameOff uint32
LineOff uint32
LineCol uint32
}

func (li *LineInfo) FileName() string {
return li.fileName
}

func (li *LineInfo) Line() string {
return li.line
}

func (li *LineInfo) LineNumber() uint32 {
return li.lineNumber
}

func (li *LineInfo) LineColumn() uint32 {
return li.lineColumn
}

func (li *LineInfo) String() string {
return li.line
}

// Marshal writes the binary representation of the LineInfo to w.
// The instruction offset is converted from bytes to instructions.
func (li LineInfo) Marshal(w io.Writer, offset uint64) error {
li.InsnOff += uint32(offset)
// The kernel expects offsets in number of raw bpf instructions,
// while the ELF tracks it in bytes.
li.InsnOff /= asm.InstructionSize
return binary.Write(w, internal.NativeEndian, li)
}
func (li *LineInfo) Marshal(w io.Writer, offset uint64) error {
if li.lineNumber > bpfLineMax {
return fmt.Errorf("line %d exceeds %d", li.lineNumber, bpfLineMax)
}

type LineInfos []LineInfo
if li.lineColumn > bpfColumnMax {
return fmt.Errorf("column %d exceeds %d", li.lineColumn, bpfColumnMax)
}

// Marshal writes the binary representation of the LineInfos to w.
func (li LineInfos) Marshal(w io.Writer, off uint64) error {
if len(li) == 0 {
return nil
bli := bpfLineInfo{
li.insnOff + uint32(offset/asm.InstructionSize),
li.fileNameOff,
li.lineOff,
(li.lineNumber << bpfLineShift) | li.lineColumn,
}
return binary.Write(w, internal.NativeEndian, &bli)
}

type LineInfos []LineInfo

// Marshal writes the BTF wire format of the LineInfos to w.
//
// offset is the start of the enclosing function in bytes.
func (li LineInfos) Marshal(w io.Writer, offset uint64) error {
for _, info := range li {
if err := info.Marshal(w, off); err != nil {
if err := info.Marshal(w, offset); err != nil {
return err
}
}
Expand All @@ -320,13 +374,13 @@ func (li LineInfos) Marshal(w io.Writer, off uint64) error {

// parseLineInfos parses a line_info sub-section within .BTF.ext ito a map of
// line infos indexed by section name.
func parseLineInfos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]LineInfos, error) {
func parseLineInfos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string][]bpfLineInfo, error) {
recordSize, err := parseExtInfoRecordSize(r, bo)
if err != nil {
return nil, err
}

result := make(map[string]LineInfos)
result := make(map[string][]bpfLineInfo)
for {
secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
if errors.Is(err, io.EOF) {
Expand All @@ -348,9 +402,9 @@ func parseLineInfos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[
// parseLineInfoRecords parses a stream of line_infos into a lineInfos.
// These records appear after a btf_ext_info_sec header in the line_info
// sub-section of .BTF.ext.
func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) (LineInfos, error) {
var out LineInfos
var li LineInfo
func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) ([]bpfLineInfo, error) {
var out []bpfLineInfo
var li bpfLineInfo

if exp, got := uint32(binary.Size(li)), recordSize; exp != got {
// BTF blob's record size is longer than we know how to parse.
Expand All @@ -366,6 +420,10 @@ func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, r
return nil, fmt.Errorf("offset %v is not aligned with instruction size", li.InsnOff)
}

// ELF tracks offset in bytes, the kernel expects raw BPF instructions.
// Convert as early as possible.
li.InsnOff /= asm.InstructionSize

out = append(out, li)
}

Expand Down
6 changes: 2 additions & 4 deletions linker.go
Expand Up @@ -2,11 +2,9 @@ package ebpf

import (
"bytes"
"encoding/binary"
"fmt"

"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal/btf"
)

// The linker is responsible for resolving bpf-to-bpf calls between programs
Expand Down Expand Up @@ -104,9 +102,9 @@ func marshalLineInfos(layout []reference) ([]byte, error) {
return nil, nil
}

buf := bytes.NewBuffer(make([]byte, 0, binary.Size(&btf.LineInfo{})*len(layout)))
var buf bytes.Buffer
for _, sym := range layout {
if err := sym.spec.BTF.LineInfos.Marshal(buf, sym.offset); err != nil {
if err := sym.spec.BTF.LineInfos.Marshal(&buf, sym.offset); err != nil {
return nil, fmt.Errorf("marshaling prog %s line infos: %w", sym.spec.Name, err)
}
}
Expand Down
4 changes: 2 additions & 2 deletions prog.go
Expand Up @@ -319,8 +319,8 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand
if err != nil {
return nil, err
}
attr.LineInfoRecSize = uint32(binary.Size(btf.LineInfo{}))
attr.LineInfoCnt = uint32(len(lib)) / attr.LineInfoRecSize
attr.LineInfoRecSize = btf.LineInfoSize
attr.LineInfoCnt = uint32(len(lib)) / btf.LineInfoSize
attr.LineInfo = sys.NewSlicePointer(lib)
}
}
Expand Down

0 comments on commit 279bd96

Please sign in to comment.