Source file src/runtime/runtime-seh_windows_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/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  		pcs[i] = ctx.GetPC()
    82  		n++
    83  		windows.RtlVirtualUnwind(0, base, ctx.GetPC(), fn, uintptr(unsafe.Pointer(ctx)), nil, &frame, nil)
    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.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  }
   192  

View as plain text