github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/runtime/unsafepoint_test.go (about) 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 }