Meditation, The Art of Exploitation

Thinking? At last I have discovered it--thought; this alone is inseparable from me. I am, I exist--that is certain. But for how long? For as long as I am thinking. For it could be, that were I totally to cease from thinking, I should totally cease to exist....I am, then, in the strict sense only a thing that thinks.

Wednesday, August 15, 2007

Linux Threads, Process, Signals, C++ Exceptions

Unix signals and C++ exceptions interaction is an thorny issue for unix/linux system programmers who develop C++ system applications. There is no way to get around unix/linux system signals (thereafter referred to as signals). Traditionally there are 2 categories of signals, syncrhonous and asynchronmous signals. e.g. SIGBUS is a synchronous signal, while SIGPIPE is an asynchronous signal. (Introduction to Unix Signals Programming)

However, the classification of signal is highly operating system dependent. A signal could be synchronous or asynchronous on different operating system kernel. Usually a synchronous signal is delivered to the process/thread that generates the signal itself, an asynchronous signal is delivered to an arbitrary process/thread that belongs to a process or thread group. (Linux Journal, Martin McCarty)

A lot of workhas been done on i386 architecture with GNU compilers on Linux kernel. Through unit test, this is the best practice I found when mixing unix signals and C++ exception: throw an exception from the signal handler and take care of the exception from exception handler. There are 2 reasons that this approach actually works.
1) Linux threads are actually process 'cloned' from a paernt process. Each linux thread has its own unique process id. This made testing very easy because one can use 'kill' comand to send arbitrary signal to a specific linux thread in a thread group without concerning about the complex process/thread signal delivery mechanism on some Unix operating system.
2) GNU compilers enable stack unwind through -fnon-call-exception (synchronous) and -fasynchronous-unwind-tables to support C++ exception.

A few notes to implement correct behavior:
1) signal handler should be as simple as possible, ideally just a throw
2) exception handling through be as simple as possible, or we might end up with a double throw situation. Make sure your exception handling code cannot generate any synchronous signal, i.e. the exception handling code has to be absolutely correct in a sense. If the exception handling code is fairly complicated, mask(block) as many as signals as you can, because if you enter another signal is triggered->signal handler->throw->catch block process again, the stack is completely messed up, a program abort is in order.

The following test code demonstrates various points of this article. I've also used a trick to by pass linux threads library to get process id directly through syscall.

g++ -fstrict-aliasing -fomit-frame-pointer -fnon-call-exceptions -fasynchronous-unwind-tables -Wall -pedantic -ansi -g -O2 -o boost_thread_signal boost_thread_signal.cpp -lpthread -lboost_thread

#include < boost/thread/thread.hpp>
#include < boost/thread/barrier.hpp>

extern "C"{
#include < execinfo.h>
#include < signal.h>
#include < pthread.h>
#include < sys/syscall.h>
#include < unistd.h>
}

#include < string>
#include < iostream>
#include < exception>

using namespace std;
using namespace boost;

#define GEN_EXCEPTION_CLASS(name, default_msg) \
class name : std::exception {\
public: \
name(const std::string & m=default_msg) : msg(m) {} \
~name() throw() {} \
const char* what() const throw() { return msg.c_str(); } \
private: \
std::string msg; \
};

typedef void (*sighandler_t)(int);

mutex mutex_;
barrier barrier_(10); // N = N_THREAD + 1 to synchronize execution
GEN_EXCEPTION_CLASS(signal_exception, "signal_exception: Signal caught in signal handler")

void * sig_handler(int signum){
// int pid = (long int)syscall(224);
// cout << "UNIX signal " << signum << " caught in " << pid << endl;
//
// 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);
//
throw signal_exception();
return 0;
}

class bthread{

private:
int tid, pid;
public:
bthread(int i) : tid(i){
pid = (long int)syscall(224);
//sighandler_t signal(int signum, sighandler_t handler);
::signal(SIGPIPE, (sighandler_t)sig_handler);
::signal(SIGALRM, (sighandler_t)sig_handler);
//(void) signal (SIGSEGV, threadsigsegvhandler);
//(void) signal (SIGTERM, threadsigtermhandler);
//sigset_t sigs_to_block;
//sigemptyset(&sigs_to_block);
//sigaddset(&sigs_to_block, SIGPIPE);
//sigaddset(&sigs_to_block, SIGINT);
//pthread_sigmask(SIG_BLOCK, &sigs_to_block, NULL);
}
void operator()()
{
try{
mutex::scoped_lock l(mutex_);
printf("The PID of this LINUX thread is: %ld\n", (long int)syscall(224));
cout << "thread id: " << ::getppid() << " " << (::getpid() + tid) << endl;
sleep(60);
}catch(signal_exception & e){
cout << e.what() << endl;
}catch(...){
cout << "something happened..." << endl;
}
}
};

int main(){
try{
boost::thread_group threads;
for (int i = 0; i < 10; ++i)
threads.create_thread(bthread(i));
threads.join_all();
}catch(signal_exception & e){
cout << e.what() << endl;
}catch(...){
cout << "something happened..." << endl;
}
}