Throwing a C++ exception from inside a Linux Signal handler
- by SoapBox
As a thought experiment more than anything I am trying to get a C++ exception thrown "from" a linux signal handler for SIGSEGV. (I'm aware this is not a solution to any real world SIGSEGV and should never actually be done, but I thought I would try it out after being asked about it, and now I can't get it out of my head until I figure out how to do it.)
Below is the closest I have come, but instead of the signal being caught properly, terminate() is being called as if no try/catch block is available. Anyone know why? Or know a way I can actually get a C++ exception from a signal handler?
The code (beware, the self modifying asm limits this to running on x86_64 if you're trying to test it):
#include <iostream>
#include <stdexcept>
#include <signal.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
using namespace std;
uint64_t oldaddr = 0;
void thrower()
{
cout << "Inside thrower" << endl;
throw std::runtime_error("SIGSEGV");
}
void segv_handler(int sig, siginfo_t *info, void *pctx)
{
ucontext_t *context = (ucontext_t *)pctx;
cout << "Inside SIGSEGV handler" << endl;
oldaddr = context->uc_mcontext.gregs[REG_RIP];
uint32_t pageSize = (uint32_t)sysconf(_SC_PAGESIZE);
uint64_t bottomOfOldPage = (oldaddr/pageSize) * pageSize;
mprotect((void*)bottomOfOldPage, pageSize*2, PROT_READ|PROT_WRITE|PROT_EXEC);
// 48 B8 xx xx xx xx xx xx xx xx = mov rax, xxxx
*((uint8_t*)(oldaddr+0)) = 0x48;
*((uint8_t*)(oldaddr+1)) = 0xB8;
*((int64_t*)(oldaddr+2)) = (int64_t)thrower;
// FF E0 = jmp rax
*((uint8_t*)(oldaddr+10)) = 0xFF;
*((uint8_t*)(oldaddr+11)) = 0xE0;
}
void func()
{
try {
*(uint32_t*)0x1234 = 123456789;
} catch (...) {
cout << "caught inside func" << endl;
throw;
}
}
int main()
{
cout << "Top of main" << endl;
struct sigaction action, old_action;
action.sa_sigaction = segv_handler;
sigemptyset(&action.sa_mask);
action.sa_flags = SA_SIGINFO | SA_RESTART | SA_NODEFER;
if (sigaction(SIGSEGV, &action, &old_action)<0)
cerr << "Error setting handler : " << strerror(errno) << endl;
try {
func();
} catch (std::exception &e) {
cout << "Caught : " << e.what() << endl;
}
cout << "Bottom of main" << endl << endl;
}
The actual output:
Top of main
Inside SIGSEGV handler
Inside thrower
terminate called after throwing an instance of 'std::runtime_error'
what(): SIGSEGV
Aborted
Expected output:
Top of main
Inside thrower
caught inside func
Caught : SIGSEGV
Bottom of main