Python3 bindings improvements (#2024)
* Allow Uc subclasses to use additional constructor args * Add missing conext reg write batch prorotype * Sort uc prototypes for better readability * Redefine internal C API structures * Add ctypes alises to improve readability * Added documentation for ctl methods * Added ctl tcg buffer size accessors * Fix tcg buffer size return type
This commit is contained in:
@@ -3,32 +3,45 @@ based on Nguyen Anh Quynnh's work
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, Optional, Sequence, Tuple, Type, TypeVar, Union
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Generic, Iterable, Iterator, Optional, Sequence, Tuple, Type, TypeVar, Union
|
||||
|
||||
import ctypes
|
||||
import functools
|
||||
import weakref
|
||||
|
||||
from unicorn import unicorn_const as uc
|
||||
from .arch.types import uc_err, uc_engine, uc_context, uc_hook_h, UcReg
|
||||
from .arch.types import uc_err, uc_engine, uc_context, uc_hook_h, UcReg, VT
|
||||
|
||||
# __version__ = f'{uc.UC_VERSION_MAJOR}.{uc.UC_VERSION_MINOR}.{uc.UC_VERSION_PATCH}'
|
||||
|
||||
MemRegionStruct = Tuple[int, int, int]
|
||||
TBStruct = Tuple[int, int, int]
|
||||
|
||||
class _uc_mem_region(ctypes.Structure):
|
||||
|
||||
class UcTupledStruct(ctypes.Structure, Generic[VT]):
|
||||
"""A base class for structures that may be converted to tuples representing
|
||||
their fields values.
|
||||
"""
|
||||
|
||||
@property
|
||||
def value(self) -> VT:
|
||||
"""Convert structure into a tuple containing its fields values. This method
|
||||
name is used to maintain consistency with other ctypes types.
|
||||
"""
|
||||
|
||||
return tuple(getattr(self, fname) for fname, *_ in self.__class__._fields_) # type: ignore
|
||||
|
||||
|
||||
class _uc_mem_region(UcTupledStruct[MemRegionStruct]):
|
||||
_fields_ = (
|
||||
('begin', ctypes.c_uint64),
|
||||
('end', ctypes.c_uint64),
|
||||
('perms', ctypes.c_uint32),
|
||||
)
|
||||
|
||||
@property
|
||||
def value(self) -> Tuple[int, int, int]:
|
||||
return tuple(getattr(self, fname) for fname, *_ in self._fields_)
|
||||
|
||||
|
||||
class uc_tb(ctypes.Structure):
|
||||
""""TranslationBlock
|
||||
class uc_tb(UcTupledStruct[TBStruct]):
|
||||
""""Translation Block
|
||||
"""
|
||||
|
||||
_fields_ = (
|
||||
@@ -154,40 +167,59 @@ def __set_lib_prototypes(lib: ctypes.CDLL) -> None:
|
||||
func.restype = restype
|
||||
func.argtypes = argtypes
|
||||
|
||||
__set_prototype('uc_version', ctypes.c_uint, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int))
|
||||
__set_prototype('uc_arch_supported', ctypes.c_bool, ctypes.c_int)
|
||||
__set_prototype('uc_open', uc_err, ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(uc_engine))
|
||||
# declare a few ctypes aliases for brevity
|
||||
char = ctypes.c_char
|
||||
s32 = ctypes.c_int32
|
||||
u32 = ctypes.c_uint32
|
||||
u64 = ctypes.c_uint64
|
||||
size_t = ctypes.c_size_t
|
||||
void_p = ctypes.c_void_p
|
||||
PTR = ctypes.POINTER
|
||||
|
||||
__set_prototype('uc_arch_supported', ctypes.c_bool, s32)
|
||||
__set_prototype('uc_close', uc_err, uc_engine)
|
||||
__set_prototype('uc_strerror', ctypes.c_char_p, uc_err)
|
||||
__set_prototype('uc_errno', uc_err, uc_engine)
|
||||
__set_prototype('uc_reg_read', uc_err, uc_engine, ctypes.c_int, ctypes.c_void_p)
|
||||
__set_prototype('uc_reg_write', uc_err, uc_engine, ctypes.c_int, ctypes.c_void_p)
|
||||
__set_prototype('uc_reg_read_batch', uc_err, uc_engine, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_void_p), ctypes.c_int)
|
||||
__set_prototype('uc_reg_write_batch', uc_err, uc_engine, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_void_p), ctypes.c_int)
|
||||
__set_prototype('uc_mem_read', uc_err, uc_engine, ctypes.c_uint64, ctypes.POINTER(ctypes.c_char), ctypes.c_size_t)
|
||||
__set_prototype('uc_mem_write', uc_err, uc_engine, ctypes.c_uint64, ctypes.POINTER(ctypes.c_char), ctypes.c_size_t)
|
||||
__set_prototype('uc_emu_start', uc_err, uc_engine, ctypes.c_uint64, ctypes.c_uint64, ctypes.c_uint64, ctypes.c_size_t)
|
||||
__set_prototype('uc_emu_stop', uc_err, uc_engine)
|
||||
__set_prototype('uc_hook_del', uc_err, uc_engine, uc_hook_h)
|
||||
__set_prototype('uc_mmio_map', uc_err, 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)
|
||||
__set_prototype('uc_mem_map', uc_err, uc_engine, ctypes.c_uint64, ctypes.c_size_t, ctypes.c_uint32)
|
||||
__set_prototype('uc_mem_map_ptr', uc_err, uc_engine, ctypes.c_uint64, ctypes.c_size_t, ctypes.c_uint32, ctypes.c_void_p)
|
||||
__set_prototype('uc_mem_unmap', uc_err, uc_engine, ctypes.c_uint64, ctypes.c_size_t)
|
||||
__set_prototype('uc_mem_protect', uc_err, uc_engine, ctypes.c_uint64, ctypes.c_size_t, ctypes.c_uint32)
|
||||
__set_prototype('uc_query', uc_err, uc_engine, ctypes.c_uint32, ctypes.POINTER(ctypes.c_size_t))
|
||||
__set_prototype('uc_context_alloc', uc_err, uc_engine, ctypes.POINTER(uc_context))
|
||||
__set_prototype('uc_free', uc_err, ctypes.c_void_p)
|
||||
__set_prototype('uc_context_save', uc_err, uc_engine, uc_context)
|
||||
__set_prototype('uc_context_restore', uc_err, uc_engine, uc_context)
|
||||
__set_prototype('uc_context_size', ctypes.c_size_t, uc_engine)
|
||||
__set_prototype('uc_context_reg_read', uc_err, uc_context, ctypes.c_int, ctypes.c_void_p)
|
||||
__set_prototype('uc_context_reg_write', uc_err, uc_context, ctypes.c_int, ctypes.c_void_p)
|
||||
__set_prototype('uc_context_reg_read_batch', uc_err, uc_context, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_void_p), ctypes.c_int)
|
||||
__set_prototype('uc_context_alloc', uc_err, uc_engine, PTR(uc_context))
|
||||
__set_prototype('uc_context_free', uc_err, uc_context)
|
||||
__set_prototype('uc_mem_regions', uc_err, uc_engine, ctypes.POINTER(ctypes.POINTER(_uc_mem_region)), ctypes.POINTER(ctypes.c_uint32))
|
||||
# https://bugs.python.org/issue42880
|
||||
__set_prototype('uc_hook_add', uc_err, uc_engine, ctypes.POINTER(uc_hook_h), ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint64, ctypes.c_uint64)
|
||||
__set_prototype('uc_ctl', uc_err, uc_engine, ctypes.c_int)
|
||||
__set_prototype('uc_context_reg_read', uc_err, uc_context, s32, void_p)
|
||||
__set_prototype('uc_context_reg_read_batch', uc_err, uc_context, PTR(s32), PTR(void_p), s32)
|
||||
__set_prototype('uc_context_reg_write', uc_err, uc_context, s32, void_p)
|
||||
__set_prototype('uc_context_reg_write_batch', uc_err, uc_context, PTR(s32), void_p, s32)
|
||||
__set_prototype('uc_context_restore', uc_err, uc_engine, uc_context)
|
||||
__set_prototype('uc_context_save', uc_err, uc_engine, uc_context)
|
||||
__set_prototype('uc_context_size', size_t, uc_engine)
|
||||
__set_prototype('uc_ctl', uc_err, uc_engine, s32)
|
||||
__set_prototype('uc_emu_start', uc_err, uc_engine, u64, u64, u64, size_t)
|
||||
__set_prototype('uc_emu_stop', uc_err, uc_engine)
|
||||
__set_prototype('uc_errno', uc_err, uc_engine)
|
||||
__set_prototype('uc_free', uc_err, void_p)
|
||||
__set_prototype('uc_hook_add', uc_err, uc_engine, PTR(uc_hook_h), s32, void_p, void_p, u64, u64)
|
||||
__set_prototype('uc_hook_del', uc_err, uc_engine, uc_hook_h)
|
||||
__set_prototype('uc_mem_map', uc_err, uc_engine, u64, size_t, u32)
|
||||
__set_prototype('uc_mem_map_ptr', uc_err, uc_engine, u64, size_t, u32, void_p)
|
||||
__set_prototype('uc_mem_protect', uc_err, uc_engine, u64, size_t, u32)
|
||||
__set_prototype('uc_mem_read', uc_err, uc_engine, u64, PTR(char), size_t)
|
||||
__set_prototype('uc_mem_regions', uc_err, uc_engine, PTR(PTR(_uc_mem_region)), PTR(u32))
|
||||
__set_prototype('uc_mem_unmap', uc_err, uc_engine, u64, size_t)
|
||||
__set_prototype('uc_mem_write', uc_err, uc_engine, u64, PTR(char), size_t)
|
||||
__set_prototype('uc_mmio_map', uc_err, uc_engine, u64, size_t, void_p, void_p, void_p, void_p)
|
||||
__set_prototype('uc_open', uc_err, u32, u32, PTR(uc_engine))
|
||||
__set_prototype('uc_query', uc_err, uc_engine, u32, PTR(size_t))
|
||||
__set_prototype('uc_reg_read', uc_err, uc_engine, s32, void_p)
|
||||
__set_prototype('uc_reg_read_batch', uc_err, uc_engine, PTR(s32), PTR(void_p), s32)
|
||||
__set_prototype('uc_reg_write', uc_err, uc_engine, s32, void_p)
|
||||
__set_prototype('uc_reg_write_batch', uc_err, uc_engine, PTR(s32), PTR(void_p), s32)
|
||||
__set_prototype('uc_strerror', ctypes.c_char_p, uc_err)
|
||||
__set_prototype('uc_version', u32, PTR(s32), PTR(s32))
|
||||
|
||||
# TODO:
|
||||
# __set_prototype('uc_context_reg_read2', uc_err, uc_context, s32, void_p, PTR(size_t))
|
||||
# __set_prototype('uc_context_reg_read_batch2', uc_err, uc_context, PTR(s32), PTR(void_p), PTR(size_t), s32)
|
||||
# __set_prototype('uc_context_reg_write2', uc_err, uc_context, s32, void_p, PTR(size_t))
|
||||
# __set_prototype('uc_context_reg_write_batch2', uc_err, uc_context, PTR(s32), PTR(void_p), PTR(size_t), s32)
|
||||
# __set_prototype('uc_reg_read2', uc_err, uc_engine, s32, void_p, PTR(size_t))
|
||||
# __set_prototype('uc_reg_read_batch2', uc_err, uc_engine, PTR(s32), PTR(void_p), PTR(size_t), s32)
|
||||
# __set_prototype('uc_reg_write2', uc_err, uc_engine, s32, void_p, PTR(size_t))
|
||||
# __set_prototype('uc_reg_write_batch2', uc_err, uc_engine, PTR(s32), PTR(void_p), PTR(size_t), s32)
|
||||
|
||||
|
||||
uclib = __load_uc_lib()
|
||||
@@ -566,7 +598,10 @@ def ucsubclass(cls):
|
||||
|
||||
return seq[:i] + tuple([repl]) + seq[i + 1:]
|
||||
|
||||
def __new_uc_subclass(cls, arch: int, mode: int):
|
||||
def __new_uc_subclass(cls, arch: int, mode: int, *args, **kwargs):
|
||||
# this method has to contain *args and **kwargs to allow Uc subclasses
|
||||
# to use additional arguments in their constructors
|
||||
|
||||
# resolve the appropriate Uc subclass
|
||||
subcls = Uc.__new__(cls, arch, mode)
|
||||
|
||||
@@ -1209,89 +1244,209 @@ class Uc(RegStateManager):
|
||||
|
||||
self.ctl(ctl, uc.UC_CTL_IO_READ_WRITE, carg0, ctypes.byref(carg1))
|
||||
|
||||
return carg1
|
||||
return carg1.value
|
||||
|
||||
def ctl_get_mode(self) -> int:
|
||||
"""Retrieve current processor mode.
|
||||
|
||||
Returns: current mode (see UC_MODE_* constants)
|
||||
"""
|
||||
|
||||
return self.__ctl_r(uc.UC_CTL_UC_MODE,
|
||||
(ctypes.c_int, None)
|
||||
)
|
||||
|
||||
def ctl_get_page_size(self) -> int:
|
||||
"""Retrieve target page size.
|
||||
|
||||
Returns: page size in bytes
|
||||
"""
|
||||
|
||||
return self.__ctl_r(uc.UC_CTL_UC_PAGE_SIZE,
|
||||
(ctypes.c_uint32, None)
|
||||
)
|
||||
|
||||
def ctl_set_page_size(self, val: int) -> None:
|
||||
"""Set target page size.
|
||||
|
||||
Args:
|
||||
val: page size to set (in bytes)
|
||||
|
||||
Raises: `UcError` in any of the following cases:
|
||||
- Unicorn architecture is not ARM
|
||||
- Unicorn has already completed its initialization
|
||||
- Page size is not a power of 2
|
||||
"""
|
||||
|
||||
self.__ctl_w(uc.UC_CTL_UC_PAGE_SIZE,
|
||||
(ctypes.c_uint32, val)
|
||||
)
|
||||
|
||||
def ctl_get_arch(self) -> int:
|
||||
"""Retrieve target architecture.
|
||||
|
||||
Returns: current architecture (see UC_ARCH_* constants)
|
||||
"""
|
||||
|
||||
return self.__ctl_r(uc.UC_CTL_UC_ARCH,
|
||||
(ctypes.c_int, None)
|
||||
)
|
||||
|
||||
def ctl_get_timeout(self) -> int:
|
||||
"""Retrieve emulation timeout.
|
||||
|
||||
Returns: timeout value set on emulation start
|
||||
"""
|
||||
|
||||
return self.__ctl_r(uc.UC_CTL_UC_TIMEOUT,
|
||||
(ctypes.c_uint64, None)
|
||||
)
|
||||
|
||||
def ctl_exits_enabled(self, val: bool) -> None:
|
||||
def ctl_exits_enabled(self, enable: bool) -> None:
|
||||
"""Instruct Unicorn whether to respect emulation exit points or ignore them.
|
||||
|
||||
Args:
|
||||
enable: `True` to enable exit points, `False` to ignore them
|
||||
"""
|
||||
|
||||
self.__ctl_w(uc.UC_CTL_UC_USE_EXITS,
|
||||
(ctypes.c_int, val)
|
||||
(ctypes.c_int, enable)
|
||||
)
|
||||
|
||||
def ctl_get_exits_cnt(self) -> int:
|
||||
"""Retrieve emulation exit points count.
|
||||
|
||||
Returns: number of emulation exit points
|
||||
|
||||
Raises: `UcErro` if Unicorn is set to ignore exits
|
||||
"""
|
||||
|
||||
return self.__ctl_r(uc.UC_CTL_UC_EXITS_CNT,
|
||||
(ctypes.c_size_t, None)
|
||||
)
|
||||
|
||||
def ctl_get_exits(self) -> Sequence[int]:
|
||||
"""Retrieve emulation exit points.
|
||||
|
||||
Returns: a tuple of all emulation exit points
|
||||
|
||||
Raises: `UcErro` if Unicorn is set to ignore exits
|
||||
"""
|
||||
|
||||
count = self.ctl_get_exits_cnt()
|
||||
arr = (ctypes.c_uint64 * count)()
|
||||
|
||||
self.ctl(uc.UC_CTL_UC_EXITS, uc.UC_CTL_IO_READ, ctypes.cast(arr, ctypes.c_void_p), ctypes.c_size_t(count))
|
||||
|
||||
return tuple(i for i in arr)
|
||||
return tuple(arr)
|
||||
|
||||
def ctl_set_exits(self, exits: Sequence[int]) -> None:
|
||||
arr = (ctypes.c_uint64 * len(exits))()
|
||||
"""Set emulation exit points.
|
||||
|
||||
for idx, exit in enumerate(exits):
|
||||
arr[idx] = exit
|
||||
Args:
|
||||
exits: a list of emulation exit points to set
|
||||
|
||||
self.ctl(uc.UC_CTL_UC_EXITS, uc.UC_CTL_IO_WRITE, ctypes.cast(arr, ctypes.c_void_p), ctypes.c_size_t(len(exits)))
|
||||
Raises: `UcErro` if Unicorn is set to ignore exits
|
||||
"""
|
||||
|
||||
arr = (ctypes.c_uint64 * len(exits))(*exits)
|
||||
|
||||
self.ctl(uc.UC_CTL_UC_EXITS, uc.UC_CTL_IO_WRITE, ctypes.cast(arr, ctypes.c_void_p), ctypes.c_size_t(len(arr)))
|
||||
|
||||
def ctl_get_cpu_model(self) -> int:
|
||||
"""Retrieve target processor model.
|
||||
|
||||
Returns: target cpu model (see UC_CPU_* constants)
|
||||
"""
|
||||
|
||||
return self.__ctl_r(uc.UC_CTL_CPU_MODEL,
|
||||
(ctypes.c_int, None)
|
||||
)
|
||||
|
||||
def ctl_set_cpu_model(self, val: int) -> None:
|
||||
def ctl_set_cpu_model(self, model: int) -> None:
|
||||
"""Set target processor model.
|
||||
|
||||
Args:
|
||||
model: cpu model to set (see UC_CPU_* constants)
|
||||
|
||||
Raises: `UcError` in any of the following cases:
|
||||
- `model` is not a valid cpu model
|
||||
- Requested cpu model is incompatible with current mode
|
||||
- Unicorn has already completed its initialization
|
||||
"""
|
||||
|
||||
self.__ctl_w(uc.UC_CTL_CPU_MODEL,
|
||||
(ctypes.c_int, val)
|
||||
(ctypes.c_int, model)
|
||||
)
|
||||
|
||||
def ctl_remove_cache(self, addr: int, end: int) -> None:
|
||||
def ctl_remove_cache(self, lbound: int, ubound: int) -> None:
|
||||
"""Invalidate translation cache for a specified region.
|
||||
|
||||
Args:
|
||||
lbound: region lower bound
|
||||
ubound: region upper bound
|
||||
|
||||
Raises: `UcError` in case the provided range bounds are invalid
|
||||
"""
|
||||
|
||||
self.__ctl_w(uc.UC_CTL_TB_REMOVE_CACHE,
|
||||
(ctypes.c_uint64, addr),
|
||||
(ctypes.c_uint64, end)
|
||||
(ctypes.c_uint64, lbound),
|
||||
(ctypes.c_uint64, ubound)
|
||||
)
|
||||
|
||||
def ctl_request_cache(self, addr: int):
|
||||
def ctl_request_cache(self, addr: int) -> TBStruct:
|
||||
"""Get translation cache info for a specified address.
|
||||
|
||||
Args:
|
||||
addr: address to get its translation cache info
|
||||
|
||||
Returns: a 3-tuple containing the base address, instructions count and
|
||||
size of the translation block containing the specified address
|
||||
"""
|
||||
|
||||
return self.__ctl_wr(uc.UC_CTL_TB_REQUEST_CACHE,
|
||||
(ctypes.c_uint64, addr),
|
||||
(uc_tb, None)
|
||||
)
|
||||
|
||||
def ctl_flush_tb(self) -> None:
|
||||
"""Flush the entire translation cache.
|
||||
"""
|
||||
|
||||
self.__ctl_w(uc.UC_CTL_TB_FLUSH)
|
||||
|
||||
def ctl_tlb_mode(self, mode: int) -> None:
|
||||
def ctl_set_tlb_mode(self, mode: int) -> None:
|
||||
"""Set TLB mode.
|
||||
|
||||
Args:
|
||||
mode: tlb mode to use (see UC_TLB_* constants)
|
||||
"""
|
||||
|
||||
self.__ctl_w(uc.UC_CTL_TLB_TYPE,
|
||||
(ctypes.c_uint, mode)
|
||||
)
|
||||
|
||||
def ctl_get_tcg_buffer_size(self) -> int:
|
||||
"""Retrieve TCG buffer size.
|
||||
|
||||
Returns: buffer size (in bytes)
|
||||
"""
|
||||
|
||||
return self.__ctl_r(uc.UC_CTL_TCG_BUFFER_SIZE,
|
||||
(ctypes.c_uint32, None)
|
||||
)
|
||||
|
||||
def ctl_set_tcg_buffer_size(self, size: int) -> None:
|
||||
"""Set TCG buffer size.
|
||||
|
||||
Args:
|
||||
size: new size to set
|
||||
"""
|
||||
|
||||
self.__ctl_w(uc.UC_CTL_TCG_BUFFER_SIZE,
|
||||
(ctypes.c_uint32, size)
|
||||
)
|
||||
|
||||
|
||||
class UcContext(RegStateManager):
|
||||
"""Unicorn internal context.
|
||||
@@ -1394,4 +1549,3 @@ UC_MMIO_WRITE_TYPE = Callable[[Uc, int, int, int, Any], None]
|
||||
|
||||
|
||||
__all__ = ['Uc', 'UcContext', 'ucsubclass', 'UcError', 'uc_version', 'version_bind', 'uc_arch_supported', 'debug']
|
||||
|
||||
|
||||
Reference in New Issue
Block a user