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);
}
};
|
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.
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.
- 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(). - "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.
- Find more resources for Linux developers in the developerWorks Linux zone.
- Get involved in the developerWorks community by participating in
developerWorks blogs.
- Browse for books on these and other technical topics.
- Order the SEK for Linux, a two-DVD set containing the latest IBM trial software for Linux from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
- Innovate your next Linux development project with IBM trial software, available for download directly from developerWorks.

Sachin has been working extensively in C++ for more than six years, including three years of research into the C++ object models of various compilers. He currently works for IBM Global Services, India. You can contact him at sachin_agrawal@in.ibm.com.
Comments (Undergoing maintenance)





