C++ exception-handling tricks for Linux
Four techniques for dealing with built-in language limitations
Retaining exception source information
In C++, whenever an exception is caught within a handler, the information about the source of the exception is lost. The exact source of the exception could provide a lot of vital information to better handle it, or the information could be appended to the error log for postmortem.
To deal with this, you can generate a stack trace in the constructor of the
exception object during the throw exception statement. ExceptionTracer
is a class that demonstrates this behavior.
Listing 1. Generating a stack trace in the exception object constructor
// Sample Program: // Compiler: gcc 3.2.3 20030502 // Linux: Red Hat #include <execinfo.h> #include <signal.h> #include <exception> #include <iostream> using namespace std; ///////////////////////////////////////////// class ExceptionTracer { public: ExceptionTracer() { void * array[25]; int nSize = backtrace(array, 25); char ** symbols = backtrace_symbols(array, nSize); for (int i = 0; i < nSize; i++) { cout << symbols[i] << endl; } free(symbols); } };
Managing signals
Whenever a process performs an offending action such that the Linux™ kernel raises a signal, the signal must be handled. The signal handler generally releases the important resources and terminates the application. In this case, all the object instances on the stack are left un-destructed. On the other hand, if such signals are instead translated to C++ exceptions, you can gracefully invoke their destructors and program multiple levels of catch blocks to better deal with the signals.
However, you must remember the following:
- If the signal type is asynchronous or irrecoverable, this approach will not work. Not all the signal types are synchronous, meaning a different thread in that process may receive them. Also, a signal type may not be recoverable, and the process must shut down. To add to the complexity, a signal type on one kernel could be synchronous and on another kernel asynchronous. You can not write a single code that will work for all.
- Some Linux/UNIX systems require a stack fix-up before you can throw the C++ exception from within your signal handler. Again, the exact nature of stack fix-up depends on your kernel.
- In general, a signal that is translated to a Java Exception by any JVM on that platform can also be translated to a C++ exception.
SignalExceptionClass
, defined in Listing 2, provides the abstraction of
a C++ exception representing a signal that the kernel might rise.
SignalTranslator
is a template class based on
SignalExceptionClass
, which actually does the translation. There
can be only one signal handler per signal per process active at any instant. Hence,
SignalTranslator
adopts a singleton design pattern. The whole
concept is demonstrated using the SegmentationFault
class for SIGSEGV
and the FloatingPointException
class for SIGFPE.
Listing 2. Translating signals to exceptions
template <class SignalExceptionClass> class SignalTranslator { private: class SingleTonTranslator { public: SingleTonTranslator() { signal(SignalExceptionClass::GetSignalNumber(), SignalHandler); } static void SignalHandler(int) { throw SignalExceptionClass(); } }; public: SignalTranslator() { static SingleTonTranslator s_objTranslator; } }; // An example for SIGSEGV class SegmentationFault : public ExceptionTracer, public exception { public: static int GetSignalNumber() {return SIGSEGV;} }; SignalTranslator<SegmentationFault> g_objSegmentationFaultTranslator; // An example for SIGFPE class FloatingPointException : public ExceptionTracer, public exception { public: static int GetSignalNumber() {return SIGFPE;} }; SignalTranslator<FloatingPointException> g_objFloatingPointExceptionTranslator;
Managing exceptions in constructors and destructors
Per ANSI C++, during construction and destruction of global (static global) variables, catching exceptions is not possible. Hence, ANSI C++ does not recommend throwing exceptions in the constructor and destructor of a class whose instances may be defined globally (static-globally). The other way to say it is, never define a global (static global) instance of a class whose constructor or destructor may throw exceptions. However, if you assume a specific compiler and a specific system, it may be doable, and fortunately, with GCC on Linux, it is.
This is demonstrated using the ExceptionHandler
class, which again
adopts a singleton design pattern. Its constructor registers an un-caught handler.
Since there can be only one un-caught handler per process active at a time, the
constructor should be invoked only once; hence, the reason for the singleton
pattern. A global (static global) instance of ExceptionHandler
should
be defined prior to the definition of the actual global (static global) variable in
question.
Listing 3. Handling exceptions in a constructor
class ExceptionHandler { private: class SingleTonHandler { public: SingleTonHandler() { set_terminate(Handler); } static void Handler() { // Exception from construction/destruction of global variables try { // re-throw throw; } catch (SegmentationFault &) { cout << "SegmentationFault" << endl; } catch (FloatingPointException &) { cout << "FloatingPointException" << endl; } catch (...) { cout << "Unknown Exception" << endl; } //if this is a thread performing some core activity abort(); // else if this is a thread used to service requests // pthread_exit(); } }; public: ExceptionHandler() { static SingleTonHandler s_objHandler; } }; ////////////////////////////////////////////////////////////////////////// class A { public: A() { //int i = 0, j = 1/i; *(int *)0 = 0; } }; // Before defining any global variable, we define a dummy instance // of ExceptionHandler object to make sure that // ExceptionHandler::SingleTonHandler::SingleTonHandler() is invoked ExceptionHandler g_objExceptionHandler; A g_a; ////////////////////////////////////////////////////////////////////////// int main(int argc, char* argv[]) { return 0; }
Handling exceptions in multi-threaded programs
Sometimes exceptions are left un-caught, which will cause the process to abort. Many
times, however, processes contain multiple threads, where a few threads perform the
core application logic while the rest service the external requests. If a service
thread does not handle an exception due to programming error, it will kill the whole
application. This may be undesirable as it promotes denial-of-service attacks by
feeding illegal requests to the application. To avoid this, an un-caught handler can
decide whether to invoke an abort or a thread-exit call. This is demonstrated
towards the end of the ExceptionHandler::SingleTonHandler::Handler()
function in Listing 3.
Conclusion
I've briefly discussed a few C++ programming design patterns to better perform the following tasks:
- Tracing the source of exception while it is in process of being thrown.
- Translating signals from the kernel to C++ exceptions.
- Catching exceptions thrown during construction and/or destruction of global variables.
- Exception handling in multithreaded processes.
I hope you're able to adopt some of these techniques to develop trouble-free code.
Downloadable resources
Related topics
- A classic text on C++ is The C++ Programming Language (Addison-Wesley, 1997) by Bjarne Stroustrup.
- An excellent resource for understanding C++ internals is Inside the C++ Object Model (Addison-Wesley, 1996) by Stanley B. Lippman.
- Don't forget also to check the system man pages for individual functions
like
backtrace()
,backtrace_symbols()
,signal()
,abort()
, andpthread_exit()
. - Innovate your next Linux development project with IBM trial software, available for download directly from developerWorks.