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
24 type MapInfo struct {
25 Type MapType
26 id MapID
27 KeySize uint32
28 ValueSize uint32
29 MaxEntries uint32
30 Flags uint32
31
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
72
73
74
75
76 func (mi *MapInfo) ID() (MapID, bool) {
77 return mi.id, mi.id > 0
78 }
79
80
81 type programStats struct {
82
83 runtime time.Duration
84
85 runCount uint64
86 }
87
88
89 type ProgramInfo struct {
90 Type ProgramType
91 id ProgramID
92
93 Tag string
94
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
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
170
171
172
173
174 func (pi *ProgramInfo) ID() (ProgramID, bool) {
175 return pi.id, pi.id > 0
176 }
177
178
179
180
181
182
183
184
185
186 func (pi *ProgramInfo) BTFID() (btf.ID, bool) {
187 return pi.btf, pi.btf > 0
188 }
189
190
191
192
193
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
202
203
204
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
213
214
215
216
217
218
219
220
221
222
223
224
225
226 func (pi *ProgramInfo) Instructions() (asm.Instructions, error) {
227
228
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
240 insns[0] = insns[0].WithSymbol(pi.Name)
241
242 return insns, nil
243 }
244
245
246
247
248
249
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
310
311
312
313
314
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 }
324
View as plain text