_objc_unexpected_exception hook not implemented on Windows
Closed this issue · 6 comments
We’d like to use the _objc_unexpected_exception
hook in order to send unhandled exceptions to our crash reporting including the exception reason for context. While this hook is handled for other platforms in eh_personality.c
, the Windows implementation in eh_win32_msvc.cc
seems to be lacking support for this.
Would there be a way to implement this in the runtime, or do we need to do what WinObjC is doing and use SetUnhandledExceptionFilter()
in our code to install a handler like this?
You can't expose a global variable from a DLL on Windows, only functions. We should expose an objc_setUnexpectedHandler
or similar (I think the Apple runtime has one of these?) that sets it (and do the same for all of the other hooks).
Good point. The Apple runtime has objc_setUncaughtExceptionHandler()
, and GNUstep Base also checks for that already.
Would you be able to give an outline of what support for this would look like in the runtime? Unfortunately I’m not familiar with SEH but I’d like to try to add this. As it looks like RtlRaiseException()
doesn’t return, would we have to install our own unhandled exception filter in the runtime and basically do what WinObjC is doing to get the NSException instance, or is there a better way to do this?
Ah, sorry, yes I was answering the wrong question. SEH is a bit different from DWARF EH in terms of model. With DWARF, you do a two-phase unwind that first finds a handler, then walks the stack again running cleanups until it reaches the handler. SEH does a one-phase walk where each stack frame runs some code in a funclet (which runs on top of the stack but has access to the stack frame that defined it) that does cleanup / catch logic in a single pass. This means that you can't just throw the exception and get an error from the first-stage unwinder if there isn't a catch, you have to register a callback to be called when the unwinder reaches a point where the exception isn't caught. This is documented here:
The one in WinObjC is approximately the right shape, it just needs to call the callback function.
Thanks for the explanation! While testing this I ran into a related issue with the information stored in EXCEPTION_RECORD.ExceptionInformation
(probably no one has used this on 64-bit so far as WinObjC is 32-bit only).
On 64-bit, all addresses in the _ThrowInfo
object are stored in 32-bit fields relative to EXCEPTION_RECORD.ExceptionInformation[3]
, which in our implementation contains the address of this variable defined at the top of objc_exception_throw()
:
Lines 106 to 109 in b32ee77
I found that the exceptTypes
variable, which is allocated on the stack further down in the function, for me is 503 bytes before &x
, which causes IMAGE_RELATIVE()
to return a negative address. Since everything is assumed to be unsigned here and in the client code (i.e. WinObjC) this results in invalid addresses.
Are local variables allocated further on the top of the stack than _alloca() memory, and is there something else we could use as the image relative base address?
@DHowett I think you originally wrote this code, would be great to get your thoughts on this.
They're relative to something on the stack because the exception object itself is also allocated on the stack. We can't (or, at least, don't) construct the Objective-C type descriptors statically, we construct them in the throw function. This is fine in the Windows model because the funclets all run on top of the stack and the stack frame containing the throw isn't destroyed until after the catch. I don't think it actually matters that this is negative - unsigned arithmetic wraps, so that should also be fine, but there may be some casts that break?
Ah yes good catch – the WinObjC code was using ptrdiff_t
for the base address, which is signed. Changing that to size_t
makes it work. I’ll try to put together a PR.