This post is written by me and my friend & colleague Madan Kumar B.N. It was written as to capture our developmental experiences. I hope in the future that we do more of such post.
Introduction
An exception is an event that occurs during the execution of a program, and requires the execution of code outside the normal flow of control. There are two kinds of exceptions: hardware exceptions and software exceptions. Hardware exceptions are initiated by the CPU. They can result from the execution of certain instruction sequences, such as division by zero or an attempt to access an invalid memory address.
Handling such Hardware Exceptions is not a common programming paradigm. The amount of documentation available on these paradigms is very less especially on the Mac. We have used these facilities and the knowledge acquired from implementing the same is put into this article. There are a few caveats in using this technique and is part of this article. This paper tries to explore facilities to handle the Hardware Exceptions on Windows and Mac Platforms.
Structured exception handling is a mechanism for handling both hardware and software exceptions on the Windows Platform.
Mach Exception Handling
Mach exceptions are synchronous interruptions to the normal flow of control within a program and could be raised in response to a variety of conditions. For purposes of simplicity, this paper considers the case of a Mach exception raised in response to an “attempt to access nonexistent memory”.
Mach exception handling method is IPC based. Whenever a Mach exception occurs, the exception details such as the exception type, the thread that caused the exception etc are converted as a message and sent through a Mach port. This Mach port can be listened too within the same process on a separate thread or in a totally different process altogether.
Setting up a mach exception handling mechanism –
1. Identify the task/process for which we would want to handle an exception message.
mach_port_t m_task = mach_task_self();
2. Identify the exception type that we want to handle( In this case “attempt to access nonexistent memory” maps to EXC_MASK_BAD_ACCESS)
3. Allocate a mach port with MACH_PORT_RIGHT_RECEIVE attribute to enable receiving messages on this port.
mach_port_allocate(m_task,MACH_PORT_RIGHT_RECEIVE,&m_handlerPort);
4. Set this port as the port over which the task reports exceptions using the API task_set_exception_ports()
task_set_exception_ports(m_task, EXC_MASK_BAD_ACCESS, m_handlerPort,EXCEPTION_DEFAULT,THREAD_STATE_NONE);
5. Spin off a separate thread to listen on this port.
pthread_t m_waitThread;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&m_waitThread, &attr,
(void *(*)(void *))&WaitForExceptions, this);
ExceptionMessage * receiveP = new ExceptionMessage();
if( !receiveP )
{
//Assert and return
return;
}
receiveP->s_header.msgh_local_port = m_handlerPort;
receiveP->s_header.msgh_size = sizeof(*receiveP);
kern_return_t result = mach_msg(&(receiveP->s_header),
MACH_RCV_MSG | MACH_RCV_LARGE, 0,
sizeof(*receiveP), m_handlerPort,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
Responding to the mach exception message
Whenever a invalid memory access is triggered by a piece of code within our program, the thread that caused this invalid access is moved to a suspended state. The current exception port is used to a deliver a message about the exception to the listener.
1. The thread that was listening in on our port for exceptions now is transferred control to allow it to respond to the Mach exception.
2. The responding thread would now have access to the current state of the victim thread.
3. The victim thread which caused the exception to be raised is suspended until the responding thread posts a reply message back on the same mach port.
4. Mac OS X system further provides mechanisms to generate a default reply message. In order to avail it, we continue further down by invoking exec_server() which accepts a request message and generates a reply message.
5. In order to generate the reply message, there are further methods that are invoked namely
a. catch_exception_raise
b. catch_exception_raise_state
c. catch_exception_raise_state_identity
6. As a use case example, the application can provide implementation of these methods and in its implementation allow for some last ditch effort to go about and save the open documents into a temporary location or write a log with some useful information etc.
kern_return_t
catch_exception_raise(mach_port_t port, mach_port_t failed_thread,
mach_port_t task, exception_type_t exception, exception_data_t code,
mach_msg_type_number_t code_count)
{
DoYourHandlingHere();
//Allow default handling to continue by return a failure from this routine
return KERN_FAILURE;
}
7. The return value from the catch_exception_raise methods determines if the exception message is to be further posted.
Beneficial to know –
1. The catch_exception_raise family of functions are “C” functions and needs to exposed through the extern “C” mechanism if used from within a C++ Program.
2. If stripping of symbols from final binary is setup to strip all symbols. The catch_exception_raise family though is standard set of routines needs to be explicitly added to the list of symbols not to be stripped. Setting the __attribute((visibility(“default”))) within the main application to indicate symbols to be exported isn’t sufficient.
3. The catch_exception_raise methods returning a value of false ensures that the default processing of the mach exception message does happen and if at all this leads to a crash, the crash reporter turns up appropriately.
4. It is always a good idea to save the default ports before setting our ports as the ports for mach exception and then resetting back the defaults before quitting.
Structured Exception Handling on Windows
Motivation
In C++ applications we use exception handling for writing robust code. With Structured Exception Handling it is not possible to catch C++ exceptions and C++ typed exception cannot catch Structured Exceptions(C ) selectively, because it is not typed in a way of C++ i.e. we do not know the type of the expection. With Structured Exception Handling it is possible to catch exceptions of type like int or C++ typed exception but the solution is not very good.
The solution would be handle both Structured Exceptions and C++ exceptions together. For using this mechanism we need to use the function:
_CRTIMP _se_translator_function __cdecl _set_se_translator(_In_opt_ _se_translator_function _NewPtFunc);
In MSDN it is described as
The _set_se_translator function provides a way to handle Win32 exceptions (C structured exceptions) as C++ typed exceptions. To allow each C exception to be handled by a C++ catch handler, first define a C++ exception wrapper class that can be used, or derived from, to attribute a specific class type to a C exception. To use this class, install a custom C exception translator function that is called by the internal exception-handling mechanism each time a C exception is raised. Within your translator function, you can throw any typed exception that can be caught by a matching C++ catch handler.
Usage
To use this mechanism we need to do two things
1. Write a C++ exception wrapper class. For example we used the following exception class(cleaned the code for visual clarity removed #ifdefs etc)
class EMyErr
{
private:
MyError mErrCode;
CMyString * mErrDescriptiveString;
public:
inline EMyErr (MyError err) : mErrCode(err), mErrDescriptiveString(NULL)
{ }
inline EMyErr(MyError err, CMyString* descriptive) : mErrCode(err), mErrDescriptiveString(descriptive)
{ }
inline EMyErr(CMyString* errstr) : mErrCode(kMoaErr_ErrorString), mErrDescriptiveString(errstr)
{}
inline MyError GetMyError(void) const
{ return mErrCode; }
inline CMyString * GetDescriptiveString(void) const
{
return mErrDescriptiveString;
}
};
2. Have our translator function
// Function to translate hardware exceptions to our moa errors.
void trans_func( unsigned int u, EXCEPTION_POINTERS* pExp )
{
UNREFERENCED(pExp);
throw EMyErr(u);
}
3. Set the translator function using _set_se_translator
_set_se_translator( trans_func );
When a Structure Exception is raised then our translator function gets called and in that we are throwing the exception with our wrapper class exception filled with useful information.
Beneficial to know:
1. We need to install trans_func(can be any name) function, in every thread of your application. The translator function is thread specific, it means that it is not globally set for all the threads in the application. In a single threaded application we could install it in constructor of application class, however in a multithreaded application we could install the translator function inside the threads.