github.com/cilium/ebpf@v0.16.0/info.go (about)

     1  package ebpf
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/hex"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"strings"
    12  	"syscall"
    13  	"time"
    14  	"unsafe"
    15  
    16  	"github.com/cilium/ebpf/asm"
    17  	"github.com/cilium/ebpf/btf"
    18  	"github.com/cilium/ebpf/internal"
    19  	"github.com/cilium/ebpf/internal/sys"
    20  	"github.com/cilium/ebpf/internal/unix"
    21  )
    22  
    23  // The *Info structs expose metadata about a program or map. Most
    24  // fields are exposed via a getter:
    25  //
    26  //     func (*MapInfo) ID() (MapID, bool)
    27  //
    28  // This is because the metadata available changes based on kernel version.
    29  // The second boolean return value indicates whether a particular field is
    30  // available on the current kernel.
    31  //
    32  // Always add new metadata as such a getter, unless you can somehow get the
    33  // value of the field on all supported kernels. Also document which version
    34  // a particular field first appeared in.
    35  //
    36  // Some metadata is a buffer which needs additional parsing. In this case,
    37  // store the undecoded data in the Info struct and provide a getter which
    38  // decodes it when necessary. See ProgramInfo.Instructions for an example.
    39  
    40  // MapInfo describes a map.
    41  type MapInfo struct {
    42  	Type       MapType
    43  	id         MapID
    44  	KeySize    uint32
    45  	ValueSize  uint32
    46  	MaxEntries uint32
    47  	Flags      uint32
    48  	// Name as supplied by user space at load time. Available from 4.15.
    49  	Name string
    50  
    51  	btf btf.ID
    52  }
    53  
    54  func newMapInfoFromFd(fd *sys.FD) (*MapInfo, error) {
    55  	var info sys.MapInfo
    56  	err := sys.ObjInfo(fd, &info)
    57  	if errors.Is(err, syscall.EINVAL) {
    58  		return newMapInfoFromProc(fd)
    59  	}
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	return &MapInfo{
    65  		MapType(info.Type),
    66  		MapID(info.Id),
    67  		info.KeySize,
    68  		info.ValueSize,
    69  		info.MaxEntries,
    70  		uint32(info.MapFlags),
    71  		unix.ByteSliceToString(info.Name[:]),
    72  		btf.ID(info.BtfId),
    73  	}, nil
    74  }
    75  
    76  func newMapInfoFromProc(fd *sys.FD) (*MapInfo, error) {
    77  	var mi MapInfo
    78  	err := scanFdInfo(fd, map[string]interface{}{
    79  		"map_type":    &mi.Type,
    80  		"key_size":    &mi.KeySize,
    81  		"value_size":  &mi.ValueSize,
    82  		"max_entries": &mi.MaxEntries,
    83  		"map_flags":   &mi.Flags,
    84  	})
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	return &mi, nil
    89  }
    90  
    91  // ID returns the map ID.
    92  //
    93  // Available from 4.13.
    94  //
    95  // The bool return value indicates whether this optional field is available.
    96  func (mi *MapInfo) ID() (MapID, bool) {
    97  	return mi.id, mi.id > 0
    98  }
    99  
   100  // BTFID returns the BTF ID associated with the Map.
   101  //
   102  // The ID is only valid as long as the associated Map is kept alive.
   103  // Available from 4.18.
   104  //
   105  // The bool return value indicates whether this optional field is available and
   106  // populated. (The field may be available but not populated if the kernel
   107  // supports the field but the Map was loaded without BTF information.)
   108  func (mi *MapInfo) BTFID() (btf.ID, bool) {
   109  	return mi.btf, mi.btf > 0
   110  }
   111  
   112  // programStats holds statistics of a program.
   113  type programStats struct {
   114  	// Total accumulated runtime of the program ins ns.
   115  	runtime time.Duration
   116  	// Total number of times the program was called.
   117  	runCount uint64
   118  	// Total number of times the programm was NOT called.
   119  	// Added in commit 9ed9e9ba2337 ("bpf: Count the number of times recursion was prevented").
   120  	recursionMisses uint64
   121  }
   122  
   123  // ProgramInfo describes a program.
   124  type ProgramInfo struct {
   125  	Type ProgramType
   126  	id   ProgramID
   127  	// Truncated hash of the BPF bytecode. Available from 4.13.
   128  	Tag string
   129  	// Name as supplied by user space at load time. Available from 4.15.
   130  	Name string
   131  
   132  	createdByUID     uint32
   133  	haveCreatedByUID bool
   134  	btf              btf.ID
   135  	stats            *programStats
   136  
   137  	maps  []MapID
   138  	insns []byte
   139  
   140  	lineInfos    []byte
   141  	numLineInfos uint32
   142  	funcInfos    []byte
   143  	numFuncInfos uint32
   144  }
   145  
   146  func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
   147  	var info sys.ProgInfo
   148  	err := sys.ObjInfo(fd, &info)
   149  	if errors.Is(err, syscall.EINVAL) {
   150  		return newProgramInfoFromProc(fd)
   151  	}
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	pi := ProgramInfo{
   157  		Type: ProgramType(info.Type),
   158  		id:   ProgramID(info.Id),
   159  		Tag:  hex.EncodeToString(info.Tag[:]),
   160  		Name: unix.ByteSliceToString(info.Name[:]),
   161  		btf:  btf.ID(info.BtfId),
   162  		stats: &programStats{
   163  			runtime:         time.Duration(info.RunTimeNs),
   164  			runCount:        info.RunCnt,
   165  			recursionMisses: info.RecursionMisses,
   166  		},
   167  	}
   168  
   169  	// Start with a clean struct for the second call, otherwise we may get EFAULT.
   170  	var info2 sys.ProgInfo
   171  
   172  	makeSecondCall := false
   173  
   174  	if info.NrMapIds > 0 {
   175  		pi.maps = make([]MapID, info.NrMapIds)
   176  		info2.NrMapIds = info.NrMapIds
   177  		info2.MapIds = sys.NewPointer(unsafe.Pointer(&pi.maps[0]))
   178  		makeSecondCall = true
   179  	} else if haveProgramInfoMapIDs() == nil {
   180  		// This program really has no associated maps.
   181  		pi.maps = make([]MapID, 0)
   182  	} else {
   183  		// The kernel doesn't report associated maps.
   184  		pi.maps = nil
   185  	}
   186  
   187  	// createdByUID and NrMapIds were introduced in the same kernel version.
   188  	if pi.maps != nil {
   189  		pi.createdByUID = info.CreatedByUid
   190  		pi.haveCreatedByUID = true
   191  	}
   192  
   193  	if info.XlatedProgLen > 0 {
   194  		pi.insns = make([]byte, info.XlatedProgLen)
   195  		info2.XlatedProgLen = info.XlatedProgLen
   196  		info2.XlatedProgInsns = sys.NewSlicePointer(pi.insns)
   197  		makeSecondCall = true
   198  	}
   199  
   200  	if info.NrLineInfo > 0 {
   201  		pi.lineInfos = make([]byte, btf.LineInfoSize*info.NrLineInfo)
   202  		info2.LineInfo = sys.NewSlicePointer(pi.lineInfos)
   203  		info2.LineInfoRecSize = btf.LineInfoSize
   204  		info2.NrLineInfo = info.NrLineInfo
   205  		pi.numLineInfos = info.NrLineInfo
   206  		makeSecondCall = true
   207  	}
   208  
   209  	if info.NrFuncInfo > 0 {
   210  		pi.funcInfos = make([]byte, btf.FuncInfoSize*info.NrFuncInfo)
   211  		info2.FuncInfo = sys.NewSlicePointer(pi.funcInfos)
   212  		info2.FuncInfoRecSize = btf.FuncInfoSize
   213  		info2.NrFuncInfo = info.NrFuncInfo
   214  		pi.numFuncInfos = info.NrFuncInfo
   215  		makeSecondCall = true
   216  	}
   217  
   218  	if makeSecondCall {
   219  		if err := sys.ObjInfo(fd, &info2); err != nil {
   220  			return nil, err
   221  		}
   222  	}
   223  
   224  	return &pi, nil
   225  }
   226  
   227  func newProgramInfoFromProc(fd *sys.FD) (*ProgramInfo, error) {
   228  	var info ProgramInfo
   229  	err := scanFdInfo(fd, map[string]interface{}{
   230  		"prog_type": &info.Type,
   231  		"prog_tag":  &info.Tag,
   232  	})
   233  	if errors.Is(err, errMissingFields) {
   234  		return nil, &internal.UnsupportedFeatureError{
   235  			Name:           "reading program info from /proc/self/fdinfo",
   236  			MinimumVersion: internal.Version{4, 10, 0},
   237  		}
   238  	}
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  
   243  	return &info, nil
   244  }
   245  
   246  // ID returns the program ID.
   247  //
   248  // Available from 4.13.
   249  //
   250  // The bool return value indicates whether this optional field is available.
   251  func (pi *ProgramInfo) ID() (ProgramID, bool) {
   252  	return pi.id, pi.id > 0
   253  }
   254  
   255  // CreatedByUID returns the Uid that created the program.
   256  //
   257  // Available from 4.15.
   258  //
   259  // The bool return value indicates whether this optional field is available.
   260  func (pi *ProgramInfo) CreatedByUID() (uint32, bool) {
   261  	return pi.createdByUID, pi.haveCreatedByUID
   262  }
   263  
   264  // BTFID returns the BTF ID associated with the program.
   265  //
   266  // The ID is only valid as long as the associated program is kept alive.
   267  // Available from 5.0.
   268  //
   269  // The bool return value indicates whether this optional field is available and
   270  // populated. (The field may be available but not populated if the kernel
   271  // supports the field but the program was loaded without BTF information.)
   272  func (pi *ProgramInfo) BTFID() (btf.ID, bool) {
   273  	return pi.btf, pi.btf > 0
   274  }
   275  
   276  // RunCount returns the total number of times the program was called.
   277  //
   278  // Can return 0 if the collection of statistics is not enabled. See EnableStats().
   279  // The bool return value indicates whether this optional field is available.
   280  func (pi *ProgramInfo) RunCount() (uint64, bool) {
   281  	if pi.stats != nil {
   282  		return pi.stats.runCount, true
   283  	}
   284  	return 0, false
   285  }
   286  
   287  // Runtime returns the total accumulated runtime of the program.
   288  //
   289  // Can return 0 if the collection of statistics is not enabled. See EnableStats().
   290  // The bool return value indicates whether this optional field is available.
   291  func (pi *ProgramInfo) Runtime() (time.Duration, bool) {
   292  	if pi.stats != nil {
   293  		return pi.stats.runtime, true
   294  	}
   295  	return time.Duration(0), false
   296  }
   297  
   298  // RecursionMisses returns the total number of times the program was NOT called.
   299  // This can happen when another bpf program is already running on the cpu, which
   300  // is likely to happen for example when you interrupt bpf program execution.
   301  func (pi *ProgramInfo) RecursionMisses() (uint64, bool) {
   302  	if pi.stats != nil {
   303  		return pi.stats.recursionMisses, true
   304  	}
   305  	return 0, false
   306  }
   307  
   308  // Instructions returns the 'xlated' instruction stream of the program
   309  // after it has been verified and rewritten by the kernel. These instructions
   310  // cannot be loaded back into the kernel as-is, this is mainly used for
   311  // inspecting loaded programs for troubleshooting, dumping, etc.
   312  //
   313  // For example, map accesses are made to reference their kernel map IDs,
   314  // not the FDs they had when the program was inserted. Note that before
   315  // the introduction of bpf_insn_prepare_dump in kernel 4.16, xlated
   316  // instructions were not sanitized, making the output even less reusable
   317  // and less likely to round-trip or evaluate to the same program Tag.
   318  //
   319  // The first instruction is marked as a symbol using the Program's name.
   320  //
   321  // If available, the instructions will be annotated with metadata from the
   322  // BTF. This includes line information and function information. Reading
   323  // this metadata requires CAP_SYS_ADMIN or equivalent. If capability is
   324  // unavailable, the instructions will be returned without metadata.
   325  //
   326  // Available from 4.13. Requires CAP_BPF or equivalent for plain instructions.
   327  // Requires CAP_SYS_ADMIN for instructions with metadata.
   328  func (pi *ProgramInfo) Instructions() (asm.Instructions, error) {
   329  	// If the calling process is not BPF-capable or if the kernel doesn't
   330  	// support getting xlated instructions, the field will be zero.
   331  	if len(pi.insns) == 0 {
   332  		return nil, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported)
   333  	}
   334  
   335  	r := bytes.NewReader(pi.insns)
   336  	var insns asm.Instructions
   337  	if err := insns.Unmarshal(r, internal.NativeEndian); err != nil {
   338  		return nil, fmt.Errorf("unmarshaling instructions: %w", err)
   339  	}
   340  
   341  	if pi.btf != 0 {
   342  		btfh, err := btf.NewHandleFromID(pi.btf)
   343  		if err != nil {
   344  			// Getting a BTF handle requires CAP_SYS_ADMIN, if not available we get an -EPERM.
   345  			// Ignore it and fall back to instructions without metadata.
   346  			if !errors.Is(err, unix.EPERM) {
   347  				return nil, fmt.Errorf("unable to get BTF handle: %w", err)
   348  			}
   349  		}
   350  
   351  		// If we have a BTF handle, we can use it to assign metadata to the instructions.
   352  		if btfh != nil {
   353  			defer btfh.Close()
   354  
   355  			spec, err := btfh.Spec(nil)
   356  			if err != nil {
   357  				return nil, fmt.Errorf("unable to get BTF spec: %w", err)
   358  			}
   359  
   360  			lineInfos, err := btf.LoadLineInfos(
   361  				bytes.NewReader(pi.lineInfos),
   362  				internal.NativeEndian,
   363  				pi.numLineInfos,
   364  				spec,
   365  			)
   366  			if err != nil {
   367  				return nil, fmt.Errorf("parse line info: %w", err)
   368  			}
   369  
   370  			funcInfos, err := btf.LoadFuncInfos(
   371  				bytes.NewReader(pi.funcInfos),
   372  				internal.NativeEndian,
   373  				pi.numFuncInfos,
   374  				spec,
   375  			)
   376  			if err != nil {
   377  				return nil, fmt.Errorf("parse func info: %w", err)
   378  			}
   379  
   380  			btf.AssignMetadataToInstructions(insns, funcInfos, lineInfos, btf.CORERelocationInfos{})
   381  		}
   382  	}
   383  
   384  	fn := btf.FuncMetadata(&insns[0])
   385  	name := pi.Name
   386  	if fn != nil {
   387  		name = fn.Name
   388  	}
   389  	insns[0] = insns[0].WithSymbol(name)
   390  
   391  	return insns, nil
   392  }
   393  
   394  // MapIDs returns the maps related to the program.
   395  //
   396  // Available from 4.15.
   397  //
   398  // The bool return value indicates whether this optional field is available.
   399  func (pi *ProgramInfo) MapIDs() ([]MapID, bool) {
   400  	return pi.maps, pi.maps != nil
   401  }
   402  
   403  func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error {
   404  	fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", fd.Int()))
   405  	if err != nil {
   406  		return err
   407  	}
   408  	defer fh.Close()
   409  
   410  	if err := scanFdInfoReader(fh, fields); err != nil {
   411  		return fmt.Errorf("%s: %w", fh.Name(), err)
   412  	}
   413  	return nil
   414  }
   415  
   416  var errMissingFields = errors.New("missing fields")
   417  
   418  func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
   419  	var (
   420  		scanner = bufio.NewScanner(r)
   421  		scanned int
   422  	)
   423  
   424  	for scanner.Scan() {
   425  		parts := strings.SplitN(scanner.Text(), "\t", 2)
   426  		if len(parts) != 2 {
   427  			continue
   428  		}
   429  
   430  		name := strings.TrimSuffix(parts[0], ":")
   431  		field, ok := fields[string(name)]
   432  		if !ok {
   433  			continue
   434  		}
   435  
   436  		if n, err := fmt.Sscanln(parts[1], field); err != nil || n != 1 {
   437  			return fmt.Errorf("can't parse field %s: %v", name, err)
   438  		}
   439  
   440  		scanned++
   441  	}
   442  
   443  	if err := scanner.Err(); err != nil {
   444  		return err
   445  	}
   446  
   447  	if len(fields) > 0 && scanned == 0 {
   448  		return ErrNotSupported
   449  	}
   450  
   451  	if scanned != len(fields) {
   452  		return errMissingFields
   453  	}
   454  
   455  	return nil
   456  }
   457  
   458  // EnableStats starts the measuring of the runtime
   459  // and run counts of eBPF programs.
   460  //
   461  // Collecting statistics can have an impact on the performance.
   462  //
   463  // Requires at least 5.8.
   464  func EnableStats(which uint32) (io.Closer, error) {
   465  	fd, err := sys.EnableStats(&sys.EnableStatsAttr{
   466  		Type: which,
   467  	})
   468  	if err != nil {
   469  		return nil, err
   470  	}
   471  	return fd, nil
   472  }
   473  
   474  var haveProgramInfoMapIDs = internal.NewFeatureTest("map IDs in program info", "4.15", func() error {
   475  	prog, err := progLoad(asm.Instructions{
   476  		asm.LoadImm(asm.R0, 0, asm.DWord),
   477  		asm.Return(),
   478  	}, SocketFilter, "MIT")
   479  	if err != nil {
   480  		return err
   481  	}
   482  	defer prog.Close()
   483  
   484  	err = sys.ObjInfo(prog, &sys.ProgInfo{
   485  		// NB: Don't need to allocate MapIds since the program isn't using
   486  		// any maps.
   487  		NrMapIds: 1,
   488  	})
   489  	if errors.Is(err, unix.EINVAL) {
   490  		// Most likely the syscall doesn't exist.
   491  		return internal.ErrNotSupported
   492  	}
   493  	if errors.Is(err, unix.E2BIG) {
   494  		// We've hit check_uarg_tail_zero on older kernels.
   495  		return internal.ErrNotSupported
   496  	}
   497  
   498  	return err
   499  })