From 78580ca8f91027cafc741f65c78c7650103b6683 Mon Sep 17 00:00:00 2001 From: Eli Date: Wed, 9 Oct 2024 09:13:42 +0300 Subject: [PATCH] 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 --- .../python/unicorn/unicorn_py3/unicorn.py | 268 ++++++++++++++---- 1 file changed, 211 insertions(+), 57 deletions(-) diff --git a/bindings/python/unicorn/unicorn_py3/unicorn.py b/bindings/python/unicorn/unicorn_py3/unicorn.py index f576542974..5e747005bf 100644 --- a/bindings/python/unicorn/unicorn_py3/unicorn.py +++ b/bindings/python/unicorn/unicorn_py3/unicorn.py @@ -3,32 +3,45 @@ """ 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_prototype(fname: str, restype: Type[ctypes._CData], *argtypes: Type[ct 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_context_alloc', uc_err, uc_engine, PTR(uc_context)) + __set_prototype('uc_context_free', uc_err, uc_context) + __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_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_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_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 __replace(seq: Tuple, item, repl) -> Tuple: 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 @@ def __ctl_wr(self, ctl: int, arg0: Arg, arg1: Arg): 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. + + Args: + exits: a list of emulation exit points to set + + Raises: `UcErro` if Unicorn is set to ignore exits + """ - for idx, exit in enumerate(exits): - arr[idx] = exit + 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(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 @@ def __del__(self) -> None: __all__ = ['Uc', 'UcContext', 'ucsubclass', 'UcError', 'uc_version', 'version_bind', 'uc_arch_supported', 'debug'] -