Skip to content

Record the last fatal error message for crash reporting on Windows #72718

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

Closed
wants to merge 1 commit into from

Conversation

hjyamauchi
Copy link
Contributor

@hjyamauchi hjyamauchi commented Mar 29, 2024

AFAICT, crashreporter is specific to Darwin and currently it's not possible to record the fatal error message in a crash dump on Windows. Use a similar technique to make it available for Windows crash dump tools.

@hjyamauchi hjyamauchi requested review from compnerd and hyp March 29, 2024 23:47
@hyp
Copy link
Contributor

hyp commented Mar 30, 2024

nice! is there a good way to test this change as well?

Copy link
Contributor

@tristanlabelle tristanlabelle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that'll be very helpful!

#if defined(_WIN32)
// The last fatal error message is accessible for crash reporting on
// Windows.
char *oldMessage = nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could reuse the same code as above by defining char** lastFatalErrorMessage = &gCRAnnotations or &gLastFatalErrorMessage based on preprocessor values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -33,3 +33,10 @@ struct crashreporter_annotations_t gCRAnnotations __attribute__((
}

#endif

#if defined(_WIN32)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the redeclaration necessary given #include "swift/Runtime/Debug.h"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, note that the one in Debug.h is extern and this here is the real definition / storage location.

@hjyamauchi
Copy link
Contributor Author

nice! is there a good way to test this change as well?

Added a test

@hjyamauchi
Copy link
Contributor Author

PTAL

}
let ppLastFatalErrorMessage = unsafeBitCast(
GetProcAddress(hSwiftCore, "gLastFatalErrorMessage"),
to: UnsafeMutablePointer<UnsafeMutablePointer<UInt8>>.self)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to cast it to Optional<UnsafeMutablePointer<Optional<UnsafeMutablePointer<UInt8>>>> instead? That way you can compare it for nil using optional instead of the bit pattern.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, PTAL

Copy link
Contributor

@hyp hyp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@DougGregor
Copy link
Member

@swift-ci please smoke test

@hyp
Copy link
Contributor

hyp commented Apr 2, 2024

@hjyamauchi Also, could you add a test case for try! failure too? I think that error description should now be captured from the same output

@compnerd
Copy link
Member

compnerd commented Apr 2, 2024

Also consider: #72785 - this is currently Windows specific which seems like a shame. Additionally, I am concerned about the uniqueness of the address of the storage when used across modules on PE/COFF where the address of a global imported would be the IAT slot IIRC.

@hyp
Copy link
Contributor

hyp commented Apr 2, 2024

@hjyamauchi here's an example of a try failure:

enum Errs: Error {
 case getMyDescription(String)
}

func throwings(_ i: Int) throws {
  if i == 0 {
    throw Errs.getMyDescription("hello")
  }
}

func testMe() {
  try!  throwings(0)
}

testMe()

@hjyamauchi
Copy link
Contributor Author

@hyp Thanks. I added a try! test.

@compnerd Would it make sense (and are you willing) to adapt the tests in this PR into #72785 ? (or I could also work on that if preferred)

@al45tair
Copy link
Contributor

al45tair commented Apr 3, 2024

I'm a little conflicted about this. I think @compnerd is right that there's an issue with the global storage on Windows, but on a more general note I'm not sure how users are expected to make use of this (on non-Darwin platforms).

What's really needed (IMO) is a Windows version of the crash handling code that I added for Darwin and Linux, and then a little bit of extra work to better integrate fatalError() with that on all platforms. The Darwin crash reporter code will stay, because that's still useful there, but on Linux and Windows getting the extra data into a crash log like this isn't trivial.

From a platform standpoint, on Windows it might actually make sense for fatalError() to report things to the Event Log using ReportEvent(). They'd end up as separate events in the application event log that you'd find before the actual crash, but you wouldn't need to have a crash handler installed or enabled to get the data.

@compnerd
Copy link
Member

compnerd commented Apr 3, 2024

Having a custom stream in the mini dump is another option on windows that we shouldn't rule out imo. That does require that we install a custom dump handler though. I'm hesitant to expose a stream writer as SPI as that would cause additional code execution in the address space after a fatalError.

@hjyamauchi
Copy link
Contributor Author

I'm a little conflicted about this. I think @compnerd is right that there's an issue with the global storage on Windows, but on a more general note I'm not sure how users are expected to make use of this (on non-Darwin platforms).

A way that I'm thinking of using this is through sentry/crashpad as in thebrowsercompany/swift-sentry#35 This works and puts the fatal error messages into sentry crash reports through a minidump.

What's really needed (IMO) is a Windows version of the crash handling code that I added for Darwin and Linux, and then a little bit of extra work to better integrate fatalError() with that on all platforms. The Darwin crash reporter code will stay, because that's still useful there, but on Linux and Windows getting the extra data into a crash log like this isn't trivial.

Can you point to "the crash handling code that I added for Darwin and Linux", if available?

From a platform standpoint, on Windows it might actually make sense for fatalError() to report things to the Event Log using ReportEvent(). They'd end up as separate events in the application event log that you'd find before the actual crash, but you wouldn't need to have a crash handler installed or enabled to get the data.

Do you mind clarifying "report things to the Event Log using ReportEvent()"? I'm not sure what ReportEvent refers to.

@compnerd
Copy link
Member

compnerd commented Apr 3, 2024

A way that I'm thinking of using this is through sentry/crashpad as in thebrowsercompany/swift-sentry#35 This works and puts the fatal error messages into sentry crash reports through a minidump.

That is a product level decision, not a language level. The only two "blessed" error reporting mechanisms for Windows would be the custom minidump writer and ReportEvent. The latter being preferable as the user may wish to have control over the minidump emission and us squatting that space would be a problem. Sentry and crashpad are both outside the scope of supportable tools for the language runtime.

Do you mind clarifying "report things to the Event Log using ReportEvent()"? I'm not sure what ReportEvent refers to.

https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-reporteventw

@al45tair
Copy link
Contributor

al45tair commented Apr 3, 2024

Can you point to "the crash handling code that I added for Darwin and Linux", if available?

The Linux crash handler is here: CrashHandlerLinux.cpp. There's a similar one for macOS, and I've been meaning to get around to writing something for Windows too. It's on by default for Linux, and I was intending to have it on by default for Windows also, at least for console applications (subject to discussing it with @compnerd, since it does mean running code in a crashed process, which is all kinds of fun).

@al45tair
Copy link
Contributor

al45tair commented Apr 3, 2024

The latter being preferable as the user may wish to have control over the minidump emission and us squatting that space would be a problem.

Agreed. The existing Linux and macOS crash handlers both make sure to crash the process the same way it would have crashed without them.

@hjyamauchi
Copy link
Contributor Author

Closing this in favor of #72785

@hjyamauchi hjyamauchi closed this Apr 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants