-
Notifications
You must be signed in to change notification settings - Fork 119
internal/fakecgo: preserve frame pointer across mstart call in threadentry #486
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
hajimehoshi
merged 4 commits into
ebitengine:main
from
gdams:fix/fakecgo-threadentry-framepointer
Jul 31, 2026
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | ||
|
|
||
| //go:build !cgo && amd64 && (darwin || freebsd || linux || netbsd) | ||
|
|
||
| package fakecgo | ||
|
|
||
| // callThreadEntryFn calls fn (runtime.mstart) while saving and restoring the | ||
| // frame pointer and other callee-saved registers around the call. mstart | ||
| // returns with BP clobbered, so without this shim the caller's frame-pointer | ||
| // epilogue would fault. Implemented in trampolines_amd64.s. | ||
| // | ||
| //go:nosplit | ||
| //go:norace | ||
| func callThreadEntryFn(fn uintptr) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | ||
|
|
||
| //go:build !cgo && !amd64 && (darwin || freebsd || linux || netbsd) | ||
|
|
||
| package fakecgo | ||
|
|
||
| import "unsafe" | ||
|
|
||
| // callThreadEntryFn calls fn (runtime.mstart). On architectures without the | ||
| // amd64 frame-pointer epilogue issue this is a plain indirect call. It must be | ||
| // nosplit and norace like the threadentry callers so it neither inserts a | ||
| // morestack preamble nor runs race instrumentation during the fragile | ||
| // thread-bootstrap window. | ||
| // | ||
| //go:nosplit | ||
| //go:norace | ||
| func callThreadEntryFn(fn uintptr) { | ||
| // fn is the code pointer. Build a func value whose first word is fn by | ||
| // pointing the closure at &fn, then call it (same trick fakecgo has always | ||
| // used to call a raw PC from Go). | ||
| fnPtr := uintptr(unsafe.Pointer(&fn)) | ||
| (*(*func())(unsafe.Pointer(&fnPtr)))() | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | ||
|
|
||
| // 386/arm are excluded: the standalone fakecgo test binary resolves pthread | ||
| // from libpthread.so.0, which fails the dynamic symbol lookup on 32-bit linux. | ||
| //go:build !cgo && (linux || darwin || freebsd || netbsd) && !386 && !arm | ||
|
|
||
| package fakecgo | ||
|
|
||
| import ( | ||
| "runtime" | ||
| "sync" | ||
| "testing" | ||
| ) | ||
|
|
||
| // TestThreadEntryReturn exercises the fakecgo threadentry -> runtime.mstart | ||
| // return path, i.e. that an M created by fakecgo can exit cleanly. | ||
| // | ||
| // When iscgo is forced true by fakecgo, the runtime creates every new OS | ||
| // thread through _cgo_thread_start -> threadentry_trampoline -> threadentry, | ||
| // which calls runtime.mstart. For a fakecgo thread the M's stack is | ||
| // system-allocated, so when the M exits, mexit(osStack=true) returns from | ||
| // mstart back into threadentry. This return path has been the source of | ||
| // crashes (a frame-pointer LEAVE fault on amd64, a bad indirect call and race | ||
| // instrumentation on other platforms), so it is worth exercising everywhere. | ||
| // | ||
| // Locking an OS thread and then letting the goroutine exit (without calling | ||
| // UnlockOSThread) forces the locked fakecgo M to exit, driving mstart to return | ||
| // into threadentry. A busy loop that never lets the goroutine exit does NOT | ||
| // reproduce the crash, because the M never exits and mstart never returns. | ||
| func TestThreadEntryReturn(t *testing.T) { | ||
| // The M exits on the first goroutine return, so a modest amount of churn is | ||
| // enough to catch a broken teardown while staying quick under emulation. | ||
| const rounds = 10 | ||
| const workers = 16 | ||
| for range rounds { | ||
| var wg sync.WaitGroup | ||
| wg.Add(workers) | ||
| for range workers { | ||
| go func() { | ||
| defer wg.Done() | ||
| runtime.LockOSThread() | ||
| // Intentionally do NOT call UnlockOSThread: returning here exits | ||
| // the goroutine while its OS thread is locked, forcing the M to | ||
| // exit and mstart to return into fakecgo.threadentry. | ||
| }() | ||
| } | ||
| wg.Wait() | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.