info.go (8120B)
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 // MapInfo describes a map. 24 type MapInfo struct { 25 Type MapType 26 id MapID 27 KeySize uint32 28 ValueSize uint32 29 MaxEntries uint32 30 Flags uint32 31 // Name as supplied by user space at load time. Available from 4.15. 32 Name string 33 } 34 35 func newMapInfoFromFd(fd *sys.FD) (*MapInfo, error) { 36 var info sys.MapInfo 37 err := sys.ObjInfo(fd, &info) 38 if errors.Is(err, syscall.EINVAL) { 39 return newMapInfoFromProc(fd) 40 } 41 if err != nil { 42 return nil, err 43 } 44 45 return &MapInfo{ 46 MapType(info.Type), 47 MapID(info.Id), 48 info.KeySize, 49 info.ValueSize, 50 info.MaxEntries, 51 info.MapFlags, 52 unix.ByteSliceToString(info.Name[:]), 53 }, nil 54 } 55 56 func newMapInfoFromProc(fd *sys.FD) (*MapInfo, error) { 57 var mi MapInfo 58 err := scanFdInfo(fd, map[string]interface{}{ 59 "map_type": &mi.Type, 60 "key_size": &mi.KeySize, 61 "value_size": &mi.ValueSize, 62 "max_entries": &mi.MaxEntries, 63 "map_flags": &mi.Flags, 64 }) 65 if err != nil { 66 return nil, err 67 } 68 return &mi, nil 69 } 70 71 // ID returns the map ID. 72 // 73 // Available from 4.13. 74 // 75 // The bool return value indicates whether this optional field is available. 76 func (mi *MapInfo) ID() (MapID, bool) { 77 return mi.id, mi.id > 0 78 } 79 80 // programStats holds statistics of a program. 81 type programStats struct { 82 // Total accumulated runtime of the program ins ns. 83 runtime time.Duration 84 // Total number of times the program was called. 85 runCount uint64 86 } 87 88 // ProgramInfo describes a program. 89 type ProgramInfo struct { 90 Type ProgramType 91 id ProgramID 92 // Truncated hash of the BPF bytecode. Available from 4.13. 93 Tag string 94 // Name as supplied by user space at load time. Available from 4.15. 95 Name string 96 97 btf btf.ID 98 stats *programStats 99 100 maps []MapID 101 insns []byte 102 } 103 104 func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { 105 var info sys.ProgInfo 106 err := sys.ObjInfo(fd, &info) 107 if errors.Is(err, syscall.EINVAL) { 108 return newProgramInfoFromProc(fd) 109 } 110 if err != nil { 111 return nil, err 112 } 113 114 pi := ProgramInfo{ 115 Type: ProgramType(info.Type), 116 id: ProgramID(info.Id), 117 Tag: hex.EncodeToString(info.Tag[:]), 118 Name: unix.ByteSliceToString(info.Name[:]), 119 btf: btf.ID(info.BtfId), 120 stats: &programStats{ 121 runtime: time.Duration(info.RunTimeNs), 122 runCount: info.RunCnt, 123 }, 124 } 125 126 // Start with a clean struct for the second call, otherwise we may get EFAULT. 127 var info2 sys.ProgInfo 128 129 if info.NrMapIds > 0 { 130 pi.maps = make([]MapID, info.NrMapIds) 131 info2.NrMapIds = info.NrMapIds 132 info2.MapIds = sys.NewPointer(unsafe.Pointer(&pi.maps[0])) 133 } 134 135 if info.XlatedProgLen > 0 { 136 pi.insns = make([]byte, info.XlatedProgLen) 137 info2.XlatedProgLen = info.XlatedProgLen 138 info2.XlatedProgInsns = sys.NewSlicePointer(pi.insns) 139 } 140 141 if info.NrMapIds > 0 || info.XlatedProgLen > 0 { 142 if err := sys.ObjInfo(fd, &info2); err != nil { 143 return nil, err 144 } 145 } 146 147 return &pi, nil 148 } 149 150 func newProgramInfoFromProc(fd *sys.FD) (*ProgramInfo, error) { 151 var info ProgramInfo 152 err := scanFdInfo(fd, map[string]interface{}{ 153 "prog_type": &info.Type, 154 "prog_tag": &info.Tag, 155 }) 156 if errors.Is(err, errMissingFields) { 157 return nil, &internal.UnsupportedFeatureError{ 158 Name: "reading program info from /proc/self/fdinfo", 159 MinimumVersion: internal.Version{4, 10, 0}, 160 } 161 } 162 if err != nil { 163 return nil, err 164 } 165 166 return &info, nil 167 } 168 169 // ID returns the program ID. 170 // 171 // Available from 4.13. 172 // 173 // The bool return value indicates whether this optional field is available. 174 func (pi *ProgramInfo) ID() (ProgramID, bool) { 175 return pi.id, pi.id > 0 176 } 177 178 // BTFID returns the BTF ID associated with the program. 179 // 180 // The ID is only valid as long as the associated program is kept alive. 181 // Available from 5.0. 182 // 183 // The bool return value indicates whether this optional field is available and 184 // populated. (The field may be available but not populated if the kernel 185 // supports the field but the program was loaded without BTF information.) 186 func (pi *ProgramInfo) BTFID() (btf.ID, bool) { 187 return pi.btf, pi.btf > 0 188 } 189 190 // RunCount returns the total number of times the program was called. 191 // 192 // Can return 0 if the collection of statistics is not enabled. See EnableStats(). 193 // The bool return value indicates whether this optional field is available. 194 func (pi *ProgramInfo) RunCount() (uint64, bool) { 195 if pi.stats != nil { 196 return pi.stats.runCount, true 197 } 198 return 0, false 199 } 200 201 // Runtime returns the total accumulated runtime of the program. 202 // 203 // Can return 0 if the collection of statistics is not enabled. See EnableStats(). 204 // The bool return value indicates whether this optional field is available. 205 func (pi *ProgramInfo) Runtime() (time.Duration, bool) { 206 if pi.stats != nil { 207 return pi.stats.runtime, true 208 } 209 return time.Duration(0), false 210 } 211 212 // Instructions returns the 'xlated' instruction stream of the program 213 // after it has been verified and rewritten by the kernel. These instructions 214 // cannot be loaded back into the kernel as-is, this is mainly used for 215 // inspecting loaded programs for troubleshooting, dumping, etc. 216 // 217 // For example, map accesses are made to reference their kernel map IDs, 218 // not the FDs they had when the program was inserted. Note that before 219 // the introduction of bpf_insn_prepare_dump in kernel 4.16, xlated 220 // instructions were not sanitized, making the output even less reusable 221 // and less likely to round-trip or evaluate to the same program Tag. 222 // 223 // The first instruction is marked as a symbol using the Program's name. 224 // 225 // Available from 4.13. Requires CAP_BPF or equivalent. 226 func (pi *ProgramInfo) Instructions() (asm.Instructions, error) { 227 // If the calling process is not BPF-capable or if the kernel doesn't 228 // support getting xlated instructions, the field will be zero. 229 if len(pi.insns) == 0 { 230 return nil, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported) 231 } 232 233 r := bytes.NewReader(pi.insns) 234 var insns asm.Instructions 235 if err := insns.Unmarshal(r, internal.NativeEndian); err != nil { 236 return nil, fmt.Errorf("unmarshaling instructions: %w", err) 237 } 238 239 // Tag the first instruction with the name of the program, if available. 240 insns[0] = insns[0].WithSymbol(pi.Name) 241 242 return insns, nil 243 } 244 245 // MapIDs returns the maps related to the program. 246 // 247 // Available from 4.15. 248 // 249 // The bool return value indicates whether this optional field is available. 250 func (pi *ProgramInfo) MapIDs() ([]MapID, bool) { 251 return pi.maps, pi.maps != nil 252 } 253 254 func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error { 255 fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", fd.Int())) 256 if err != nil { 257 return err 258 } 259 defer fh.Close() 260 261 if err := scanFdInfoReader(fh, fields); err != nil { 262 return fmt.Errorf("%s: %w", fh.Name(), err) 263 } 264 return nil 265 } 266 267 var errMissingFields = errors.New("missing fields") 268 269 func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error { 270 var ( 271 scanner = bufio.NewScanner(r) 272 scanned int 273 ) 274 275 for scanner.Scan() { 276 parts := strings.SplitN(scanner.Text(), "\t", 2) 277 if len(parts) != 2 { 278 continue 279 } 280 281 name := strings.TrimSuffix(parts[0], ":") 282 field, ok := fields[string(name)] 283 if !ok { 284 continue 285 } 286 287 if n, err := fmt.Sscanln(parts[1], field); err != nil || n != 1 { 288 return fmt.Errorf("can't parse field %s: %v", name, err) 289 } 290 291 scanned++ 292 } 293 294 if err := scanner.Err(); err != nil { 295 return err 296 } 297 298 if len(fields) > 0 && scanned == 0 { 299 return ErrNotSupported 300 } 301 302 if scanned != len(fields) { 303 return errMissingFields 304 } 305 306 return nil 307 } 308 309 // EnableStats starts the measuring of the runtime 310 // and run counts of eBPF programs. 311 // 312 // Collecting statistics can have an impact on the performance. 313 // 314 // Requires at least 5.8. 315 func EnableStats(which uint32) (io.Closer, error) { 316 fd, err := sys.EnableStats(&sys.EnableStatsAttr{ 317 Type: which, 318 }) 319 if err != nil { 320 return nil, err 321 } 322 return fd, nil 323 }