Source file src/cmd/cgo/internal/test/callback_windows.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 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", // hidden by inlining 73 "test.goCallback", 74 "test._Cfunc_callback", 75 "test.nestedCall.func1", 76 // "test.nestedCall", // hidden by inlining 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 // This test is brittle in the face of inliner changes 88 f := runtime.FuncForPC(pc[i] - 1) 89 if f == nil { 90 continue 91 } 92 fname := f.Name() 93 switch fname { 94 case "goCallback": 95 // TODO(qmuntal): investigate why this function doesn't appear 96 // when using the external linker. 97 continue 98 } 99 // In module mode, this package has a fully-qualified import path. 100 // Remove it if present. 101 fname = strings.TrimPrefix(fname, "cmd/cgo/internal/") 102 if !strings.HasPrefix(fname, "test.") { 103 continue 104 } 105 got = append(got, fname) 106 } 107 if !reflect.DeepEqual(want, got) { 108 t.Errorf("incorrect backtrace:\nwant:\t%v\ngot:\t%v", want, got) 109 } 110 } 111