-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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
runtime: use of AddVectoredExceptionHandler on windows is ill-advised #56082
Comments
(CC @golang/windows) |
cc @golang/runtime |
SEH exceptions from non-Go DLLs should be ignored by firstcontinuehandler. The firstcontinuehandler calls isgoexception function to determine if exception is raised by Go, and if not, then passes exception handling back to non-Go DLL by returning EXCEPTION_CONTINUE_SEARCH. I suspect the problem that you encountered is that you are having exception on non-Go thread. I don't believe that is supported by Go runtime. If my memory does not fail me Go introduced use of 2 handlers with AddVectoredExceptionHandler to solve problems that you just described - see #8006 for details. I am pretty sure we tried to implement SEH handlers on amd64 but failed. Windows amd64 binaries that support SEH handling require complete stack information present in the exe. We could not find enough details to implement that. Alex |
The problem with vectored exception handlers is that they see every exception regardless of whether the exception is one that was going to be handled or not. You are correct that in the original issue that prompted this one, the problem likely happens because we have a Windows exception in a non-Go thread. However even if this was a Windows exception in a Go thread it is not clear to me why the Go runtime should see the exception. A lot of Windows API's use exceptions to implement complicated control flow and injecting Go code execution in that flow can cause problems. I propose instead an alternative policy for unhandled exception handling that ensures that no Go code runs unless the exception is genuinely not going to be handled. (Please note that I have a limited understanding of the Go runtime and my proposal may in fact be misguided.) The following C code illustrates: static VOID UnhandledException(PEXCEPTION_POINTERS ExceptionInfo)
{
// ... additional code goes here
ExitProcess(ExceptionInfo->ExceptionRecord->ExceptionCode);
}
static LONG WINAPI UnhandledHandler(PEXCEPTION_POINTERS ExceptionInfo)
{
UnhandledException(ExceptionInfo);
return EXCEPTION_CONTINUE_EXECUTION;
}
static LONG NTAPI ContinueHandler(PEXCEPTION_POINTERS ExceptionInfo)
{
UnhandledException(ExceptionInfo);
return EXCEPTION_EXECUTE_HANDLER;
}
VOID InitializeExceptionHandling(VOID)
{
SetUnhandledExceptionFilter(UnhandledHandler);
AddVectoredContinueHandler(0, ContinueHandler);
} This works by setting an "unhandled exception filter" and a "vectored continue handler". There are two cases to consider:
Notice that we have eliminated the need for I close with my understanding of the Windows exception dispatching mechanism. It proceeds in three phases:
EDIT: I attach here a small C++ program that demonstrates that this technique works (at least on my Win11 laptop): veh.zip |
Thank you @billziss-gh for your detailed explanation. Unfortunately I don't have time and knowledge to even consider your proposal. So leaving it to others. CC @qmuntal in case you are interested @qmuntal perhaps you also might be interested to be part of @golang/windows group. Alex |
Thanks for the detailed context @billziss-gh, this thread contains good info, I'll need time to process it. Setting aside the vectored vs structured exception handling discussion, I'm still not convinced that the current approach precludes other DLLs to handle exceptions raised in non-Go threads. @billziss-gh could you provide a minimal program that demonstrates the issue, so I want better understand what you need? Something like this: https://github.com/golang/go/tree/master/src/runtime/testdata/testwinlib.
@alexbrainman please add me to that group. |
I have created throw-away repo golang-veh which allows for some experimentation with the following scenarios:
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):
Results for non-Go threads were not good:
Overall it looks to me that the exception handling code for Windows needs some TLC. EDIT: My tests were done with:
|
Change https://go.dev/cl/442896 mentions this issue: |
Thanks for the sample repo. It helped me understand the problem and (hopefully) come up with a solution for arm64, see CL 442896. I still don't know what you would expect from How could we do a better job? We could print stack traces, but IMO they would be meaningless as the exception would have happened in a non-Go thread which we have little information. Handling the exception at the end of the chain with |
@qmuntal I appreciate your work on this and it looks like it fixes the important problem with
My primary point when I opened this issue was that the Go runtime should not inject code in the control flow of non-Go code (e.g. a Windows API) that experiences an exception that is going to be handled. This is not just pedantic: the issue with There were some additional issues that came up during subsequent experimentation. For example,
I agree, but this is also why I ask the question: why does the Go runtime catch these exceptions? FYI, I did experiment with a patch along the lines of: diff --git a/src/runtime/signal_windows.go b/src/runtime/signal_windows.go
index 0f1929e09a..df536688f1 100644
--- a/src/runtime/signal_windows.go
+++ b/src/runtime/signal_windows.go
@@ -28,14 +28,8 @@ func firstcontinuetramp()
func lastcontinuetramp()
func initExceptionHandler() {
- stdcall2(_AddVectoredExceptionHandler, 1, abi.FuncPCABI0(exceptiontramp))
- if _AddVectoredContinueHandler == nil || GOARCH == "386" {
- // use SetUnhandledExceptionFilter for windows-386 or
- // if VectoredContinueHandler is unavailable.
- // note: SetUnhandledExceptionFilter handler won't be called, if debugging.
- stdcall1(_SetUnhandledExceptionFilter, abi.FuncPCABI0(lastcontinuetramp))
- } else {
- stdcall2(_AddVectoredContinueHandler, 1, abi.FuncPCABI0(firstcontinuetramp))
+ stdcall1(_SetUnhandledExceptionFilter, abi.FuncPCABI0(lastcontinuetramp))
+ if _AddVectoredContinueHandler != nil {
stdcall2(_AddVectoredContinueHandler, 0, abi.FuncPCABI0(lastcontinuetramp))
}
} Unfortunately this does not pass all tests and I did not have enough time to research why. In any case I do not have a strong opinion on this matter as I am only an interested user and not a stakeholder of this project. I am happy to close this issue if you do not believe that any further changes are warranted. |
@billziss-gh as I understand it, there are 2 things that Go exception is trying to achieve. Go needs to be able to recover from CPU exceptions raised in Go code - like division by 0 or reading or writing memory pointed by nil pointer. See go/src/runtime/signal_windows.go Lines 65 to 89 in 61f0409
And any go program crash should end with stack trace printed. The stack trace is not printed if go program is running under debugger, but otherwise, stack trace must always be displayed. As far as I remember, #8006 (that I mentioned above) is about stack trace was missing in some situations. If you can still meet these 2 requirements with new exception handler code, I think it should be good enough. CC @aarzilli in case this discussion affects Delve Alex |
AFAIK it shouldn't, if a CL for this happens I'll be happy to check that it works with delve. |
Don't get me wrong @billziss-gh, it would be okay for me to switch to I've even tried to use |
If there is no current G while handling an exception it means the exception was originated in a non-Go thread. The best we can do is ignore the exception and let it flow through other vectored and structured error handlers. I've removed badsignal2 from sigtramp because we can't really know if the signal is bad or not, it might be handled later in the chain. Fixes #50877 Updates #56082 Change-Id: Ica159eb843629986d1fb5482f0b59a9c1ed91698 Reviewed-on: https://go-review.googlesource.com/c/go/+/442896 Reviewed-by: Alex Brainman <[email protected]> Auto-Submit: Michael Pratt <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Michael Pratt <[email protected]> Run-TryBot: Quim Muntal <[email protected]> Reviewed-by: David Chase <[email protected]>
If there is no current G while handling an exception it means the exception was originated in a non-Go thread. The best we can do is ignore the exception and let it flow through other vectored and structured error handlers. I've removed badsignal2 from sigtramp because we can't really know if the signal is bad or not, it might be handled later in the chain. Fixes golang#50877 Updates golang#56082 Change-Id: Ica159eb843629986d1fb5482f0b59a9c1ed91698 Reviewed-on: https://go-review.googlesource.com/c/go/+/442896 Reviewed-by: Alex Brainman <[email protected]> Auto-Submit: Michael Pratt <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Michael Pratt <[email protected]> Run-TryBot: Quim Muntal <[email protected]> Reviewed-by: David Chase <[email protected]>
Change https://go.dev/cl/457875 mentions this issue: |
Heads up: I'm trying to instruct Go's runtime to respect SEH in other modules. CL 457875 is an attempt to do so, but still WIP. |
What version of Go are you using (
go version
)?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
OutputWhat 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
usesAddVectoredExceptionHandler
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
rclone/rclone#5828 (comment)
The text was updated successfully, but these errors were encountered: