Source file src/runtime/unsafepoint_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  package runtime_test
     6  
     7  import (
     8  	"internal/testenv"
     9  	"os"
    10  	"os/exec"
    11  	"reflect"
    12  	"runtime"
    13  	"strconv"
    14  	"strings"
    15  	"testing"
    16  )
    17  
    18  // This is the function we'll be testing.
    19  // It has a simple write barrier in it.
    20  func setGlobalPointer() {
    21  	globalPointer = nil
    22  }
    23  
    24  var globalPointer *int
    25  
    26  func TestUnsafePoint(t *testing.T) {
    27  	testenv.MustHaveExec(t)
    28  	switch runtime.GOARCH {
    29  	case "amd64", "arm64":
    30  	default:
    31  		t.Skipf("test not enabled for %s", runtime.GOARCH)
    32  	}
    33  
    34  	// Get a reference we can use to ask the runtime about
    35  	// which of its instructions are unsafe preemption points.
    36  	f := runtime.FuncForPC(reflect.ValueOf(setGlobalPointer).Pointer())
    37  
    38  	// Disassemble the test function.
    39  	// Note that normally "go test runtime" would strip symbols
    40  	// and prevent this step from working. So there's a hack in
    41  	// cmd/go/internal/test that exempts runtime tests from
    42  	// symbol stripping.
    43  	cmd := exec.Command(testenv.GoToolPath(t), "tool", "objdump", "-s", "setGlobalPointer", os.Args[0])
    44  	out, err := cmd.CombinedOutput()
    45  	if err != nil {
    46  		t.Fatalf("can't objdump %v", err)
    47  	}
    48  	lines := strings.Split(string(out), "\n")[1:]
    49  
    50  	// Walk through assembly instructions, checking preemptible flags.
    51  	var entry uint64
    52  	var startedWB bool
    53  	var doneWB bool
    54  	instructionCount := 0
    55  	unsafeCount := 0
    56  	for _, line := range lines {
    57  		line = strings.TrimSpace(line)
    58  		t.Logf("%s", line)
    59  		parts := strings.Fields(line)
    60  		if len(parts) < 4 {
    61  			continue
    62  		}
    63  		if !strings.HasPrefix(parts[0], "unsafepoint_test.go:") {
    64  			continue
    65  		}
    66  		pc, err := strconv.ParseUint(parts[1][2:], 16, 64)
    67  		if err != nil {
    68  			t.Fatalf("can't parse pc %s: %v", parts[1], err)
    69  		}
    70  		if entry == 0 {
    71  			entry = pc
    72  		}
    73  		// Note that some platforms do ASLR, so the PCs in the disassembly
    74  		// don't match PCs in the address space. Only offsets from function
    75  		// entry make sense.
    76  		unsafe := runtime.UnsafePoint(f.Entry() + uintptr(pc-entry))
    77  		t.Logf("unsafe: %v\n", unsafe)
    78  		instructionCount++
    79  		if unsafe {
    80  			unsafeCount++
    81  		}
    82  
    83  		// All the instructions inside the write barrier must be unpreemptible.
    84  		if startedWB && !doneWB && !unsafe {
    85  			t.Errorf("instruction %s must be marked unsafe, but isn't", parts[1])
    86  		}
    87  
    88  		// Detect whether we're in the write barrier.
    89  		switch runtime.GOARCH {
    90  		case "arm64":
    91  			if parts[3] == "MOVWU" {
    92  				// The unpreemptible region starts after the
    93  				// load of runtime.writeBarrier.
    94  				startedWB = true
    95  			}
    96  			if parts[3] == "MOVD" && parts[4] == "ZR," {
    97  				// The unpreemptible region ends after the
    98  				// write of nil.
    99  				doneWB = true
   100  			}
   101  		case "amd64":
   102  			if parts[3] == "CMPL" {
   103  				startedWB = true
   104  			}
   105  			if parts[3] == "MOVQ" && parts[4] == "$0x0," {
   106  				doneWB = true
   107  			}
   108  		}
   109  	}
   110  
   111  	if instructionCount == 0 {
   112  		t.Errorf("no instructions")
   113  	}
   114  	if unsafeCount == instructionCount {
   115  		t.Errorf("no interruptible instructions")
   116  	}
   117  	// Note that there are other instructions marked unpreemptible besides
   118  	// just the ones required by the write barrier. Those include possibly
   119  	// the preamble and postamble, as well as bleeding out from the
   120  	// write barrier proper into adjacent instructions (in both directions).
   121  	// Hopefully we can clean up the latter at some point.
   122  }
   123  

View as plain text