github.com/cilium/ebpf@v0.10.0/link/uprobe_test.go (about) 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 // This symbol will not be present in Executable (elf.SHN_UNDEF). 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 // NB: It's not possible to invoke the uprobe since we use an arbitrary 98 // address. 99 up, err := bashEx.Uprobe("open", prog, &UprobeOptions{ 100 // arm64 doesn't seem to allow addresses on the first page. Use 101 // the first byte of the second page. 102 Address: uint64(os.Getpagesize()), 103 }) 104 if err != nil { 105 t.Fatal(err) 106 } 107 defer up.Close() 108 } 109 110 func TestUprobeWithPID(t *testing.T) { 111 prog := mustLoadProgram(t, ebpf.Kprobe, 0, "") 112 113 up, err := bashEx.Uprobe(bashSym, prog, &UprobeOptions{PID: os.Getpid()}) 114 if err != nil { 115 t.Fatal(err) 116 } 117 defer up.Close() 118 } 119 120 func TestUprobeWithNonExistentPID(t *testing.T) { 121 prog := mustLoadProgram(t, ebpf.Kprobe, 0, "") 122 123 // trying to open a perf event on a non-existent PID will return ESRCH. 124 _, err := bashEx.Uprobe(bashSym, prog, &UprobeOptions{PID: -2}) 125 if !errors.Is(err, unix.ESRCH) { 126 t.Fatalf("expected ESRCH, got %v", err) 127 } 128 } 129 130 func TestUretprobe(t *testing.T) { 131 c := qt.New(t) 132 133 prog := mustLoadProgram(t, ebpf.Kprobe, 0, "") 134 135 up, err := bashEx.Uretprobe(bashSym, prog, nil) 136 c.Assert(err, qt.IsNil) 137 defer up.Close() 138 139 testLink(t, up, prog) 140 } 141 142 // Test u(ret)probe creation using perf_uprobe PMU. 143 func TestUprobeCreatePMU(t *testing.T) { 144 // Requires at least 4.17 (e12f03d7031a "perf/core: Implement the 'perf_kprobe' PMU") 145 testutils.SkipOnOldKernel(t, "4.17", "perf_kprobe PMU") 146 147 c := qt.New(t) 148 149 // Fetch the offset from the /bin/bash Executable already defined. 150 off, err := bashEx.address(bashSym, &UprobeOptions{}) 151 c.Assert(err, qt.IsNil) 152 153 // Prepare probe args. 154 args := probeArgs{ 155 symbol: bashSym, 156 path: bashEx.path, 157 offset: off, 158 pid: perfAllThreads, 159 } 160 161 // uprobe PMU 162 pu, err := pmuUprobe(args) 163 c.Assert(err, qt.IsNil) 164 defer pu.Close() 165 166 c.Assert(pu.typ, qt.Equals, uprobeEvent) 167 168 // uretprobe PMU 169 args.ret = true 170 pr, err := pmuUprobe(args) 171 c.Assert(err, qt.IsNil) 172 defer pr.Close() 173 174 c.Assert(pr.typ, qt.Equals, uretprobeEvent) 175 } 176 177 // Test fallback behaviour on kernels without perf_uprobe PMU available. 178 func TestUprobePMUUnavailable(t *testing.T) { 179 c := qt.New(t) 180 181 // Fetch the offset from the /bin/bash Executable already defined. 182 off, err := bashEx.address(bashSym, &UprobeOptions{}) 183 c.Assert(err, qt.IsNil) 184 185 // Prepare probe args. 186 args := probeArgs{ 187 symbol: bashSym, 188 path: bashEx.path, 189 offset: off, 190 pid: perfAllThreads, 191 } 192 193 pk, err := pmuUprobe(args) 194 if err == nil { 195 pk.Close() 196 t.Skipf("Kernel supports perf_uprobe PMU, not asserting error.") 197 } 198 199 // Expect ErrNotSupported. 200 c.Assert(errors.Is(err, ErrNotSupported), qt.IsTrue, qt.Commentf("got error: %s", err)) 201 } 202 203 // Test tracefs u(ret)probe creation on all kernel versions. 204 func TestUprobeTraceFS(t *testing.T) { 205 c := qt.New(t) 206 207 // Fetch the offset from the /bin/bash Executable already defined. 208 off, err := bashEx.address(bashSym, &UprobeOptions{}) 209 c.Assert(err, qt.IsNil) 210 211 // Prepare probe args. 212 args := probeArgs{ 213 symbol: sanitizeSymbol(bashSym), 214 path: bashEx.path, 215 offset: off, 216 pid: perfAllThreads, 217 } 218 219 // Open and close tracefs u(ret)probes, checking all errors. 220 up, err := tracefsUprobe(args) 221 c.Assert(err, qt.IsNil) 222 c.Assert(up.Close(), qt.IsNil) 223 c.Assert(up.typ, qt.Equals, uprobeEvent) 224 225 args.ret = true 226 up, err = tracefsUprobe(args) 227 c.Assert(err, qt.IsNil) 228 c.Assert(up.Close(), qt.IsNil) 229 c.Assert(up.typ, qt.Equals, uretprobeEvent) 230 231 // Create two identical trace events, ensure their IDs differ. 232 args.ret = false 233 u1, err := tracefsUprobe(args) 234 c.Assert(err, qt.IsNil) 235 defer u1.Close() 236 c.Assert(u1.tracefsID, qt.Not(qt.Equals), 0) 237 238 u2, err := tracefsUprobe(args) 239 c.Assert(err, qt.IsNil) 240 defer u2.Close() 241 c.Assert(u2.tracefsID, qt.Not(qt.Equals), 0) 242 243 // Compare the uprobes' tracefs IDs. 244 c.Assert(u1.tracefsID, qt.Not(qt.CmpEquals()), u2.tracefsID) 245 } 246 247 // Test u(ret)probe creation writing directly to <tracefs>/uprobe_events. 248 func TestUprobeCreateTraceFS(t *testing.T) { 249 c := qt.New(t) 250 251 // Fetch the offset from the /bin/bash Executable already defined. 252 off, err := bashEx.address(bashSym, &UprobeOptions{}) 253 c.Assert(err, qt.IsNil) 254 255 // Sanitize the symbol in order to be used in tracefs API. 256 ssym := sanitizeSymbol(bashSym) 257 258 pg, _ := randomGroup("ebpftest") 259 rg, _ := randomGroup("ebpftest") 260 261 // Tee up cleanups in case any of the Asserts abort the function. 262 defer func() { 263 _ = closeTraceFSProbeEvent(uprobeType, pg, ssym) 264 _ = closeTraceFSProbeEvent(uprobeType, rg, ssym) 265 }() 266 267 // Prepare probe args. 268 args := probeArgs{ 269 group: pg, 270 symbol: ssym, 271 path: bashEx.path, 272 offset: off, 273 } 274 275 // Create a uprobe. 276 _, err = createTraceFSProbeEvent(uprobeType, args) 277 c.Assert(err, qt.IsNil) 278 279 // Attempt to create an identical uprobe using tracefs, 280 // expect it to fail with os.ErrExist. 281 _, err = createTraceFSProbeEvent(uprobeType, args) 282 c.Assert(errors.Is(err, os.ErrExist), qt.IsTrue, 283 qt.Commentf("expected consecutive uprobe creation to contain os.ErrExist, got: %v", err)) 284 285 // Expect a successful close of the uprobe. 286 c.Assert(closeTraceFSProbeEvent(uprobeType, pg, ssym), qt.IsNil) 287 288 args.group = rg 289 args.ret = true 290 291 // Same test for a kretprobe. 292 _, err = createTraceFSProbeEvent(uprobeType, args) 293 c.Assert(err, qt.IsNil) 294 295 _, err = createTraceFSProbeEvent(uprobeType, args) 296 c.Assert(os.IsExist(err), qt.IsFalse, 297 qt.Commentf("expected consecutive uretprobe creation to contain os.ErrExist, got: %v", err)) 298 299 // Expect a successful close of the uretprobe. 300 c.Assert(closeTraceFSProbeEvent(uprobeType, rg, ssym), qt.IsNil) 301 } 302 303 func TestUprobeSanitizedSymbol(t *testing.T) { 304 tests := []struct { 305 symbol string 306 expected string 307 }{ 308 {"readline", "readline"}, 309 {"main.Func123", "main_Func123"}, 310 {"a.....a", "a_a"}, 311 {"./;'{}[]a", "_a"}, 312 {"***xx**xx###", "_xx_xx_"}, 313 {`@P#r$i%v^3*+t)i&k++--`, "_P_r_i_v_3_t_i_k_"}, 314 } 315 316 for i, tt := range tests { 317 t.Run(fmt.Sprint(i), func(t *testing.T) { 318 sanitized := sanitizeSymbol(tt.symbol) 319 if tt.expected != sanitized { 320 t.Errorf("Expected sanitized symbol to be '%s', got '%s'", tt.expected, sanitized) 321 } 322 }) 323 } 324 } 325 326 func TestUprobeToken(t *testing.T) { 327 tests := []struct { 328 args probeArgs 329 expected string 330 }{ 331 {probeArgs{path: "/bin/bash"}, "/bin/bash:0x0"}, 332 {probeArgs{path: "/bin/bash", offset: 1}, "/bin/bash:0x1"}, 333 {probeArgs{path: "/bin/bash", offset: 65535}, "/bin/bash:0xffff"}, 334 {probeArgs{path: "/bin/bash", offset: 65536}, "/bin/bash:0x10000"}, 335 {probeArgs{path: "/bin/bash", offset: 1, refCtrOffset: 1}, "/bin/bash:0x1(0x1)"}, 336 {probeArgs{path: "/bin/bash", offset: 1, refCtrOffset: 65535}, "/bin/bash:0x1(0xffff)"}, 337 } 338 339 for i, tt := range tests { 340 t.Run(fmt.Sprint(i), func(t *testing.T) { 341 po := uprobeToken(tt.args) 342 if tt.expected != po { 343 t.Errorf("Expected path:offset to be '%s', got '%s'", tt.expected, po) 344 } 345 }) 346 } 347 } 348 349 func TestUprobeProgramCall(t *testing.T) { 350 tests := []struct { 351 name string 352 elf string 353 args []string 354 sym string 355 }{ 356 { 357 "bash", 358 "/bin/bash", 359 []string{"--help"}, 360 "main", 361 }, 362 { 363 "go-binary", 364 path.Join(build.Default.GOROOT, "bin/go"), 365 []string{"version"}, 366 "main.main", 367 }, 368 } 369 370 for _, tt := range tests { 371 t.Run(tt.name, func(t *testing.T) { 372 if tt.name == "go-binary" { 373 // https://github.com/cilium/ebpf/issues/406 374 testutils.SkipOnOldKernel(t, "4.14", "uprobes on Go binaries silently fail on kernel < 4.14") 375 } 376 377 m, p := newUpdaterMapProg(t, ebpf.Kprobe, 0) 378 379 // Load the executable. 380 ex, err := OpenExecutable(tt.elf) 381 if err != nil { 382 t.Fatal(err) 383 } 384 385 // Open Uprobe on the executable for the given symbol 386 // and attach it to the ebpf program created above. 387 u, err := ex.Uprobe(tt.sym, p, nil) 388 if errors.Is(err, ErrNoSymbol) { 389 // Assume bash::main and go::main.main always exists 390 // and skip the test if the symbol can't be found as 391 // certain OS (eg. Debian) strip binaries. 392 t.Skipf("executable %s appear to be stripped, skipping", tt.elf) 393 } 394 if err != nil { 395 t.Fatal(err) 396 } 397 398 // Trigger ebpf program call. 399 trigger := func(t *testing.T) { 400 if err := exec.Command(tt.elf, tt.args...).Run(); err != nil { 401 t.Fatal(err) 402 } 403 } 404 trigger(t) 405 406 // Assert that the value at index 0 has been updated to 1. 407 assertMapValue(t, m, 0, 1) 408 409 // Detach the Uprobe. 410 if err := u.Close(); err != nil { 411 t.Fatal(err) 412 } 413 414 // Reset map value to 0 at index 0. 415 if err := m.Update(uint32(0), uint32(0), ebpf.UpdateExist); err != nil { 416 t.Fatal(err) 417 } 418 419 // Retrigger the ebpf program call. 420 trigger(t) 421 422 // Assert that this time the value has not been updated. 423 assertMapValue(t, m, 0, 0) 424 }) 425 } 426 } 427 428 func TestUprobeProgramWrongPID(t *testing.T) { 429 m, p := newUpdaterMapProg(t, ebpf.Kprobe, 0) 430 431 // Load the '/bin/bash' executable. 432 ex, err := OpenExecutable("/bin/bash") 433 if err != nil { 434 t.Fatal(err) 435 } 436 437 // Open Uprobe on '/bin/bash' for the symbol 'main' 438 // and attach it to the ebpf program created above. 439 // Create the perf-event with the current process' PID 440 // to make sure the event is not fired when we will try 441 // to trigger the program execution via exec. 442 u, err := ex.Uprobe("main", p, &UprobeOptions{PID: os.Getpid()}) 443 if err != nil { 444 t.Fatal(err) 445 } 446 defer u.Close() 447 448 // Trigger ebpf program call. 449 if err := exec.Command("/bin/bash", "--help").Run(); err != nil { 450 t.Fatal(err) 451 } 452 453 // Assert that the value at index 0 is still 0. 454 assertMapValue(t, m, 0, 0) 455 } 456 457 func TestHaveRefCtrOffsetPMU(t *testing.T) { 458 testutils.CheckFeatureTest(t, haveRefCtrOffsetPMU) 459 }