LinuxQuestions.org
Review your favorite Linux distribution.
Go Back   LinuxQuestions.org > Blogs > rainbowsally
User Name
Password

Notices


Rate this Entry

Example: sig-handler funcs for C and C++ (throw-catch) PART 2

Posted 05-13-2012 at 11:37 PM by rainbowsally
Updated 05-14-2012 at 07:39 AM by rainbowsally (possible cpu hang if -O2 switch is used)

UPDATE:
Note: The signal-handler files can't be compiled with the -O2 switch and it's probably best if you only trap the signals you really intend to redirect, such as SIGSEGV (11). If you compile what we have here with the -O2 switch your cpu may hang if you try to exit using Ctrl-C for example. See implementation notes added to signal-handler.c below.

See part 1 for main functions to test this in C and C++.

To throw and catch stuff like segfaults in C++ it's easiest to translate the signal and throw a user defined code if the segfault has not been handled. And by "handled" we mean it's not so screwed up that we'd have to return to the shell. This functionality is imperative if you intend to run your own interpreter loop where you don't want to lose all the data in memory unless it's absolutely necessary.

The translation (in C++) is done by pushing and popping handlers which start the 'throw' chain (unwinding the stack) when appropriate. See part 1 for examples in C and in C++.

In C we don't translate because we can't 'throw(code)' the same way we can in C++. This difference may be confusing, but if you think of the handler_code (variable) as being a flag indicating whether a code has been 'HANDLER_THROW'-ed things might start making a bit of sense. Generally, HANDLER_THROW() only needs to be done in C because C++ has its own throw-catch built-ins.

Here's the code and header for sig-handler funcs. The 64-bit code is untested and may need some touch-ups, probably mostly syntactical, the asm and constants should be ok. BITS is set in the header, which you may want to define in the Makefile... but this might get you started in your own implementations of this concept either way.

file: src/signal-handler.h
Code:
// signal-handler.h

#ifndef signal_handler_h
#define signal_handler_h

// signal-handler.h

///////////////////////////////////////////////////////////
// catch segfaults and continue
#include <signal.h>  // segfault handling
#include <setjmp.h>  // reset eflags 0x100 (trap) flag
#include <sys/ucontext.h> // register names

#define BITS 32
// #define BITS 64

#ifdef __cplusplus
// intercept throw-catch errors too.
#include <exception>
extern std::terminate_handler old_term_handler;

// used only once in main()
#define BEGIN_MAIN_HANDLER() \
    pvt_init_sighandler(pvt_exception_mediator); \
    std::set_terminate(pvt_handler_throw)

// done once at the end of main() before returning.
#define END_MAIN_HANDLER() \
  std::set_terminate(old_term_handler)
#else

#define BEGIN_MAIN_HANDLER() \
    pvt_init_sighandler(pvt_exception_mediator)

#define END_MAIN_HANDLER()

#endif // __cplusplus

// begins a handled block and may include a {handler function} 
// that may reset the throw_code variable if the problem is handled.
#define PUSH_HANDLER() \
  { \
    throw_code = handler_code = 0; \
    handler_link_ptr  cont_next; \
    pvt_push_handler(&cont_next); \
    if(!setjmp(handler_link->jb) && ! handler_code) \
    {

// followed by { what to try } which may include try-catch 
// expressions. if cont is false, will pop_handler prior to 
// a 'return' or (more typically) a 'throw(int code) function
// to balance push/pops.
#define HANDLE(cont) }else{ if(!cont) pvt_pop_handler();

// ends a handler block
#define POP_HANDLER() \
    } \
  } \
  pvt_pop_handler()

typedef struct
{
  void* prev;
  jmp_buf jb;
}handler_link_ptr;

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

// Used in C++ for the signal number to translate trapped signals to 
// throw and catch.  Signal number 11 is segfault.  These can often be 
// non-fatal errors though the linux os prefers to unload the binary and 
// return to the shell when these happen.
extern int throw_code;

// used in to simulate throw-catch through handlers in C using HANDLER_THROW(code)
// See HANDLER_THROW() below.
extern int handler_code;

// Used mainly in C, similar to try-catch throw function.  This will call
// the HANDLER currently pushed, so some care is required to avoid recursion 
// or other problems.  It's generally best to throw to the caller after 
// setting a flag in the HANDLER section.
void HANDLER_THROW(int code);

// global handler linked list (points to current)
extern handler_link_ptr *handler_link;

// used internally by PUSH/POP macros
void pvt_push_handler(handler_link_ptr* o);
void pvt_pop_handler();

// used to call c/c++ exception handlers from trapped signals
void pvt_exception_mediator(int);

// used in BEGIN_MAIN_HANDLER() to trap all signals in the 
// range 1-15 for handling and/or conversion to throw-catch
void pvt_init_sighandler(void (*fn)(int));

// used internally to call handler(s) with current throw_code
void pvt_handler_throw();

#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus

#endif // signal_handler_h
file: src/signal-handler.c
Code:
// signal-handler.c

#include <signal-handler.h>
#include <stdio.h> // fprintf()
#include <stdlib.h> // exit()

// used to untrap and begin throw-catch
int throw_code;

#ifdef __cplusplus
std::terminate_handler old_term_handler;
extern "C" {
#endif // __cplusplus
  
int handler_code;

// used mainly in C, similar to try-catch throw function
void HANDLER_THROW(int code)
{
  if(!code) 
  {
    fprintf(stderr, "Can't HANDLER_THROW( zero ), aborting...\n");
    exit(1);
  }
  handler_code = code;
  pvt_handler_throw();
}

  
static handler_link_ptr _handler_link;
handler_link_ptr *handler_link = &_handler_link;

void pvt_push_handler(handler_link_ptr* o) 
{
  handler_link_ptr* save = handler_link;
  handler_link = o;
  handler_link->prev = save;
}

void pvt_pop_handler() 
{ 
  handler_link = (handler_link_ptr*)handler_link->prev; 
}


void pvt_handler_throw()
{
  longjmp(handler_link->jb, throw_code);
}

void handle(int err)
{
  throw_code = err;
  pvt_handler_throw();
}


#if BITS == 32

  // returns ebp from cpu
  static void** get_ebp()
  {
    asm(
        "mov %ebp, %esp;\n"
        "popl %ebp;\n"
        "mov %ebp, %eax;\n"
        "ret;\n"
       );
    return 0; // not used
  }
#else 
#if BITS == 64
  // returns rdx from cpu
  static void** get_rdx()
  {
    asm(
        "mov %rbp, %rsp;\n"
        "popl %rbp;\n"
        "mov %rdx, %rax;\n"
        "ret;\n"
       );
    return 0; // not used
  }
#else
#warning BITS must be defined (either 32 or 64)
#endif // 64
#endif // 32
  
  static struct sigaction new_action;
  
  void pvt_init_sighandler(void (*fn)(int))
  { 
    new_action.sa_handler = fn;
    sigemptyset (&new_action.sa_mask);
    new_action.sa_flags = SA_SIGINFO;
/// *****************************************
/// THIS COULD POTENTIALLY HANG YOUR CPU
// If all you want to trap is segfaults,
// replace this with 
// sigaction(11, &new_action, 0);
// and repeat for other signals you want to 
// catch.  This may not be a problem in C++
// but it is in C, so be aware of this potential
// problem.

    for(int i = 0; i < 16; i++)
      sigaction( i, &new_action, 0);
  }

  // a pointer we can get at in asm
  void** psig_context; 

  typedef struct
  {
    const char c;
  }ERRSTRING;
    

  void pvt_exception_mediator(int signum)
  {
    throw_code = signum;
    
#if BITS == 32
// see /usr/include/sys/ucontext.h
    
#define CTX_TRAPNO 12
#define CTX_EIP 14
    void** p = get_ebp();
    p += 2; // &signum
    p = (void**)p[2];                     // pointers
    p += 5;                               // psig_context
    psig_context = (void**)p;
  
  // get context
    void*** context = (void***)psig_context;

    // turn off trap flag
    context[CTX_TRAPNO] = (void**)0;
    // set new eip as though it were a call
    context[CTX_EIP] = (void**)&pvt_handler_throw;

#else
#if BITS == 64
// see /usr/include/sys/ucontext.h
#define CTX_TRAPNO 20
#define CTX_RIP 16

    void**p;
    
    p = (void**)get_rdx();
    p+=5;
    psig_context = p;
    void***context = (void***)psig_context;
    context[REG_RIP] = (void**)&handler_throw;

#else
#warning BITS must be defined (either 32 or 64)
#endif // 64
#endif // 32
  }

#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
The Computer Mad Science Team

:-)
Posted in Uncategorized
Views 1705 Comments 0
« Prev     Main     Next »
Total Comments 0

Comments

 

  



All times are GMT -5. The time now is 10:35 AM.

Main Menu
Advertisement
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration