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:
22
.github/workflows/build-wheels-publish.yml
vendored
22
.github/workflows/build-wheels-publish.yml
vendored
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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__":
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user