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 cgotest 6 7 /* 8 #include <windows.h> 9 USHORT 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 */ 45 import "C" 46 47 import ( 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. 58 func 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