Repository navigation
runtime: windows/386: suspending a thread inside NtContinue crashes recovery from a fault #81943
Description
Go version
go1.27.1 and master (be1160f), windows/386.
What operating system and processor architecture are you using?
Windows Server 2025 (WoW64). Intel Xeon Platinum 8488C (AWS m7i); also reproduced, with the CL 828906 guard applied, on AMD EPYC 7R13 (m6a).
What did you do?
Built this program with GOARCH=386 and ran it on 64-bit Windows with GODEBUG=asyncpreemptoff=1. Without that setting, #79249 / #71818 usually crashes or hangs first; see the notes.
package main
import (
"fmt"
"io"
"runtime"
"runtime/pprof"
"sync"
"time"
)
func fault() {
defer func() { recover() }()
var p *int
*p = 1
}
func main() {
// Profile at 1 kHz so threads are suspended often. (StartCPUProfile
// then warns that the rate is already set; that is expected.)
runtime.SetCPUProfileRate(1000)
pprof.StartCPUProfile(io.Discard)
go func() {
for {
runtime.GC()
}
}()
for start := time.Now(); time.Since(start) < time.Minute; {
var wg sync.WaitGroup
for i := 0; i < 8; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 50; j++ {
fault()
}
}()
}
wg.Wait()
}
fmt.Println("OK")
}What did you see happen?
Most runs crash within seconds:
Exception 0x0 0xc0000005 0x0 0x7771b7ba
PC=0x7771b7ba
windows/386, GODEBUG=asyncpreemptoff=1 |
crashed runs |
|---|---|
| go1.27.1 | 68/73 |
| master (be1160f) | 27/29 |
The PC is KiUserExceptionDispatcher+0x4a, right after call RtlRaiseException. ntdll only reaches that code when ZwContinue returns. The dispatcher then raises a noncontinuable exception whose code is the returned status (0), chained to the original 0xc0000005 record.
With async preemption off, nothing is ever injected: the CPU profiler only suspends threads and reads their context. In an instrumented runtime's WER dump, the crashing thread was last seen with CONTEXT_SERVICE_ACTIVE at ZwContinue+0xc, i.e. suspended inside NtContinue. The context it was resuming (Eip=sigresume) was valid.
What did you expect to see?
OK: each fault recovers through sigpanic.
Notes
- Scope: windows/386 under WoW64. Native windows/amd64 (EPYC 7R13) with the same program had no crashes in 90 runs (over 10⁹ recovered faults). Any 386 program that recovers from faults while the CPU profiler runs, or while the GC preempts it, can crash. The GC alone hits it, just less often.
- https://go.dev/cl/828906 (runtime: intermittent startup panics across multiple Go applications on Windows 11 #79249) unmasks it. On stock Go with async preemption on, runtime: intermittent startup panics across multiple Go applications on Windows 11 #79249 usually fails first. With that CL, the same program, without the GODEBUG setting, crashed 140 of 141 runs with this signature.
- Likely mechanism (not verified): WoW64's
NtContinuerestores the full 32-bit context on its way back, and an external suspend or context read in that window loses it, soNtContinuetakes the normal syscall return witheax=0. - Fix (CL to follow): keep
preemptMandprofileLoopfrom suspending a thread between Go's exception handler starting andsigpanicrunning. A per-M flag with a small handshake does this, and the M stays non-preemptible untilsigpanic. On top of CL 828906's guard, it brought this crash from 56/96 runs to 0/65 on Intel and AMD. This overlaps with go.dev/cl/823085 (@rhysh), which refactors the same suspension code.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTodo
Activity
gabyhelp commentedon Oct 1, 2026
Related Issues
Related Code Changes
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
dmitshur commentedon Oct 5, 2026
CC @golang/windows, @golang/runtime.