PageRenderTime 42ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/rpython/rlib/_stacklet_shadowstack.py

https://bitbucket.org/pypy/pypy/
Python | 184 lines | 139 code | 24 blank | 21 comment | 10 complexity | a23486f6b77b3a44501aced76f555719 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
  1. from rpython.rlib import _rffi_stacklet as _c
  2. from rpython.rlib.debug import ll_assert
  3. from rpython.rlib import rgc
  4. from rpython.rtyper.annlowlevel import llhelper, MixLevelHelperAnnotator
  5. from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
  6. from rpython.rtyper.lltypesystem.lloperation import llop
  7. from rpython.annotator import model as annmodel
  8. from rpython.rtyper.llannotation import lltype_to_annotation
  9. #
  10. # A GC wrapper around the C stacklet handles, with additionally a
  11. # copy of the shadowstack (for all stacklets different than the main)
  12. #
  13. STACKLET = lltype.GcStruct('Stacklet',
  14. ('s_handle', _c.handle),
  15. ('s_sscopy', llmemory.Address),
  16. rtti=True)
  17. STACKLET_PTR = lltype.Ptr(STACKLET)
  18. NULL_STACKLET = lltype.nullptr(STACKLET)
  19. def complete_destrptr(gctransformer):
  20. translator = gctransformer.translator
  21. mixlevelannotator = MixLevelHelperAnnotator(translator.rtyper)
  22. args_s = [lltype_to_annotation(STACKLET_PTR)]
  23. s_result = annmodel.s_None
  24. destrptr = mixlevelannotator.delayedfunction(stacklet_destructor,
  25. args_s, s_result)
  26. mixlevelannotator.finish()
  27. lltype.attachRuntimeTypeInfo(STACKLET, destrptr=destrptr)
  28. # Note: it's important that this is a light finalizer, otherwise
  29. # the GC will call it but still expect the object to stay around for
  30. # a while---and it can't stay around, because s_sscopy points to
  31. # freed nonsense and customtrace() will crash
  32. @rgc.must_be_light_finalizer
  33. def stacklet_destructor(stacklet):
  34. sscopy = stacklet.s_sscopy
  35. if sscopy:
  36. llmemory.raw_free(sscopy)
  37. h = stacklet.s_handle
  38. if h:
  39. _c.destroy(h)
  40. SIZEADDR = llmemory.sizeof(llmemory.Address)
  41. def customtrace(gc, obj, callback, arg):
  42. stacklet = llmemory.cast_adr_to_ptr(obj, STACKLET_PTR)
  43. sscopy = stacklet.s_sscopy
  44. if sscopy:
  45. length_bytes = sscopy.signed[0]
  46. while length_bytes > 0:
  47. addr = sscopy + length_bytes
  48. gc._trace_callback(callback, arg, addr)
  49. length_bytes -= SIZEADDR
  50. lambda_customtrace = lambda: customtrace
  51. def sscopy_detach_shadow_stack():
  52. base = llop.gc_adr_of_root_stack_base(llmemory.Address).address[0]
  53. top = llop.gc_adr_of_root_stack_top(llmemory.Address).address[0]
  54. length_bytes = top - base
  55. result = llmemory.raw_malloc(SIZEADDR + length_bytes)
  56. if result:
  57. result.signed[0] = length_bytes
  58. llmemory.raw_memcopy(base, result + SIZEADDR, length_bytes)
  59. llop.gc_adr_of_root_stack_top(llmemory.Address).address[0] = base
  60. return result
  61. def sscopy_attach_shadow_stack(sscopy):
  62. base = llop.gc_adr_of_root_stack_base(llmemory.Address).address[0]
  63. ll_assert(llop.gc_adr_of_root_stack_top(llmemory.Address).address[0]==base,
  64. "attach_shadow_stack: ss is not empty?")
  65. length_bytes = sscopy.signed[0]
  66. llmemory.raw_memcopy(sscopy + SIZEADDR, base, length_bytes)
  67. llop.gc_adr_of_root_stack_top(llmemory.Address).address[0] = (
  68. base + length_bytes)
  69. llmemory.raw_free(sscopy)
  70. def alloc_stacklet():
  71. new_stacklet = lltype.malloc(STACKLET)
  72. new_stacklet.s_handle = _c.null_handle
  73. new_stacklet.s_sscopy = llmemory.NULL
  74. return new_stacklet
  75. def attach_handle_on_stacklet(stacklet, h):
  76. ll_assert(stacklet.s_handle == _c.null_handle, "attach stacklet 1: garbage")
  77. ll_assert(stacklet.s_sscopy == llmemory.NULL, "attach stacklet 2: garbage")
  78. if not h:
  79. raise MemoryError
  80. elif _c.is_empty_handle(h):
  81. ll_assert(gcrootfinder.sscopy == llmemory.NULL,
  82. "empty_handle but sscopy != NULL")
  83. return NULL_STACKLET
  84. else:
  85. # This is a return that gave us a real handle. Store it.
  86. stacklet.s_handle = h
  87. stacklet.s_sscopy = gcrootfinder.sscopy
  88. ll_assert(gcrootfinder.sscopy != llmemory.NULL,
  89. "!empty_handle but sscopy == NULL")
  90. gcrootfinder.sscopy = llmemory.NULL
  91. llop.gc_writebarrier(lltype.Void, llmemory.cast_ptr_to_adr(stacklet))
  92. return stacklet
  93. def consume_stacklet(stacklet):
  94. h = stacklet.s_handle
  95. ll_assert(bool(h), "consume_stacklet: null handle")
  96. stacklet.s_handle = _c.null_handle
  97. stacklet.s_sscopy = llmemory.NULL
  98. return h
  99. def _new_callback(h, arg):
  100. # There is a fresh stacklet object waiting on the gcrootfinder,
  101. # so populate it with data that represents the parent suspended
  102. # stacklet and detach the stacklet object from gcrootfinder.
  103. stacklet = gcrootfinder.fresh_stacklet
  104. gcrootfinder.fresh_stacklet = NULL_STACKLET
  105. ll_assert(stacklet != NULL_STACKLET, "_new_callback: NULL #1")
  106. stacklet = attach_handle_on_stacklet(stacklet, h)
  107. ll_assert(stacklet != NULL_STACKLET, "_new_callback: NULL #2")
  108. #
  109. # Call the main function provided by the (RPython) user.
  110. stacklet = gcrootfinder.runfn(stacklet, arg)
  111. #
  112. # Here, 'stacklet' points to the target stacklet to which we want
  113. # to jump to next. Read the 'handle' and forget about the
  114. # stacklet object.
  115. gcrootfinder.sscopy = llmemory.NULL
  116. return consume_stacklet(stacklet)
  117. def _new(thread_handle, arg):
  118. # No shadowstack manipulation here (no usage of gc references)
  119. sscopy = sscopy_detach_shadow_stack()
  120. gcrootfinder.sscopy = sscopy
  121. if not sscopy:
  122. return _c.null_handle
  123. h = _c.new(thread_handle, llhelper(_c.run_fn, _new_callback), arg)
  124. sscopy_attach_shadow_stack(sscopy)
  125. return h
  126. _new._dont_inline_ = True
  127. def _switch(h):
  128. # No shadowstack manipulation here (no usage of gc references)
  129. sscopy = sscopy_detach_shadow_stack()
  130. gcrootfinder.sscopy = sscopy
  131. if not sscopy:
  132. return _c.null_handle
  133. h = _c.switch(h)
  134. sscopy_attach_shadow_stack(sscopy)
  135. return h
  136. _switch._dont_inline_ = True
  137. class StackletGcRootFinder(object):
  138. fresh_stacklet = NULL_STACKLET
  139. @staticmethod
  140. def new(thrd, callback, arg):
  141. rgc.register_custom_trace_hook(STACKLET, lambda_customtrace)
  142. result_stacklet = alloc_stacklet()
  143. gcrootfinder.fresh_stacklet = alloc_stacklet()
  144. gcrootfinder.runfn = callback
  145. thread_handle = thrd._thrd
  146. h = _new(thread_handle, arg)
  147. return attach_handle_on_stacklet(result_stacklet, h)
  148. @staticmethod
  149. def switch(stacklet):
  150. # 'stacklet' has a handle to target, i.e. where to switch to
  151. h = consume_stacklet(stacklet)
  152. h = _switch(h)
  153. return attach_handle_on_stacklet(stacklet, h)
  154. @staticmethod
  155. def is_empty_handle(stacklet):
  156. return not stacklet
  157. @staticmethod
  158. def get_null_handle():
  159. return NULL_STACKLET
  160. gcrootfinder = StackletGcRootFinder()