[pypy-commit] pypy default: hg merge shadowstack-issue2722

arigo pypy.commits at gmail.com
Wed May 8 09:24:14 EDT 2019


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r96579:52462bc541a3
Date: 2019-05-08 15:23 +0200
http://bitbucket.org/pypy/pypy/changeset/52462bc541a3/

Log:	hg merge shadowstack-issue2722

diff --git a/pypy/module/sys/vm.py b/pypy/module/sys/vm.py
--- a/pypy/module/sys/vm.py
+++ b/pypy/module/sys/vm.py
@@ -49,12 +49,19 @@
 reserves 768KB of stack space, which should suffice (on Linux,
 depending on the compiler settings) for ~1400 calls.  Setting the
 value to N reserves N/1000 times 768KB of stack space.
+
+Note that there are other factors that also limit the stack size.
+The operating system typically sets a maximum which can be changed
+manually (e.g. with "ulimit" on Linux) for the main thread.  For other
+threads you can configure the limit by calling "threading.stack_size()".
 """
     from rpython.rlib.rstack import _stack_set_length_fraction
+    from rpython.rlib.rgc import increase_root_stack_depth
     if new_limit <= 0:
         raise oefmt(space.w_ValueError, "recursion limit must be positive")
     space.sys.recursionlimit = new_limit
     _stack_set_length_fraction(new_limit * 0.001)
+    increase_root_stack_depth(int(new_limit * 0.001 * 163840))
 
 def getrecursionlimit(space):
     """Return the last value set by setrecursionlimit().
diff --git a/rpython/memory/gctransform/framework.py b/rpython/memory/gctransform/framework.py
--- a/rpython/memory/gctransform/framework.py
+++ b/rpython/memory/gctransform/framework.py
@@ -572,6 +572,8 @@
             self.move_out_of_nursery_ptr = getfn(GCClass.move_out_of_nursery,
                                               [s_gc, SomeAddress()],
                                               SomeAddress())
+        if hasattr(self.root_walker, 'build_increase_root_stack_depth_ptr'):
+            self.root_walker.build_increase_root_stack_depth_ptr(getfn)
 
 
     def create_custom_trace_funcs(self, gc, rtyper):
@@ -1652,6 +1654,12 @@
         else:
             hop.rename("same_as")
 
+    def gct_gc_increase_root_stack_depth(self, hop):
+        if not hasattr(self.root_walker, 'gc_increase_root_stack_depth_ptr'):
+            return
+        hop.genop("direct_call",
+                  [self.root_walker.gc_increase_root_stack_depth_ptr,
+                   hop.spaceop.args[0]])
 
 
 class TransformerLayoutBuilder(gctypelayout.TypeLayoutBuilder):
diff --git a/rpython/memory/gctransform/shadowstack.py b/rpython/memory/gctransform/shadowstack.py
--- a/rpython/memory/gctransform/shadowstack.py
+++ b/rpython/memory/gctransform/shadowstack.py
@@ -140,6 +140,7 @@
         # this is a dict {tid: SHADOWSTACKREF}, where the tid for the
         # current thread may be missing so far
         gcdata.thread_stacks = None
+        shadow_stack_pool.has_threads = True
 
         # Return the thread identifier, as an integer.
         get_tid = rthread.get_ident
@@ -252,6 +253,15 @@
         self.gc_modified_shadowstack_ptr = getfn(gc_modified_shadowstack,
                                                  [], annmodel.s_None)
 
+    def build_increase_root_stack_depth_ptr(self, getfn):
+        shadow_stack_pool = self.shadow_stack_pool
+        def gc_increase_root_stack_depth(new_size):
+            shadow_stack_pool.increase_root_stack_depth(new_size)
+
+        self.gc_increase_root_stack_depth_ptr = getfn(
+                gc_increase_root_stack_depth, [annmodel.SomeInteger()],
+                annmodel.s_None)
+
     def postprocess_graph(self, gct, graph, any_inlining):
         from rpython.memory.gctransform import shadowcolor
         if any_inlining:
@@ -269,6 +279,7 @@
     """
     _alloc_flavor_ = "raw"
     root_stack_depth = 163840
+    has_threads = False
 
     def __init__(self, gcdata):
         self.unused_full_stack = llmemory.NULL
@@ -337,6 +348,44 @@
             if self.unused_full_stack == llmemory.NULL:
                 raise MemoryError
 
+    def increase_root_stack_depth(self, new_depth):
+        if new_depth <= self.root_stack_depth:
+            return     # can't easily decrease the size
+        if self.unused_full_stack:
+            llmemory.raw_free(self.unused_full_stack)
+            self.unused_full_stack = llmemory.NULL
+        used = self.gcdata.root_stack_top - self.gcdata.root_stack_base
+        addr = self._resize(self.gcdata.root_stack_base, used, new_depth)
+        self.gcdata.root_stack_base = addr
+        self.gcdata.root_stack_top  = addr + used
+        # no gc operations above: we just switched shadowstacks
+        if self.has_threads:
+            self._resize_thread_shadowstacks(new_depth)
+        self.root_stack_depth = new_depth
+
+    def _resize_thread_shadowstacks(self, new_depth):
+        if self.gcdata.thread_stacks is not None:
+            for ssref in self.gcdata.thread_stacks.values():
+                if ssref.base:
+                    used = ssref.top - ssref.base
+                    addr = self._resize(ssref.base, used, new_depth)
+                    ssref.base = addr
+                    ssref.top = addr + used
+    _resize_thread_shadowstacks._dont_inline_ = True
+
+    def _resize(self, base, used, new_depth):
+        new_size = sizeofaddr * new_depth
+        ll_assert(used <= new_size, "shadowstack resize: overflow detected")
+        addr = llmemory.raw_malloc(new_size)
+        if addr == llmemory.NULL:
+            raise MemoryError
+        # note that we don't know the total memory size of 'base', but we
+        # know the size of the part that is used right now, and we only need
+        # to copy that
+        llmemory.raw_memmove(base, addr, used)
+        llmemory.raw_free(base)
+        return addr
+
 
 def get_shadowstackref(root_walker, gctransformer):
     if hasattr(gctransformer, '_SHADOWSTACKREF'):
diff --git a/rpython/rlib/rgc.py b/rpython/rlib/rgc.py
--- a/rpython/rlib/rgc.py
+++ b/rpython/rlib/rgc.py
@@ -690,6 +690,13 @@
         hop.exception_cannot_occur()
         return hop.genop('gc_move_out_of_nursery', hop.args_v, resulttype=hop.r_result)
 
+ at jit.dont_look_inside
+def increase_root_stack_depth(new_depth):
+    """Shadowstack: make sure the size of the shadowstack is at least
+    'new_depth' pointers."""
+    from rpython.rtyper.lltypesystem.lloperation import llop
+    llop.gc_increase_root_stack_depth(lltype.Void, new_depth)
+
 # ____________________________________________________________
 
 
diff --git a/rpython/rtyper/llinterp.py b/rpython/rtyper/llinterp.py
--- a/rpython/rtyper/llinterp.py
+++ b/rpython/rtyper/llinterp.py
@@ -1226,6 +1226,9 @@
     def op_gc_move_out_of_nursery(self, obj):
         raise NotImplementedError("gc_move_out_of_nursery")
 
+    def op_gc_increase_root_stack_depth(self, new_depth):
+        raise NotImplementedError("gc_increase_root_stack_depth")
+
     def op_revdb_stop_point(self, *args):
         pass
     def op_revdb_send_answer(self, *args):
diff --git a/rpython/rtyper/lltypesystem/lloperation.py b/rpython/rtyper/lltypesystem/lloperation.py
--- a/rpython/rtyper/lltypesystem/lloperation.py
+++ b/rpython/rtyper/lltypesystem/lloperation.py
@@ -530,6 +530,7 @@
     'gc_rawrefcount_next_dead':         LLOp(),
 
     'gc_move_out_of_nursery':           LLOp(),
+    'gc_increase_root_stack_depth':     LLOp(canrun=True),
 
     'gc_push_roots'        : LLOp(),  # temporary: list of roots to save
     'gc_pop_roots'         : LLOp(),  # temporary: list of roots to restore
diff --git a/rpython/rtyper/lltypesystem/opimpl.py b/rpython/rtyper/lltypesystem/opimpl.py
--- a/rpython/rtyper/lltypesystem/opimpl.py
+++ b/rpython/rtyper/lltypesystem/opimpl.py
@@ -776,6 +776,9 @@
 def op_gc_move_out_of_nursery(obj):
     return obj
 
+def op_gc_increase_root_stack_depth(new_depth):
+    pass
+
 def op_revdb_do_next_call():
     pass
 
diff --git a/rpython/translator/c/gc.py b/rpython/translator/c/gc.py
--- a/rpython/translator/c/gc.py
+++ b/rpython/translator/c/gc.py
@@ -452,7 +452,6 @@
         #
         yield ('typedef struct { void %s; } pypy_ss_t;'
                    % ', '.join(['*s%d' % i for i in range(numcolors)]))
-        yield 'pypy_ss_t *ss;'
         funcgen.gcpol_ss = gcpol_ss
 
     def OP_GC_PUSH_ROOTS(self, funcgen, op):
@@ -462,26 +461,27 @@
         raise Exception("gc_pop_roots should be removed by postprocess_graph")
 
     def OP_GC_ENTER_ROOTS_FRAME(self, funcgen, op):
-        return 'ss = (pypy_ss_t *)%s; %s = (void *)(ss+1);' % (
-            funcgen.gcpol_ss, funcgen.gcpol_ss)
+        return '%s += sizeof(pypy_ss_t);' % (funcgen.gcpol_ss,)
 
     def OP_GC_LEAVE_ROOTS_FRAME(self, funcgen, op):
-        return '%s = (void *)ss;' % funcgen.gcpol_ss
+        return '%s -= sizeof(pypy_ss_t);' % (funcgen.gcpol_ss,)
 
     def OP_GC_SAVE_ROOT(self, funcgen, op):
         num = op.args[0].value
         exprvalue = funcgen.expr(op.args[1])
-        return 'ss->s%d = (void *)%s;\t/* gc_save_root */' % (num, exprvalue)
+        return '((pypy_ss_t *)%s)[-1].s%d = (void *)%s;' % (
+            funcgen.gcpol_ss, num, exprvalue)
 
     def OP_GC_RESTORE_ROOT(self, funcgen, op):
         num = op.args[0].value
         exprvalue = funcgen.expr(op.args[1])
         typename = funcgen.db.gettype(op.args[1].concretetype)
-        result = '%s = (%s)ss->s%d;' % (exprvalue, cdecl(typename, ''), num)
+        result = '%s = (%s)((pypy_ss_t *)%s)[-1].s%d;' % (
+            exprvalue, cdecl(typename, ''), funcgen.gcpol_ss, num)
         if isinstance(op.args[1], Constant):
-            return '/* %s\t* gc_restore_root */' % result
+            return '/* %s */' % result
         else:
-            return '%s\t/* gc_restore_root */' % result
+            return result
 
 
 class AsmGcRootFrameworkGcPolicy(BasicFrameworkGcPolicy):
diff --git a/rpython/translator/c/test/test_newgc.py b/rpython/translator/c/test/test_newgc.py
--- a/rpython/translator/c/test/test_newgc.py
+++ b/rpython/translator/c/test/test_newgc.py
@@ -1912,6 +1912,45 @@
     def test_total_gc_time(self):
         res = self.run("total_gc_time")
         assert res > 0 # should take a few microseconds
+
+    def define_increase_root_stack_depth(cls):
+        class X:
+            pass
+        def g(n):
+            if n <= 0:
+                return None
+            x = X()
+            x.n = n
+            x.next = g(n - 1)
+            return x
+        def f(depth):
+            from rpython.rlib.rstack import _stack_set_length_fraction
+            _stack_set_length_fraction(50.0)
+            # ^^^ the default is enough for at least 10'000 (but less than
+            # 100'000) recursions of the simple function g().  We multiply
+            # it by 50.0 to make sure that 200'000 works.  The default
+            # shadowstack depth is 163'840 entries, so 200'000 overflows
+            # that default shadowstack depth, and gives a segfault unless
+            # the following line works too.
+            from rpython.rlib.rgc import increase_root_stack_depth
+            increase_root_stack_depth(depth + 100)
+            #
+            g(depth)
+            return 42
+        return f
+
+    def test_increase_root_stack_depth(self):
+        if not sys.platform.startswith('linux'):
+            py.test.skip("linux only")
+        #
+        def myrunner(args):
+            args1 = ['/bin/bash', '-c', 'ulimit -s unlimited && %s' %
+                     (' '.join(args),)]
+            return subprocess.check_output(args1)
+        res = self.run("increase_root_stack_depth", 200000, runner=myrunner)
+        assert res == 42
+
+
 # ____________________________________________________________________
 
 class TaggedPointersTest(object):


More information about the pypy-commit mailing list