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.
9 "internal/syscall/windows"
22 func TestSehLookupFunctionEntry(t *testing.T) {
23 if runtime.GOARCH != "amd64" {
24 t.Skip("skipping amd64-only test")
26 // This test checks that Win32 is able to retrieve
27 // function metadata stored in the .pdata section
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() {
38 fnwithoutframe := func() {}
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},
52 for _, tt := range tests {
54 fn := windows.RtlLookupFunctionEntry(tt.pc, &base, nil)
57 t.Errorf("%s: unexpected frame", tt.name)
62 t.Errorf("%s: missing frame", tt.name)
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()
73 pcs := make([]uintptr, 15)
74 var base, frame uintptr
76 for i := 0; i < len(pcs); i++ {
77 fn := windows.RtlLookupFunctionEntry(ctx.GetPC(), &base, nil)
83 windows.RtlVirtualUnwind(0, base, ctx.GetPC(), fn, uintptr(unsafe.Pointer(ctx)), nil, &frame, nil)
88 // SEH unwinding does not report inlined frames.
91 func sehf3(pan bool) []uintptr {
96 func sehf4(pan bool) []uintptr {
105 func testSehCallersEqual(t *testing.T, pcs []uintptr, want []string) {
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) {
115 case "runtime.panicmem":
116 // These functions are skipped as they appear inconsistently depending
117 // whether inlining is on or off.
120 got = append(got, name)
122 if !slices.Equal(want, got) {
123 t.Fatalf("wanted %v, got %v", want, got)
127 func TestSehUnwind(t *testing.T) {
128 if runtime.GOARCH != "amd64" {
129 t.Skip("skipping amd64-only test")
132 testSehCallersEqual(t, pcs, []string{"runtime_test.sehCallers", "runtime_test.sehf4",
133 "runtime_test.sehf3", "runtime_test.TestSehUnwind"})
136 func TestSehUnwindPanic(t *testing.T) {
137 if runtime.GOARCH != "amd64" {
138 t.Skip("skipping amd64-only test")
140 want := []string{"runtime_test.sehCallers", "runtime_test.TestSehUnwindPanic.func1", "runtime.gopanic",
141 "runtime_test.sehf4", "runtime_test.sehf3", "runtime_test.TestSehUnwindPanic"}
143 if r := recover(); r == nil {
144 t.Fatal("did not panic")
147 testSehCallersEqual(t, pcs, want)
152 func TestSehUnwindDoublePanic(t *testing.T) {
153 if runtime.GOARCH != "amd64" {
154 t.Skip("skipping amd64-only test")
156 want := []string{"runtime_test.sehCallers", "runtime_test.TestSehUnwindDoublePanic.func1.1", "runtime.gopanic",
157 "runtime_test.TestSehUnwindDoublePanic.func1", "runtime.gopanic", "runtime_test.TestSehUnwindDoublePanic"}
160 if recover() == nil {
161 t.Fatal("did not panic")
164 testSehCallersEqual(t, pcs, want)
166 if recover() == nil {
167 t.Fatal("did not panic")
174 func TestSehUnwindNilPointerPanic(t *testing.T) {
175 if runtime.GOARCH != "amd64" {
176 t.Skip("skipping amd64-only test")
178 want := []string{"runtime_test.sehCallers", "runtime_test.TestSehUnwindNilPointerPanic.func1", "runtime.gopanic",
179 "runtime.sigpanic", "runtime_test.TestSehUnwindNilPointerPanic"}
181 if r := recover(); r == nil {
182 t.Fatal("did not panic")
185 testSehCallersEqual(t, pcs, want)
189 t.Fatal("did not see nil pointer panic")