Skip to content

Commit

Permalink
program: retrieve xlated Instructions from Program
Browse files Browse the repository at this point in the history
Allow introspecting instructions after they have been translated by and
loaded into the kernel.

Signed-off-by: Timo Beckers <timo@isovalent.com>
  • Loading branch information
ti-mo committed Jan 17, 2022
1 parent 32bb96d commit 4eed175
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 4 deletions.
43 changes: 41 additions & 2 deletions info.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ebpf

import (
"bufio"
"bytes"
"encoding/hex"
"errors"
"fmt"
Expand All @@ -12,6 +13,7 @@ import (
"time"
"unsafe"

"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/sys"
Expand Down Expand Up @@ -95,7 +97,8 @@ type ProgramInfo struct {
btf btf.ID
stats *programStats

maps []MapID
maps []MapID
insns []byte
}

func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
Expand Down Expand Up @@ -129,7 +132,13 @@ func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
info2.MapIds = sys.NewPointer(unsafe.Pointer(&pi.maps[0]))
}

if info.NrMapIds > 0 {
if info.XlatedProgLen > 0 {
pi.insns = make([]byte, info.XlatedProgLen)
info2.XlatedProgLen = info.XlatedProgLen
info2.XlatedProgInsns = sys.NewSlicePointer(pi.insns)
}

if info.NrMapIds > 0 || info.XlatedProgLen > 0 {
if err := sys.ObjInfo(fd, &info2); err != nil {
return nil, err
}
Expand Down Expand Up @@ -199,6 +208,36 @@ func (pi *ProgramInfo) Runtime() (time.Duration, bool) {
return time.Duration(0), false
}

// 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
// inspecting loaded programs for troubleshooting, dumping, etc.
//
// For example, map accesses are made to reference their kernel map IDs,
// not the FDs they had when the program was inserted.
//
// The first instruction is marked as a symbol using the Program's name.
//
// Available from 4.13. Requires CAP_BPF or equivalent.
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.
if len(pi.insns) == 0 {
return nil, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported)
}

r := bytes.NewReader(pi.insns)
var insns asm.Instructions
if err := insns.Unmarshal(r, internal.NativeEndian); err != nil {
return nil, fmt.Errorf("unmarshaling instructions: %w", err)
}

// Tag the first instruction with the name of the program, if available.
insns[0] = insns[0].Sym(pi.Name)

return insns, nil
}

// MapIDs returns the maps related to the program.
//
// Available from 4.15.
Expand Down
6 changes: 5 additions & 1 deletion internal/cmd/gentypes/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,11 @@ import (
}{
{
"ProgInfo", "bpf_prog_info",
[]patch{replace(objName, "name"), replace(pointer, "map_ids")},
[]patch{
replace(objName, "name"),
replace(pointer, "xlated_prog_insns"),
replace(pointer, "map_ids"),
},
},
{
"MapInfo", "bpf_map_info",
Expand Down
2 changes: 1 addition & 1 deletion internal/sys/types.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions prog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,41 @@ func TestProgramBindMap(t *testing.T) {
}
}

func TestProgramInstructions(t *testing.T) {
name := "test_prog"
spec := &ProgramSpec{
Type: SocketFilter,
Name: name,
Instructions: asm.Instructions{
asm.LoadImm(asm.R0, -1, asm.DWord).Sym(name),
asm.Mov.Imm32(asm.R0, 0),
asm.Return(),
},
License: "MIT",
}

prog, err := NewProgram(spec)
if err != nil {
t.Fatal(err)
}
defer prog.Close()

pi, err := prog.Info()
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal(err)
}

insns, err := pi.Instructions()
if err != nil {
t.Fatal(err)
}

if diff := cmp.Diff(insns, spec.Instructions); diff != "" {
t.Fatal(diff)
}
}

type testReaderAt struct {
file *os.File
read bool
Expand Down

0 comments on commit 4eed175

Please sign in to comment.