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