Fix and simplify Uc deallocation
This commit is contained in:
@@ -270,54 +270,6 @@ def debug() -> str:
|
|||||||
return f'python-{all_archs}-c{lib_maj}.{lib_min}-b{bnd_maj}.{bnd_min}'
|
return f'python-{all_archs}-c{lib_maj}.{lib_min}-b{bnd_maj}.{bnd_min}'
|
||||||
|
|
||||||
|
|
||||||
class UcRef(weakref.ref):
|
|
||||||
"""A simple subclass to allow property assignment.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_uch: Optional[uc_engine]
|
|
||||||
_class: Type[Uc]
|
|
||||||
|
|
||||||
|
|
||||||
class UcCleanupManager:
|
|
||||||
"""A utility class to track `Uc` instances and properly release their resources
|
|
||||||
upon destruction.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_refs: MutableMapping[int, UcRef]
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._refs = {}
|
|
||||||
|
|
||||||
|
|
||||||
def register(self, obj: Uc):
|
|
||||||
ref = UcRef(obj, self._finalizer)
|
|
||||||
ref._uch = obj._uch
|
|
||||||
ref._class = obj.__class__
|
|
||||||
|
|
||||||
self._refs[id(ref)] = ref
|
|
||||||
|
|
||||||
|
|
||||||
def _finalizer(self, ref: UcRef) -> None:
|
|
||||||
# note: this method must be completely self-contained and cannot have any references
|
|
||||||
# to anything else in this module.
|
|
||||||
#
|
|
||||||
# This is because it may be called late in the Python interpreter's shutdown phase, at
|
|
||||||
# which point the module's variables may already have been deinitialized and set to None.
|
|
||||||
#
|
|
||||||
# Not respecting that can lead to errors such as:
|
|
||||||
# Exception AttributeError:
|
|
||||||
# "'NoneType' object has no attribute 'release_handle'"
|
|
||||||
# in <bound method UcCleanupManager._finalizer of
|
|
||||||
# <unicorn.unicorn.UcCleanupManager object at 0x7f0bb83e4310>> ignored
|
|
||||||
#
|
|
||||||
# For that reason, we do not try to access the `Uc` class directly here but instead use
|
|
||||||
# the saved `._class` reference.
|
|
||||||
del self._refs[id(ref)]
|
|
||||||
|
|
||||||
if ref._uch is not None:
|
|
||||||
ref._class.release_handle(ref._uch)
|
|
||||||
|
|
||||||
|
|
||||||
def _cast_func(functype: Type[ctypes._FuncPointer], pyfunc: Callable):
|
def _cast_func(functype: Type[ctypes._FuncPointer], pyfunc: Callable):
|
||||||
return ctypes.cast(functype(pyfunc), functype)
|
return ctypes.cast(functype(pyfunc), functype)
|
||||||
|
|
||||||
@@ -423,8 +375,6 @@ class Uc(RegStateManager):
|
|||||||
"""Unicorn Engine class.
|
"""Unicorn Engine class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__cleanup = UcCleanupManager()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __is_compliant() -> bool:
|
def __is_compliant() -> bool:
|
||||||
"""Checks whether Unicorn binding version complies with Unicorn library.
|
"""Checks whether Unicorn binding version complies with Unicorn library.
|
||||||
@@ -507,11 +457,17 @@ class Uc(RegStateManager):
|
|||||||
self._mmio_callbacks: MutableMapping[Tuple[int, int], Tuple[Optional[ctypes._FuncPointer], Optional[ctypes._FuncPointer]]] = {}
|
self._mmio_callbacks: MutableMapping[Tuple[int, int], Tuple[Optional[ctypes._FuncPointer], Optional[ctypes._FuncPointer]]] = {}
|
||||||
|
|
||||||
self._hook_exception: Optional[Exception] = None
|
self._hook_exception: Optional[Exception] = None
|
||||||
Uc.__cleanup.register(self)
|
|
||||||
|
# create a finalizer object that will apropriately free up resources when
|
||||||
|
# this instance undergoes garbage collection.
|
||||||
|
self.__finalizer = weakref.finalize(self, Uc.release_handle, self._uch)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def release_handle(uch: uc_engine) -> None:
|
def release_handle(uch: uc_engine) -> None:
|
||||||
|
# this method and its arguments must not have any reference to the Uc instance being
|
||||||
|
# destroyed. namely, this method cannot be a bound method.
|
||||||
|
|
||||||
if uch:
|
if uch:
|
||||||
try:
|
try:
|
||||||
status = uclib.uc_close(uch)
|
status = uclib.uc_close(uch)
|
||||||
|
|||||||
Reference in New Issue
Block a user