summaryrefslogtreecommitdiffstats
path: root/src/runtime/runtime-seh_windows_test.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
commitccd992355df7192993c666236047820244914598 (patch)
treef00fea65147227b7743083c6148396f74cd66935 /src/runtime/runtime-seh_windows_test.go
parentInitial commit. (diff)
downloadgolang-1.21-ccd992355df7192993c666236047820244914598.tar.xz
golang-1.21-ccd992355df7192993c666236047820244914598.zip
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/runtime/runtime-seh_windows_test.go')
-rw-r--r--src/runtime/runtime-seh_windows_test.go191
1 files changed, 191 insertions, 0 deletions
diff --git a/src/runtime/runtime-seh_windows_test.go b/src/runtime/runtime-seh_windows_test.go
new file mode 100644
index 0000000..27e4f49
--- /dev/null
+++ b/src/runtime/runtime-seh_windows_test.go
@@ -0,0 +1,191 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package runtime_test
+
+import (
+ "internal/abi"
+ "internal/syscall/windows"
+ "runtime"
+ "slices"
+ "testing"
+ "unsafe"
+)
+
+func sehf1() int {
+ return sehf1()
+}
+
+func sehf2() {}
+
+func TestSehLookupFunctionEntry(t *testing.T) {
+ if runtime.GOARCH != "amd64" {
+ t.Skip("skipping amd64-only test")
+ }
+ // This test checks that Win32 is able to retrieve
+ // function metadata stored in the .pdata section
+ // by the Go linker.
+ // Win32 unwinding will fail if this test fails,
+ // as RtlUnwindEx uses RtlLookupFunctionEntry internally.
+ // If that's the case, don't bother investigating further,
+ // first fix the .pdata generation.
+ sehf1pc := abi.FuncPCABIInternal(sehf1)
+ var fnwithframe func()
+ fnwithframe = func() {
+ fnwithframe()
+ }
+ fnwithoutframe := func() {}
+ tests := []struct {
+ name string
+ pc uintptr
+ hasframe bool
+ }{
+ {"no frame func", abi.FuncPCABIInternal(sehf2), false},
+ {"no func", sehf1pc - 1, false},
+ {"func at entry", sehf1pc, true},
+ {"func in prologue", sehf1pc + 1, true},
+ {"anonymous func with frame", abi.FuncPCABIInternal(fnwithframe), true},
+ {"anonymous func without frame", abi.FuncPCABIInternal(fnwithoutframe), false},
+ {"pc at func body", runtime.NewContextStub().GetPC(), true},
+ }
+ for _, tt := range tests {
+ var base uintptr
+ fn := windows.RtlLookupFunctionEntry(tt.pc, &base, nil)
+ if !tt.hasframe {
+ if fn != 0 {
+ t.Errorf("%s: unexpected frame", tt.name)
+ }
+ continue
+ }
+ if fn == 0 {
+ t.Errorf("%s: missing frame", tt.name)
+ }
+ }
+}
+
+func sehCallers() []uintptr {
+ // We don't need a real context,
+ // RtlVirtualUnwind just needs a context with
+ // valid a pc, sp and fp (aka bp).
+ ctx := runtime.NewContextStub()
+
+ pcs := make([]uintptr, 15)
+ var base, frame uintptr
+ var n int
+ for i := 0; i < len(pcs); i++ {
+ fn := windows.RtlLookupFunctionEntry(ctx.GetPC(), &base, nil)
+ if fn == 0 {
+ break
+ }
+ pcs[i] = ctx.GetPC()
+ n++
+ windows.RtlVirtualUnwind(0, base, ctx.GetPC(), fn, uintptr(unsafe.Pointer(ctx)), nil, &frame, nil)
+ }
+ return pcs[:n]
+}
+
+// SEH unwinding does not report inlined frames.
+//
+//go:noinline
+func sehf3(pan bool) []uintptr {
+ return sehf4(pan)
+}
+
+//go:noinline
+func sehf4(pan bool) []uintptr {
+ var pcs []uintptr
+ if pan {
+ panic("sehf4")
+ }
+ pcs = sehCallers()
+ return pcs
+}
+
+func testSehCallersEqual(t *testing.T, pcs []uintptr, want []string) {
+ t.Helper()
+ got := make([]string, 0, len(want))
+ for _, pc := range pcs {
+ fn := runtime.FuncForPC(pc)
+ if fn == nil || len(got) >= len(want) {
+ break
+ }
+ name := fn.Name()
+ switch name {
+ case "runtime.deferCallSave", "runtime.runOpenDeferFrame", "runtime.panicmem":
+ // These functions are skipped as they appear inconsistently depending
+ // whether inlining is on or off.
+ continue
+ }
+ got = append(got, name)
+ }
+ if !slices.Equal(want, got) {
+ t.Fatalf("wanted %v, got %v", want, got)
+ }
+}
+
+func TestSehUnwind(t *testing.T) {
+ if runtime.GOARCH != "amd64" {
+ t.Skip("skipping amd64-only test")
+ }
+ pcs := sehf3(false)
+ testSehCallersEqual(t, pcs, []string{"runtime_test.sehCallers", "runtime_test.sehf4",
+ "runtime_test.sehf3", "runtime_test.TestSehUnwind"})
+}
+
+func TestSehUnwindPanic(t *testing.T) {
+ if runtime.GOARCH != "amd64" {
+ t.Skip("skipping amd64-only test")
+ }
+ want := []string{"runtime_test.sehCallers", "runtime_test.TestSehUnwindPanic.func1", "runtime.gopanic",
+ "runtime_test.sehf4", "runtime_test.sehf3", "runtime_test.TestSehUnwindPanic"}
+ defer func() {
+ if r := recover(); r == nil {
+ t.Fatal("did not panic")
+ }
+ pcs := sehCallers()
+ testSehCallersEqual(t, pcs, want)
+ }()
+ sehf3(true)
+}
+
+func TestSehUnwindDoublePanic(t *testing.T) {
+ if runtime.GOARCH != "amd64" {
+ t.Skip("skipping amd64-only test")
+ }
+ want := []string{"runtime_test.sehCallers", "runtime_test.TestSehUnwindDoublePanic.func1.1", "runtime.gopanic",
+ "runtime_test.TestSehUnwindDoublePanic.func1", "runtime.gopanic", "runtime_test.TestSehUnwindDoublePanic"}
+ defer func() {
+ defer func() {
+ if recover() == nil {
+ t.Fatal("did not panic")
+ }
+ pcs := sehCallers()
+ testSehCallersEqual(t, pcs, want)
+ }()
+ if recover() == nil {
+ t.Fatal("did not panic")
+ }
+ panic(2)
+ }()
+ panic(1)
+}
+
+func TestSehUnwindNilPointerPanic(t *testing.T) {
+ if runtime.GOARCH != "amd64" {
+ t.Skip("skipping amd64-only test")
+ }
+ want := []string{"runtime_test.sehCallers", "runtime_test.TestSehUnwindNilPointerPanic.func1", "runtime.gopanic",
+ "runtime.sigpanic", "runtime_test.TestSehUnwindNilPointerPanic"}
+ defer func() {
+ if r := recover(); r == nil {
+ t.Fatal("did not panic")
+ }
+ pcs := sehCallers()
+ testSehCallersEqual(t, pcs, want)
+ }()
+ var p *int
+ if *p == 3 {
+ t.Fatal("did not see nil pointer panic")
+ }
+}