Skip to content

Commit

Permalink
btf,info: Fix bad instruction offset when parsing infos from kernel
Browse files Browse the repository at this point in the history
When BTF ext info is encoded in ELF the instruction offsets are in
bytes, but when you pass them to the kernel they must be instruction
indices. Therefore we divide the offset by the instruction size in
the parsing logic for ELF.

This was missed during the initial implementation of reading back BTF
ext info from the kernel. This would cause an error when loading
back ext info which was not a multiple of the instruction size or
a bad instruction offset if it was.

Fix this by making LoadLineInfos and LoadFuncInfos work on the kernel
format which contains instruction offsets while the ELF parser uses
bytes instead.

Signed-off-by: Dylan Reimerink <dylan.reimerink@isovalent.com>
Co-developed-by: Lorenz Bauer <lmb@isovalent.com>
  • Loading branch information
dylandreimerink authored and lmb committed Oct 19, 2023
1 parent c42caeb commit 50436f2
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 30 deletions.
42 changes: 24 additions & 18 deletions btf/ext_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,13 +391,14 @@ func newFuncInfos(bfis []bpfFuncInfo, spec *Spec) (FuncInfos, error) {
return fis, nil
}

// LoadFuncInfos parses btf func info in wire format.
// LoadFuncInfos parses BTF func info in kernel wire format.
func LoadFuncInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec *Spec) (FuncInfos, error) {
fis, err := parseFuncInfoRecords(
reader,
bo,
FuncInfoSize,
recordNum,
false,
)
if err != nil {
return FuncInfos{}, fmt.Errorf("parsing BTF func info: %w", err)
Expand Down Expand Up @@ -441,7 +442,7 @@ func parseFuncInfos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map
return nil, err
}

records, err := parseFuncInfoRecords(r, bo, recordSize, infoHeader.NumInfo)
records, err := parseFuncInfoRecords(r, bo, recordSize, infoHeader.NumInfo, true)
if err != nil {
return nil, fmt.Errorf("section %v: %w", secName, err)
}
Expand All @@ -453,7 +454,7 @@ func parseFuncInfos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map
// parseFuncInfoRecords parses a stream of func_infos into a funcInfos.
// These records appear after a btf_ext_info_sec header in the func_info
// sub-section of .BTF.ext.
func parseFuncInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) ([]bpfFuncInfo, error) {
func parseFuncInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32, offsetInBytes bool) ([]bpfFuncInfo, error) {
var out []bpfFuncInfo
var fi bpfFuncInfo

Expand All @@ -467,13 +468,15 @@ func parseFuncInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, r
return nil, fmt.Errorf("can't read function info: %v", err)
}

if fi.InsnOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("offset %v is not aligned with instruction size", fi.InsnOff)
}
if offsetInBytes {
if fi.InsnOff%asm.InstructionSize != 0 {
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
// ELF tracks offset in bytes, the kernel expects raw BPF instructions.
// Convert as early as possible.
fi.InsnOff /= asm.InstructionSize
}

out = append(out, fi)
}
Expand Down Expand Up @@ -537,13 +540,14 @@ type bpfLineInfo struct {
LineCol uint32
}

// LoadLineInfos parses btf line info in wire format.
// LoadLineInfos parses BTF line info in kernel wire format.
func LoadLineInfos(reader io.Reader, bo binary.ByteOrder, recordNum uint32, spec *Spec) (LineInfos, error) {
lis, err := parseLineInfoRecords(
reader,
bo,
LineInfoSize,
recordNum,
false,
)
if err != nil {
return LineInfos{}, fmt.Errorf("parsing BTF line info: %w", err)
Expand Down Expand Up @@ -649,7 +653,7 @@ func parseLineInfos(r io.Reader, bo binary.ByteOrder, strings *stringTable) (map
return nil, err
}

records, err := parseLineInfoRecords(r, bo, recordSize, infoHeader.NumInfo)
records, err := parseLineInfoRecords(r, bo, recordSize, infoHeader.NumInfo, true)
if err != nil {
return nil, fmt.Errorf("section %v: %w", secName, err)
}
Expand All @@ -661,7 +665,7 @@ 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) ([]bpfLineInfo, error) {
func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32, offsetInBytes bool) ([]bpfLineInfo, error) {
var out []bpfLineInfo
var li bpfLineInfo

Expand All @@ -675,13 +679,15 @@ func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, r
return nil, fmt.Errorf("can't read line info: %v", err)
}

if li.InsnOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("offset %v is not aligned with instruction size", li.InsnOff)
}
if offsetInBytes {
if li.InsnOff%asm.InstructionSize != 0 {
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
// 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
46 changes: 34 additions & 12 deletions info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,18 +370,23 @@ func TestHaveProgramInfoMapIDs(t *testing.T) {
func TestProgInfoExtBTF(t *testing.T) {
testutils.SkipOnOldKernel(t, "5.0", "Program BTF (func/line_info)")

spec, err := LoadCollectionSpec("testdata/raw_tracepoint-el.elf")
spec, err := LoadCollectionSpec(fmt.Sprintf("testdata/loader-%s.elf", internal.ClangEndian))
if err != nil {
t.Fatal(err)
}

coll, err := NewCollection(spec)
var obj struct {
Main *Program `ebpf:"xdp_prog"`
}

err = spec.LoadAndAssign(&obj, nil)
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal(err)
}
defer coll.Close()
defer obj.Main.Close()

info, err := coll.Programs["sched_process_exec"].Info()
info, err := obj.Main.Info()
if err != nil {
t.Fatal(err)
}
Expand All @@ -391,17 +396,34 @@ func TestProgInfoExtBTF(t *testing.T) {
t.Fatal(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)
expectedLineInfoCount := 26
expectedFuncInfo := map[string]bool{
"xdp_prog": false,
"static_fn": false,
"global_fn2": false,
"global_fn3": false,
}

lineInfoCount := 0

for _, ins := range inst {
if ins.Source() != nil {
lineInfoCount++
}

fn := btf.FuncMetadata(&ins)
if fn != nil {
expectedFuncInfo[fn.Name] = true
}
}

fn := btf.FuncMetadata(&inst[0])
if fn == nil {
t.Fatal("Func metadata missing")
if lineInfoCount != expectedLineInfoCount {
t.Errorf("expected %d line info entries, got %d", expectedLineInfoCount, lineInfoCount)
}

if fn.Name != "sched_process_exec" {
t.Fatalf("Func metadata incorrect. Got '%s', expected: 'sched_process_exec'", fn.Name)
for fn, found := range expectedFuncInfo {
if !found {
t.Errorf("func %q not found", fn)
}
}
}

0 comments on commit 50436f2

Please sign in to comment.