From 160649575e59a63f9fa21fbdba4d6183871053de Mon Sep 17 00:00:00 2001 From: Antelox Date: Wed, 23 Oct 2024 11:02:15 +0200 Subject: [PATCH] Python binding: - Added missing `license` field in pyproject.toml file - Fixed editable mode install and some more code cleanup in setup.py - Refreshed README.md - Replaced f-string formatter in tests with `format` method in order to be py2-compatible - Fixed typos - PEP8 fixes --- bindings/python/README.md | 17 +-- bindings/python/pyproject.toml | 1 + bindings/python/setup.py | 30 +--- bindings/python/tests/test_arm64.py | 5 +- bindings/python/tests/test_ctl.py | 26 +++- .../python/tests/test_network_auditing.py | 2 +- bindings/python/tests/test_x86.py | 9 +- bindings/python/unicorn/unicorn_py2.py | 129 +++++++++++------- .../python/unicorn/unicorn_py3/unicorn.py | 26 ++-- 9 files changed, 131 insertions(+), 114 deletions(-) diff --git a/bindings/python/README.md b/bindings/python/README.md index 21c84432d7..11e6e5b6a8 100644 --- a/bindings/python/README.md +++ b/bindings/python/README.md @@ -25,27 +25,21 @@ Originally written by Nguyen Anh Quynh, polished and redesigned by elicn, mainta Install a prebuilt wheel from PyPI: ```bash -pip3 install unicorn +python3 -m pip install unicorn ``` In case you would like to develop the bindings: ```bash -# Python3 -DEBUG=1 THREADS=4 pip3 install --user -e . +DEBUG=1 THREADS=4 python3 -m pip install --user -e . # Workaround for Pylance -DEBUG=1 THREADS=4 pip3 install --user -e . --config-settings editable_mode=strict -# Python2 -DEBUG=1 THREADS=4 pip install -e . +DEBUG=1 THREADS=4 python3 -m pip install --user -e . --config-settings editable_mode=strict ``` or install it by building it by yourself: ```bash -# Python3 -THREADS=4 pip3 install --user . -# Python2, unfortunately `pip2` doesn't support in-tree build -THREADS=4 python3 setup.py install +THREADS=4 python3 -m pip install --user . ``` Explanations for arguments: @@ -59,4 +53,5 @@ Note that you should setup a valid building environment according to docs/COMPIL ## Python2 compatibility -By default, Unicorn python bindings will be maintained against Python3 as it offers more powerful features which improves developing efficiency. Meanwhile, Unicorn will only keep compatible with all features Unicorn1 offers regarding Python2 because Python2 has reached end-of-life for more than 3 years as the time of writing this README. While offering all features for both Python2 & Python3 is desirable and doable, it inevitably costs too much efforts to maintain and few users really rely on this. Therefore, we assume that if users still stick to Python2, previous Unicorn1 features we offer should be enough. If you really want some new features Unicorn2 offers, please check and pull request to `unicorn/unicorn_py2``. We are happy to review and accept! \ No newline at end of file +By default, Unicorn python bindings works with Python3.7 and above, as it offers more powerful features which improves developing efficiency compared to Python2. However, Unicorn will only keep compatible with all features Unicorn1 offers regarding Python2 because it has reached end-of-life for more than 3 years at the time of writing this README. While offering all features for both Python2 & Python3 is desirable and doable, it inevitably costs too much efforts to maintain and few users really rely on this. Therefore, we assume that if users still stick to Python2, previous Unicorn1 features should be enough. If you really want some new features Unicorn2 offers, please check and pull request to `unicorn/unicorn_py2`. We are happy to review and accept! +Even though the build of wheel packages requires Python3, it's still possible to re-tag the wheel produced from Python3 with `py2` tag and then run `python2 -m pip install `. For detailed commands please refer to our workflow files. diff --git a/bindings/python/pyproject.toml b/bindings/python/pyproject.toml index 6857ca1cbf..8da778ab10 100644 --- a/bindings/python/pyproject.toml +++ b/bindings/python/pyproject.toml @@ -12,6 +12,7 @@ authors = [ description = "Unicorn CPU emulator engine" readme = "README.md" keywords = ["emulation", "qemu", "unicorn"] +license = { text = "BSD License" } classifiers = [ 'License :: OSI Approved :: BSD License', 'Programming Language :: Python :: 2.7', diff --git a/bindings/python/setup.py b/bindings/python/setup.py index 270f215232..b031f69e3f 100755 --- a/bindings/python/setup.py +++ b/bindings/python/setup.py @@ -8,9 +8,8 @@ import subprocess import sys from setuptools import setup -from setuptools.command.build import build +from setuptools.command.build_py import build_py from setuptools.command.sdist import sdist -from setuptools.command.bdist_egg import bdist_egg log = logging.getLogger(__name__) @@ -134,7 +133,7 @@ def run(self): return super().run() -class CustomBuild(build): +class CustomBuild(build_py): def run(self): if 'LIBUNICORN_PATH' in os.environ: log.info("Skipping building C extensions since LIBUNICORN_PATH is set") @@ -144,30 +143,7 @@ def run(self): return super().run() -class CustomBDistEgg(bdist_egg): - def run(self): - self.run_command('build') - return super().run() - - -cmdclass = {'build': CustomBuild, 'sdist': CustomSDist, 'bdist_egg': CustomBDistEgg} - -try: - from setuptools.command.develop import develop - - - class CustomDevelop(develop): - def run(self): - log.info("Building C extensions") - build_libraries() - return super().run() - - - cmdclass['develop'] = CustomDevelop -except ImportError: - print("Proper 'develop' support unavailable.") - setup( - cmdclass=cmdclass, + cmdclass={'build_py': CustomBuild, 'sdist': CustomSDist}, has_ext_modules=lambda: True, # It's not a Pure Python wheel ) diff --git a/bindings/python/tests/test_arm64.py b/bindings/python/tests/test_arm64.py index d615f894a9..c56f5e4269 100755 --- a/bindings/python/tests/test_arm64.py +++ b/bindings/python/tests/test_arm64.py @@ -87,7 +87,8 @@ def test_arm64_read_sctlr(): def test_arm64_hook_mrs(): def _hook_mrs(uc, reg, cp_reg, _): - print(f">>> Hook MRS instruction: reg = 0x{reg:x}(UC_ARM64_REG_X2) cp_reg = {cp_reg}") + print(">>> Hook MRS instruction: reg = {reg}(UC_ARM64_REG_X2) cp_reg = {cp_reg}".format(reg=hex(reg), + cp_reg=cp_reg)) uc.reg_write(reg, 0x114514) print(">>> Write 0x114514 to X") @@ -111,7 +112,7 @@ def _hook_mrs(uc, reg, cp_reg, _): # Start emulation mu.emu_start(0x1000, 0x1000 + len(ARM64_MRS_CODE)) - print(f">>> X2 = {mu.reg_read(UC_ARM64_REG_X2):x}") + print(">>> X2 = {reg}".format(reg=hex(mu.reg_read(UC_ARM64_REG_X2)))) except UcError as e: print("ERROR: %s" % e) diff --git a/bindings/python/tests/test_ctl.py b/bindings/python/tests/test_ctl.py index 5a00340c1f..a7cae70b78 100755 --- a/bindings/python/tests/test_ctl.py +++ b/bindings/python/tests/test_ctl.py @@ -2,6 +2,8 @@ # Sample code for Unicorn. # By Lazymio(@wtdcode), 2021 +import pytest +import sys from unicorn import * from unicorn.x86_const import * from datetime import datetime @@ -20,7 +22,9 @@ def test_uc_ctl_read(): timeout = uc.ctl_get_timeout() - print(f">>> arch={arch} mode={mode} page size={page_size} timeout={timeout}") + print(">>> arch={arch} mode={mode} page size={page_size} timeout={timeout}".format(arch=arch, mode=mode, + page_size=page_size, + timeout=timeout)) def time_emulation(uc, start, end): @@ -31,6 +35,8 @@ def time_emulation(uc, start, end): return (datetime.now() - n).total_seconds() * 1e6 +# TODO: Check if worth adapting the ctl_request_cache method for py2 binding +@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.6 or higher") def test_uc_ctl_tb_cache(): # Initialize emulator in X86-32bit mode uc = Uc(UC_ARCH_X86, UC_MODE_32) @@ -52,7 +58,8 @@ def test_uc_ctl_tb_cache(): # Now we request cache for all TBs. for i in range(8): tb = uc.ctl_request_cache(addr + i * 512) - print(f">>> TB is cached at {hex(tb[0])} which has {tb[1]} instructions with {tb[2]} bytes") + print(">>> TB is cached at {0} which has {1} instructions with {2} bytes".format(hex(tb[0]), hex(tb[1]), + hex(tb[2]))) # Do emulation with all TB cached. cached = time_emulation(uc, addr, addr + len(code)) @@ -63,17 +70,22 @@ def test_uc_ctl_tb_cache(): evicted = time_emulation(uc, addr, addr + len(code)) - print(f">>> Run time: First time {standard}, Cached: {cached}, Cached evicted: {evicted}") + print(">>> Run time: First time {standard}, Cached: {cached}, Cached evicted: {evicted}".format(standard=standard, + cached=cached, + evicted=evicted)) def trace_new_edge(uc, cur, prev, data): - print(f">>> Getting a new edge from {hex(prev.pc + prev.size - 1)} to {hex(cur.pc)}") + print(">>> Getting a new edge from {0} to {1}".format(hex(prev.pc + prev.size - 1), hex(cur.pc))) def trace_tcg_sub(uc, address, arg1, arg2, size, data): - print(f">>> Get a tcg sub opcode at {hex(address)} with args: {arg1} and {arg2}") + print(">>> Get a tcg sub opcode at {address} with args: {arg1} and {arg2}".format(address=hex(address), arg1=arg1, + arg2=arg2)) +# TODO: Check if worth adapting the hook_add method for py2 binding +@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.6 or higher") def test_uc_ctl_exits(): uc = Uc(UC_ARCH_X86, UC_MODE_32) addr = 0x1000 @@ -110,7 +122,7 @@ def test_uc_ctl_exits(): eax = uc.reg_read(UC_X86_REG_EAX) ebx = uc.reg_read(UC_X86_REG_EBX) - print(f">>> eax = {hex(eax)} and ebx = {hex(ebx)} after the first emulation") + print(">>> eax = {0} and ebx = {1} after the first emulation".format(hex(eax), hex(ebx))) # This should stop at ADDRESS + 8, even though we don't provide an exit. uc.emu_start(addr, 0) @@ -118,7 +130,7 @@ def test_uc_ctl_exits(): eax = uc.reg_read(UC_X86_REG_EAX) ebx = uc.reg_read(UC_X86_REG_EBX) - print(f">>> eax = {hex(eax)} and ebx = {hex(ebx)} after the first emulation") + print(">>> eax = {0} and ebx = {1} after the first emulation".format(hex(eax), hex(ebx))) if __name__ == "__main__": diff --git a/bindings/python/tests/test_network_auditing.py b/bindings/python/tests/test_network_auditing.py index 1d0c7a7875..145e4f6ed4 100755 --- a/bindings/python/tests/test_network_auditing.py +++ b/bindings/python/tests/test_network_auditing.py @@ -356,7 +356,7 @@ def hook_intr(uc, intno, user_data): fd = args[0] how = args[1] - msg = "fd(%d) is shutted down because of %d" % (fd, how) + msg = "fd(%d) is shut down because of %d" % (fd, how) fd_chains.add_log(fd, msg) print_sockcall(msg) diff --git a/bindings/python/tests/test_x86.py b/bindings/python/tests/test_x86.py index f8edce06b0..3f1539b665 100755 --- a/bindings/python/tests/test_x86.py +++ b/bindings/python/tests/test_x86.py @@ -639,13 +639,16 @@ def test_x86_16(): def mmio_read_cb(uc, offset, size, data): - print(f">>> Read IO memory at offset {hex(offset)} with {hex(size)} bytes and return 0x19260817") + print(">>> Read IO memory at offset {offset} with {size} bytes and return 0x19260817".format(offset=hex(offset), + size=hex(size))) return 0x19260817 def mmio_write_cb(uc, offset, size, value, data): - print(f">>> Write value {hex(value)} to IO memory at offset {hex(offset)} with {hex(size)} bytes") + print(">>> Write value {value} to IO memory at offset {offset} with {size} bytes".format(value=hex(value), + offset=hex(offset), + size=hex(size))) def test_i386_mmio(): @@ -668,7 +671,7 @@ def test_i386_mmio(): mu.emu_start(0x10000, 0x10000 + len(X86_MMIO_CODE)) # now print out some registers - print(f">>> Emulation done. ECX={hex(mu.reg_read(UC_X86_REG_ECX))}") + print(">>> Emulation done. ECX={reg}".format(reg=hex(mu.reg_read(UC_X86_REG_ECX)))) except UcError as e: print("ERROR: %s" % e) diff --git a/bindings/python/unicorn/unicorn_py2.py b/bindings/python/unicorn/unicorn_py2.py index cc5450f0f2..e93ca96265 100644 --- a/bindings/python/unicorn/unicorn_py2.py +++ b/bindings/python/unicorn/unicorn_py2.py @@ -3,17 +3,16 @@ import ctypes import ctypes.util import distutils.sysconfig -from functools import wraps -import pkg_resources import inspect import os.path +import pkg_resources import sys import weakref -import functools +from functools import wraps, partial from collections import namedtuple -# We can't place this file in a separate folder due to Python2 limitations but -# anyway we just maintain it with minimum efforts and it has been more than 3 +# We can't place this file in a separate folder due to Python2 limitations, but +# anyway we just maintain it with minimum efforts, and it has been more than 3 # years since EOL of Python2 so it should be fine. from . import x86_const, arm_const, arm64_const, unicorn_const as uc @@ -23,16 +22,11 @@ if not hasattr(sys.modules[__name__], "__file__"): __file__ = inspect.getfile(inspect.currentframe()) -_python2 = sys.version_info[0] < 3 -if _python2: - range = xrange - -_lib = { 'darwin': 'libunicorn.2.dylib', - 'win32': 'unicorn.dll', - 'cygwin': 'cygunicorn.dll', - 'linux': 'libunicorn.so.2', - 'linux2': 'libunicorn.so.2' } - +_lib = {'darwin': 'libunicorn.2.dylib', + 'win32': 'unicorn.dll', + 'cygwin': 'cygunicorn.dll', + 'linux': 'libunicorn.so.2', + 'linux2': 'libunicorn.so.2'} # Windows DLL in dependency order _all_windows_dlls = ( @@ -43,6 +37,7 @@ _loaded_windows_dlls = set() + def _load_win_support(path): for dll in _all_windows_dlls: if dll in _loaded_windows_dlls: @@ -51,18 +46,20 @@ def _load_win_support(path): lib_file = os.path.join(path, dll) if ('/' not in path and '\\' not in path) or os.path.exists(lib_file): try: - #print('Trying to load Windows library', lib_file) + # print('Trying to load Windows library', lib_file) ctypes.cdll.LoadLibrary(lib_file) - #print('SUCCESS') + # print('SUCCESS') _loaded_windows_dlls.add(dll) except OSError as e: - #print('FAIL to load %s' %lib_file, e) + # print('FAIL to load %s' %lib_file, e) continue + # Initial attempt: load all dlls globally if sys.platform in ('win32', 'cygwin'): _load_win_support('') + def _load_lib(path): try: if sys.platform in ('win32', 'cygwin'): @@ -70,12 +67,13 @@ def _load_lib(path): lib_file = os.path.join(path, _lib.get(sys.platform, 'libunicorn.so.2')) dll = ctypes.cdll.LoadLibrary(lib_file) - #print('SUCCESS') + # print('SUCCESS') return dll except OSError as e: - #print('FAIL to load %s' %lib_file, e) + # print('FAIL to load %s' %lib_file, e) return None + _uc = None # Loading attempts, in order @@ -95,7 +93,7 @@ def _load_lib(path): os.getenv('PATH', '')] # print(_path_list) -#print("-" * 80) +# print("-" * 80) for _path in _path_list: if _path is None: continue @@ -104,6 +102,7 @@ def _load_lib(path): else: raise ImportError("ERROR: fail to load the dynamic library.") + # __version__ = "%u.%u.%u" % (uc.UC_VERSION_MAJOR, uc.UC_VERSION_MINOR, uc.UC_VERSION_EXTRA) # setup all the function prototype @@ -112,7 +111,9 @@ def _setup_prototype(lib, fname, restype, *argtypes): getattr(lib, fname).restype = restype getattr(lib, fname).argtypes = argtypes except AttributeError: - raise ImportError("ERROR: Fail to setup some function prototypes. Make sure you have cleaned your unicorn1 installation.") + raise ImportError( + "ERROR: Fail to setup some function prototypes. Make sure you have cleaned your unicorn1 installation.") + ucerr = ctypes.c_int uc_mode = ctypes.c_int @@ -121,13 +122,15 @@ def _setup_prototype(lib, fname, restype, *argtypes): uc_context = ctypes.c_void_p uc_hook_h = ctypes.c_size_t + class _uc_mem_region(ctypes.Structure): _fields_ = [ ("begin", ctypes.c_uint64), - ("end", ctypes.c_uint64), + ("end", ctypes.c_uint64), ("perms", ctypes.c_uint32), ] + class uc_tb(ctypes.Structure): """"TranslationBlock""" _fields_ = [ @@ -136,6 +139,7 @@ class uc_tb(ctypes.Structure): ("size", ctypes.c_uint16) ] + _setup_prototype(_uc, "uc_version", ctypes.c_uint, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)) _setup_prototype(_uc, "uc_arch_supported", ctypes.c_bool, ctypes.c_int) _setup_prototype(_uc, "uc_open", ucerr, ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(uc_engine)) @@ -146,12 +150,15 @@ class uc_tb(ctypes.Structure): _setup_prototype(_uc, "uc_reg_write", ucerr, uc_engine, ctypes.c_int, ctypes.c_void_p) _setup_prototype(_uc, "uc_mem_read", ucerr, uc_engine, ctypes.c_uint64, ctypes.POINTER(ctypes.c_char), ctypes.c_size_t) _setup_prototype(_uc, "uc_mem_write", ucerr, uc_engine, ctypes.c_uint64, ctypes.POINTER(ctypes.c_char), ctypes.c_size_t) -_setup_prototype(_uc, "uc_emu_start", ucerr, uc_engine, ctypes.c_uint64, ctypes.c_uint64, ctypes.c_uint64, ctypes.c_size_t) +_setup_prototype(_uc, "uc_emu_start", ucerr, uc_engine, ctypes.c_uint64, ctypes.c_uint64, ctypes.c_uint64, + ctypes.c_size_t) _setup_prototype(_uc, "uc_emu_stop", ucerr, uc_engine) _setup_prototype(_uc, "uc_hook_del", ucerr, uc_engine, uc_hook_h) -_setup_prototype(_uc, "uc_mmio_map", ucerr, uc_engine, ctypes.c_uint64, ctypes.c_size_t, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p) +_setup_prototype(_uc, "uc_mmio_map", ucerr, uc_engine, ctypes.c_uint64, ctypes.c_size_t, ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p) _setup_prototype(_uc, "uc_mem_map", ucerr, uc_engine, ctypes.c_uint64, ctypes.c_size_t, ctypes.c_uint32) -_setup_prototype(_uc, "uc_mem_map_ptr", ucerr, uc_engine, ctypes.c_uint64, ctypes.c_size_t, ctypes.c_uint32, ctypes.c_void_p) +_setup_prototype(_uc, "uc_mem_map_ptr", ucerr, uc_engine, ctypes.c_uint64, ctypes.c_size_t, ctypes.c_uint32, + ctypes.c_void_p) _setup_prototype(_uc, "uc_mem_unmap", ucerr, uc_engine, ctypes.c_uint64, ctypes.c_size_t) _setup_prototype(_uc, "uc_mem_protect", ucerr, uc_engine, ctypes.c_uint64, ctypes.c_size_t, ctypes.c_uint32) _setup_prototype(_uc, "uc_query", ucerr, uc_engine, ctypes.c_uint32, ctypes.POINTER(ctypes.c_size_t)) @@ -163,9 +170,11 @@ class uc_tb(ctypes.Structure): _setup_prototype(_uc, "uc_context_reg_read", ucerr, uc_context, ctypes.c_int, ctypes.c_void_p) _setup_prototype(_uc, "uc_context_reg_write", ucerr, uc_context, ctypes.c_int, ctypes.c_void_p) _setup_prototype(_uc, "uc_context_free", ucerr, uc_context) -_setup_prototype(_uc, "uc_mem_regions", ucerr, uc_engine, ctypes.POINTER(ctypes.POINTER(_uc_mem_region)), ctypes.POINTER(ctypes.c_uint32)) +_setup_prototype(_uc, "uc_mem_regions", ucerr, uc_engine, ctypes.POINTER(ctypes.POINTER(_uc_mem_region)), + ctypes.POINTER(ctypes.c_uint32)) # https://bugs.python.org/issue42880 -_setup_prototype(_uc, "uc_hook_add", ucerr, uc_engine, ctypes.POINTER(uc_hook_h), ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint64, ctypes.c_uint64) +_setup_prototype(_uc, "uc_hook_add", ucerr, uc_engine, ctypes.POINTER(uc_hook_h), ctypes.c_int, ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_uint64, ctypes.c_uint64) _setup_prototype(_uc, "uc_ctl", ucerr, uc_engine, ctypes.c_int) UC_HOOK_CODE_CB = ctypes.CFUNCTYPE(None, uc_engine, ctypes.c_uint64, ctypes.c_size_t, ctypes.c_void_p) @@ -203,6 +212,7 @@ class uc_tb(ctypes.Structure): None, uc_engine, ctypes.c_uint64, ctypes.c_uint64, ctypes.c_uint64, ctypes.c_void_p ) + # access to error code via @errno of UcError class UcError(Exception): def __init__(self, errno): @@ -232,28 +242,30 @@ def version_bind(): def uc_arch_supported(query): return _uc.uc_arch_supported(query) + # uc_reg_read/write and uc_context_reg_read/write. def reg_read(reg_read_func, arch, reg_id, opt=None): if arch == uc.UC_ARCH_X86: - if reg_id in [x86_const.UC_X86_REG_IDTR, x86_const.UC_X86_REG_GDTR, x86_const.UC_X86_REG_LDTR, x86_const.UC_X86_REG_TR]: + if reg_id in [x86_const.UC_X86_REG_IDTR, x86_const.UC_X86_REG_GDTR, x86_const.UC_X86_REG_LDTR, + x86_const.UC_X86_REG_TR]: reg = uc_x86_mmr() status = reg_read_func(reg_id, ctypes.byref(reg)) if status != uc.UC_ERR_OK: raise UcError(status) return reg.selector, reg.base, reg.limit, reg.flags - if reg_id in range(x86_const.UC_X86_REG_FP0, x86_const.UC_X86_REG_FP0+8): + if reg_id in xrange(x86_const.UC_X86_REG_FP0, x86_const.UC_X86_REG_FP0 + 8): reg = uc_x86_float80() status = reg_read_func(reg_id, ctypes.byref(reg)) if status != uc.UC_ERR_OK: raise UcError(status) return reg.mantissa, reg.exponent - if reg_id in range(x86_const.UC_X86_REG_XMM0, x86_const.UC_X86_REG_XMM0+8): + if reg_id in xrange(x86_const.UC_X86_REG_XMM0, x86_const.UC_X86_REG_XMM0 + 8): reg = uc_x86_xmm() status = reg_read_func(reg_id, ctypes.byref(reg)) if status != uc.UC_ERR_OK: raise UcError(status) return reg.low_qword | (reg.high_qword << 64) - if reg_id in range(x86_const.UC_X86_REG_YMM0, x86_const.UC_X86_REG_YMM0+16): + if reg_id in xrange(x86_const.UC_X86_REG_YMM0, x86_const.UC_X86_REG_YMM0 + 16): reg = uc_x86_ymm() status = reg_read_func(reg_id, ctypes.byref(reg)) if status != uc.UC_ERR_OK: @@ -291,7 +303,8 @@ def reg_read(reg_read_func, arch, reg_id, opt=None): raise UcError(status) return reg.val - elif reg_id in range(arm64_const.UC_ARM64_REG_Q0, arm64_const.UC_ARM64_REG_Q31+1) or range(arm64_const.UC_ARM64_REG_V0, arm64_const.UC_ARM64_REG_V31+1): + elif reg_id in xrange(arm64_const.UC_ARM64_REG_Q0, arm64_const.UC_ARM64_REG_Q31 + 1) or xrange( + arm64_const.UC_ARM64_REG_V0, arm64_const.UC_ARM64_REG_V31 + 1): reg = uc_arm64_neon128() status = reg_read_func(reg_id, ctypes.byref(reg)) if status != uc.UC_ERR_OK: @@ -305,26 +318,28 @@ def reg_read(reg_read_func, arch, reg_id, opt=None): raise UcError(status) return reg.value + def reg_write(reg_write_func, arch, reg_id, value): reg = None if arch == uc.UC_ARCH_X86: - if reg_id in [x86_const.UC_X86_REG_IDTR, x86_const.UC_X86_REG_GDTR, x86_const.UC_X86_REG_LDTR, x86_const.UC_X86_REG_TR]: + if reg_id in [x86_const.UC_X86_REG_IDTR, x86_const.UC_X86_REG_GDTR, x86_const.UC_X86_REG_LDTR, + x86_const.UC_X86_REG_TR]: assert isinstance(value, tuple) and len(value) == 4 reg = uc_x86_mmr() reg.selector = value[0] reg.base = value[1] reg.limit = value[2] reg.flags = value[3] - if reg_id in range(x86_const.UC_X86_REG_FP0, x86_const.UC_X86_REG_FP0+8): + if reg_id in xrange(x86_const.UC_X86_REG_FP0, x86_const.UC_X86_REG_FP0 + 8): reg = uc_x86_float80() reg.mantissa = value[0] reg.exponent = value[1] - if reg_id in range(x86_const.UC_X86_REG_XMM0, x86_const.UC_X86_REG_XMM0+8): + if reg_id in xrange(x86_const.UC_X86_REG_XMM0, x86_const.UC_X86_REG_XMM0 + 8): reg = uc_x86_xmm() reg.low_qword = value & 0xffffffffffffffff reg.high_qword = value >> 64 - if reg_id in range(x86_const.UC_X86_REG_YMM0, x86_const.UC_X86_REG_YMM0+16): + if reg_id in xrange(x86_const.UC_X86_REG_YMM0, x86_const.UC_X86_REG_YMM0 + 16): reg = uc_x86_ymm() reg.first_qword = value & 0xffffffffffffffff reg.second_qword = (value >> 64) & 0xffffffffffffffff @@ -336,7 +351,8 @@ def reg_write(reg_write_func, arch, reg_id, value): reg.value = value[1] if arch == uc.UC_ARCH_ARM64: - if reg_id in range(arm64_const.UC_ARM64_REG_Q0, arm64_const.UC_ARM64_REG_Q31+1) or range(arm64_const.UC_ARM64_REG_V0, arm64_const.UC_ARM64_REG_V31+1): + if reg_id in xrange(arm64_const.UC_ARM64_REG_Q0, arm64_const.UC_ARM64_REG_Q31 + 1) or xrange( + arm64_const.UC_ARM64_REG_V0, arm64_const.UC_ARM64_REG_V31 + 1): reg = uc_arm64_neon128() reg.low_qword = value & 0xffffffffffffffff reg.high_qword = value >> 64 @@ -364,6 +380,7 @@ def reg_write(reg_write_func, arch, reg_id, value): return + def _catch_hook_exception(func): @wraps(func) def wrapper(self, *args, **kwargs): @@ -396,6 +413,7 @@ class uc_arm_cp_reg(ctypes.Structure): ("val", ctypes.c_uint64) ] + class uc_arm64_cp_reg(ctypes.Structure): """ARM64 coprocessors registers for instructions MRS, MSR""" _fields_ = [ @@ -407,21 +425,24 @@ class uc_arm64_cp_reg(ctypes.Structure): ("val", ctypes.c_uint64) ] + class uc_x86_mmr(ctypes.Structure): """Memory-Management Register for instructions IDTR, GDTR, LDTR, TR.""" _fields_ = [ ("selector", ctypes.c_uint16), # not used by GDTR and IDTR - ("base", ctypes.c_uint64), # handle 32 or 64 bit CPUs + ("base", ctypes.c_uint64), # handle 32 or 64 bit CPUs ("limit", ctypes.c_uint32), - ("flags", ctypes.c_uint32), # not used by GDTR and IDTR + ("flags", ctypes.c_uint32), # not used by GDTR and IDTR ] + class uc_x86_msr(ctypes.Structure): _fields_ = [ ("rid", ctypes.c_uint32), ("value", ctypes.c_uint64), ] + class uc_x86_float80(ctypes.Structure): """Float80""" _fields_ = [ @@ -437,6 +458,7 @@ class uc_x86_xmm(ctypes.Structure): ("high_qword", ctypes.c_uint64), ] + class uc_x86_ymm(ctypes.Structure): """256-bit ymm register""" _fields_ = [ @@ -446,6 +468,7 @@ class uc_x86_ymm(ctypes.Structure): ("fourth_qword", ctypes.c_uint64), ] + class uc_arm64_neon128(ctypes.Structure): """128-bit neon register""" _fields_ = [ @@ -453,10 +476,12 @@ class uc_arm64_neon128(ctypes.Structure): ("high_qword", ctypes.c_uint64), ] + # Subclassing ref to allow property assignment. class UcRef(weakref.ref): pass + # This class tracks Uc instance destruction and releases handles. class UcCleanupManager(object): def __init__(self): @@ -486,6 +511,7 @@ def _finalizer(self, ref): del self._refs[id(ref)] ref._class.release_handle(ref._uch) + class Uc(object): _cleanup = UcCleanupManager() @@ -540,11 +566,11 @@ def emu_stop(self): # return the value of a register def reg_read(self, reg_id, opt=None): - return reg_read(functools.partial(_uc.uc_reg_read, self._uch), self._arch, reg_id, opt) + return reg_read(partial(_uc.uc_reg_read, self._uch), self._arch, reg_id, opt) # write to a register def reg_write(self, reg_id, value): - return reg_write(functools.partial(_uc.uc_reg_write, self._uch), self._arch, reg_id, value) + return reg_write(partial(_uc.uc_reg_write, self._uch), self._arch, reg_id, value) # read from MSR - X86 only def msr_read(self, msr_id): @@ -681,7 +707,8 @@ def _hook_insn_sys_cb(self, handle, reg, pcp_reg, user_data): (cb, data) = self._callbacks[user_data] - return cb(self, reg, uc_arm64_cp_reg_tuple(cp_reg.crn, cp_reg.crm, cp_reg.op0, cp_reg.op1, cp_reg.op2, cp_reg.val), data) + return cb(self, reg, + uc_arm64_cp_reg_tuple(cp_reg.crn, cp_reg.crm, cp_reg.op0, cp_reg.op1, cp_reg.op2, cp_reg.val), data) @_catch_hook_exception def _hook_insn_out_cb(self, handle, port, size, value, user_data): @@ -711,7 +738,7 @@ def __ctl_w(self, ctl, nr): return self.__ctl(ctl, nr, uc.UC_CTL_IO_WRITE) def __ctl_rw(self, ctl, nr): - return self.__ctl(ctl, nr, uc.UC_CTL_IO_READ_WRITE) + return self.__ctl(ctl, nr, uc.UC_CTL_IO_READ_WRITE) def __ctl_r_1_arg(self, ctl, ctp): arg = ctp() @@ -795,7 +822,8 @@ def hook_add(self, htype, callback, user_data=None, begin=1, end=0, arg1=0, arg2 cb = ctypes.cast(UC_HOOK_INSN_OUT_CB(self._hook_insn_out_cb), UC_HOOK_INSN_OUT_CB) if arg1 in (x86_const.UC_X86_INS_SYSCALL, x86_const.UC_X86_INS_SYSENTER): # SYSCALL/SYSENTER instruction cb = ctypes.cast(UC_HOOK_INSN_SYSCALL_CB(self._hook_insn_syscall_cb), UC_HOOK_INSN_SYSCALL_CB) - if arg1 in (arm64_const.UC_ARM64_INS_MRS, arm64_const.UC_ARM64_INS_MSR, arm64_const.UC_ARM64_INS_SYS, arm64_const.UC_ARM64_INS_SYSL): + if arg1 in (arm64_const.UC_ARM64_INS_MRS, arm64_const.UC_ARM64_INS_MSR, arm64_const.UC_ARM64_INS_SYS, + arm64_const.UC_ARM64_INS_SYSL): cb = ctypes.cast(UC_HOOK_INSN_SYS_CB(self._hook_insn_sys_cb), UC_HOOK_INSN_SYS_CB) status = _uc.uc_hook_add( self._uch, ctypes.byref(_h2), htype, cb, @@ -807,7 +835,8 @@ def hook_add(self, htype, callback, user_data=None, begin=1, end=0, arg1=0, arg2 flags = ctypes.c_int(arg2) status = _uc.uc_hook_add( - self._uch, ctypes.byref(_h2), htype, ctypes.cast(UC_HOOK_TCG_OPCODE_CB(self._hook_tcg_op_cb), UC_HOOK_TCG_OPCODE_CB), + self._uch, ctypes.byref(_h2), htype, + ctypes.cast(UC_HOOK_TCG_OPCODE_CB(self._hook_tcg_op_cb), UC_HOOK_TCG_OPCODE_CB), ctypes.cast(self._callback_count, ctypes.c_void_p), ctypes.c_uint64(begin), ctypes.c_uint64(end), opcode, flags ) @@ -905,7 +934,7 @@ def mem_regions(self): raise UcError(status) try: - for i in range(count.value): + for i in xrange(count.value): yield (regions[i].begin, regions[i].end, regions[i].perms) finally: _uc.uc_free(regions) @@ -940,11 +969,11 @@ def mode(self): # return the value of a register def reg_read(self, reg_id, opt=None): - return reg_read(functools.partial(_uc.uc_context_reg_read, self._context), self.arch, reg_id, opt) + return reg_read(partial(_uc.uc_context_reg_read, self._context), self.arch, reg_id, opt) # write to a register def reg_write(self, reg_id, value): - return reg_write(functools.partial(_uc.uc_context_reg_write, self._context), self.arch, reg_id, value) + return reg_write(partial(_uc.uc_context_reg_write, self._context), self.arch, reg_id, value) # Make UcContext picklable def __getstate__(self): @@ -990,4 +1019,4 @@ def debug(): return "python-%s-c%u.%u-b%u.%u" % ( all_archs, major, minor, uc.UC_API_MAJOR, uc.UC_API_MINOR - ) \ No newline at end of file + ) diff --git a/bindings/python/unicorn/unicorn_py3/unicorn.py b/bindings/python/unicorn/unicorn_py3/unicorn.py index 9b5f8e0cb5..3e6f20ba9e 100644 --- a/bindings/python/unicorn/unicorn_py3/unicorn.py +++ b/bindings/python/unicorn/unicorn_py3/unicorn.py @@ -283,7 +283,7 @@ def __str__(self) -> str: def uc_version() -> Tuple[int, int, int]: """Retrieve Unicorn library version. - Returns: a tuple containing major, minor and a combined verion number + Returns: a tuple containing major, minor and a combined version number """ major = ctypes.c_int() @@ -300,7 +300,7 @@ def uc_version() -> Tuple[int, int, int]: def version_bind() -> Tuple[int, int, int]: """Retrieve Unicorn bindings version. - Returns: a tuple containing major, minor and a combined verion number + Returns: a tuple containing major, minor and a combined version number """ major = uc.UC_API_MAJOR @@ -319,7 +319,7 @@ def uc_arch_supported(atype: int) -> bool: def debug() -> str: - """Get verbose verion string. + """Get verbose version string. """ archs = ( @@ -559,7 +559,7 @@ def __seq_tuple(elem: Union[int, Tuple[int, Any]]) -> Tuple[int, Type, Any]: return self._reg_read_batch([__seq_tuple(elem) for elem in reg_data]) def reg_write_batch(self, reg_data: Sequence[Tuple[int, Any]]) -> None: - """Write a sequece of architectural registers. This provides with faster means to + """Write a sequence of architectural registers. This provides with faster means to write multiple registers. Args: @@ -599,7 +599,7 @@ def ucsubclass(cls): # inherit from UcIntel and only then Uc, instead of Uc directly. that is: # Pegasus -> UcIntel -> Uc -> RegStateManager -> object # - # note that all Pegasus subclasses will have the same inheritence chain, + # note that all Pegasus subclasses will have the same inheritance chain, # regardless of the arch and mode the might use to initialize. def __replace(seq: Tuple, item, repl) -> Tuple: @@ -713,7 +713,7 @@ def __init__(self, arch: int, mode: int, cpu: Optional[int] = None) -> None: self._hook_exception: Optional[Exception] = None - # create a finalizer object that will apropriately free up resources when + # create a finalizer object that will appropriately free up resources when # this instance undergoes garbage collection. self.__finalizer = weakref.finalize(self, Uc.release_handle, self._uch) @@ -871,7 +871,7 @@ def mem_unmap(self, address: int, size: int) -> None: # TODO: this is where mmio callbacks need to be released from cache, # but we cannot tell whether this is an mmio range. also, memory ranges - # might be splitted by 'map_protect' after they were mapped, so the + # might be split by 'map_protect' after they were mapped, so the # (start, end) tuple may not be suitable for retrieving the callbacks. # # here we try to do that on a best-effort basis: @@ -910,10 +910,10 @@ def mmio_map(self, address: int, size: int, size : range size (in bytes) read_cb : read callback to invoke upon read access. if not specified, reads \ from the mmio range will be silently dropped - read_ud : optinal context object to pass on to the read callback - write_cb : write callback to invoke unpon a write access. if not specified, writes \ + read_ud : optional context object to pass on to the read callback + write_cb : write callback to invoke upon a write access. if not specified, writes \ to the mmio range will be silently dropped - write_ud : optinal context object to pass on to the write callback + write_ud : optional context object to pass on to the write callback """ @uccallback(self, MMIO_READ_CFUNC) @@ -946,7 +946,7 @@ def mem_regions(self) -> Iterator[Tuple[int, int, int]]: Returns: an iterator whose elements contain begin, end and perms properties of each range - Raises: `UcError` in case an itnernal error has been encountered + Raises: `UcError` in case an internal error has been encountered """ regions = ctypes.POINTER(uc_mem_region)() @@ -1024,7 +1024,7 @@ def __do_hook_add(self, htype: int, fptr: ctypes._FuncPointer, begin: int, end: if status != uc.UC_ERR_OK: raise UcError(status) - # hold a reference to the funcion pointer to prevent it from being gc-ed + # hold a reference to the function pointer to prevent it from being gc-ed self._callbacks[handle.value] = fptr return handle.value @@ -1056,7 +1056,7 @@ def __hook_intr_cb(uc: Uc, intno: int, key: int): def __hook_insn(): # each arch is expected to overload hook_add and implement this handler on their own. # if we got here, it means this particular architecture does not support hooking any - # instruction and so we fail + # instruction, and so we fail raise UcError(uc.UC_ERR_ARG) def __hook_code():