1 /** 2 * Handle page protection errors using D errors (exceptions). $(D NullPointerError) is 3 * thrown when dereferencing null pointers. A system-dependent error is thrown in other 4 * cases. 5 * 6 * Note: Only x86 and x86_64 are supported for now. 7 * 8 * License: Distributed under the 9 * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). 10 * (See accompanying file LICENSE_1_0.txt) 11 * Authors: Amaury SECHET, FeepingCreature, Vladimir Panteleev 12 * Source: $(DRUNTIMESRC etc/linux/memory.d) 13 */ 14 15 module etc.linux.memoryerror; 16 17 version (CRuntime_Glibc) 18 { 19 version (X86) 20 version = MemoryErrorSupported; 21 version (X86_64) 22 version = MemoryErrorSupported; 23 } 24 25 version (MemoryErrorSupported): 26 @system: 27 28 import core.sys.posix.signal; 29 import core.sys.posix.ucontext; 30 31 // Register and unregister memory error handler. 32 33 bool registerMemoryErrorHandler() nothrow 34 { 35 sigaction_t action; 36 action.sa_sigaction = &handleSignal; 37 action.sa_flags = SA_SIGINFO; 38 39 auto oldptr = &old_sigaction; 40 41 return !sigaction(SIGSEGV, &action, oldptr); 42 } 43 44 bool deregisterMemoryErrorHandler() nothrow 45 { 46 auto oldptr = &old_sigaction; 47 48 return !sigaction(SIGSEGV, oldptr, null); 49 } 50 51 /** 52 * Thrown on POSIX systems when a SIGSEGV signal is received. 53 */ 54 class InvalidPointerError : Error 55 { 56 this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) nothrow 57 { 58 super("", file, line, next); 59 } 60 61 this(Throwable next, string file = __FILE__, size_t line = __LINE__) nothrow 62 { 63 super("", file, line, next); 64 } 65 } 66 67 /** 68 * Thrown on null pointer dereferences. 69 */ 70 class NullPointerError : InvalidPointerError 71 { 72 this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) nothrow 73 { 74 super(file, line, next); 75 } 76 77 this(Throwable next, string file = __FILE__, size_t line = __LINE__) nothrow 78 { 79 super(file, line, next); 80 } 81 } 82 83 unittest 84 { 85 int* getNull() { return null; } 86 87 assert(registerMemoryErrorHandler()); 88 89 bool b; 90 91 try 92 { 93 *getNull() = 42; 94 } 95 catch (NullPointerError) 96 { 97 b = true; 98 } 99 100 assert(b); 101 102 b = false; 103 104 try 105 { 106 *getNull() = 42; 107 } 108 catch (InvalidPointerError) 109 { 110 b = true; 111 } 112 113 assert(b); 114 115 assert(deregisterMemoryErrorHandler()); 116 } 117 118 // Signal handler space. 119 120 private: 121 122 __gshared sigaction_t old_sigaction; 123 124 alias typeof(ucontext_t.init.uc_mcontext.gregs[0]) RegType; 125 126 version (X86_64) 127 { 128 static RegType savedRDI, savedRSI; 129 130 extern(C) 131 void handleSignal(int signum, siginfo_t* info, void* contextPtr) nothrow 132 { 133 auto context = cast(ucontext_t*)contextPtr; 134 135 // Save registers into global thread local, to allow recovery. 136 savedRDI = context.uc_mcontext.gregs[REG_RDI]; 137 savedRSI = context.uc_mcontext.gregs[REG_RSI]; 138 139 // Hijack current context so we call our handler. 140 auto rip = context.uc_mcontext.gregs[REG_RIP]; 141 auto addr = cast(RegType) info.si_addr; 142 context.uc_mcontext.gregs[REG_RDI] = addr; 143 context.uc_mcontext.gregs[REG_RSI] = rip; 144 context.uc_mcontext.gregs[REG_RIP] = cast(RegType) ((rip != addr)?&sigsegvDataHandler:&sigsegvCodeHandler); 145 } 146 147 // All handler functions must be called with faulting address in RDI and original RIP in RSI. 148 149 // This function is called when the segfault's cause is to call an invalid function pointer. 150 void sigsegvCodeHandler() 151 { 152 asm 153 { 154 naked; 155 156 // Handle the stack for an invalid function call (segfault at RIP). 157 // With the return pointer, the stack is now alligned. 158 push RBP; 159 mov RBP, RSP; 160 161 jmp sigsegvDataHandler; 162 } 163 } 164 165 void sigsegvDataHandler() 166 { 167 asm 168 { 169 naked; 170 171 push RSI; // return address (original RIP). 172 push RBP; // old RBP 173 mov RBP, RSP; 174 175 pushfq; // Save flags. 176 push RAX; // RAX, RCX, RDX, and R8 to R11 are trash registers and must be preserved as local variables. 177 push RCX; 178 push RDX; 179 push R8; 180 push R9; 181 push R10; 182 push R11; // With 10 pushes, the stack is still aligned. 183 184 // Parameter address is already set as RAX. 185 call sigsegvUserspaceProcess; 186 187 // Restore RDI and RSI values. 188 call restoreRDI; 189 push RAX; // RDI is in RAX. It is pushed and will be poped back to RDI. 190 191 call restoreRSI; 192 mov RSI, RAX; 193 194 pop RDI; 195 196 // Restore trash registers value. 197 pop R11; 198 pop R10; 199 pop R9; 200 pop R8; 201 pop RDX; 202 pop RCX; 203 pop RAX; 204 popfq; // Restore flags. 205 206 // Return 207 pop RBP; 208 ret; 209 } 210 } 211 212 // The return value is stored in EAX and EDX, so this function restore the correct value for theses registers. 213 RegType restoreRDI() 214 { 215 return savedRDI; 216 } 217 218 RegType restoreRSI() 219 { 220 return savedRSI; 221 } 222 } 223 else version (X86) 224 { 225 static RegType savedEAX, savedEDX; 226 227 extern(C) 228 void handleSignal(int signum, siginfo_t* info, void* contextPtr) nothrow 229 { 230 auto context = cast(ucontext_t*)contextPtr; 231 232 // Save registers into global thread local, to allow recovery. 233 savedEAX = context.uc_mcontext.gregs[REG_EAX]; 234 savedEDX = context.uc_mcontext.gregs[REG_EDX]; 235 236 // Hijack current context so we call our handler. 237 auto eip = context.uc_mcontext.gregs[REG_EIP]; 238 auto addr = cast(RegType) info.si_addr; 239 context.uc_mcontext.gregs[REG_EAX] = addr; 240 context.uc_mcontext.gregs[REG_EDX] = eip; 241 context.uc_mcontext.gregs[REG_EIP] = cast(RegType) ((eip != addr)?&sigsegvDataHandler:&sigsegvCodeHandler); 242 } 243 244 // All handler functions must be called with faulting address in EAX and original EIP in EDX. 245 246 // This function is called when the segfault's cause is to call an invalid function pointer. 247 void sigsegvCodeHandler() 248 { 249 asm 250 { 251 naked; 252 253 // Handle the stack for an invalid function call (segfault at EIP). 254 // 4 bytes are used for function pointer; We need 12 byte to keep stack aligned. 255 sub ESP, 12; 256 mov 8[ESP], EBP; 257 mov EBP, ESP; 258 259 jmp sigsegvDataHandler; 260 } 261 } 262 263 void sigsegvDataHandler() 264 { 265 asm 266 { 267 naked; 268 269 // We jump directly here if we are in a valid function call case. 270 push EDX; // return address (original EIP). 271 push EBP; // old EBP 272 mov EBP, ESP; 273 274 pushfd; // Save flags. 275 push ECX; // ECX is a trash register and must be preserved as local variable. 276 // 4 pushes have been done. The stack is aligned. 277 278 // Parameter address is already set as EAX. 279 call sigsegvUserspaceProcess; 280 281 // Restore register values and return. 282 call restoreRegisters; 283 284 pop ECX; 285 popfd; // Restore flags. 286 287 // Return 288 pop EBP; 289 ret; 290 } 291 } 292 293 // The return value is stored in EAX and EDX, so this function restore the correct value for theses registers. 294 RegType[2] restoreRegisters() 295 { 296 RegType[2] restore; 297 restore[0] = savedEAX; 298 restore[1] = savedEDX; 299 300 return restore; 301 } 302 } 303 else 304 { 305 static assert(false, "Unsupported architecture."); 306 } 307 308 // This should be calculated by druntime. 309 // TODO: Add a core.memory function for this. 310 enum PAGE_SIZE = 4096; 311 312 // The first 64Kb are reserved for detecting null pointer dereferences. 313 enum MEMORY_RESERVED_FOR_NULL_DEREFERENCE = 4096 * 16; 314 315 // User space handler 316 void sigsegvUserspaceProcess(void* address) 317 { 318 // SEGV_MAPERR, SEGV_ACCERR. 319 // The first page is protected to detect null dereferences. 320 if ((cast(size_t) address) < MEMORY_RESERVED_FOR_NULL_DEREFERENCE) 321 { 322 throw new NullPointerError(); 323 } 324 325 throw new InvalidPointerError(); 326 }