Tuesday, April 24, 2012

C - Try example - Catch memory exception and recover gracefully


I had a need for a try/catch in C. So I wrote a sample program to demo try/catch using some more esoteric functions in the C language.  The main functions that do the heavy lifting are:

siglongjmp()
 
and 
 
sigsetjmp()
 
and setting signal handlers for  
 
 
 sigaction(SIGBUS, &handle, NULL);
 sigaction(SIGSEGV, &handle, NULL);


One run:

$ time ./a.out
Bus error

real    0m3.061s
user    0m0.730s
sys     0m1.000s


This is 1,000,000 tries with 1000 bad references interspaced between things.

The Bus error is a normal error that I do _not_ catch.  I want to let
normal bad things still happen, I only catch the things that are being
"tried".

This works on mac os x, will try it out on the linux box tonight.

Funny thing, this doesn't catch SIGSEGV on the mac when compiled with
optimizations.  Don't know why yet.

So, for right now, this may be suitable for putting into the debug build.

*** Revised ***


I added the volatile keyword in for the local variables in tryvar()
and the optimized version was fixed on mac.

When optimized the code ran like this on the mac:

engr-221:~/projects/improved/CatchMemException jrogers$ time ./a.out
Bus error

real    0m2.783s
user    0m0.870s
sys     0m1.060s


The Linux version runs 10 times faster on an Athlon XP 2100

The program follows:

/*
 * Test the procedure to catch a memory exception
 */

#include <sys wait.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>

int violation = 0;
int try = 0;
sigjmp_buf env;


/*
 *      Handle the SIGSEGV signal when a memory segmentation error occurs.
 */

void
handle_segfault()
{
 if (try) {
  /* when we are in a try do this when there is a memory fault */
  violation = 1;
  siglongjmp(env, 1);
 } else {
  /* restore normal signal handling if we run into an issue outside of a try */
         struct sigaction handle;

         handle.sa_flags = NULL;
         handle.sa_handler = NULL;

         sigaction(SIGBUS, &handle, NULL);
         sigaction(SIGSEGV, &handle, NULL);
 }
}

/*
 *      Check the given memory allocation
 * Trap any segmentation faults
 * Return 1 if would have crashed
 * Return 0 if good.
 */

int
tryvar (void * test){

 volatile char x;
 volatile char * vtest;
 vtest = (char *) test;

 try = 1;
 x = sigsetjmp(env,1);

 if (x == 0)
  x = (char) *vtest;

 x = violation;
 violation = 0;
 try = 0;

 return x;
}

/*
 *     Initialize the signal handlers for memory faults
 */

void

InitializeSignalHandlers(){

        struct sigaction handle;

        handle.sa_flags = SA_SIGINFO;
        handle.sa_handler = handle_segfault;

        sigaction(SIGBUS, &handle, NULL);
        sigaction(SIGSEGV, &handle, NULL);
}

/*
 *     Initialize everything and run the tests.
 */

int
main (){

 void * test;
 int * vtest;
 int a, b, x;

 test = 0;
 vtest = (int *)test;

 InitializeSignalHandlers();

 for (b=0; b< 1000; b++){
  for (a=0; a< 1000; a++){ 
   if (tryvar (&a)){
    printf("a\n");
   } else {
   }
  }

  if (tryvar (test)){
  } else {
   printf("b\n");
  }
 }

 /* Cause a bus error */
 x = (int) *vtest;

 printf("%d\n", x);

 return 0;
}

No comments:

Post a Comment