]> Cypherpunks repositories - gostls13.git/commit
runtime: acquire/release C TSAN lock when calling cgo symbolizer/tracebacker
authorMichael Pratt <mpratt@google.com>
Fri, 30 May 2025 21:05:41 +0000 (17:05 -0400)
committerGopher Robot <gobot@golang.org>
Thu, 25 Sep 2025 17:22:54 +0000 (10:22 -0700)
commitd7abfe4f0dc91568648a66495b9f5d7ebc0f22b5
tree68658c34ec0f3f744b7cb24878109198adb94a51
parent393d91aea060e5b379e7913e524026d0672a96a7
runtime: acquire/release C TSAN lock when calling cgo symbolizer/tracebacker

When calling into C via cmd/cgo, the generated code calls
_cgo_tsan_acquire / _cgo_tsan_release around the C call to report a
dummy lock to the C/C++ TSAN runtime. This is necessary because the
C/C++ TSAN runtime does not understand synchronization within Go and
would otherwise report false positive race reports. See the comment in
cmd/cgo/out.go for more details.

Various C functions in runtime/cgo also contain manual calls to
_cgo_tsan_acquire/release where necessary to suppress race reports.

However, the cgo symbolizer and cgo traceback functions called from
callCgoSymbolizer and cgoContextPCs, respectively, do not have any
instrumentation [1]. They call directly into user C functions with no
TSAN instrumentation.

This means they have an opportunity to report false race conditions. The
most direct way is via their argument. Both are passed a pointer to a
struct stored on the Go stack, and both write to fields of the struct.
If two calls are passed the same pointer from different threads, the C
TSAN runtime will think this is a race.

This is simple to achieve for the cgo symbolizer function, which the
new regression test does. callCgoSymbolizer is called on the standard
goroutine stack, so the argument is a pointer into the goroutine stack.
If the goroutine moves Ms between two calls, it will look like a race.

On the other hand, cgoContextPCs is called on the system stack. Each M
has a unique system stack, so for it to pass the same argument pointer
on different threads would require the first M to exit, free its stack,
and the same region of address space to be used as the stack for a new
M. Theoretically possible, but quite unlikely.

Both of these are addressed by providing a C wrapper in runtime/cgo that
calls _cgo_tsan_acquire/_cgo_tsan_release around calls to the symbolizer
and traceback functions.

There is a lot of room for future cleanup here. Most runtime/cgo
functions have manual instrumentation in their C implementation. That
could be removed in favor of instrumentation in the runtime. We could
even theoretically remove the instrumentation from cmd/cgo and move it
to cgocall. None of these are necessary, but may make things more
consistent and easier to follow.

[1] Note that the cgo traceback function called from the signal handler
via x_cgo_callers _does_ have manual instrumentation.

Fixes #73949.

Cq-Include-Trybots: luci.golang.try:gotip-freebsd-amd64,gotip-linux-amd64-longtest,gotip-windows-amd64-longtest
Change-Id: I6a6a636c9daa38f7fd00694af76b75cb93ba1886
Reviewed-on: https://go-review.googlesource.com/c/go/+/677955
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
13 files changed:
src/cmd/cgo/internal/testsanitizers/testdata/tsan_tracebackctxt/main.go [new file with mode: 0644]
src/cmd/cgo/internal/testsanitizers/testdata/tsan_tracebackctxt/tracebackctxt_c.c [new file with mode: 0644]
src/cmd/cgo/internal/testsanitizers/tsan_test.go
src/runtime/cgo.go
src/runtime/cgo/callbacks.go
src/runtime/cgo/gcc_context.c
src/runtime/cgo/gcc_libinit.c
src/runtime/cgo/gcc_libinit_windows.c
src/runtime/cgo/libcgo.h
src/runtime/symtab.go
src/runtime/testdata/testprog/setcgotraceback.go [new file with mode: 0644]
src/runtime/traceback.go
src/runtime/traceback_test.go