Python bindings: Fix editable install + Execute Python2.7 workflow tests (#2044)

* 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

* GitHub Action: Install Python2.7 and run tests for re-tagged wheels on native arch runners only

* Python bindings:
- Use #x formatter to format hex values
This commit is contained in:
@Antelox
2024-12-07 07:52:21 +01:00
committed by GitHub
parent 3a01515367
commit f78a3f2f59
10 changed files with 152 additions and 114 deletions

View File

@@ -160,7 +160,9 @@ jobs:
run: |
python -m pip install -U pip wheel && Get-ChildItem -Path wheelhouse/ -Filter *cp38*.whl | Foreach-Object {
python -m wheel tags --python-tag='py2' --abi-tag=none $_.FullName
break
}
- name: '🚧 Python 2.7 wheels re-tagging - Non-Windows'
if: matrix.os != 'windows-2019'
env:
@@ -168,6 +170,26 @@ jobs:
run: |
python3 -m pip install -U pip wheel && python3 -m wheel tags --python-tag='py2' --abi-tag=none wheelhouse/*cp38*.whl
- uses: LizardByte/setup-python-action@master
if: (matrix.os == 'ubuntu-latest' && matrix.arch == 'x86_64' && matrix.cibw_build == 'cp38-manylinux') || matrix.os == 'macos-latest' || (matrix.os == 'windows-2019' && matrix.arch == 'AMD64')
with:
python-version: 2.7
- name: 'Python 2.7 tests - Windows'
if: matrix.os == 'windows-2019' && matrix.arch == 'AMD64'
run: |
C:\Python27\python.exe -m pip install -U pip pytest && Get-ChildItem -Path wheelhouse/ -Filter *py2*.whl | Foreach-Object {
C:\Python27\python.exe -m pip install $_.FullName
C:\Python27\python.exe -m pytest bindings/python/tests
break
}
# we install and test python2.7 wheels only on native arch
# NOTE: no python2.7 support for macos-13: https://github.com/LizardByte/setup-python-action/issues/2
- name: 'Python 2.7 tests - Non-Windows'
if: (matrix.os == 'ubuntu-latest' && matrix.arch == 'x86_64' && matrix.cibw_build == 'cp38-manylinux') || matrix.os == 'macos-latest'
run: python2 -m pip install wheelhouse/*py2*.whl && python2 -m pip install -U pip pytest && python2 -m pytest bindings/python/tests
- uses: actions/upload-artifact@v4
with:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}-py38

View File

@@ -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!
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 <retagged-wheel-py>`. For detailed commands please refer to our workflow files.

View File

@@ -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',

View File

@@ -8,9 +8,8 @@ import shutil
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 @@ class CustomSDist(sdist):
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 @@ class CustomBuild(build):
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
)

View File

@@ -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:#x}(UC_ARM64_REG_X2) cp_reg = {cp_reg}".format(reg=reg,
cp_reg=cp_reg))
uc.reg_write(reg, 0x114514)
print(">>> Write 0x114514 to X")
@@ -111,7 +112,7 @@ def test_arm64_hook_mrs():
# 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:#x}".format(reg=mu.reg_read(UC_ARM64_REG_X2)))
except UcError as e:
print("ERROR: %s" % e)

View File

@@ -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 bindings
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 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,7 @@ 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 {:#x} which has {} instructions with {} bytes".format(tb[0], tb[1], tb[2]))
# Do emulation with all TB cached.
cached = time_emulation(uc, addr, addr + len(code))
@@ -63,17 +69,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 {:#x} to {:#x}".format(prev.pc + prev.size - 1, 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:#x} with args: {arg1} and {arg2}".format(address=address, arg1=arg1,
arg2=arg2))
# TODO: Check if worth adapting the hook_add method for py2 bindings
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher")
def test_uc_ctl_exits():
uc = Uc(UC_ARCH_X86, UC_MODE_32)
addr = 0x1000
@@ -110,7 +121,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 = {eax:#x} and ebx = {ebx:#x} after the first emulation".format(eax=eax, ebx=ebx))
# This should stop at ADDRESS + 8, even though we don't provide an exit.
uc.emu_start(addr, 0)
@@ -118,7 +129,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 = {eax:#x} and ebx = {ebx:#x} after the first emulation".format(eax=eax, ebx=ebx))
if __name__ == "__main__":

View File

@@ -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)

View File

@@ -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:#x} with {size:#x} bytes and return 0x19260817".format(offset=offset,
size=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:#x} to IO memory at offset {offset:#x} with {size:#x} bytes".format(value=value,
offset=offset,
size=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:#x}".format(reg=mu.reg_read(UC_X86_REG_ECX)))
except UcError as e:
print("ERROR: %s" % e)

View File

@@ -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 @@ ucsubclass = 0
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 @@ _all_windows_dlls = (
_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 @@ _path_list = [os.getenv('LIBUNICORN_PATH', None),
os.getenv('PATH', '')]
# print(_path_list)
#print("-" * 80)
# print("-" * 80)
for _path in _path_list:
if _path is None: continue
@@ -104,6 +102,7 @@ for _path in _path_list:
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 @@ uc_engine = ctypes.c_void_p
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 @@ _setup_prototype(_uc, "uc_reg_read", ucerr, uc_engine, ctypes.c_int, ctypes.c_vo
_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 @@ _setup_prototype(_uc, "uc_context_size", ctypes.c_size_t, uc_engine)
_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 @@ UC_HOOK_TCG_OPCODE_CB = ctypes.CFUNCTYPE(
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 @@ class UcCleanupManager(object):
del self._refs[id(ref)]
ref._class.release_handle(ref._uch)
class Uc(object):
_cleanup = UcCleanupManager()
@@ -540,11 +566,11 @@ class Uc(object):
# 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 @@ class Uc(object):
(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 @@ class Uc(object):
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 @@ class Uc(object):
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 @@ class Uc(object):
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 @@ class Uc(object):
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 @@ class UcContext:
# 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
)
)

View File

@@ -283,7 +283,7 @@ class UcError(Exception):
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 @@ class RegStateManager:
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 @@ class Uc(RegStateManager):
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 @@ class Uc(RegStateManager):
# 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 @@ class Uc(RegStateManager):
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 @@ class Uc(RegStateManager):
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 @@ class Uc(RegStateManager):
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 @@ class Uc(RegStateManager):
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():