// 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 cgotest /* #include USHORT backtrace(ULONG FramesToCapture, PVOID *BackTrace) { #ifdef _AMD64_ CONTEXT context; RtlCaptureContext(&context); ULONG64 ControlPc; ControlPc = context.Rip; int i; for (i = 0; i < FramesToCapture; i++) { PRUNTIME_FUNCTION FunctionEntry; ULONG64 ImageBase; VOID *HandlerData; ULONG64 EstablisherFrame; FunctionEntry = RtlLookupFunctionEntry(ControlPc, &ImageBase, NULL); if (!FunctionEntry) { // For simplicity, don't unwind leaf entries, which are not used in this test. break; } else { RtlVirtualUnwind(0, ImageBase, ControlPc, FunctionEntry, &context, &HandlerData, &EstablisherFrame, NULL); } ControlPc = context.Rip; // Check if we left the user range. if (ControlPc < 0x10000) { break; } BackTrace[i] = (PVOID)(ControlPc); } return i; #else return 0; #endif } */ import "C" import ( "internal/testenv" "reflect" "runtime" "strings" "testing" "unsafe" ) // Test that the stack can be unwound through a call out and call back // into Go. func testCallbackCallersSEH(t *testing.T) { testenv.SkipIfOptimizationOff(t) // This test requires inlining. if runtime.Compiler != "gc" { // The exact function names are not going to be the same. t.Skip("skipping for non-gc toolchain") } if runtime.GOARCH != "amd64" { // TODO: support SEH on other architectures. t.Skip("skipping on non-amd64") } // Only frames in the test package are checked. want := []string{ "test._Cfunc_backtrace", "test.testCallbackCallersSEH.func1.1", "test.testCallbackCallersSEH.func1", "test.goCallback", "test._Cfunc_callback", "test.nestedCall.func1", "test.nestedCall", "test.testCallbackCallersSEH", "test.TestCallbackCallersSEH", } pc := make([]uintptr, 100) n := 0 nestedCall(func() { n = int(C.backtrace(C.DWORD(len(pc)), (*C.PVOID)(unsafe.Pointer(&pc[0])))) }) got := make([]string, 0, n) for i := 0; i < n; i++ { f := runtime.FuncForPC(pc[i] - 1) if f == nil { continue } fname := f.Name() switch fname { case "goCallback": // TODO(qmuntal): investigate why this function doesn't appear // when using the external linker. continue } // In module mode, this package has a fully-qualified import path. // Remove it if present. fname = strings.TrimPrefix(fname, "cmd/cgo/internal/") if !strings.HasPrefix(fname, "test.") { continue } got = append(got, fname) } if !reflect.DeepEqual(want, got) { t.Errorf("incorrect backtrace:\nwant:\t%v\ngot:\t%v", want, got) } }