diff --git a/info.go b/info.go index 5fc656e66..cf692c762 100644 --- a/info.go +++ b/info.go @@ -2,6 +2,7 @@ package ebpf import ( "bufio" + "bytes" "encoding/hex" "errors" "fmt" @@ -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" @@ -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) { @@ -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 } @@ -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. diff --git a/internal/cmd/gentypes/main.go b/internal/cmd/gentypes/main.go index 58272f313..587e42106 100644 --- a/internal/cmd/gentypes/main.go +++ b/internal/cmd/gentypes/main.go @@ -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", diff --git a/internal/sys/types.go b/internal/sys/types.go index 9646a5d1a..ab40cef6d 100644 --- a/internal/sys/types.go +++ b/internal/sys/types.go @@ -456,7 +456,7 @@ type ProgInfo struct { JitedProgLen uint32 XlatedProgLen uint32 JitedProgInsns uint64 - XlatedProgInsns uint64 + XlatedProgInsns Pointer LoadTime uint64 CreatedByUid uint32 NrMapIds uint32 diff --git a/prog_test.go b/prog_test.go index fc599a573..c6a14ca68 100644 --- a/prog_test.go +++ b/prog_test.go @@ -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