Contents


C++ exception-handling tricks for Linux

Four techniques for dealing with built-in language limitations

Comments

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:

  1. 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.
  2. 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.
  3. 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(), and pthread_exit().
  • "Writing good exceptions" (developerWorks, May 2003) offers some practical advice on throwing and catching exceptions and refining your exception-handling sensibilities.
  • Java™ programmers might be interested to read "Best practices in EJB exception handling" (developerWorks, May 2002), which illustrates techniques for faster problem resolution.
  • Innovate your next Linux development project with IBM trial software, available for download directly from developerWorks.

Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Linux
ArticleID=49678
ArticleTitle=C++ exception-handling tricks for Linux
publish-date=02232005