The OpenD Programming Language

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 }