Handling mach exceptions in 64bit OS X application
Asked Answered
Q

1

15

I have been able to register my own mach port to capture mach exceptions in my applications and it works beautifully when I target 32 bit. However when I target 64 bit, my exception handler catch_exception_raise() gets called but the array of exception codes that is passed to the handler are 32 bits wide. This is expected in a 32 bit build but not in 64 bit.

In the case where I catch EXC_BAD_ACCESS the first code is the error number and the second code should be the address of the fault. Since the second code is 32 bits wide the high 32 bits of the 64 bit fault address is truncated.

I found a flag in <mach/exception_types.h> I can pass in task_set_exception_ports() called MACH_EXCEPTION_CODES which from looking at the Darwin sources appears to control the size of the codes passed to the handler. It looks like it is meant to be ored with the behavior passed in to task_set_exception_ports().

However when I do this and trigger an exception, my mach port gets notified, I call exc_server() but my handler never gets called, and when the reply message is sent back to the kernel I get the default exception behavior.

I am targeting the 10.6 SDK.

I really wish apple would document this stuff better. Any one have any ideas?

Quincey answered 13/5, 2010 at 2:25 Comment(2)
I wonder if this has more to do with the kernel you're running; you'd get 32-bit exceptions under the 32-bit kernel and 64-bit exceptions under the 64-bit kernel? The 32-bit kernel is still the default on 10.6, but if you have a new-enough Mac, you can try it...Specialize
The 32-bit kernel is fully 64-bit aware and will pass either 64-bit or 32-bit exceptions to an application. It all depends on how your app asks for, and handles them.Quincey
Q
20

Well, I figured it out.

To handle mach exceptions, you have to register a mach port for the exceptions you are interested in. You then wait for a message to arrive on the port in another thread. When a message arrives, you call exc_server() whose implementation is provided by System.library. exec_server() takes the message that arrived and calls one of three handlers that you must provide. catch_exception_raise(), catch_exception_raise_state(), or catch_exception_raise_state_identity() depending on the arguments you passed to task_set_exception_ports(). This is how it is done for 32 bit apps.

For 64 bit apps, the 32 bit method still works but the data passed to you in your handler may be truncated to 32 bits. To get 64 bit data passed to your handlers requires a little extra work that is not very straight forward and as far as I can tell not very well documented. I stumbled onto the solution by looking at the sources for GDB.

Instead of calling exc_server() when a message arrives at the port, you have to call mach_exc_server() instead. The handlers also have to have different names as well catch_mach_exception_raise(), catch_mach_exception_raise_state(), and catch_mach_exception_raise_state_identity(). The parameters for the handlers are the same as their 32 bit counterparts. The problem is that mach_exc_server() is not provided for you the way exc_server() is. To get the implementation for mach_exc_server() requires the use of the MIG (Mach Interface Generator) utility. MIG takes an interface definition file and generates a set of source files that include a server function that dispatches mach messages to handlers you provide. The 10.5 and 10.6 SDKs include a MIG definition file <mach_exc.defs> for the exception messages and will generate the mach_exc_server() function. You then include the generated source files in your project and then you are good to go.

The nice thing is that if you are targeting 10.6+ (and maybe 10.5) you can use the same exception handling for both 32 and 64 bit. Just OR the exception behavior with MACH_EXCEPTION_CODES when you set your exception ports. The exception codes will come through as 64 bit values but you can truncate them to 32 bits in your 32 bit build.

I took the mach_exc.defs file and copied it to my source directory, opened a terminal and used the command mig -v mach_exc.defs. This generated mach_exc.h, mach_excServer.c, and mach_excUser.c. I then included those files in my project, added the correct declaration for the server function in my source file and implemented my handlers. I then built my app and was good to go.

Well, this not the best description, but hopefully it helps someone else out.

Quincey answered 15/5, 2010 at 6:11 Comment(7)
Thanks, this is the only documentation i have been able to find on this.Tyranny
When you have handles the original exception do you need to signal the original thread somehow, in order for it to continue? It seems to be stuck waiting..Tyranny
Cheers Brad, I had the same problem and this solved it. The relevant GDB code is here for anyone interested.Transubstantiation
@Tyranny you need to return KERN_SUCCESS from the exception handler (e.g. catch_mach_exception_raise()), then message the exception port back using the message buffer setup for you by mach_exc_server(). If you want to modify machine registers or anything else before continuing the thread you can either do it in the exception handler, or by suspending the thread (thread_suspend()) before messaging back the exception port, then resuming after modifications have been made.Transubstantiation
An actual code snippet would be great, I've spent two days trying to figure out how to catch EXC_BAD_ACCESS and print a stack trace. No luck so far.Catamnesis
This seems to still be fairly valid, even 8 years later. However, now there is no mach_exc_server, even after MIG generation. In fact, exc_server is now generated via the same mig invocation shown above. I've not quite gotten there yet, but it seems like the 32/64 bit handling has been integrated and works for both cases, (by calling exc_server) depending on how the generated source is compiled into your program.Samurai
For me the handlers would not fire at all without ORing the MACH_EXCEPTION_CODES. I ended up using tests/hw_breakpoint_step_arm64.c inside the XNU source code as an example too.Turki

© 2022 - 2024 — McMap. All rights reserved.