Skip to content

runtime: windows: a fault while printing an exception hangs the process instead of exiting #81944

Open

Description

@lizthegrey

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 8488C.

What did you do?

Ran this program, built with GOARCH=386, on 64-bit Windows. It triggers #71818 / #79249, which hands Go's exception handler a garbage exception record and context.

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?

Every run (go1.27.1: 4/4; master: 5/5) printed a complete crash report and then hung, using no CPU, until it was killed. Abridged:

Exception 0x100c7 0xf68f88e8 0x8c483ff 0x0
PC=0x0

fatal error: unexpected signal during runtime execution
[signal 0xc0000005 code=0x0 addr=0x0 pc=0x8020c8]

runtime stack:
runtime.throw(...)
runtime.sigpanic()
runtime.(*unwinder).initAt(...)
runtime.traceback1(...)
runtime.tracebacktrap(...)
runtime.winthrow(...)
runtime.lastcontinuehandler(...)
...
[all goroutines]
<hang>

The #71818 builder logs show the same thing: a crash report, then "Test killed: ran too long (4m0s)".

The hang has nothing to do with WoW64. Any fault inside winthrow can trigger it:

  1. winthrow marks the process as panicking with panicking.Store(1) directly, so m.dying stays 0.
  2. Its tracebacktrap faults on the garbage context (PC=0, SP=0). The nested throw → startpanic_m sees dying == 0, so it increments panicking to 2.
  3. dopanic_m decrements it to 1, concludes that "some other m is panicking", and calls lock(&deadlock) twice. That thread waits forever for itself.

A full dump of a hung process shows the throwing thread in dopanic_m → lock2 → semasleep.

What did you expect to see?

The process exits with status 2 after printing the crash report.

Fix

https://go.dev/cl/423137 (@prattmic, 2022) changes winthrow to use startpanic_m, which fixes this hang for the reproducer (its 2022 TryBot run failed TestG0StackOverflow on Windows, so it needs re-checking against current master). With it applied to master be1160f, all 237 runs exited promptly with status 2. 235 showed the Exception 0x100c7 crash; the other 2 failed with "unexpected return pc", which is the same corruption.

Possibly related: #50144.

Activity

  1. dmitshur commented on Oct 5, 2026

    @dmitshur
    Member

    CC @golang/windows, @golang/runtime.

  2. added this to the Backlog milestone on Oct 5, 2026
  3. added
    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.
    on Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.OS-Windowscompiler/runtimeIssues related to the Go compiler and/or runtime.

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions