From 883f65d0e44468de25a638b5d90c8946a762fb08 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bj=C3=B6rn=20Lindqvist?= Date: Sat, 15 Aug 2015 00:22:50 +0200 Subject: [PATCH] VM: fix one callstack overflow problem by "unlocking" the callstacks border pages Also a new vocab compiler.tests.callstack-overflow which is supposed to contain all tests for callstack overflow-related problems. --- .../compiler/tests/callstack-overflow.factor | 76 +++++++++++++++++++ core/continuations/continuations-tests.factor | 25 ------ core/kernel/kernel-tests.factor | 11 --- vm/gc.cpp | 4 + 4 files changed, 80 insertions(+), 36 deletions(-) create mode 100644 basis/compiler/tests/callstack-overflow.factor diff --git a/basis/compiler/tests/callstack-overflow.factor b/basis/compiler/tests/callstack-overflow.factor new file mode 100644 index 0000000000..7157e64770 --- /dev/null +++ b/basis/compiler/tests/callstack-overflow.factor @@ -0,0 +1,76 @@ +USING: accessors classes.struct continuations kernel kernel.private literals +math memory sequences system threads.private tools.dispatch.private +tools.test ; +QUALIFIED: vm +IN: compiler.tests.callstack-overflow + +! This test file is for all callstack overflow-related problems. + +: pre ( -- ) + nano-count 0 = [ ] [ ] if ; + +: post ( -- ) ; + +: do-overflow ( -- ) + pre do-overflow post ; + +: recurse ( -- ? ) + [ do-overflow f ] [ ] recover second ERROR-CALLSTACK-OVERFLOW = ; + +: overflow-c ( -- ) overflow-c overflow-c ; + +: overflow/w-primitive ( -- ) + reset-dispatch-stats overflow/w-primitive post ; + +: get-context ( -- ctx ) context vm:context memory>struct ; + +: remaining-stack ( -- n ) + get-context [ callstack-top>> ] [ callstack-seg>> start>> ] bi - ; + +: overflow/w-compact-gc ( -- ) + remaining-stack dup 500 < [ + drop compact-gc + ] [ drop overflow/w-compact-gc ] if post ; + +! The VM cannot recover from callstack overflow on Windows, because no +! facility exists to run memory protection fault handlers on an +! alternate callstack. So we punt on the whole test-suite. +os windows? [ + + ! This tries to verify that enough bytes are cut off from the + ! callstack to run the error handler. It appears that the previous + ! limit of 1024 bytes didn't give the gc enough stack space to + ! work with, so we bumped that limit to 16384. + { t } [ + 10 [ recurse ] replicate [ ] all? + ] unit-test + + ! ! See how well callstack overflow is handled + ! [ clear drop ] must-fail + ! + ! : callstack-overflow callstack-overflow f ; + ! [ callstack-overflow ] must-fail + [ overflow-c ] [ + 2 head ${ "kernel-error" ERROR-CALLSTACK-OVERFLOW } = + ] must-fail-with + + ! The way this is problematic is because a primitive is + ! involved. reset-dispatch-stats is called, decreasing RSP by cell + ! bytes and then there is < 0x20 bytes stack left. Then SUB RSP, + ! 0x18 is called to setup the call frame. Then the context is + ! saved and ctx->callstack_top is set to RSP - 8 which is below + ! the stack limit. Then dereferencing ctx->callstack_top segfaults + ! so we need to handle the case specially in + ! dispatch_non_resumable_signal(). + [ overflow/w-primitive ] [ + 2 head ${ "kernel-error" ERROR-CALLSTACK-OVERFLOW } = + ] must-fail-with + + ! Load up the stack until there is < 500 bytes of it left. Then + ! run a big gc cycle. 500 bytes isn't enough, so a callstack + ! overflow would occur during the gc which we can't handle. The + ! solution is to for the duration of the gc unlock the segment's + ! lower guard page which gives it pagesize (4096) more bytes to + ! play with. + { } [ overflow/w-compact-gc ] unit-test +] unless diff --git a/core/continuations/continuations-tests.factor b/core/continuations/continuations-tests.factor index dea2c40ae7..a3c31b45ad 100644 --- a/core/continuations/continuations-tests.factor +++ b/core/continuations/continuations-tests.factor @@ -49,31 +49,6 @@ IN: continuations.tests gc ] unit-test -! ! See how well callstack overflow is handled -! [ clear drop ] must-fail -! -! : callstack-overflow callstack-overflow f ; -! [ callstack-overflow ] must-fail - -! This tries to verify that enough bytes are cut off from the -! callstack to run the error handler. -: pre ( -- ) nano-count 0 = [ ] [ ] if ; - -: post ( -- ) ; - -: do-overflow ( -- ) - pre do-overflow post ; - -: recurse ( -- ? ) - [ do-overflow f ] [ ] recover - second ERROR-CALLSTACK-OVERFLOW = ; - -os windows? [ - { t } [ - 10 [ recurse ] replicate [ ] all? - ] unit-test -] unless - : don't-compile-me ( -- ) ; : foo ( -- ) get-callstack "c" set don't-compile-me ; : bar ( -- a b ) 1 foo 2 ; diff --git a/core/kernel/kernel-tests.factor b/core/kernel/kernel-tests.factor index 8442eb3a09..c461bba7d2 100644 --- a/core/kernel/kernel-tests.factor +++ b/core/kernel/kernel-tests.factor @@ -73,17 +73,6 @@ IN: kernel.tests { } [ :c ] unit-test -: overflow-c ( -- ) overflow-c overflow-c ; - -! The VM cannot recover from callstack overflow on Windows, -! because no facility exists to run memory protection -! fault handlers on an alternate callstack. -os windows? [ - [ overflow-c ] [ - 2 head ${ "kernel-error" ERROR-CALLSTACK-OVERFLOW } = - ] must-fail-with -] unless - [ -7 ] must-fail { 3 } [ t 3 and ] unit-test diff --git a/vm/gc.cpp b/vm/gc.cpp index 91be631d90..f3c5ff43fc 100644 --- a/vm/gc.cpp +++ b/vm/gc.cpp @@ -122,6 +122,8 @@ void factor_vm::gc(gc_op op, cell requested_size) { FACTOR_ASSERT(!data->high_fragmentation_p()); current_gc = new gc_state(op, this); + if (ctx) + ctx->callstack_seg->set_border_locked(false); atomic::store(¤t_gc_p, true); /* Keep trying to GC higher and higher generations until we don't run @@ -179,6 +181,8 @@ void factor_vm::gc(gc_op op, cell requested_size) { end_gc(); atomic::store(¤t_gc_p, false); + if (ctx) + ctx->callstack_seg->set_border_locked(true); delete current_gc; current_gc = NULL; -- 2.34.1