diff --git a/asm/instruction.go b/asm/instruction.go index d376b1dcb..6ae149528 100644 --- a/asm/instruction.go +++ b/asm/instruction.go @@ -309,6 +309,26 @@ func (ins Instruction) Size() uint64 { // Instructions is an eBPF program. type Instructions []Instruction +// Unmarshal unmarshals an Instructions from a binary instruction stream. +func (insns *Instructions) Unmarshal(r io.Reader, bo binary.ByteOrder) error { + var offset uint64 + for { + var ins Instruction + n, err := ins.Unmarshal(r, bo) + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return fmt.Errorf("offset %d: %w", offset, err) + } + + *insns = append(*insns, ins) + offset += n + } + + return nil +} + // Name returns the name of the function insns belongs to, if any. func (insns Instructions) Name() string { if len(insns) == 0 { diff --git a/info.go b/info.go index 5fc656e66..40b4e5414 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.NewPointer(unsafe.Pointer(&pi.insns[0])) + } + + if info.NrMapIds > 0 || info.XlatedProgLen > 0 { if err := sys.ObjInfo(fd, &info2); err != nil { return nil, err } @@ -199,6 +208,35 @@ 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, the field will be empty. + if len(pi.insns) == 0 { + return nil, fmt.Errorf("getting xlated instructions: %w", os.ErrPermission) + } + + 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..5a967c839 100644 --- a/prog_test.go +++ b/prog_test.go @@ -686,6 +686,61 @@ func TestProgramBindMap(t *testing.T) { } } +func TestProgramInstructions(t *testing.T) { + testutils.SkipOnOldKernel(t, "4.13", "BPF_OBJ_GET_INFO_BY_FD") + + arr := createArray(t) + defer arr.Close() + + info, err := arr.Info() + if err != nil { + t.Fatal(err) + } + + id, ok := info.ID() + if !ok { + t.Fatal("Can't determine map ID") + } + + name := "test_prog" + + spec := &ProgramSpec{ + Type: SocketFilter, + Name: name, + Instructions: asm.Instructions{ + asm.LoadImm(asm.R0, -1, asm.DWord).Sym(name), + asm.LoadMapPtr(asm.R1, arr.FD()), + asm.Mov.Imm32(asm.R0, 0), + asm.Return(), + }, + License: "MIT", + } + + prog, err := NewProgram(spec) + if err != nil { + t.Fatal(err) + } + defer prog.Close() + + // Replace the map's fd in spec with the map's ID, + // which is what the xlated instructions reflect. + spec.Instructions[1].Constant = int64(id) + + pi, err := prog.Info() + 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