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  }