github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/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 // 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 uint32(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 createdByUID uint32 98 haveCreatedByUID bool 99 btf btf.ID 100 stats *programStats 101 102 maps []MapID 103 insns []byte 104 105 lineInfos []byte 106 numLineInfos uint32 107 funcInfos []byte 108 numFuncInfos uint32 109 } 110 111 func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { 112 var info sys.ProgInfo 113 err := sys.ObjInfo(fd, &info) 114 if errors.Is(err, syscall.EINVAL) { 115 return newProgramInfoFromProc(fd) 116 } 117 if err != nil { 118 return nil, err 119 } 120 121 pi := ProgramInfo{ 122 Type: ProgramType(info.Type), 123 id: ProgramID(info.Id), 124 Tag: hex.EncodeToString(info.Tag[:]), 125 Name: unix.ByteSliceToString(info.Name[:]), 126 btf: btf.ID(info.BtfId), 127 stats: &programStats{ 128 runtime: time.Duration(info.RunTimeNs), 129 runCount: info.RunCnt, 130 }, 131 } 132 133 // Start with a clean struct for the second call, otherwise we may get EFAULT. 134 var info2 sys.ProgInfo 135 136 makeSecondCall := false 137 138 if info.NrMapIds > 0 { 139 pi.maps = make([]MapID, info.NrMapIds) 140 info2.NrMapIds = info.NrMapIds 141 info2.MapIds = sys.NewPointer(unsafe.Pointer(&pi.maps[0])) 142 makeSecondCall = true 143 } else if haveProgramInfoMapIDs() == nil { 144 // This program really has no associated maps. 145 pi.maps = make([]MapID, 0) 146 } else { 147 // The kernel doesn't report associated maps. 148 pi.maps = nil 149 } 150 151 // createdByUID and NrMapIds were introduced in the same kernel version. 152 if pi.maps != nil { 153 pi.createdByUID = info.CreatedByUid 154 pi.haveCreatedByUID = true 155 } 156 157 if info.XlatedProgLen > 0 { 158 pi.insns = make([]byte, info.XlatedProgLen) 159 info2.XlatedProgLen = info.XlatedProgLen 160 info2.XlatedProgInsns = sys.NewSlicePointer(pi.insns) 161 makeSecondCall = true 162 } 163 164 if info.NrLineInfo > 0 { 165 pi.lineInfos = make([]byte, btf.LineInfoSize*info.NrLineInfo) 166 info2.LineInfo = sys.NewSlicePointer(pi.lineInfos) 167 info2.LineInfoRecSize = btf.LineInfoSize 168 info2.NrLineInfo = info.NrLineInfo 169 pi.numLineInfos = info.NrLineInfo 170 makeSecondCall = true 171 } 172 173 if info.NrFuncInfo > 0 { 174 pi.funcInfos = make([]byte, btf.FuncInfoSize*info.NrFuncInfo) 175 info2.FuncInfo = sys.NewSlicePointer(pi.funcInfos) 176 info2.FuncInfoRecSize = btf.FuncInfoSize 177 info2.NrFuncInfo = info.NrFuncInfo 178 pi.numFuncInfos = info.NrFuncInfo 179 makeSecondCall = true 180 } 181 182 if makeSecondCall { 183 if err := sys.ObjInfo(fd, &info2); err != nil { 184 return nil, err 185 } 186 } 187 188 return &pi, nil 189 } 190 191 func newProgramInfoFromProc(fd *sys.FD) (*ProgramInfo, error) { 192 var info ProgramInfo 193 err := scanFdInfo(fd, map[string]interface{}{ 194 "prog_type": &info.Type, 195 "prog_tag": &info.Tag, 196 }) 197 if errors.Is(err, errMissingFields) { 198 return nil, &internal.UnsupportedFeatureError{ 199 Name: "reading program info from /proc/self/fdinfo", 200 MinimumVersion: internal.Version{4, 10, 0}, 201 } 202 } 203 if err != nil { 204 return nil, err 205 } 206 207 return &info, nil 208 } 209 210 // ID returns the program ID. 211 // 212 // Available from 4.13. 213 // 214 // The bool return value indicates whether this optional field is available. 215 func (pi *ProgramInfo) ID() (ProgramID, bool) { 216 return pi.id, pi.id > 0 217 } 218 219 // CreatedByUID returns the Uid that created the program. 220 // 221 // Available from 4.15. 222 // 223 // The bool return value indicates whether this optional field is available. 224 func (pi *ProgramInfo) CreatedByUID() (uint32, bool) { 225 return pi.createdByUID, pi.haveCreatedByUID 226 } 227 228 // BTFID returns the BTF ID associated with the program. 229 // 230 // The ID is only valid as long as the associated program is kept alive. 231 // Available from 5.0. 232 // 233 // The bool return value indicates whether this optional field is available and 234 // populated. (The field may be available but not populated if the kernel 235 // supports the field but the program was loaded without BTF information.) 236 func (pi *ProgramInfo) BTFID() (btf.ID, bool) { 237 return pi.btf, pi.btf > 0 238 } 239 240 // RunCount returns the total number of times the program was called. 241 // 242 // Can return 0 if the collection of statistics is not enabled. See EnableStats(). 243 // The bool return value indicates whether this optional field is available. 244 func (pi *ProgramInfo) RunCount() (uint64, bool) { 245 if pi.stats != nil { 246 return pi.stats.runCount, true 247 } 248 return 0, false 249 } 250 251 // Runtime returns the total accumulated runtime of the program. 252 // 253 // Can return 0 if the collection of statistics is not enabled. See EnableStats(). 254 // The bool return value indicates whether this optional field is available. 255 func (pi *ProgramInfo) Runtime() (time.Duration, bool) { 256 if pi.stats != nil { 257 return pi.stats.runtime, true 258 } 259 return time.Duration(0), false 260 } 261 262 // Instructions returns the 'xlated' instruction stream of the program 263 // after it has been verified and rewritten by the kernel. These instructions 264 // cannot be loaded back into the kernel as-is, this is mainly used for 265 // inspecting loaded programs for troubleshooting, dumping, etc. 266 // 267 // For example, map accesses are made to reference their kernel map IDs, 268 // not the FDs they had when the program was inserted. Note that before 269 // the introduction of bpf_insn_prepare_dump in kernel 4.16, xlated 270 // instructions were not sanitized, making the output even less reusable 271 // and less likely to round-trip or evaluate to the same program Tag. 272 // 273 // The first instruction is marked as a symbol using the Program's name. 274 // 275 // If available, the instructions will be annotated with metadata from the 276 // BTF. This includes line information and function information. Reading 277 // this metadata requires CAP_SYS_ADMIN or equivalent. If capability is 278 // unavailable, the instructions will be returned without metadata. 279 // 280 // Available from 4.13. Requires CAP_BPF or equivalent for plain instructions. 281 // Requires CAP_SYS_ADMIN for instructions with metadata. 282 func (pi *ProgramInfo) Instructions() (asm.Instructions, error) { 283 // If the calling process is not BPF-capable or if the kernel doesn't 284 // support getting xlated instructions, the field will be zero. 285 if len(pi.insns) == 0 { 286 return nil, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported) 287 } 288 289 r := bytes.NewReader(pi.insns) 290 var insns asm.Instructions 291 if err := insns.Unmarshal(r, internal.NativeEndian); err != nil { 292 return nil, fmt.Errorf("unmarshaling instructions: %w", err) 293 } 294 295 if pi.btf != 0 { 296 btfh, err := btf.NewHandleFromID(pi.btf) 297 if err != nil { 298 // Getting a BTF handle requires CAP_SYS_ADMIN, if not available we get an -EPERM. 299 // Ignore it and fall back to instructions without metadata. 300 if !errors.Is(err, unix.EPERM) { 301 return nil, fmt.Errorf("unable to get BTF handle: %w", err) 302 } 303 } 304 305 // If we have a BTF handle, we can use it to assign metadata to the instructions. 306 if btfh != nil { 307 defer btfh.Close() 308 309 spec, err := btfh.Spec(nil) 310 if err != nil { 311 return nil, fmt.Errorf("unable to get BTF spec: %w", err) 312 } 313 314 lineInfos, err := btf.LoadLineInfos( 315 bytes.NewReader(pi.lineInfos), 316 internal.NativeEndian, 317 pi.numLineInfos, 318 spec, 319 ) 320 if err != nil { 321 return nil, fmt.Errorf("parse line info: %w", err) 322 } 323 324 funcInfos, err := btf.LoadFuncInfos( 325 bytes.NewReader(pi.funcInfos), 326 internal.NativeEndian, 327 pi.numFuncInfos, 328 spec, 329 ) 330 if err != nil { 331 return nil, fmt.Errorf("parse func info: %w", err) 332 } 333 334 btf.AssignMetadataToInstructions(insns, funcInfos, lineInfos, btf.CORERelocationInfos{}) 335 } 336 } 337 338 fn := btf.FuncMetadata(&insns[0]) 339 name := pi.Name 340 if fn != nil { 341 name = fn.Name 342 } 343 insns[0] = insns[0].WithSymbol(name) 344 345 return insns, nil 346 } 347 348 // MapIDs returns the maps related to the program. 349 // 350 // Available from 4.15. 351 // 352 // The bool return value indicates whether this optional field is available. 353 func (pi *ProgramInfo) MapIDs() ([]MapID, bool) { 354 return pi.maps, pi.maps != nil 355 } 356 357 func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error { 358 fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", fd.Int())) 359 if err != nil { 360 return err 361 } 362 defer fh.Close() 363 364 if err := scanFdInfoReader(fh, fields); err != nil { 365 return fmt.Errorf("%s: %w", fh.Name(), err) 366 } 367 return nil 368 } 369 370 var errMissingFields = errors.New("missing fields") 371 372 func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error { 373 var ( 374 scanner = bufio.NewScanner(r) 375 scanned int 376 ) 377 378 for scanner.Scan() { 379 parts := strings.SplitN(scanner.Text(), "\t", 2) 380 if len(parts) != 2 { 381 continue 382 } 383 384 name := strings.TrimSuffix(parts[0], ":") 385 field, ok := fields[string(name)] 386 if !ok { 387 continue 388 } 389 390 if n, err := fmt.Sscanln(parts[1], field); err != nil || n != 1 { 391 return fmt.Errorf("can't parse field %s: %v", name, err) 392 } 393 394 scanned++ 395 } 396 397 if err := scanner.Err(); err != nil { 398 return err 399 } 400 401 if len(fields) > 0 && scanned == 0 { 402 return ErrNotSupported 403 } 404 405 if scanned != len(fields) { 406 return errMissingFields 407 } 408 409 return nil 410 } 411 412 // EnableStats starts the measuring of the runtime 413 // and run counts of eBPF programs. 414 // 415 // Collecting statistics can have an impact on the performance. 416 // 417 // Requires at least 5.8. 418 func EnableStats(which uint32) (io.Closer, error) { 419 fd, err := sys.EnableStats(&sys.EnableStatsAttr{ 420 Type: which, 421 }) 422 if err != nil { 423 return nil, err 424 } 425 return fd, nil 426 } 427 428 var haveProgramInfoMapIDs = internal.NewFeatureTest("map IDs in program info", "4.15", func() error { 429 prog, err := progLoad(asm.Instructions{ 430 asm.LoadImm(asm.R0, 0, asm.DWord), 431 asm.Return(), 432 }, SocketFilter, "MIT") 433 if err != nil { 434 return err 435 } 436 defer prog.Close() 437 438 err = sys.ObjInfo(prog, &sys.ProgInfo{ 439 // NB: Don't need to allocate MapIds since the program isn't using 440 // any maps. 441 NrMapIds: 1, 442 }) 443 if errors.Is(err, unix.EINVAL) { 444 // Most likely the syscall doesn't exist. 445 return internal.ErrNotSupported 446 } 447 if errors.Is(err, unix.E2BIG) { 448 // We've hit check_uarg_tail_zero on older kernels. 449 return internal.ErrNotSupported 450 } 451 452 return err 453 })