1 package link
2
3 import (
4 "errors"
5 "fmt"
6 "go/build"
7 "os"
8 "os/exec"
9 "path"
10 "testing"
11
12 qt "github.com/frankban/quicktest"
13
14 "github.com/cilium/ebpf"
15 "github.com/cilium/ebpf/internal/testutils"
16 "github.com/cilium/ebpf/internal/unix"
17 )
18
19 var (
20 bashEx, _ = OpenExecutable("/bin/bash")
21 bashSym = "main"
22 )
23
24 func TestExecutable(t *testing.T) {
25 _, err := OpenExecutable("")
26 if err == nil {
27 t.Fatal("create executable: expected error on empty path")
28 }
29
30 if bashEx.path != "/bin/bash" {
31 t.Fatalf("create executable: unexpected path '%s'", bashEx.path)
32 }
33
34 _, err = bashEx.address(bashSym, &UprobeOptions{})
35 if err != nil {
36 t.Fatalf("find offset: %v", err)
37 }
38
39 _, err = bashEx.address("bogus", &UprobeOptions{})
40 if err == nil {
41 t.Fatal("find symbol: expected error")
42 }
43 }
44
45 func TestExecutableOffset(t *testing.T) {
46 c := qt.New(t)
47
48 symbolOffset, err := bashEx.address(bashSym, &UprobeOptions{})
49 if err != nil {
50 t.Fatal(err)
51 }
52
53 offset, err := bashEx.address(bashSym, &UprobeOptions{Address: 0x1})
54 if err != nil {
55 t.Fatal(err)
56 }
57 c.Assert(offset, qt.Equals, uint64(0x1))
58
59 offset, err = bashEx.address(bashSym, &UprobeOptions{Offset: 0x2})
60 if err != nil {
61 t.Fatal(err)
62 }
63 c.Assert(offset, qt.Equals, symbolOffset+0x2)
64
65 offset, err = bashEx.address(bashSym, &UprobeOptions{Address: 0x1, Offset: 0x2})
66 if err != nil {
67 t.Fatal(err)
68 }
69 c.Assert(offset, qt.Equals, uint64(0x1+0x2))
70 }
71
72 func TestUprobe(t *testing.T) {
73 c := qt.New(t)
74
75 prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")
76
77 up, err := bashEx.Uprobe(bashSym, prog, nil)
78 c.Assert(err, qt.IsNil)
79 defer up.Close()
80
81 testLink(t, up, prog)
82 }
83
84 func TestUprobeExtNotFound(t *testing.T) {
85 prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")
86
87
88 _, err := bashEx.Uprobe("open", prog, nil)
89 if err == nil {
90 t.Fatal("expected error")
91 }
92 }
93
94 func TestUprobeExtWithOpts(t *testing.T) {
95 prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")
96
97
98
99 up, err := bashEx.Uprobe("open", prog, &UprobeOptions{Address: 0x1})
100 if err != nil {
101 t.Fatal(err)
102 }
103 defer up.Close()
104 }
105
106 func TestUprobeWithPID(t *testing.T) {
107 prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")
108
109 up, err := bashEx.Uprobe(bashSym, prog, &UprobeOptions{PID: os.Getpid()})
110 if err != nil {
111 t.Fatal(err)
112 }
113 defer up.Close()
114 }
115
116 func TestUprobeWithNonExistentPID(t *testing.T) {
117 prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")
118
119
120 _, err := bashEx.Uprobe(bashSym, prog, &UprobeOptions{PID: -2})
121 if !errors.Is(err, unix.ESRCH) {
122 t.Fatalf("expected ESRCH, got %v", err)
123 }
124 }
125
126 func TestUretprobe(t *testing.T) {
127 c := qt.New(t)
128
129 prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")
130
131 up, err := bashEx.Uretprobe(bashSym, prog, nil)
132 c.Assert(err, qt.IsNil)
133 defer up.Close()
134
135 testLink(t, up, prog)
136 }
137
138
139 func TestUprobeCreatePMU(t *testing.T) {
140
141 testutils.SkipOnOldKernel(t, "4.17", "perf_kprobe PMU")
142
143 c := qt.New(t)
144
145
146 off, err := bashEx.address(bashSym, &UprobeOptions{})
147 c.Assert(err, qt.IsNil)
148
149
150 args := probeArgs{
151 symbol: bashSym,
152 path: bashEx.path,
153 offset: off,
154 pid: perfAllThreads,
155 }
156
157
158 pu, err := pmuUprobe(args)
159 c.Assert(err, qt.IsNil)
160 defer pu.Close()
161
162 c.Assert(pu.typ, qt.Equals, uprobeEvent)
163
164
165 args.ret = true
166 pr, err := pmuUprobe(args)
167 c.Assert(err, qt.IsNil)
168 defer pr.Close()
169
170 c.Assert(pr.typ, qt.Equals, uretprobeEvent)
171 }
172
173
174 func TestUprobePMUUnavailable(t *testing.T) {
175 c := qt.New(t)
176
177
178 off, err := bashEx.address(bashSym, &UprobeOptions{})
179 c.Assert(err, qt.IsNil)
180
181
182 args := probeArgs{
183 symbol: bashSym,
184 path: bashEx.path,
185 offset: off,
186 pid: perfAllThreads,
187 }
188
189 pk, err := pmuUprobe(args)
190 if err == nil {
191 pk.Close()
192 t.Skipf("Kernel supports perf_uprobe PMU, not asserting error.")
193 }
194
195
196 c.Assert(errors.Is(err, ErrNotSupported), qt.IsTrue, qt.Commentf("got error: %s", err))
197 }
198
199
200 func TestUprobeTraceFS(t *testing.T) {
201 c := qt.New(t)
202
203
204 off, err := bashEx.address(bashSym, &UprobeOptions{})
205 c.Assert(err, qt.IsNil)
206
207
208 args := probeArgs{
209 symbol: sanitizeSymbol(bashSym),
210 path: bashEx.path,
211 offset: off,
212 pid: perfAllThreads,
213 }
214
215
216 up, err := tracefsUprobe(args)
217 c.Assert(err, qt.IsNil)
218 c.Assert(up.Close(), qt.IsNil)
219 c.Assert(up.typ, qt.Equals, uprobeEvent)
220
221 args.ret = true
222 up, err = tracefsUprobe(args)
223 c.Assert(err, qt.IsNil)
224 c.Assert(up.Close(), qt.IsNil)
225 c.Assert(up.typ, qt.Equals, uretprobeEvent)
226
227
228 args.ret = false
229 u1, err := tracefsUprobe(args)
230 c.Assert(err, qt.IsNil)
231 defer u1.Close()
232 c.Assert(u1.tracefsID, qt.Not(qt.Equals), 0)
233
234 u2, err := tracefsUprobe(args)
235 c.Assert(err, qt.IsNil)
236 defer u2.Close()
237 c.Assert(u2.tracefsID, qt.Not(qt.Equals), 0)
238
239
240 c.Assert(u1.tracefsID, qt.Not(qt.CmpEquals()), u2.tracefsID)
241 }
242
243
244
245
246
247 func TestUprobeCreateTraceFS(t *testing.T) {
248 testutils.SkipOnOldKernel(t, "5.0", "<tracefs>/uprobe_events doesn't reject duplicate events")
249
250 c := qt.New(t)
251
252
253 off, err := bashEx.address(bashSym, &UprobeOptions{})
254 c.Assert(err, qt.IsNil)
255
256
257 ssym := sanitizeSymbol(bashSym)
258
259 pg, _ := randomGroup("ebpftest")
260 rg, _ := randomGroup("ebpftest")
261
262
263 defer func() {
264 _ = closeTraceFSProbeEvent(uprobeType, pg, ssym)
265 _ = closeTraceFSProbeEvent(uprobeType, rg, ssym)
266 }()
267
268
269 args := probeArgs{
270 group: pg,
271 symbol: ssym,
272 path: bashEx.path,
273 offset: off,
274 }
275
276
277 err = createTraceFSProbeEvent(uprobeType, args)
278 c.Assert(err, qt.IsNil)
279
280
281
282 err = createTraceFSProbeEvent(uprobeType, args)
283 c.Assert(errors.Is(err, os.ErrExist), qt.IsTrue,
284 qt.Commentf("expected consecutive uprobe creation to contain os.ErrExist, got: %v", err))
285
286
287 c.Assert(closeTraceFSProbeEvent(uprobeType, pg, ssym), qt.IsNil)
288
289 args.group = rg
290 args.ret = true
291
292
293 err = createTraceFSProbeEvent(uprobeType, args)
294 c.Assert(err, qt.IsNil)
295
296 err = createTraceFSProbeEvent(uprobeType, args)
297 c.Assert(os.IsExist(err), qt.IsFalse,
298 qt.Commentf("expected consecutive uretprobe creation to contain os.ErrExist, got: %v", err))
299
300
301 c.Assert(closeTraceFSProbeEvent(uprobeType, rg, ssym), qt.IsNil)
302 }
303
304 func TestUprobeSanitizedSymbol(t *testing.T) {
305 tests := []struct {
306 symbol string
307 expected string
308 }{
309 {"readline", "readline"},
310 {"main.Func123", "main_Func123"},
311 {"a.....a", "a_a"},
312 {"./;'{}[]a", "_a"},
313 {"***xx**xx###", "_xx_xx_"},
314 {`@P#r$i%v^3*+t)i&k++--`, "_P_r_i_v_3_t_i_k_"},
315 }
316
317 for i, tt := range tests {
318 t.Run(fmt.Sprint(i), func(t *testing.T) {
319 sanitized := sanitizeSymbol(tt.symbol)
320 if tt.expected != sanitized {
321 t.Errorf("Expected sanitized symbol to be '%s', got '%s'", tt.expected, sanitized)
322 }
323 })
324 }
325 }
326
327 func TestUprobeToken(t *testing.T) {
328 tests := []struct {
329 args probeArgs
330 expected string
331 }{
332 {probeArgs{path: "/bin/bash"}, "/bin/bash:0x0"},
333 {probeArgs{path: "/bin/bash", offset: 1}, "/bin/bash:0x1"},
334 {probeArgs{path: "/bin/bash", offset: 65535}, "/bin/bash:0xffff"},
335 {probeArgs{path: "/bin/bash", offset: 65536}, "/bin/bash:0x10000"},
336 {probeArgs{path: "/bin/bash", offset: 1, refCtrOffset: 1}, "/bin/bash:0x1(0x1)"},
337 {probeArgs{path: "/bin/bash", offset: 1, refCtrOffset: 65535}, "/bin/bash:0x1(0xffff)"},
338 }
339
340 for i, tt := range tests {
341 t.Run(fmt.Sprint(i), func(t *testing.T) {
342 po := uprobeToken(tt.args)
343 if tt.expected != po {
344 t.Errorf("Expected path:offset to be '%s', got '%s'", tt.expected, po)
345 }
346 })
347 }
348 }
349
350 func TestUprobeProgramCall(t *testing.T) {
351 tests := []struct {
352 name string
353 elf string
354 args []string
355 sym string
356 }{
357 {
358 "bash",
359 "/bin/bash",
360 []string{"--help"},
361 "main",
362 },
363 {
364 "go-binary",
365 path.Join(build.Default.GOROOT, "bin/go"),
366 []string{"version"},
367 "main.main",
368 },
369 }
370
371 for _, tt := range tests {
372 t.Run(tt.name, func(t *testing.T) {
373 if tt.name == "go-binary" {
374
375 testutils.SkipOnOldKernel(t, "4.14", "uprobes on Go binaries silently fail on kernel < 4.14")
376 }
377
378 m, p := newUpdaterMapProg(t, ebpf.Kprobe)
379
380
381 ex, err := OpenExecutable(tt.elf)
382 if err != nil {
383 t.Fatal(err)
384 }
385
386
387
388 u, err := ex.Uprobe(tt.sym, p, nil)
389 if errors.Is(err, ErrNoSymbol) {
390
391
392
393 t.Skipf("executable %s appear to be stripped, skipping", tt.elf)
394 }
395 if err != nil {
396 t.Fatal(err)
397 }
398
399
400 trigger := func(t *testing.T) {
401 if err := exec.Command(tt.elf, tt.args...).Run(); err != nil {
402 t.Fatal(err)
403 }
404 }
405 trigger(t)
406
407
408 assertMapValue(t, m, 0, 1)
409
410
411 if err := u.Close(); err != nil {
412 t.Fatal(err)
413 }
414
415
416 if err := m.Update(uint32(0), uint32(0), ebpf.UpdateExist); err != nil {
417 t.Fatal(err)
418 }
419
420
421 trigger(t)
422
423
424 assertMapValue(t, m, 0, 0)
425 })
426 }
427 }
428
429 func TestUprobeProgramWrongPID(t *testing.T) {
430 m, p := newUpdaterMapProg(t, ebpf.Kprobe)
431
432
433 ex, err := OpenExecutable("/bin/bash")
434 if err != nil {
435 t.Fatal(err)
436 }
437
438
439
440
441
442
443 u, err := ex.Uprobe("main", p, &UprobeOptions{PID: os.Getpid()})
444 if err != nil {
445 t.Fatal(err)
446 }
447 defer u.Close()
448
449
450 if err := exec.Command("/bin/bash", "--help").Run(); err != nil {
451 t.Fatal(err)
452 }
453
454
455 assertMapValue(t, m, 0, 0)
456 }
457
458 func TestHaveRefCtrOffsetPMU(t *testing.T) {
459 testutils.CheckFeatureTest(t, haveRefCtrOffsetPMU)
460 }
461
View as plain text