| // Copyright 2014 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. |
| |
| // Issue 7978. Stack tracing didn't work during cgo code after calling a Go |
| // callback. Make sure GC works and the stack trace is correct. |
| |
| package cgotest |
| |
| /* |
| #include <stdint.h> |
| |
| void issue7978cb(void); |
| |
| #if defined(__APPLE__) && defined(__arm__) |
| // on Darwin/ARM, libSystem doesn't provide implementation of the __sync_fetch_and_add |
| // primitive, and although gcc supports it, it doesn't inline its definition. |
| // Clang could inline its definition, so we require clang on Darwin/ARM. |
| #if defined(__clang__) |
| #define HAS_SYNC_FETCH_AND_ADD 1 |
| #else |
| #define HAS_SYNC_FETCH_AND_ADD 0 |
| #endif |
| #else |
| #define HAS_SYNC_FETCH_AND_ADD 1 |
| #endif |
| |
| // use ugly atomic variable sync since that doesn't require calling back into |
| // Go code or OS dependencies |
| static void issue7978c(uint32_t *sync) { |
| #if HAS_SYNC_FETCH_AND_ADD |
| while(__sync_fetch_and_add(sync, 0) != 0) |
| ; |
| __sync_fetch_and_add(sync, 1); |
| while(__sync_fetch_and_add(sync, 0) != 2) |
| ; |
| issue7978cb(); |
| __sync_fetch_and_add(sync, 1); |
| while(__sync_fetch_and_add(sync, 0) != 6) |
| ; |
| #endif |
| } |
| */ |
| import "C" |
| |
| import ( |
| "runtime" |
| "runtime/debug" |
| "strings" |
| "sync/atomic" |
| "testing" |
| ) |
| |
| var issue7978sync uint32 |
| |
| func issue7978check(t *testing.T, wantFunc string, badFunc string, depth int) { |
| runtime.GC() |
| buf := make([]byte, 65536) |
| trace := string(buf[:runtime.Stack(buf, true)]) |
| for _, goroutine := range strings.Split(trace, "\n\n") { |
| if strings.Contains(goroutine, "test.issue7978go") { |
| trace := strings.Split(goroutine, "\n") |
| // look for the expected function in the stack |
| for i := 0; i < depth; i++ { |
| if badFunc != "" && strings.Contains(trace[1+2*i], badFunc) { |
| t.Errorf("bad stack: found %s in the stack:\n%s", badFunc, goroutine) |
| return |
| } |
| if strings.Contains(trace[1+2*i], wantFunc) { |
| return |
| } |
| } |
| t.Errorf("bad stack: didn't find %s in the stack:\n%s", wantFunc, goroutine) |
| return |
| } |
| } |
| t.Errorf("bad stack: goroutine not found. Full stack dump:\n%s", trace) |
| } |
| |
| func issue7978wait(store uint32, wait uint32) { |
| if store != 0 { |
| atomic.StoreUint32(&issue7978sync, store) |
| } |
| for atomic.LoadUint32(&issue7978sync) != wait { |
| runtime.Gosched() |
| } |
| } |
| |
| //export issue7978cb |
| func issue7978cb() { |
| // Force a stack growth from the callback to put extra |
| // pressure on the runtime. See issue #17785. |
| growStack(64) |
| issue7978wait(3, 4) |
| } |
| |
| func growStack(n int) int { |
| var buf [128]int |
| if n == 0 { |
| return 0 |
| } |
| return buf[growStack(n-1)] |
| } |
| |
| func issue7978go() { |
| C.issue7978c((*C.uint32_t)(&issue7978sync)) |
| issue7978wait(7, 8) |
| } |
| |
| func test7978(t *testing.T) { |
| if runtime.Compiler == "gccgo" { |
| t.Skip("gccgo can not do stack traces of C code") |
| } |
| if C.HAS_SYNC_FETCH_AND_ADD == 0 { |
| t.Skip("clang required for __sync_fetch_and_add support on darwin/arm") |
| } |
| debug.SetTraceback("2") |
| issue7978sync = 0 |
| go issue7978go() |
| // test in c code, before callback |
| issue7978wait(0, 1) |
| issue7978check(t, "_Cfunc_issue7978c(", "", 1) |
| // test in go code, during callback |
| issue7978wait(2, 3) |
| issue7978check(t, "test.issue7978cb(", "test.issue7978go", 3) |
| // test in c code, after callback |
| issue7978wait(4, 5) |
| issue7978check(t, "_Cfunc_issue7978c(", "_cgoexpwrap", 1) |
| // test in go code, after return from cgo |
| issue7978wait(6, 7) |
| issue7978check(t, "test.issue7978go(", "", 3) |
| atomic.StoreUint32(&issue7978sync, 8) |
| } |