go: runtime: use of AddVectoredExceptionHandler on windows is ill-advised

What version of Go are you using (go version)?

> go version
go version go1.19 windows/arm64

Does this issue reproduce with the latest release?

Yes, with the latest major version (1.19).

What operating system and processor architecture are you using (go env)?

go env Output
> go env
set GO111MODULE=
set GOARCH=arm64
set GOBIN=
set GOCACHE=C:\Users\billziss\AppData\Local\go-build
set GOENV=C:\Users\billziss\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFLAGS=
set GOHOSTARCH=arm64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Users\billziss\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\billziss\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\Program Files\Go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_arm64
set GOVCS=
set GOVERSION=go1.19
set GCCGO=gccgo
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=NUL
set GOWORK=
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-mthreads -fno-caret-diagnostics -Qunused-arguments -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=C:\Users\billziss\AppData\Local\Temp\go-build481145130=/tmp/go-build -gno-record-gcc-switches

What did you do?

I ran a Golang program that uses a DLL that raises a benign Windows exception in a non-Golang thread. This results in a call to runtime.badsignal2.

What did you expect to see?

I expected the program to run without any problems as the Windows API can handle the benign exception internally.

What did you see instead?

On windows/arm64 I saw a crash because of related problem #56080.

Problem and solution

The initExceptionHandler uses AddVectoredExceptionHandler to add a Vectored Exception Handler. This use looks ill-advised. Windows components (and some third party applications and DLLs) regularly use Structured Exception Handling to report benign errrors, such as “Access Denied”. Vectored Exception Handlers run prior to the regular Structured Exception Handlers in Windows. This means that the Golang runtime preempts the regular processing of benign errors and mistakenly thinks that an unrecoverable error has happened.

A likely fix is not to call AddVectoredExceptionHandler.

See also

#56080 https://github.com/rclone/rclone/issues/5828#issuecomment-1269934140

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Comments: 15 (13 by maintainers)

Commits related to this issue

Most upvoted comments

@qmuntal

I have created throw-away repo golang-veh which allows for some experimentation with the following scenarios:

  • RaiseExcept: Raise and catch an exception in a Go thread.
  • RaiseNoExcept: Raise but do not catch an exception in a Go thread.
  • ThreadRaiseExcept: Raise and catch an exception in a non-Go thread.
  • ThreadRaiseNoExcept: Raise but do not catch an exception in a non-Go thread.

I then tried these scenarios with and without the debugger (WinDbg Preview) on x64 and arm64.

Results for Go threads were good (with or without the debugger):

  • RaiseExcept: Raising and catching an exception in a Go thread works as expected. (Although I confirmed under WinDbg that sigtramp is called, because of Golang’s use of vectored exception handlers.)

  • RaiseNoExcept: Raising and not catching the exception in a Go thread also works as expected, in that the Golang runtime catches the exception and prints a reasonable stack trace.

Results for non-Go threads were not good:

  • ThreadRaiseExcept:

    • x64: Raising and catching an exception in a non-Go thread works as expected in x64 (with or without the debugger). (Again I confirmed that sigtramp is called, because of Golang’s use of vectored exception handlers.)
    • ARM64: Raising and catching an exception in a non-Go thread does not work on ARM64.
      • Without the debugger: The program crashed in badsignal2 (#56080)
      • With the debugger: The program should have crashed in badsignal2, but by chance the R3 register is initialized to 0, which allows the program to print runtime: signal received on thread not created by Go endlessly. (The endless printing is because the exception handling mechanism is reentered because of how runtime.abort tries to abort the process by causing another exception.)
  • ThreadRaiseNoExcept:

    • x64: Behavior differs with and without the debugger:
      • Without the debugger: Program simply dies without any diagnostic, no error dialogs or WER, etc.
      • With the debugger: Program returns from ThreadRaiseNoExcept. It should not since we raised an exception that is not being caught. (This is likely because the debugger is able to continue exceptions.)
    • ARM64: This never executes under ARM64, because we have already bailed when running the previous test ThreadRaiseExcept.

Overall it looks to me that the exception handling code for Windows needs some TLC.


EDIT: My tests were done with:

  • x64: stock Go 1.18.1
  • ARM64: stock Go 1.19

CC @aarzilli in case this discussion affects Delve

AFAIK it shouldn’t, if a CL for this happens I’ll be happy to check that it works with delve.