• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5package cgotest
6
7/*
8#include <windows.h>
9USHORT backtrace(ULONG FramesToCapture, PVOID *BackTrace) {
10#ifdef _AMD64_
11	CONTEXT context;
12	RtlCaptureContext(&context);
13	ULONG64 ControlPc;
14	ControlPc = context.Rip;
15	int i;
16	for (i = 0; i < FramesToCapture; i++) {
17		PRUNTIME_FUNCTION FunctionEntry;
18		ULONG64 ImageBase;
19		VOID *HandlerData;
20		ULONG64 EstablisherFrame;
21
22		FunctionEntry = RtlLookupFunctionEntry(ControlPc, &ImageBase, NULL);
23
24		if (!FunctionEntry) {
25			// For simplicity, don't unwind leaf entries, which are not used in this test.
26			break;
27		} else {
28			RtlVirtualUnwind(0, ImageBase, ControlPc, FunctionEntry, &context, &HandlerData, &EstablisherFrame, NULL);
29		}
30
31		ControlPc = context.Rip;
32		// Check if we left the user range.
33		if (ControlPc < 0x10000) {
34			break;
35		}
36
37		BackTrace[i] = (PVOID)(ControlPc);
38	}
39	return i;
40#else
41	return 0;
42#endif
43}
44*/
45import "C"
46
47import (
48	"internal/testenv"
49	"reflect"
50	"runtime"
51	"strings"
52	"testing"
53	"unsafe"
54)
55
56// Test that the stack can be unwound through a call out and call back
57// into Go.
58func testCallbackCallersSEH(t *testing.T) {
59	testenv.SkipIfOptimizationOff(t) // This test requires inlining.
60	if runtime.Compiler != "gc" {
61		// The exact function names are not going to be the same.
62		t.Skip("skipping for non-gc toolchain")
63	}
64	if runtime.GOARCH != "amd64" {
65		// TODO: support SEH on other architectures.
66		t.Skip("skipping on non-amd64")
67	}
68	// Only frames in the test package are checked.
69	want := []string{
70		"test._Cfunc_backtrace",
71		"test.testCallbackCallersSEH.func1.1",
72		"test.testCallbackCallersSEH.func1",
73		"test.goCallback",
74		"test._Cfunc_callback",
75		"test.nestedCall.func1",
76		"test.nestedCall",
77		"test.testCallbackCallersSEH",
78		"test.TestCallbackCallersSEH",
79	}
80	pc := make([]uintptr, 100)
81	n := 0
82	nestedCall(func() {
83		n = int(C.backtrace(C.DWORD(len(pc)), (*C.PVOID)(unsafe.Pointer(&pc[0]))))
84	})
85	got := make([]string, 0, n)
86	for i := 0; i < n; i++ {
87		f := runtime.FuncForPC(pc[i] - 1)
88		if f == nil {
89			continue
90		}
91		fname := f.Name()
92		switch fname {
93		case "goCallback":
94			// TODO(qmuntal): investigate why this function doesn't appear
95			// when using the external linker.
96			continue
97		}
98		// In module mode, this package has a fully-qualified import path.
99		// Remove it if present.
100		fname = strings.TrimPrefix(fname, "cmd/cgo/internal/")
101		if !strings.HasPrefix(fname, "test.") {
102			continue
103		}
104		got = append(got, fname)
105	}
106	if !reflect.DeepEqual(want, got) {
107		t.Errorf("incorrect backtrace:\nwant:\t%v\ngot:\t%v", want, got)
108	}
109}
110