[pypy-commit] pypy stacklet: Shadowstack support, version 1.

arigo noreply at buildbot.pypy.org
Mon Aug 8 17:08:47 CEST 2011


Author: Armin Rigo <arigo at tunes.org>
Branch: stacklet
Changeset: r46372:d2364a7ee11b
Date: 2011-08-08 17:07 +0200
http://bitbucket.org/pypy/pypy/changeset/d2364a7ee11b/

Log:	Shadowstack support, version 1.

diff --git a/pypy/config/translationoption.py b/pypy/config/translationoption.py
--- a/pypy/config/translationoption.py
+++ b/pypy/config/translationoption.py
@@ -26,8 +26,7 @@
         "translation", "Translation Options", [
     BoolOption("stacklet", "enable stackless features during compilation",
                default=False, cmdline="--stacklet",
-               requires=[("translation.type_system", "lltype"),
-                         ("translation.gcrootfinder", "asmgcc")]), # XXX temp
+               requires=[("translation.type_system", "lltype")]),
     ChoiceOption("type_system", "Type system to use when RTyping",
                  ["lltype", "ootype"], cmdline=None, default="lltype",
                  requires={
diff --git a/pypy/rlib/_stacklet_asmgcc.py b/pypy/rlib/_stacklet_asmgcc.py
--- a/pypy/rlib/_stacklet_asmgcc.py
+++ b/pypy/rlib/_stacklet_asmgcc.py
@@ -223,7 +223,7 @@
     suspstack = NULL_SUSPSTACK
 
     def new(self, thrd, callback, arg):
-        self.thrd = thrd
+        self.thrd = thrd._thrd
         self.runfn = callback
         self.arg = arg
         # make a fresh new clean SUSPSTACK
@@ -236,7 +236,7 @@
         return self.get_result_suspstack(h)
 
     def switch(self, thrd, suspstack):
-        self.thrd = thrd
+        self.thrd = thrd._thrd
         self.suspstack = suspstack
         h = pypy_asm_stackwalk2(llhelper(FUNCNOARG_P, _switch_callback),
                                 alternateanchor)
@@ -264,16 +264,14 @@
 
     def destroy(self, thrd, suspstack):
         h = suspstack.handle
-        a = suspstack.anchor
         suspstack.handle = _c.null_handle
-        _c.destroy(thrd, h)
-        lltype.free(a, flavor='raw')
+        _c.destroy(thrd._thrd, h)
 
     def is_empty_handle(self, suspstack):
         return not suspstack
 
     def get_null_handle(self):
-        return lltype.nullptr(SUSPSTACK)
+        return NULL_SUSPSTACK
 
 
 gcrootfinder = StackletGcRootFinder()
diff --git a/pypy/rlib/_stacklet_n_a.py b/pypy/rlib/_stacklet_n_a.py
--- a/pypy/rlib/_stacklet_n_a.py
+++ b/pypy/rlib/_stacklet_n_a.py
@@ -7,19 +7,20 @@
     __metaclass__ = StaticMethods
 
     def new(thrd, callback, arg):
-        h = _c.new(thrd, llhelper(_c.run_fn, callback), arg)
+        h = _c.new(thrd._thrd, llhelper(_c.run_fn, callback), arg)
         if not h:
             raise MemoryError
         return h
     new._annspecialcase_ = 'specialize:arg(1)'
 
     def switch(thrd, h):
-        h = _c.switch(thrd, h)
+        h = _c.switch(thrd._thrd, h)
         if not h:
             raise MemoryError
         return h
 
-    destroy = _c.destroy
+    def destroy(thrd, h):
+        _c.destroy(thrd._thrd, h)
 
     is_empty_handle = _c.is_empty_handle
 
diff --git a/pypy/rlib/_stacklet_shadowstack.py b/pypy/rlib/_stacklet_shadowstack.py
new file mode 100644
--- /dev/null
+++ b/pypy/rlib/_stacklet_shadowstack.py
@@ -0,0 +1,196 @@
+from pypy.rlib import _rffi_stacklet as _c
+from pypy.rpython.lltypesystem import lltype, llmemory, rffi
+from pypy.rpython.lltypesystem.lloperation import llop
+from pypy.rlib import rstacklet
+from pypy.rlib.debug import ll_assert
+from pypy.rpython.annlowlevel import llhelper
+
+
+PTR_SUSPSTACK = lltype.Ptr(lltype.GcForwardReference())
+SUSPSTACK = lltype.GcStruct('SuspStack',
+                            ('handle', _c.handle),
+                            ('shadowstack_stop', llmemory.Address), # low (old)
+                            ('shadowstack_start', llmemory.Address),# high(new)
+                            ('shadowstack_saved', lltype.Signed),   # number
+                            ('shadowstack_prev', PTR_SUSPSTACK),
+                            # copy of the shadowstack, but in reversed order:
+                            # the first items here are the last items in the
+                            # real shadowstack.  Only items 0 to saved-1 are
+                            # set up; the rest is still in the real shadowstack
+                            ('shadowstack_copy', lltype.Array(llmemory.GCREF)))
+PTR_SUSPSTACK.TO.become(SUSPSTACK)
+NULL_SUSPSTACK = lltype.nullptr(SUSPSTACK)
+
+sizeofaddr = llmemory.sizeof(llmemory.Address)
+
+def repr_suspstack(suspstack):
+    return '<SuspStack %d: stop=%d, start=%d, saved=%d>' % (
+        rffi.cast(lltype.Signed, suspstack.handle),
+        rffi.cast(lltype.Signed, suspstack.shadowstack_stop),
+        rffi.cast(lltype.Signed, suspstack.shadowstack_start),
+        suspstack.shadowstack_saved)
+
+
+# every thread has a 'current stack stop', which is like
+# g_current_stack_stop in stacklet.c but about the shadowstack
+rstacklet.StackletThread._current_shadowstack_stop = llmemory.NULL
+
+# every thread has a 'stack chain' too, similar to g_stack_chain_head.
+rstacklet.StackletThread._shadowstack_chain_head = NULL_SUSPSTACK
+
+
+def root_stack_top():
+    return llop.gc_adr_of_root_stack_top(llmemory.Address).address[0]
+
+def set_root_stack_top(newaddr):
+    llop.gc_adr_of_root_stack_top(llmemory.Address).address[0] = newaddr
+
+
+def _new_runfn(h, arg):
+    gcrootfinder.thrd._current_shadowstack_stop = root_stack_top()
+    suspstack = gcrootfinder.attach_handle_on_suspstack(h)
+    suspstack = gcrootfinder.runfn(suspstack, arg)
+    return gcrootfinder.consume_suspstack(suspstack)
+
+
+class StackletGcRootFinder(object):
+    suspstack = NULL_SUSPSTACK
+
+    def new(self, thrd, callback, arg):
+        if thrd._current_shadowstack_stop == llmemory.NULL:
+            thrd._current_shadowstack_stop = root_stack_top()
+        self.allocate_source_suspstack(thrd)
+        self.runfn = callback
+        h = _c.new(thrd._thrd, llhelper(_c.run_fn, _new_runfn), arg)
+        return self.get_result_suspstack(h)
+
+    def switch(self, thrd, suspstack):
+        self.allocate_source_suspstack(thrd)
+        h = self.consume_suspstack(suspstack)
+        h2 = _c.switch(thrd._thrd, h)
+        return self.get_result_suspstack(h2)
+
+    def allocate_source_suspstack(self, thrd):
+        # Attach to 'self.suspstack' a SUSPSTACK that represents the
+        # old, but still current, stacklet.  All that is left to
+        # fill is 'self.suspstack.handle', done later by a call
+        # to attach_handle_on_suspstack().
+        rst = root_stack_top()
+        if thrd._current_shadowstack_stop > rst:
+            thrd._current_shadowstack_stop = rst
+        count = (rst - thrd._current_shadowstack_stop) // sizeofaddr
+        newsuspstack = lltype.malloc(SUSPSTACK, count)
+        newsuspstack.shadowstack_stop = thrd._current_shadowstack_stop
+        newsuspstack.shadowstack_start = rst
+        newsuspstack.shadowstack_saved = 0
+        newsuspstack.shadowstack_prev = thrd._shadowstack_chain_head
+        thrd._shadowstack_chain_head = newsuspstack
+        self.suspstack = newsuspstack
+        self.thrd = thrd
+
+    def attach_handle_on_suspstack(self, handle):
+        s = self.suspstack
+        self.suspstack = NULL_SUSPSTACK
+        s.handle = handle
+        print 'ATTACH HANDLE', repr_suspstack(s)
+        return s
+
+    def get_result_suspstack(self, h):
+        #
+        # Return from a new() or a switch(): 'h' is a handle, possibly
+        # an empty one, that says from where we switched to.
+        if not h:
+            # oups, we didn't actually switch anywhere, but just got
+            # an out-of-memory condition.  Restore the current suspstack.
+            self.consume_suspstack(self.suspstack)
+            self.suspstack = NULL_SUSPSTACK
+            self.thrd = None
+            raise MemoryError
+        #
+        if _c.is_empty_handle(h):
+            self.thrd = None
+            return NULL_SUSPSTACK
+        else:
+            # This is a return that gave us a real handle.  Store it.
+            return self.attach_handle_on_suspstack(h)
+
+    def consume_suspstack(self, suspstack):
+        print 'CONSUME', repr_suspstack(suspstack)
+        #
+        # We want to switch to or return to 'suspstack'.  First get
+        # how far we have to clean up the shadowstack.
+        target = suspstack.shadowstack_stop
+        #
+        # Clean the shadowstack up to that position.
+        self.clear_shadowstack(target, suspstack)
+        #
+        # Now restore data from suspstack.shadowstack_copy.
+        self.restore_suspstack(suspstack)
+        #
+        # Set the new root stack bounds.
+        self.thrd._current_shadowstack_stop = target
+        set_root_stack_top(suspstack.shadowstack_start)
+        #
+        # Now the real shadowstack is ready for 'suspstack'.
+        return suspstack.handle
+
+    def clear_shadowstack(self, target_stop, targetsuspstack):
+        # NB. see also g_clear_stack() in stacklet.c.
+        #
+        current = self.thrd._shadowstack_chain_head
+        #
+        # save and unlink suspstacks that are completely within
+        # the area to clear.
+        while bool(current) and current.shadowstack_stop >= target_stop:
+            prev = current.shadowstack_prev
+            current.shadowstack_prev = NULL_SUSPSTACK
+            if current != targetsuspstack:
+                # don't bother saving away targetsuspstack, because
+                # it would be immediately restored
+                self._save(current, current.shadowstack_stop)
+            current = prev
+        #
+        # save a partial stack
+        if bool(current) and current.shadowstack_start > target_stop:
+            self._save(current, target_stop)
+        #
+        self.thrd._shadowstack_chain_head = current
+
+    def _save(self, suspstack, stop):
+        # See g_save() in stacklet.c.
+        num1 = suspstack.shadowstack_saved
+        num2 = (suspstack.shadowstack_start - stop) // sizeofaddr
+        ll_assert(stop >= suspstack.shadowstack_stop, "stacklet+shadowstack#1")
+        source = suspstack.shadowstack_start - num1 * sizeofaddr
+        while num1 < num2:
+            source -= sizeofaddr
+            addr = source.address[0]
+            gcref = llmemory.cast_adr_to_ptr(addr, llmemory.GCREF)
+            suspstack.shadowstack_copy[num1] = gcref
+            num1 += 1
+        suspstack.shadowstack_saved = num1
+
+    def restore_suspstack(self, suspstack):
+        target = suspstack.shadowstack_start
+        saved = suspstack.shadowstack_saved
+        suspstack.shadowstack_saved = 0
+        i = 0
+        while i < saved:
+            addr = llmemory.cast_ptr_to_adr(suspstack.shadowstack_copy[i])
+            target -= sizeofaddr
+            target.address[0] = addr
+            i += 1
+
+    def destroy(self, thrd, suspstack):
+        h = suspstack.handle
+        suspstack.handle = _c.null_handle
+        _c.destroy(thrd._thrd, h)
+
+    def is_empty_handle(self, suspstack):
+        return not suspstack
+
+    def get_null_handle(self):
+        return NULL_SUSPSTACK
+
+
+gcrootfinder = StackletGcRootFinder()
diff --git a/pypy/rlib/rstacklet.py b/pypy/rlib/rstacklet.py
--- a/pypy/rlib/rstacklet.py
+++ b/pypy/rlib/rstacklet.py
@@ -17,14 +17,14 @@
             _c.deletethread(thrd)
 
     def new(self, callback, arg=llmemory.NULL):
-        return self._gcrootfinder.new(self._thrd, callback, arg)
+        return self._gcrootfinder.new(self, callback, arg)
     new._annspecialcase_ = 'specialize:arg(1)'
 
     def switch(self, stacklet):
-        return self._gcrootfinder.switch(self._thrd, stacklet)
+        return self._gcrootfinder.switch(self, stacklet)
 
     def destroy(self, stacklet):
-        self._gcrootfinder.destroy(self._thrd, stacklet)
+        self._gcrootfinder.destroy(self, stacklet)
 
     def is_empty_handle(self, stacklet):
         return self._gcrootfinder.is_empty_handle(stacklet)
diff --git a/pypy/rlib/test/test_rstacklet.py b/pypy/rlib/test/test_rstacklet.py
--- a/pypy/rlib/test/test_rstacklet.py
+++ b/pypy/rlib/test/test_rstacklet.py
@@ -1,7 +1,7 @@
 import gc
 from pypy.rlib import rstacklet, rrandom
 from pypy.rlib.rarithmetic import intmask
-from pypy.rpython.lltypesystem import lltype, rffi
+from pypy.rpython.lltypesystem import lltype, llmemory, rffi
 from pypy.translator.c.test.test_standalone import StandaloneTests
 
 
@@ -26,7 +26,7 @@
     @here_is_a_test
     def test_new(self):
         print 'start'
-        h = self.sthread.new(empty_callback, rffi.cast(rffi.VOIDP, 123))
+        h = self.sthread.new(empty_callback, rffi.cast(llmemory.Address, 123))
         print 'end', h
         assert self.sthread.is_empty_handle(h)
 
@@ -40,7 +40,7 @@
     def test_simple_switch(self):
         self.status = 0
         h = self.sthread.new(switchbackonce_callback,
-                             rffi.cast(rffi.VOIDP, 321))
+                             rffi.cast(llmemory.Address, 321))
         assert not self.sthread.is_empty_handle(h)
         self.nextstatus(2)
         h = self.sthread.switch(h)
@@ -110,7 +110,7 @@
                 # start a new stacklet
                 print "NEW", n
                 h = runner.sthread.new(variousstackdepths_callback,
-                                       rffi.cast(rffi.VOIDP, n))
+                                       rffi.cast(llmemory.Address, n))
             else:
                 # switch to this stacklet
                 print "switch to", n
@@ -251,6 +251,10 @@
     gc = 'minimark'
     gcrootfinder = 'asmgcc'
 
+class TestStackletShadowStack(BaseTestStacklet):
+    gc = 'minimark'
+    gcrootfinder = 'shadowstack'
+
 
 def target(*args):
     return entry_point, None
diff --git a/pypy/rpython/memory/gctransform/shadowstack.py b/pypy/rpython/memory/gctransform/shadowstack.py
--- a/pypy/rpython/memory/gctransform/shadowstack.py
+++ b/pypy/rpython/memory/gctransform/shadowstack.py
@@ -64,6 +64,9 @@
         if self.collect_stacks_from_other_threads is not None:
             self.collect_stacks_from_other_threads(collect_stack_root)
 
+    def need_stacklet_support(self):
+        pass     # no special code needed here
+
     def need_thread_support(self, gctransformer, getfn):
         from pypy.module.thread import ll_thread    # xxx fish
         from pypy.rpython.memory.support import AddressDict


More information about the pypy-commit mailing list