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:
Eli
2024-10-09 09:13:42 +03:00
committed by GitHub
parent 26268e69af
commit 78580ca8f9

View File

@@ -3,32 +3,45 @@ based on Nguyen Anh Quynnh's work
""" """
from __future__ import annotations 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 ctypes
import functools import functools
import weakref import weakref
from unicorn import unicorn_const as uc 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}' # __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_ = ( _fields_ = (
('begin', ctypes.c_uint64), ('begin', ctypes.c_uint64),
('end', ctypes.c_uint64), ('end', ctypes.c_uint64),
('perms', ctypes.c_uint32), ('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(UcTupledStruct[TBStruct]):
class uc_tb(ctypes.Structure): """"Translation Block
""""TranslationBlock
""" """
_fields_ = ( _fields_ = (
@@ -154,40 +167,59 @@ def __set_lib_prototypes(lib: ctypes.CDLL) -> None:
func.restype = restype func.restype = restype
func.argtypes = argtypes func.argtypes = argtypes
__set_prototype('uc_version', ctypes.c_uint, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)) # declare a few ctypes aliases for brevity
__set_prototype('uc_arch_supported', ctypes.c_bool, ctypes.c_int) char = ctypes.c_char
__set_prototype('uc_open', uc_err, ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(uc_engine)) 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_close', uc_err, uc_engine)
__set_prototype('uc_strerror', ctypes.c_char_p, uc_err) __set_prototype('uc_context_alloc', uc_err, uc_engine, PTR(uc_context))
__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_free', uc_err, 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)) __set_prototype('uc_context_reg_read', uc_err, uc_context, s32, void_p)
# https://bugs.python.org/issue42880 __set_prototype('uc_context_reg_read_batch', uc_err, uc_context, PTR(s32), PTR(void_p), s32)
__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_context_reg_write', uc_err, uc_context, s32, void_p)
__set_prototype('uc_ctl', uc_err, uc_engine, ctypes.c_int) __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() uclib = __load_uc_lib()
@@ -566,7 +598,10 @@ def ucsubclass(cls):
return seq[:i] + tuple([repl]) + seq[i + 1:] 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 # resolve the appropriate Uc subclass
subcls = Uc.__new__(cls, arch, mode) 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)) self.ctl(ctl, uc.UC_CTL_IO_READ_WRITE, carg0, ctypes.byref(carg1))
return carg1 return carg1.value
def ctl_get_mode(self) -> int: 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, return self.__ctl_r(uc.UC_CTL_UC_MODE,
(ctypes.c_int, None) (ctypes.c_int, None)
) )
def ctl_get_page_size(self) -> int: 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, return self.__ctl_r(uc.UC_CTL_UC_PAGE_SIZE,
(ctypes.c_uint32, None) (ctypes.c_uint32, None)
) )
def ctl_set_page_size(self, val: int) -> 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, self.__ctl_w(uc.UC_CTL_UC_PAGE_SIZE,
(ctypes.c_uint32, val) (ctypes.c_uint32, val)
) )
def ctl_get_arch(self) -> int: 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, return self.__ctl_r(uc.UC_CTL_UC_ARCH,
(ctypes.c_int, None) (ctypes.c_int, None)
) )
def ctl_get_timeout(self) -> int: 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, return self.__ctl_r(uc.UC_CTL_UC_TIMEOUT,
(ctypes.c_uint64, None) (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, self.__ctl_w(uc.UC_CTL_UC_USE_EXITS,
(ctypes.c_int, val) (ctypes.c_int, enable)
) )
def ctl_get_exits_cnt(self) -> int: 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, return self.__ctl_r(uc.UC_CTL_UC_EXITS_CNT,
(ctypes.c_size_t, None) (ctypes.c_size_t, None)
) )
def ctl_get_exits(self) -> Sequence[int]: 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() count = self.ctl_get_exits_cnt()
arr = (ctypes.c_uint64 * count)() 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)) 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: 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): Args:
arr[idx] = exit 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: 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, return self.__ctl_r(uc.UC_CTL_CPU_MODEL,
(ctypes.c_int, None) (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, 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, self.__ctl_w(uc.UC_CTL_TB_REMOVE_CACHE,
(ctypes.c_uint64, addr), (ctypes.c_uint64, lbound),
(ctypes.c_uint64, end) (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, return self.__ctl_wr(uc.UC_CTL_TB_REQUEST_CACHE,
(ctypes.c_uint64, addr), (ctypes.c_uint64, addr),
(uc_tb, None) (uc_tb, None)
) )
def ctl_flush_tb(self) -> None: def ctl_flush_tb(self) -> None:
"""Flush the entire translation cache.
"""
self.__ctl_w(uc.UC_CTL_TB_FLUSH) 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, self.__ctl_w(uc.UC_CTL_TLB_TYPE,
(ctypes.c_uint, mode) (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): class UcContext(RegStateManager):
"""Unicorn internal context. """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'] __all__ = ['Uc', 'UcContext', 'ucsubclass', 'UcError', 'uc_version', 'version_bind', 'uc_arch_supported', 'debug']