Source file src/runtime/vdso_test.go

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build (freebsd && (386 || amd64 || arm || arm64 || riscv64)) || (linux && (386 || amd64 || arm || arm64 || loong64 || mips64 || mips64le || ppc64 || ppc64le || riscv64 || s390x))
     6  
     7  package runtime_test
     8  
     9  import (
    10  	"bytes"
    11  	"internal/asan"
    12  	"internal/goarch"
    13  	"internal/testenv"
    14  	"os"
    15  	"os/exec"
    16  	"path/filepath"
    17  	"syscall"
    18  	"testing"
    19  	"time"
    20  )
    21  
    22  // TestUsingVDSO tests that we are actually using the VDSO to fetch
    23  // the time.
    24  func TestUsingVDSO(t *testing.T) {
    25  	if asan.Enabled {
    26  		t.Skip("test fails with ASAN because the ASAN leak checker won't run under strace")
    27  	}
    28  
    29  	const calls = 100
    30  
    31  	if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
    32  		// Fetch the time a lot.
    33  		var total int64
    34  		for i := 0; i < calls; i++ {
    35  			total += time.Now().UnixNano()
    36  		}
    37  		os.Exit(0)
    38  	}
    39  
    40  	t.Parallel()
    41  
    42  	// Look for strace in /bin or /usr/bin. Don't assume that some
    43  	// strace on PATH is the one that we want.
    44  	strace := "/bin/strace"
    45  	if _, err := os.Stat(strace); err != nil {
    46  		strace = "/usr/bin/strace"
    47  		if _, err := os.Stat(strace); err != nil {
    48  			t.Skipf("skipping test because strace not found: %v", err)
    49  		}
    50  	}
    51  
    52  	exe, err := os.Executable()
    53  	if err != nil {
    54  		t.Skipf("skipping because Executable failed: %v", err)
    55  	}
    56  
    57  	traceFilter := "clock_gettime"
    58  
    59  	if goarch.PtrSize == 4 {
    60  		// Trace both clock_gettime and clock_gettime64: 32-bit Linux glibc uses
    61  		// the y2038-safe clock_gettime64 syscall, and strace's -e filter is an
    62  		// exact match, not a prefix.
    63  		traceFilter += ",clock_gettime64"
    64  	}
    65  
    66  	t.Logf("GO_WANT_HELPER_PROCESS=1 %s -f -e %s %s -test.run=^TestUsingVDSO$", strace, traceFilter, exe)
    67  	cmd := testenv.Command(t, strace, "-f", "-e", traceFilter, exe, "-test.run=^TestUsingVDSO$")
    68  	cmd = testenv.CleanCmdEnv(cmd)
    69  	cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1")
    70  	out, err := cmd.CombinedOutput()
    71  	if len(out) > 0 {
    72  		t.Logf("%s", out)
    73  	}
    74  	if err != nil {
    75  		if err, ok := err.(*exec.ExitError); ok && err.Sys().(syscall.WaitStatus).Signaled() {
    76  			if !bytes.Contains(out, []byte("+++ killed by")) {
    77  				// strace itself occasionally crashes.
    78  				// Here, it exited with a signal, but
    79  				// the strace log didn't report any
    80  				// signal from the child process.
    81  				t.Log(err)
    82  				testenv.SkipFlaky(t, 63734)
    83  			}
    84  		}
    85  		t.Fatal(err)
    86  	}
    87  
    88  	if got := bytes.Count(out, []byte("gettime")); got >= calls {
    89  		t.Logf("found %d gettime calls, want < %d", got, calls)
    90  
    91  		// Try to double-check that a C program uses the VDSO.
    92  		tempdir := t.TempDir()
    93  		cfn := filepath.Join(tempdir, "time.c")
    94  		cexe := filepath.Join(tempdir, "time")
    95  		if err := os.WriteFile(cfn, []byte(vdsoCProgram), 0o644); err != nil {
    96  			t.Fatal(err)
    97  		}
    98  		cc := os.Getenv("CC")
    99  		if cc == "" {
   100  			cc, err = exec.LookPath("gcc")
   101  			if err != nil {
   102  				cc, err = exec.LookPath("clang")
   103  				if err != nil {
   104  					t.Skip("can't verify VDSO status, no C compiler")
   105  				}
   106  			}
   107  		}
   108  
   109  		t.Logf("%s -o %s %s", cc, cexe, cfn)
   110  		cmd = testenv.Command(t, cc, "-o", cexe, cfn)
   111  		cmd = testenv.CleanCmdEnv(cmd)
   112  		out, err = cmd.CombinedOutput()
   113  		if len(out) > 0 {
   114  			t.Logf("%s", out)
   115  		}
   116  		if err != nil {
   117  			t.Skipf("can't verify VDSO status, C compiled failed: %v", err)
   118  		}
   119  
   120  		t.Logf("%s -f -e %s %s", strace, traceFilter, cexe)
   121  		cmd = testenv.Command(t, strace, "-f", "-e", traceFilter, cexe)
   122  		cmd = testenv.CleanCmdEnv(cmd)
   123  		out, err = cmd.CombinedOutput()
   124  		if len(out) > 0 {
   125  			t.Logf("%s", out)
   126  		}
   127  		if err != nil {
   128  			t.Skipf("can't verify VDSO status, C program failed: %v", err)
   129  		}
   130  
   131  		if cgot := bytes.Count(out, []byte("gettime")); cgot >= 100 {
   132  			t.Logf("found %d gettime calls, want < %d", cgot, 100)
   133  			t.Log("C program does not use VDSO either")
   134  			return
   135  		}
   136  
   137  		// The Go program used the system call but the C
   138  		// program did not. This is a VDSO failure for Go.
   139  		t.Errorf("did not use VDSO system call")
   140  	}
   141  }
   142  
   143  const vdsoCProgram = `
   144  #include <stdio.h>
   145  #include <time.h>
   146  
   147  int main() {
   148  	int i;
   149  	time_t tot;
   150  	for (i = 0; i < 100; i++) {
   151  		struct timespec ts;
   152  		clock_gettime(CLOCK_MONOTONIC, &ts);
   153  		tot += ts.tv_nsec;
   154  	}
   155  	printf("%d\n", (int)(tot));
   156  	return 0;
   157  }
   158  `
   159  

View as plain text