Here is a proof of concept of how we can recover from segfault.

This isn't perfect as it doesn't protect everything (like floating point registers). This is mostly because I can't find the precise documentation about what must be saved or not.

The handler call a naked function that will set up a stack simulation a standard call, and then call a D function. This function recieve as parameter the memory address that cause the segfault. We can do whatever we want in the D function, at this point we have a clean stack.

Then, if the function call return the standard way, things are set back and the code triggering the segfault ran again.

In the example below, I use memory protection to trigger the segfault. In the handler, I remove the memory protection, so the program can continue its execution.

The code is based on Vladimir Panteleev's prototype.

import core.sys.posix.signal;
import core.sys.posix.ucontext;
import std.stdio;

// Missing details from Druntime

version(X86_64)
{
        enum
        {
                REG_R8 = 0,
                REG_R9,
                REG_R10,
                REG_R11,
                REG_R12,
                REG_R13,
                REG_R14,
                REG_R15,
                REG_RDI,
                REG_RSI,
                REG_RBP,
                REG_RBX,
                REG_RDX,
                REG_RAX,
                REG_RCX,
                REG_RSP,
                REG_RIP,
                REG_EFL,
                REG_CSGSFS,             /* Actually short cs, gs, fs, __pad0.  
*/
                REG_ERR,
                REG_TRAPNO,
                REG_OLDMASK,
                REG_CR2
        }
}
else
version (X86)
{
        enum
        {
                REG_GS = 0,
                REG_FS,
                REG_ES,
                REG_DS,
                REG_EDI,
                REG_ESI,
                REG_EBP,
                REG_ESP,
                REG_EBX,
                REG_EDX,
                REG_ECX,
                REG_EAX,
                REG_TRAPNO,
                REG_ERR,
                REG_EIP,
                REG_CS,
                REG_EFL,
                REG_UESP,
                REG_SS
        }
}

// Init

shared static this()
{
        sigaction_t action;
        action.sa_sigaction = &handleSignal;
        action.sa_flags = SA_SIGINFO;
        sigaction(SIGSEGV, &action, null);
}

// Sighandler space

alias typeof({ucontext_t uc; return uc.uc_mcontext.gregs[0];}()) REG_TYPE;
static REG_TYPE saved_EAX, saved_EDX;

extern(C)
void handleSignal(int signum, siginfo_t* info, void* contextPtr)
{
        auto context = cast(ucontext_t*)contextPtr;
        
        // Save registers into global thread local, to allow recovery.
        saved_EAX = context.uc_mcontext.gregs[REG_EAX];
        saved_EDX = context.uc_mcontext.gregs[REG_EDX];
        
        // Hijack current context so we call our handler.
context.uc_mcontext.gregs[REG_EAX] = cast(REG_TYPE) info._sifields._sigfault.si_addr;
        context.uc_mcontext.gregs[REG_EDX] = context.uc_mcontext.gregs[REG_EIP];
context.uc_mcontext.gregs[REG_EIP] = cast(REG_TYPE) &sigsegv_userspace_handler;
}

// User space

// This function must be called with faulting address in EAX and original EIP in EDX.
void sigsegv_userspace_handler() {
        asm {
                naked;
                push EDX;       // return address (original EIP).
                push EBP;       // old ebp
                mov EBP, ESP;
                
push ECX; // ECX is a trash register and must be preserved as local variable.
                
                // Parameter address is already set as EAX.
                call sigsegv_userspace_process;
                
                // Restore register values and return.
                call restore_registers;
                
                pop ECX;
                
                // Return
                pop EBP;
                ret;
        }
}

// The return value is stored in EAX and EDX, so this function restore the correct value for theses registers.
REG_TYPE[2] restore_registers() {
        return [saved_EAX, saved_EDX];
}

// User space handler

class SignalError : Error
{
        this(string msg)
        {
                super(msg);
        }
}

extern(C) int mprotect(void*, size_t, int);

void sigsegv_userspace_process(void* address) {
        import std.stdio;
        writeln("Handler starting.");
        writeln("SEGFAULT triggered at address : ", address);
        
        // Dirty trick to get stack trace, for debug purpose.
        try {
                throw new SignalError("SIGSEGV");
        } catch(SignalError se) {
                writeln(se.toString());
        }
        
// Allow write access to memory. So when we return the operation causing SEGFAULT will succeed.
        import core.sys.posix.sys.mman;
        mprotect(address, 4096, PROT_READ|PROT_WRITE);
        
        writeln("Handler ending.");
        
        // throw new SignalError("SIGSEGV");
}

// Demonstration

void foo(void* x) {
        *(cast(int*) x) = 1;
}

void main() {
        import core.sys.posix.sys.mman;
        import std.stdio;
        
void* x = mmap(cast(void*) 0x12340000, 4096, PROT_NONE, MAP_PRIVATE|MAP_ANON, -1, 0);
        if(x == cast(void*) 0x12340000) {
                writeln("Try to write at ", x);
                foo(x);
        } else {
                write("Can't mmap :(");
        }
        
        assert(*(cast(int*) x) == 1);
        writeln("Value successfully written ! SIGSEGV recovered !");
}

Reply via email to