Skip to content

Commit

Permalink
Python3 bindings improvements (#2024)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
elicn authored Oct 9, 2024
1 parent 26268e6 commit 78580ca
Showing 1 changed file with 211 additions and 57 deletions.
268 changes: 211 additions & 57 deletions bindings/python/unicorn/unicorn_py3/unicorn.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_ = (
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1394,4 +1549,3 @@ def __del__(self) -> None:


__all__ = ['Uc', 'UcContext', 'ucsubclass', 'UcError', 'uc_version', 'version_bind', 'uc_arch_supported', 'debug']

0 comments on commit 78580ca

Please sign in to comment.