...

Source file src/github.com/cilium/ebpf/link/uprobe_test.go

Documentation: github.com/cilium/ebpf/link

     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  	// This Uprobe is broken and will not work because the offset is not
    98  	// correct. This is expected since the offset is provided by the user.
    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  	// trying to open a perf event on a non-existent PID will return ESRCH.
   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  // Test u(ret)probe creation using perf_uprobe PMU.
   139  func TestUprobeCreatePMU(t *testing.T) {
   140  	// Requires at least 4.17 (e12f03d7031a "perf/core: Implement the 'perf_kprobe' PMU")
   141  	testutils.SkipOnOldKernel(t, "4.17", "perf_kprobe PMU")
   142  
   143  	c := qt.New(t)
   144  
   145  	// Fetch the offset from the /bin/bash Executable already defined.
   146  	off, err := bashEx.address(bashSym, &UprobeOptions{})
   147  	c.Assert(err, qt.IsNil)
   148  
   149  	// Prepare probe args.
   150  	args := probeArgs{
   151  		symbol: bashSym,
   152  		path:   bashEx.path,
   153  		offset: off,
   154  		pid:    perfAllThreads,
   155  	}
   156  
   157  	// uprobe PMU
   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  	// uretprobe PMU
   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  // Test fallback behaviour on kernels without perf_uprobe PMU available.
   174  func TestUprobePMUUnavailable(t *testing.T) {
   175  	c := qt.New(t)
   176  
   177  	// Fetch the offset from the /bin/bash Executable already defined.
   178  	off, err := bashEx.address(bashSym, &UprobeOptions{})
   179  	c.Assert(err, qt.IsNil)
   180  
   181  	// Prepare probe args.
   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  	// Expect ErrNotSupported.
   196  	c.Assert(errors.Is(err, ErrNotSupported), qt.IsTrue, qt.Commentf("got error: %s", err))
   197  }
   198  
   199  // Test tracefs u(ret)probe creation on all kernel versions.
   200  func TestUprobeTraceFS(t *testing.T) {
   201  	c := qt.New(t)
   202  
   203  	// Fetch the offset from the /bin/bash Executable already defined.
   204  	off, err := bashEx.address(bashSym, &UprobeOptions{})
   205  	c.Assert(err, qt.IsNil)
   206  
   207  	// Prepare probe args.
   208  	args := probeArgs{
   209  		symbol: sanitizeSymbol(bashSym),
   210  		path:   bashEx.path,
   211  		offset: off,
   212  		pid:    perfAllThreads,
   213  	}
   214  
   215  	// Open and close tracefs u(ret)probes, checking all errors.
   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  	// Create two identical trace events, ensure their IDs differ.
   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  	// Compare the uprobes' tracefs IDs.
   240  	c.Assert(u1.tracefsID, qt.Not(qt.CmpEquals()), u2.tracefsID)
   241  }
   242  
   243  // Test u(ret)probe creation writing directly to <tracefs>/uprobe_events.
   244  // Only runs on 5.0 and over. Earlier versions ignored writes of duplicate
   245  // events, while 5.0 started returning -EEXIST when a uprobe event already
   246  // exists.
   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  	// Fetch the offset from the /bin/bash Executable already defined.
   253  	off, err := bashEx.address(bashSym, &UprobeOptions{})
   254  	c.Assert(err, qt.IsNil)
   255  
   256  	// Sanitize the symbol in order to be used in tracefs API.
   257  	ssym := sanitizeSymbol(bashSym)
   258  
   259  	pg, _ := randomGroup("ebpftest")
   260  	rg, _ := randomGroup("ebpftest")
   261  
   262  	// Tee up cleanups in case any of the Asserts abort the function.
   263  	defer func() {
   264  		_ = closeTraceFSProbeEvent(uprobeType, pg, ssym)
   265  		_ = closeTraceFSProbeEvent(uprobeType, rg, ssym)
   266  	}()
   267  
   268  	// Prepare probe args.
   269  	args := probeArgs{
   270  		group:  pg,
   271  		symbol: ssym,
   272  		path:   bashEx.path,
   273  		offset: off,
   274  	}
   275  
   276  	// Create a uprobe.
   277  	err = createTraceFSProbeEvent(uprobeType, args)
   278  	c.Assert(err, qt.IsNil)
   279  
   280  	// Attempt to create an identical uprobe using tracefs,
   281  	// expect it to fail with os.ErrExist.
   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  	// Expect a successful close of the kprobe.
   287  	c.Assert(closeTraceFSProbeEvent(uprobeType, pg, ssym), qt.IsNil)
   288  
   289  	args.group = rg
   290  	args.ret = true
   291  
   292  	// Same test for a kretprobe.
   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  	// Expect a successful close of the uretprobe.
   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  				// https://github.com/cilium/ebpf/issues/406
   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  			// Load the executable.
   381  			ex, err := OpenExecutable(tt.elf)
   382  			if err != nil {
   383  				t.Fatal(err)
   384  			}
   385  
   386  			// Open Uprobe on the executable for the given symbol
   387  			// and attach it to the ebpf program created above.
   388  			u, err := ex.Uprobe(tt.sym, p, nil)
   389  			if errors.Is(err, ErrNoSymbol) {
   390  				// Assume bash::main and go::main.main always exists
   391  				// and skip the test if the symbol can't be found as
   392  				// certain OS (eg. Debian) strip binaries.
   393  				t.Skipf("executable %s appear to be stripped, skipping", tt.elf)
   394  			}
   395  			if err != nil {
   396  				t.Fatal(err)
   397  			}
   398  
   399  			// Trigger ebpf program call.
   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  			// Assert that the value at index 0 has been updated to 1.
   408  			assertMapValue(t, m, 0, 1)
   409  
   410  			// Detach the Uprobe.
   411  			if err := u.Close(); err != nil {
   412  				t.Fatal(err)
   413  			}
   414  
   415  			// Reset map value to 0 at index 0.
   416  			if err := m.Update(uint32(0), uint32(0), ebpf.UpdateExist); err != nil {
   417  				t.Fatal(err)
   418  			}
   419  
   420  			// Retrigger the ebpf program call.
   421  			trigger(t)
   422  
   423  			// Assert that this time the value has not been updated.
   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  	// Load the '/bin/bash' executable.
   433  	ex, err := OpenExecutable("/bin/bash")
   434  	if err != nil {
   435  		t.Fatal(err)
   436  	}
   437  
   438  	// Open Uprobe on '/bin/bash' for the symbol 'main'
   439  	// and attach it to the ebpf program created above.
   440  	// Create the perf-event with the current process' PID
   441  	// to make sure the event is not fired when we will try
   442  	// to trigger the program execution via exec.
   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  	// Trigger ebpf program call.
   450  	if err := exec.Command("/bin/bash", "--help").Run(); err != nil {
   451  		t.Fatal(err)
   452  	}
   453  
   454  	// Assert that the value at index 0 is still 0.
   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