mirror of https://go.googlesource.com/go
192 lines
4.7 KiB
Go
192 lines
4.7 KiB
Go
// 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.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")
|
|
}
|
|
}
|