Turning a SIGSEGV into a regular function call under Linux, allowing throw

deadalnix deadalnix at gmail.com
Thu Mar 15 07:11:56 PDT 2012


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 !");
}


More information about the Digitalmars-d mailing list