Source code for this article may be found here.
The title of this article may look weird. In deed, why would someone want to use Vectored Exception Handling in Linux, while this OS provides a perfectly working mechanism - signals? Well, there are several possible answers:
- Many programmers, who started their career with Windows programming, are getting a bit frustrated when it comes to exception (signal) handling in Linux and keep asking about Linux analogs of Structured or Vectored Exception Handling.
- It may be interesting to try to extend the existing model.
- Boredom - as simple as that.
Single Handler vs Chained Handlers
It is a quite common assumption, that Linux signal handling model does not allow multiple handlers for the same signal. Another assumption is that in Linux, signal handlers are not provided with context information. These very assumptions are the main reason those switching from Windows development to Linux are looking for Windows-like solutions.
This assumption is obviously not true and is based on the fact that majority (still) of internet resources only refer to sighandler_t signal(int signum, sighandler_t handler) function. Despite the fact that this function's return value may be used for chaining signal handlers. Needless to mention that sigaction is still (although, this sounds weird) "gaining popularity".
The problem is, however, that implementation of such chaining mechanism must be done by a developer and is not provided by libc .
Winux (Windows to Linux) Method
Let me skip the description of Linux signal handling model as it is covered here (as well as on many online resources) and get straight to the customized implementation of the Vectored Exception handling mechanism.
Despite the fact, that VEH has been with us since Windows XP, it is still not known enough among developers (who tend to think that SEH is the only way), although, it provides more power (and leaves stack for better purposes). VectoredHandlers are "known" across all the threads running in the process and, therefore, saves us some time, as we do not need to register yet another handler when entering a function (however, both things are good in Windows).
The internal implementation of VEH is very simple. The OS maintains a linked list of handlers registered with AddVectoredExceptionHandler API function, which allows to add new handler to either the top of the list or it's tail and returns a handle to the handler. In order to remove certain VectoredHandler, RemoveVectoredExceptionHandler API function should be called.
This way, the intention is to implement these two functions for Linux targeted C code.
Basis - Definitions and Prototypes
Nothing we can do about it - we have to start with some definitions and prototypes:
EXCEPTION_CONTINUE_EXECUTION and EXCEPTION_CONTINUE_SEARCH are defined in Winbase.h and represent the two values that may be returned by VectoredHandler. They, actually, speak for themselves.
EXCEPTION_POINTERS is a structure used to pass pointers to siginfo_t and ucontext_t to the registered pseudo handlers, much like Windows passes pointers to ExceptionRecord and ContextRecord.
vectored_handler_t - this type represents pointers to our pseudo handlers.
As we need some infrastructure to store information about the registered pseudo handlers, we define the VECTORED_HANDLER_ENTRY structure - a member of a linked list (vector) of VectoredHandlers.
This linked list is represented by head and tail pointers to VECTORED_HANDLER_ENTRY structures.
The AddVectoredExceptionHandler and RemoveVectoredExceptionHandler prototypes are almost identical to those of Windows with minor differences, the most important of which, is the int parameter in AddVectoredExceptionHandler. This parameter is the number of the signal, which the pseudo handler, specified by vectored_handler_t, is intended to handle.
WalkTheVector - this is the actual handler, which will be known to the system. When a new signal is being registered, this function is specified as its handler before a call to sigaction is executed.
SIGNAL_RECORD
In order to make things simpler, we are not going to handle all possible signals by default, instead, when a new handler is being added to the list, for a signal which has no handler, SIGNAL_RECORD structure is created and added to the list of signal records. It's members contain all the information needed by our real handler - WalkTheVector. This is the way to register signals with the Linux VEH library. When the last handler for a given signal is being removed, the corresponding SIGNAL_RECORD structure is removed from the list of registered signals as well.
Members of the structure:
signum - number of corresponding signal.
old - this structure is only filled if the specified signal already had another (not related to our library) associated handler.
current - this structure, in turn, describes the new handler being installed.
As it has been mentioned above, this library installs the same handler for every signal being registered - WalkTheVector.
It has also been mentioned that registration/unregistration of signals is performed automatically by either AddVectoredExceptionHandler or RemoveVectoredExceptionHandler functions.
Usage
It is as simple to use this library, as it is to use those API functions in Windows (just don't forget to link to it ;-) ).
Here is the working example:
Note for nerds: yes, of course, you have to include <stdio.h> and "veh.h", and include <stdlib.h>, <signal.h>, <ucontext.h> and <sys/ucontext.h> in the "veh.h" and no, this code does not perform any checks, as it is not production code, but a presentation.
And the output of this example looks like this:
Exception Handling Implementation
While in normal situation, in Linux, you typically assign one handler to one signal, in this case, you assign the same handler to any signal that you want to handle:
Once a signal is received (given that it has been registered), it is passed to the WalkTheVector function, which, in turn, iterates through all the added VectoredHandlers until either reaching end of list or encountering a handler that returns EXCEPTION_CONTINUE_EXECUTION, in which case, WalkTheVector peacefully returns or prints an error message and kills the process if no relevant handler was found.
ExceptionRecord and ContextRecord Analogs
Each handler in the list receives this information in EXCEPTION_POINTERS structure, much like in Windows. However, if you are new to Linux, or have never dealt with signals, my suggestion is to take a look at the following two include files: bits/siginfo.h and bits/sigcontext.h (or read this post) as siginfo_t and ucontext_t structures have nothing to do woth EXCEPTION_RECORD and CNOTEXT_RECORD structures in Windows.
That's it for now. I hope this post was helpful or at least interesting.
See you at the next!