github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/runtime/runtime-seh_windows_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/abi" 9 "internal/syscall/windows" 10 "runtime" 11 "slices" 12 "testing" 13 "unsafe" 14 ) 15 16 func sehf1() int { 17 return sehf1() 18 } 19 20 func sehf2() {} 21 22 func TestSehLookupFunctionEntry(t *testing.T) { 23 if runtime.GOARCH != "amd64" { 24 t.Skip("skipping amd64-only test") 25 } 26 // This test checks that Win32 is able to retrieve 27 // function metadata stored in the .pdata section 28 // by the Go linker. 29 // Win32 unwinding will fail if this test fails, 30 // as RtlUnwindEx uses RtlLookupFunctionEntry internally. 31 // If that's the case, don't bother investigating further, 32 // first fix the .pdata generation. 33 sehf1pc := abi.FuncPCABIInternal(sehf1) 34 var fnwithframe func() 35 fnwithframe = func() { 36 fnwithframe() 37 } 38 fnwithoutframe := func() {} 39 tests := []struct { 40 name string 41 pc uintptr 42 hasframe bool 43 }{ 44 {"no frame func", abi.FuncPCABIInternal(sehf2), false}, 45 {"no func", sehf1pc - 1, false}, 46 {"func at entry", sehf1pc, true}, 47 {"func in prologue", sehf1pc + 1, true}, 48 {"anonymous func with frame", abi.FuncPCABIInternal(fnwithframe), true}, 49 {"anonymous func without frame", abi.FuncPCABIInternal(fnwithoutframe), false}, 50 {"pc at func body", runtime.NewContextStub().GetPC(), true}, 51 } 52 for _, tt := range tests { 53 var base uintptr 54 fn := windows.RtlLookupFunctionEntry(tt.pc, &base, nil) 55 if !tt.hasframe { 56 if fn != 0 { 57 t.Errorf("%s: unexpected frame", tt.name) 58 } 59 continue 60 } 61 if fn == 0 { 62 t.Errorf("%s: missing frame", tt.name) 63 } 64 } 65 } 66 67 func sehCallers() []uintptr { 68 // We don't need a real context, 69 // RtlVirtualUnwind just needs a context with 70 // valid a pc, sp and fp (aka bp). 71 ctx := runtime.NewContextStub() 72 73 pcs := make([]uintptr, 15) 74 var base, frame uintptr 75 var n int 76 for i := 0; i < len(pcs); i++ { 77 fn := windows.RtlLookupFunctionEntry(ctx.GetPC(), &base, nil) 78 if fn == 0 { 79 break 80 } 81 windows.RtlVirtualUnwind(0, base, ctx.GetPC(), fn, uintptr(unsafe.Pointer(&ctx)), nil, &frame, nil) 82 n++ 83 pcs[i] = ctx.GetPC() 84 } 85 return pcs[:n] 86 } 87 88 // SEH unwinding does not report inlined frames. 89 // 90 //go:noinline 91 func sehf3(pan bool) []uintptr { 92 return sehf4(pan) 93 } 94 95 //go:noinline 96 func sehf4(pan bool) []uintptr { 97 var pcs []uintptr 98 if pan { 99 panic("sehf4") 100 } 101 pcs = sehCallers() 102 return pcs 103 } 104 105 func testSehCallersEqual(t *testing.T, pcs []uintptr, want []string) { 106 t.Helper() 107 got := make([]string, 0, len(want)) 108 for _, pc := range pcs { 109 fn := runtime.FuncForPC(pc) 110 if fn == nil || len(got) >= len(want) { 111 break 112 } 113 name := fn.Name() 114 switch name { 115 case "runtime.deferCallSave", "runtime.runOpenDeferFrame", "runtime.panicmem": 116 // These functions are skipped as they appear inconsistently depending 117 // whether inlining is on or off. 118 continue 119 } 120 got = append(got, name) 121 } 122 if !slices.Equal(want, got) { 123 t.Fatalf("wanted %v, got %v", want, got) 124 } 125 } 126 127 func TestSehUnwind(t *testing.T) { 128 if runtime.GOARCH != "amd64" { 129 t.Skip("skipping amd64-only test") 130 } 131 pcs := sehf3(false) 132 testSehCallersEqual(t, pcs, []string{"runtime_test.sehCallers", "runtime_test.sehf4", 133 "runtime_test.sehf3", "runtime_test.TestSehUnwind"}) 134 } 135 136 func TestSehUnwindPanic(t *testing.T) { 137 if runtime.GOARCH != "amd64" { 138 t.Skip("skipping amd64-only test") 139 } 140 want := []string{"runtime_test.sehCallers", "runtime_test.TestSehUnwindPanic.func1", "runtime.gopanic", 141 "runtime_test.sehf4", "runtime_test.sehf3", "runtime_test.TestSehUnwindPanic"} 142 defer func() { 143 if r := recover(); r == nil { 144 t.Fatal("did not panic") 145 } 146 pcs := sehCallers() 147 testSehCallersEqual(t, pcs, want) 148 }() 149 sehf3(true) 150 } 151 152 func TestSehUnwindDoublePanic(t *testing.T) { 153 if runtime.GOARCH != "amd64" { 154 t.Skip("skipping amd64-only test") 155 } 156 want := []string{"runtime_test.sehCallers", "runtime_test.TestSehUnwindDoublePanic.func1.1", "runtime.gopanic", 157 "runtime_test.TestSehUnwindDoublePanic.func1", "runtime.gopanic", "runtime_test.TestSehUnwindDoublePanic"} 158 defer func() { 159 defer func() { 160 if recover() == nil { 161 t.Fatal("did not panic") 162 } 163 pcs := sehCallers() 164 testSehCallersEqual(t, pcs, want) 165 }() 166 if recover() == nil { 167 t.Fatal("did not panic") 168 } 169 panic(2) 170 }() 171 panic(1) 172 } 173 174 func TestSehUnwindNilPointerPanic(t *testing.T) { 175 if runtime.GOARCH != "amd64" { 176 t.Skip("skipping amd64-only test") 177 } 178 want := []string{"runtime_test.sehCallers", "runtime_test.TestSehUnwindNilPointerPanic.func1", "runtime.gopanic", 179 "runtime.sigpanic", "runtime_test.TestSehUnwindNilPointerPanic"} 180 defer func() { 181 if r := recover(); r == nil { 182 t.Fatal("did not panic") 183 } 184 pcs := sehCallers() 185 testSehCallersEqual(t, pcs, want) 186 }() 187 var p *int 188 if *p == 3 { 189 t.Fatal("did not see nil pointer panic") 190 } 191 }