Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
330 lines (263 sloc) 8.282 kB
/**
* Handle page protection errors using D errors (exceptions). $(D NullPointerError) is
* thrown when dereferencing null pointers. A system-dependent error is thrown in other
* cases.
*
* Note: Only x86 and x86_64 are supported for now.
*
* License: Distributed under the
* $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
* (See accompanying file LICENSE_1_0.txt)
* Authors: Amaury SECHET, FeepingCreature, Vladimir Panteleev
* Source: $(DRUNTIMESRC src/etc/linux/memory.d)
*/
module etc.linux.memoryerror;
version (CRuntime_Glibc)
{
version (X86)
version = MemoryErrorSupported;
version (X86_64)
version = MemoryErrorSupported;
}
version (MemoryErrorSupported):
@system:
import core.sys.posix.signal;
import core.sys.posix.ucontext;
// Register and unregister memory error handler.
bool registerMemoryErrorHandler()
{
sigaction_t action;
action.sa_sigaction = &handleSignal;
action.sa_flags = SA_SIGINFO;
auto oldptr = &old_sigaction;
return !sigaction(SIGSEGV, &action, oldptr);
}
bool deregisterMemoryErrorHandler()
{
auto oldptr = &old_sigaction;
return !sigaction(SIGSEGV, oldptr, null);
}
/**
* Thrown on POSIX systems when a SIGSEGV signal is received.
*/
class InvalidPointerError : Error
{
this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
{
super("", file, line, next);
}
this(Throwable next, string file = __FILE__, size_t line = __LINE__)
{
super("", file, line, next);
}
}
/**
* Thrown on null pointer dereferences.
*/
class NullPointerError : InvalidPointerError
{
this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
{
super(file, line, next);
}
this(Throwable next, string file = __FILE__, size_t line = __LINE__)
{
super(file, line, next);
}
}
version (unittest)
{
int* getNull() { return null; }
}
unittest
{
assert(registerMemoryErrorHandler());
bool b;
try
{
*getNull() = 42;
}
catch (NullPointerError)
{
b = true;
}
assert(b);
b = false;
try
{
*getNull() = 42;
}
catch (InvalidPointerError)
{
b = true;
}
assert(b);
assert(deregisterMemoryErrorHandler());
}
// Signal handler space.
private:
__gshared sigaction_t old_sigaction;
alias typeof(ucontext_t.init.uc_mcontext.gregs[0]) RegType;
version (X86_64)
{
static RegType savedRDI, savedRSI;
extern(C)
void handleSignal(int signum, siginfo_t* info, void* contextPtr) nothrow
{
auto context = cast(ucontext_t*)contextPtr;
// Save registers into global thread local, to allow recovery.
savedRDI = context.uc_mcontext.gregs[REG_RDI];
savedRSI = context.uc_mcontext.gregs[REG_RSI];
// Hijack current context so we call our handler.
auto rip = context.uc_mcontext.gregs[REG_RIP];
auto addr = cast(RegType) info.si_addr;
context.uc_mcontext.gregs[REG_RDI] = addr;
context.uc_mcontext.gregs[REG_RSI] = rip;
context.uc_mcontext.gregs[REG_RIP] = cast(RegType) ((rip != addr)?&sigsegvDataHandler:&sigsegvCodeHandler);
}
// All handler functions must be called with faulting address in RDI and original RIP in RSI.
// This function is called when the segfault's cause is to call an invalid function pointer.
void sigsegvCodeHandler()
{
asm
{
naked;
// Handle the stack for an invalid function call (segfault at RIP).
// With the return pointer, the stack is now alligned.
push RBP;
mov RBP, RSP;
jmp sigsegvDataHandler;
}
}
void sigsegvDataHandler()
{
asm
{
naked;
push RSI; // return address (original RIP).
push RBP; // old RBP
mov RBP, RSP;
pushfq; // Save flags.
push RAX; // RAX, RCX, RDX, and R8 to R11 are trash registers and must be preserved as local variables.
push RCX;
push RDX;
push R8;
push R9;
push R10;
push R11; // With 10 pushes, the stack is still aligned.
// Parameter address is already set as RAX.
call sigsegvUserspaceProcess;
// Restore RDI and RSI values.
call restoreRDI;
push RAX; // RDI is in RAX. It is pushed and will be poped back to RDI.
call restoreRSI;
mov RSI, RAX;
pop RDI;
// Restore trash registers value.
pop R11;
pop R10;
pop R9;
pop R8;
pop RDX;
pop RCX;
pop RAX;
popfq; // Restore flags.
// Return
pop RBP;
ret;
}
}
// The return value is stored in EAX and EDX, so this function restore the correct value for theses registers.
RegType restoreRDI()
{
return savedRDI;
}
RegType restoreRSI()
{
return savedRSI;
}
}
else version (X86)
{
static RegType savedEAX, savedEDX;
extern(C)
void handleSignal(int signum, siginfo_t* info, void* contextPtr) nothrow
{
auto context = cast(ucontext_t*)contextPtr;
// Save registers into global thread local, to allow recovery.
savedEAX = context.uc_mcontext.gregs[REG_EAX];
savedEDX = context.uc_mcontext.gregs[REG_EDX];
// Hijack current context so we call our handler.
auto eip = context.uc_mcontext.gregs[REG_EIP];
auto addr = cast(RegType) info.si_addr;
context.uc_mcontext.gregs[REG_EAX] = addr;
context.uc_mcontext.gregs[REG_EDX] = eip;
context.uc_mcontext.gregs[REG_EIP] = cast(RegType) ((eip != addr)?&sigsegvDataHandler:&sigsegvCodeHandler);
}
// All handler functions must be called with faulting address in EAX and original EIP in EDX.
// This function is called when the segfault's cause is to call an invalid function pointer.
void sigsegvCodeHandler()
{
asm
{
naked;
// Handle the stack for an invalid function call (segfault at EIP).
// 4 bytes are used for function pointer; We need 12 byte to keep stack aligned.
sub ESP, 12;
mov 8[ESP], EBP;
mov EBP, ESP;
jmp sigsegvDataHandler;
}
}
void sigsegvDataHandler()
{
asm
{
naked;
// We jump directly here if we are in a valid function call case.
push EDX; // return address (original EIP).
push EBP; // old EBP
mov EBP, ESP;
pushfd; // Save flags.
push ECX; // ECX is a trash register and must be preserved as local variable.
// 4 pushes have been done. The stack is aligned.
// Parameter address is already set as EAX.
call sigsegvUserspaceProcess;
// Restore register values and return.
call restoreRegisters;
pop ECX;
popfd; // Restore flags.
// Return
pop EBP;
ret;
}
}
// The return value is stored in EAX and EDX, so this function restore the correct value for theses registers.
RegType[2] restoreRegisters()
{
RegType[2] restore;
restore[0] = savedEAX;
restore[1] = savedEDX;
return restore;
}
}
else
{
static assert(false, "Unsupported architecture.");
}
// This should be calculated by druntime.
// TODO: Add a core.memory function for this.
enum PAGE_SIZE = 4096;
// The first 64Kb are reserved for detecting null pointer dereferences.
enum MEMORY_RESERVED_FOR_NULL_DEREFERENCE = 4096 * 16;
// User space handler
void sigsegvUserspaceProcess(void* address)
{
// SEGV_MAPERR, SEGV_ACCERR.
// The first page is protected to detect null dereferences.
if((cast(size_t) address) < MEMORY_RESERVED_FOR_NULL_DEREFERENCE)
{
throw new NullPointerError();
}
throw new InvalidPointerError();
}
Jump to Line
Something went wrong with that request. Please try again.