boolean handle_control_stack_guard_triggered(os_context_t *context,void *addr){
struct thread *th=arch_os_get_current_thread();
+
/* note the os_context hackery here. When the signal handler returns,
* it won't go back to what it was doing ... */
- if(addr>=(void *)CONTROL_STACK_GUARD_PAGE(th) &&
- addr<(void *)(CONTROL_STACK_GUARD_PAGE(th)+os_vm_page_size)) {
- /* we hit the end of the control stack. disable protection
- * temporarily so the error handler has some headroom */
- protect_control_stack_guard_page(th->pid,0L);
-
- arrange_return_to_lisp_function
- (context, SymbolFunction(CONTROL_STACK_EXHAUSTED_ERROR));
- return 1;
+ if(addr >= CONTROL_STACK_GUARD_PAGE(th) &&
+ addr < CONTROL_STACK_GUARD_PAGE(th) + os_vm_page_size) {
+ /* We hit the end of the control stack: disable guard page
+ * protection so the error handler has some headroom, protect the
+ * previous page so that we can catch returns from the guard page
+ * and restore it. */
+ protect_control_stack_guard_page(th->pid,0);
+ protect_control_stack_return_guard_page(th->pid,1);
+
+ arrange_return_to_lisp_function
+ (context, SymbolFunction(CONTROL_STACK_EXHAUSTED_ERROR));
+ return 1;
+ }
+ else if(addr >= CONTROL_STACK_RETURN_GUARD_PAGE(th) &&
+ addr < CONTROL_STACK_RETURN_GUARD_PAGE(th) + os_vm_page_size) {
+ /* We're returning from the guard page: reprotect it, and
+ * unprotect this one. This works even if we somehow missed
+ * the return-guard-page, and hit it on our way to new
+ * exhaustion instead. */
+ protect_control_stack_guard_page(th->pid,1);
+ protect_control_stack_return_guard_page(th->pid,0);
+ return 1;
}
else return 0;
}
(OS_VM_PROT_READ|OS_VM_PROT_EXECUTE) : OS_VM_PROT_ALL);
}
+void protect_control_stack_return_guard_page(pid_t t_id, int protect_p) {
+ struct thread *th = find_thread_by_pid(t_id);
+ os_protect(CONTROL_STACK_RETURN_GUARD_PAGE(th),
+ os_vm_page_size,protect_p ?
+ (OS_VM_PROT_READ|OS_VM_PROT_EXECUTE) : OS_VM_PROT_ALL);
+}
#include <thread.h>
#ifdef LISP_FEATURE_STACK_GROWS_DOWNWARD_NOT_UPWARD
#define CONTROL_STACK_GUARD_PAGE(th) ((void *)(th->control_stack_start))
+#define CONTROL_STACK_RETURN_GUARD_PAGE(th) (CONTROL_STACK_GUARD_PAGE(th) + os_vm_page_size)
#else
-#define CONTROL_STACK_GUARD_PAGE(th) \
- (((void *)(th->control_stack_end)) - os_vm_page_size)
+#define CONTROL_STACK_GUARD_PAGE(th) (((void *)(th->control_stack_end)) - os_vm_page_size)
+#define CONTROL_STACK_RETURN_GUARD_PAGE(th) (CONTROL_STACK_GUARD_PAGE(th) - os_vm_page_size)
#endif
extern void validate(void);
extern void protect_control_stack_guard_page(pid_t t_id, int protect_p);
+extern void protect_control_stack_return_guard_page(pid_t t_id, int protect_p);
#endif
/* note for anyone trying to port an architecture's support files
;;; Post 0.7.6.1, this was rewritten to use mprotect()-based stack
;;; protection which does not require lisp code to check anything,
;;; and works at all optimization settings. However, it now signals a
-;;; STORAGE-CONDITION instead of an ERROR, so this test needs revising
-(locally
- (defun recurse () (recurse) (recurse))
- (handler-case
- (recurse)
- (storage-condition (c) (declare (ignore c)) (quit :unix-status 104))))
-\f
-;;; oops
-(quit :unix-status 1)
+;;; STORAGE-CONDITION instead of an ERROR.
+
+(defun recurse ()
+ (recurse)
+ (recurse))
+
+(defvar *count* 100)
+
+;;; Base-case: detecting exhaustion
+(assert (eq :exhausted
+ (handler-case
+ (recurse)
+ (storage-condition (c)
+ (declare (ignore c))
+ :exhausted))))
+
+;;; Check that non-local control transfers restore the stack
+;;; exhaustion checking after unwinding -- and that previous test
+;;; didn't break it.
+(let ((exhaust-count 0)
+ (recurse-count 0))
+ (tagbody
+ :retry
+ (handler-bind ((storage-condition (lambda (c)
+ (declare (ignore c))
+ (if (= *count* (incf exhaust-count))
+ (go :stop)
+ (go :retry)))))
+ (incf recurse-count)
+ (recurse))
+ :stop)
+ (assert (= exhaust-count recurse-count *count*)))
+
+;;; Check that we can safely use user-provided restarts to
+;;; unwind.
+(let ((exhaust-count 0)
+ (recurse-count 0))
+ (block nil
+ (handler-bind ((storage-condition (lambda (c)
+ (declare (ignore c))
+ (if (= *count* (incf exhaust-count))
+ (return)
+ (invoke-restart (find-restart 'ok))))))
+ (loop
+ (with-simple-restart (ok "ok")
+ (incf recurse-count)
+ (recurse)))))
+ (assert (= exhaust-count recurse-count *count*)))
+
+;;; OK!
+(quit :unix-status 104)
;;; checkins which aren't released. (And occasionally for internal
;;; versions, especially for internal versions off the main CVS
;;; branch, it gets hairier, e.g. "0.pre7.14.flaky4.13".)
-"0.8.14.23"
+"0.8.14.24"