from __future__ import annotations
import binascii
import copy
import ctypes
import importlib.resources
import itertools
import logging
import sys
import threading
import time

import cffi  # lmao

import archinfo
import claripy
import pyvex

import angr
from angr.engines.vex.claripy import ccall
from angr.sim_state import SimState
from angr import sim_options as options
from angr.engines.vex.claripy.irop import operations as irop_ops
from angr.errors import SimMemoryError, SimSegfaultError, SimUnicornError, SimUnicornUnsupport, SimValueError
from angr.misc.testing import is_testing
from .plugin import SimStatePlugin

l = logging.getLogger(name=__name__)
ffi = cffi.FFI()

try:
    import unicorn
    from unicorn.unicorn import _uc
except ImportError:
    l.info("Unicorn is not installed. Support disabled.")
    unicorn = None  # type: ignore
    _uc = None  # type: ignore


class MEM_PATCH(ctypes.Structure):
    """
    struct mem_update_t
    """


MEM_PATCH._fields_ = [("address", ctypes.c_uint64), ("length", ctypes.c_uint64), ("next", ctypes.POINTER(MEM_PATCH))]


class TRANSMIT_RECORD(ctypes.Structure):
    """
    struct transmit_record_t
    """

    _fields_ = [("fd", ctypes.c_uint32), ("data", ctypes.c_void_p), ("count", ctypes.c_uint32)]


class TaintEntityEnum:
    """
    taint_entity_enum_t
    """

    TAINT_ENTITY_REG = 0
    TAINT_ENTITY_TMP = 1
    TAINT_ENTITY_MEM = 2
    TAINT_ENTITY_NONE = 3


class MemoryValue(ctypes.Structure):
    """
    struct memory_value_t
    """

    _MAX_MEM_ACCESS_SIZE = 8

    _fields_ = [
        ("address", ctypes.c_uint64),
        ("value", ctypes.c_uint8),
        ("is_value_set", ctypes.c_bool),
        ("is_value_symbolic", ctypes.c_bool),
    ]


class RegisterValue(ctypes.Structure):
    """
    struct register_value_t
    """

    _MAX_REGISTER_BYTE_SIZE = 32

    _fields_ = [
        ("offset", ctypes.c_uint64),
        ("value", ctypes.c_uint8 * _MAX_REGISTER_BYTE_SIZE),
        ("size", ctypes.c_int64),
    ]


class VEXStmtDetails(ctypes.Structure):
    """
    struct sym_vex_stmt_details_t
    """

    _fields_ = [
        ("stmt_idx", ctypes.c_int64),
        ("has_memory_dep", ctypes.c_bool),
        ("memory_values", ctypes.POINTER(MemoryValue)),
        ("memory_values_count", ctypes.c_uint64),
    ]


class BlockDetails(ctypes.Structure):
    """
    struct sym_block_details_ret_t
    """

    _fields_ = [
        ("block_addr", ctypes.c_uint64),
        ("block_size", ctypes.c_uint64),
        ("block_trace_ind", ctypes.c_int64),
        ("has_symbolic_exit", ctypes.c_bool),
        ("symbolic_vex_stmts", ctypes.POINTER(VEXStmtDetails)),
        ("symbolic_vex_stmts_count", ctypes.c_uint64),
        ("register_values", ctypes.POINTER(RegisterValue)),
        ("register_values_count", ctypes.c_uint64),
    ]


class STOP:
    """
    enum stop_t
    """

    STOP_NORMAL = 0
    STOP_STOPPOINT = 1
    STOP_ERROR = 2
    STOP_SYSCALL = 3
    STOP_EXECNONE = 4
    STOP_ZEROPAGE = 5
    STOP_NOSTART = 6
    STOP_SEGFAULT = 7
    STOP_ZERO_DIV = 8
    STOP_NODECODE = 9
    STOP_HLT = 10
    STOP_VEX_LIFT_FAILED = 11
    STOP_SYMBOLIC_PC = 12
    STOP_SYMBOLIC_READ_ADDR = 13
    STOP_SYMBOLIC_READ_SYMBOLIC_TRACKING_DISABLED = 14
    STOP_SYMBOLIC_WRITE_ADDR = 15
    STOP_SYMBOLIC_BLOCK_EXIT_CONDITION = 16
    STOP_SYMBOLIC_BLOCK_EXIT_TARGET = 17
    STOP_UNSUPPORTED_STMT_PUTI = 18
    STOP_UNSUPPORTED_STMT_STOREG = 19
    STOP_UNSUPPORTED_STMT_LOADG = 20
    STOP_UNSUPPORTED_STMT_CAS = 21
    STOP_UNSUPPORTED_STMT_LLSC = 22
    STOP_UNSUPPORTED_STMT_DIRTY = 23
    STOP_UNSUPPORTED_EXPR_GETI = 24
    STOP_UNSUPPORTED_STMT_UNKNOWN = 25
    STOP_UNSUPPORTED_EXPR_UNKNOWN = 26
    STOP_UNKNOWN_MEMORY_WRITE_SIZE = 27
    STOP_SYSCALL_ARM = 28
    STOP_X86_CPUID = 29

    stop_message = {}
    stop_message[STOP_NORMAL] = "Reached maximum steps"
    stop_message[STOP_STOPPOINT] = "Hit a stop point"
    stop_message[STOP_ERROR] = "Something wrong"
    stop_message[STOP_SYSCALL] = "Unable to handle syscall"
    stop_message[STOP_EXECNONE] = "Fetching empty page"
    stop_message[STOP_ZEROPAGE] = "Accessing zero page"
    stop_message[STOP_NOSTART] = "Failed to start"
    stop_message[STOP_SEGFAULT] = "Permissions or mapping error"
    stop_message[STOP_ZERO_DIV] = "Divide by zero"
    stop_message[STOP_NODECODE] = "Instruction decoding error"
    stop_message[STOP_HLT] = "hlt instruction encountered"
    stop_message[STOP_VEX_LIFT_FAILED] = "Failed to lift block to VEX"
    stop_message[STOP_SYMBOLIC_PC] = "Instruction pointer became symbolic"
    stop_message[STOP_SYMBOLIC_READ_ADDR] = "Attempted to read from symbolic address"
    stop_message[STOP_SYMBOLIC_READ_SYMBOLIC_TRACKING_DISABLED] = (
        "Attempted to read symbolic data from memory but symbolic tracking is disabled"
    )
    stop_message[STOP_SYMBOLIC_WRITE_ADDR] = "Attempted to write to symbolic address"
    stop_message[STOP_SYMBOLIC_BLOCK_EXIT_CONDITION] = "Guard condition of block's exit statement is symbolic"
    stop_message[STOP_SYMBOLIC_BLOCK_EXIT_TARGET] = "Target of default exit of block is symbolic"
    stop_message[STOP_UNSUPPORTED_STMT_PUTI] = "Symbolic taint propagation for PutI statement not yet supported"
    stop_message[STOP_UNSUPPORTED_STMT_STOREG] = "Symbolic taint propagation for StoreG statement not yet supported"
    stop_message[STOP_UNSUPPORTED_STMT_LOADG] = "Symbolic taint propagation for LoadG statement not yet supported"
    stop_message[STOP_UNSUPPORTED_STMT_CAS] = "Symbolic taint propagation for CAS statement not yet supported"
    stop_message[STOP_UNSUPPORTED_STMT_LLSC] = "Symbolic taint propagation for LLSC statement not yet supported"
    stop_message[STOP_UNSUPPORTED_STMT_DIRTY] = "Symbolic taint propagation for Dirty statement not yet supported"
    stop_message[STOP_UNSUPPORTED_EXPR_GETI] = "Symbolic taint propagation for GetI expression not yet supported"
    stop_message[STOP_UNSUPPORTED_STMT_UNKNOWN] = "Canoo propagate symbolic taint for unsupported VEX statement type"
    stop_message[STOP_UNSUPPORTED_EXPR_UNKNOWN] = "Cannot propagate symbolic taint for unsupported VEX expression"
    stop_message[STOP_UNKNOWN_MEMORY_WRITE_SIZE] = "Unicorn failed to determine size of memory write"
    stop_message[STOP_SYSCALL_ARM] = "ARM syscalls are currently not supported by SimEngineUnicorn"
    stop_message[STOP_X86_CPUID] = "Block executes cpuid which should be handled in VEX engine"

    symbolic_stop_reasons = {
        STOP_SYMBOLIC_PC,
        STOP_SYMBOLIC_READ_ADDR,
        STOP_SYMBOLIC_READ_SYMBOLIC_TRACKING_DISABLED,
        STOP_SYMBOLIC_WRITE_ADDR,
        STOP_SYMBOLIC_BLOCK_EXIT_CONDITION,
        STOP_SYMBOLIC_BLOCK_EXIT_TARGET,
        STOP_SYSCALL_ARM,
        STOP_X86_CPUID,
    }

    unsupported_reasons = {
        STOP_UNSUPPORTED_STMT_PUTI,
        STOP_UNSUPPORTED_STMT_STOREG,
        STOP_UNSUPPORTED_STMT_LOADG,
        STOP_UNSUPPORTED_STMT_CAS,
        STOP_UNSUPPORTED_STMT_LLSC,
        STOP_UNSUPPORTED_STMT_DIRTY,
        STOP_UNSUPPORTED_STMT_UNKNOWN,
        STOP_UNSUPPORTED_EXPR_UNKNOWN,
        STOP_VEX_LIFT_FAILED,
    }

    @staticmethod
    def name_stop(num):
        for item in dir(STOP):
            if item.startswith("STOP_") and getattr(STOP, item) == num:
                return item
        raise ValueError(num)

    @staticmethod
    def get_stop_msg(stop_reason):
        if stop_reason in STOP.stop_message:
            return STOP.stop_message[stop_reason]

        return "Unknown stop reason"


class StopDetails(ctypes.Structure):
    """
    struct stop_details_t
    """

    _fields_ = [
        ("stop_reason", ctypes.c_int),
        ("block_addr", ctypes.c_uint64),
        ("block_size", ctypes.c_uint64),
    ]


class SimOSEnum:
    """
    enum simos_t
    """

    SIMOS_CGC = 0
    SIMOS_LINUX = 1
    SIMOS_OTHER = 2


#
# Memory mapping errors - only used internally
#


class MemoryMappingError(Exception):  # pylint: disable=missing-class-docstring
    pass


class AccessingZeroPageError(MemoryMappingError):  # pylint: disable=missing-class-docstring
    pass


class FetchingZeroPageError(MemoryMappingError):  # pylint: disable=missing-class-docstring
    pass


class SegfaultError(MemoryMappingError):  # pylint: disable=missing-class-docstring
    pass


class MixedPermissonsError(MemoryMappingError):  # pylint: disable=missing-class-docstring
    pass


#
# This annotation is added to constraints that Unicorn generates in aggressive concretization mode
#


class AggressiveConcretizationAnnotation(claripy.SimplificationAvoidanceAnnotation):
    # pylint: disable=missing-class-docstring
    def __init__(self, addr):
        claripy.SimplificationAvoidanceAnnotation.__init__(self)
        self.unicorn_start_addr = addr


#
# Because Unicorn leaks like crazy, we use one Uc object per thread...
#

_unicounter = itertools.count()


class Uniwrapper(unicorn.Uc if unicorn is not None else object):
    # pylint: disable=non-parent-init-called,missing-class-docstring
    def __init__(self, arch, cache_key, thumb=False):
        l.debug("Creating unicorn state!")
        self.arch = arch
        self.cache_key = cache_key
        self.wrapped_mapped = set()
        self.wrapped_hooks = set()
        self.id = None
        uc_mode = arch.uc_mode_thumb if thumb else arch.uc_mode
        unicorn.Uc.__init__(self, arch.uc_arch, uc_mode)

    def hook_add(self, htype, callback, user_data=None, begin=1, end=0, arg1=0):
        h = unicorn.Uc.hook_add(self, htype, callback, user_data=user_data, begin=begin, end=end, arg1=arg1)
        # l.debug("Hook: %s,%s -> %s", htype, callback.__name__, h)
        self.wrapped_hooks.add(h)
        return h

    def hook_del(self, h):
        # l.debug("Clearing hook %s", h)
        unicorn.Uc.hook_del(self, h)
        self.wrapped_hooks.discard(h)
        return h

    def mem_map(self, addr, size, perms=7):
        # l.debug("Mapping %d bytes at %#x", size, addr)
        m = unicorn.Uc.mem_map(self, addr, size, perms=perms)
        self.wrapped_mapped.add((addr, size))
        return m

    def mem_map_ptr(self, addr, size, perms, ptr):
        m = unicorn.Uc.mem_map_ptr(self, addr, size, perms, ptr)
        self.wrapped_mapped.add((addr, size))
        return m

    def mem_unmap(self, addr, size):
        # l.debug("Unmapping %d bytes at %#x", size, addr)
        m = unicorn.Uc.mem_unmap(self, addr, size)
        self.wrapped_mapped.discard((addr, size))
        return m

    def mem_reset(self):
        # l.debug("Resetting memory.")
        for addr, size in self.wrapped_mapped:
            # l.debug("Unmapping %d bytes at %#x", size, addr)
            unicorn.Uc.mem_unmap(self, addr, size)
        self.wrapped_mapped.clear()

    def hook_reset(self):
        # l.debug("Resetting hooks.")
        for h in self.wrapped_hooks:
            # l.debug("Clearing hook %s", h)
            unicorn.Uc.hook_del(self, h)
        self.wrapped_hooks.clear()

    def reset(self):
        self.mem_reset()
        # self.hook_reset()
        # l.debug("Reset complete.")


_unicorn_tls = threading.local()
_unicorn_tls.uc = None


class _VexCacheInfo(ctypes.Structure):
    """
    VexCacheInfo struct from vex
    """

    _fields_ = [
        ("num_levels", ctypes.c_uint),
        ("num_caches", ctypes.c_uint),
        ("caches", ctypes.c_void_p),
        ("icaches_maintain_coherence", ctypes.c_bool),
    ]


class _VexArchInfo(ctypes.Structure):
    """
    VexArchInfo struct from vex
    """

    _fields_ = [
        ("hwcaps", ctypes.c_uint),
        ("endness", ctypes.c_int),
        ("hwcache_info", _VexCacheInfo),
        ("ppc_icache_line_szB", ctypes.c_int),
        ("ppc_dcbz_szB", ctypes.c_uint),
        ("ppc_dcbzl_szB", ctypes.c_uint),
        ("arm64_dMinLine_lg2_szB", ctypes.c_uint),
        ("arm64_iMinLine_lg2_szB", ctypes.c_uint),
        ("x86_cr0", ctypes.c_uint),
    ]


def _load_native():
    if sys.platform == "darwin":
        libfile = "unicornlib.dylib"
    elif sys.platform in {"win32", "cygwin"}:
        libfile = "unicornlib.dll"
    else:
        libfile = "unicornlib.so"

    try:
        angr_path = str(importlib.resources.files("angr") / libfile)
        h = ctypes.CDLL(angr_path)

        VexArch = ctypes.c_int
        uc_err = ctypes.c_int
        state_t = ctypes.c_void_p
        stop_t = ctypes.c_int
        uc_engine_t = ctypes.c_void_p

        def _setup_prototype(handle, func, restype, *argtypes):
            realname = "simunicorn_" + func
            _setup_prototype_explicit(handle, realname, restype, *argtypes)
            setattr(handle, func, getattr(handle, realname))

        def _setup_prototype_explicit(handle, func, restype, *argtypes):
            getattr(handle, func).restype = restype
            getattr(handle, func).argtypes = argtypes

        # _setup_prototype_explicit(h, 'logSetLogLevel', None, ctypes.c_uint64)
        _setup_prototype(h, "setup_imports", ctypes.c_bool, ctypes.c_char_p)
        _setup_prototype(
            h,
            "alloc",
            state_t,
            uc_engine_t,
            ctypes.c_uint64,
            ctypes.c_uint64,
            ctypes.c_bool,
            ctypes.c_bool,
            ctypes.c_bool,
        )
        _setup_prototype(h, "dealloc", None, state_t)
        _setup_prototype(h, "hook", None, state_t)
        _setup_prototype(h, "unhook", None, state_t)
        _setup_prototype(h, "start", uc_err, state_t, ctypes.c_uint64, ctypes.c_uint64)
        _setup_prototype(h, "stop", None, state_t, stop_t)
        _setup_prototype(h, "sync", ctypes.POINTER(MEM_PATCH), state_t)
        _setup_prototype(h, "bbl_addrs", ctypes.POINTER(ctypes.c_uint64), state_t)
        _setup_prototype(h, "stack_pointers", ctypes.POINTER(ctypes.c_uint64), state_t)
        _setup_prototype(h, "bbl_addr_count", ctypes.c_uint64, state_t)
        _setup_prototype(h, "syscall_count", ctypes.c_uint64, state_t)
        _setup_prototype(h, "step", ctypes.c_uint64, state_t)
        _setup_prototype(h, "activate_page", None, state_t, ctypes.c_uint64, ctypes.c_void_p, ctypes.c_void_p)
        _setup_prototype(h, "set_last_block_details", None, state_t, ctypes.c_uint64, ctypes.c_int64, ctypes.c_int64)
        _setup_prototype(h, "set_stops", None, state_t, ctypes.c_uint64, ctypes.POINTER(ctypes.c_uint64))
        _setup_prototype(
            h, "cache_page", ctypes.c_bool, state_t, ctypes.c_uint64, ctypes.c_uint64, ctypes.c_char_p, ctypes.c_uint64
        )
        _setup_prototype(h, "uncache_pages_touching_region", None, state_t, ctypes.c_uint64, ctypes.c_uint64)
        _setup_prototype(h, "clear_page_cache", None, state_t)
        _setup_prototype(h, "enable_symbolic_reg_tracking", None, state_t, VexArch, _VexArchInfo)
        _setup_prototype(h, "disable_symbolic_reg_tracking", None, state_t)
        _setup_prototype(h, "symbolic_register_data", None, state_t, ctypes.c_uint64, ctypes.POINTER(ctypes.c_uint64))
        _setup_prototype(h, "get_symbolic_registers", ctypes.c_uint64, state_t, ctypes.POINTER(ctypes.c_uint64))
        _setup_prototype(h, "is_interrupt_handled", ctypes.c_bool, state_t)
        _setup_prototype(
            h,
            "set_cgc_syscall_details",
            None,
            state_t,
            ctypes.c_uint32,
            ctypes.c_uint64,
            ctypes.c_uint32,
            ctypes.c_uint64,
            ctypes.c_uint64,
            ctypes.c_uint32,
            ctypes.c_uint64,
        )
        _setup_prototype(h, "process_transmit", ctypes.POINTER(TRANSMIT_RECORD), state_t, ctypes.c_uint32)
        _setup_prototype(h, "set_tracking", None, state_t, ctypes.c_bool, ctypes.c_bool)
        _setup_prototype(h, "executed_pages", ctypes.c_uint64, state_t)
        _setup_prototype(h, "in_cache", ctypes.c_bool, state_t, ctypes.c_uint64)
        if unicorn is not None:
            _setup_prototype(h, "set_map_callback", None, state_t, unicorn.unicorn.UC_HOOK_MEM_INVALID_CB)
        _setup_prototype(
            h,
            "set_vex_to_unicorn_reg_mappings",
            None,
            state_t,
            ctypes.POINTER(ctypes.c_uint64),
            ctypes.POINTER(ctypes.c_uint64),
            ctypes.POINTER(ctypes.c_uint64),
            ctypes.c_uint64,
        )
        _setup_prototype(h, "set_artificial_registers", None, state_t, ctypes.POINTER(ctypes.c_uint64), ctypes.c_uint64)
        _setup_prototype(h, "get_count_of_blocks_with_symbolic_vex_stmts", ctypes.c_uint64, state_t)
        _setup_prototype(
            h, "get_details_of_blocks_with_symbolic_vex_stmts", None, state_t, ctypes.POINTER(BlockDetails)
        )
        _setup_prototype(h, "get_stop_details", StopDetails, state_t)
        _setup_prototype(h, "set_register_blacklist", None, state_t, ctypes.POINTER(ctypes.c_uint64), ctypes.c_uint64)
        _setup_prototype(
            h,
            "set_cpu_flags_details",
            None,
            state_t,
            ctypes.POINTER(ctypes.c_uint64),
            ctypes.POINTER(ctypes.c_uint64),
            ctypes.POINTER(ctypes.c_uint64),
            ctypes.c_uint64,
        )
        _setup_prototype(
            h,
            "set_fd_bytes",
            state_t,
            ctypes.c_uint64,
            ctypes.c_void_p,
            ctypes.c_void_p,
            ctypes.c_uint64,
            ctypes.c_uint64,
        )
        _setup_prototype(
            h,
            "set_random_syscall_data",
            None,
            state_t,
            ctypes.POINTER(ctypes.c_uint64),
            ctypes.POINTER(ctypes.c_uint64),
            ctypes.c_uint64,
        )
        _setup_prototype(
            h,
            "set_vex_cc_reg_data",
            None,
            state_t,
            ctypes.POINTER(ctypes.c_uint64),
            ctypes.POINTER(ctypes.c_uint64),
            ctypes.c_uint64,
        )
        _setup_prototype(h, "get_count_of_writes_to_reexecute", ctypes.c_uint64, state_t)
        _setup_prototype(
            h,
            "get_concrete_writes_to_reexecute",
            None,
            state_t,
            ctypes.POINTER(ctypes.c_uint64),
            ctypes.POINTER(ctypes.c_uint8),
        )
        _setup_prototype(
            h,
            "set_fp_regs_fp_ops_vex_codes",
            None,
            state_t,
            ctypes.c_uint64,
            ctypes.c_uint64,
            ctypes.POINTER(ctypes.c_uint64),
            ctypes.c_uint32,
        )
        _setup_prototype(
            h,
            "get_heap_base",
            ctypes.c_uint64,
            state_t,
        )
        _setup_prototype(
            h,
            "set_heap_base",
            None,
            state_t,
            ctypes.c_uint64,
        )
        _setup_prototype(
            h,
            "set_ucproc",
            ctypes.c_bool,
            state_t,
            ctypes.c_uint64,
            ctypes.c_char_p,
        )

        l.info("native plugin is enabled")

        return h
    except (OSError, AttributeError) as e:
        l.error('failed loading "%s", unicorn support disabled (%s)', libfile, e)
        raise ImportError("Unable to import native SimUnicorn support") from e


try:
    _UC_NATIVE = _load_native()
    # _UC_NATIVE.logSetLogLevel(2)
except ImportError:
    _UC_NATIVE = None

if _uc is not None and _UC_NATIVE is not None and not _UC_NATIVE.setup_imports(_uc._name.encode()):
    l.error("Unicorn engine has an incompatible API. Support disabled.")
    unicorn = None


class Unicorn(SimStatePlugin):
    """
    setup the unicorn engine for a state
    """

    UC_CONFIG = {}  # config cache for each arch

    def __init__(
        self,
        syscall_hooks=None,
        cache_key=None,
        unicount=None,
        symbolic_var_counts=None,
        symbolic_inst_counts=None,
        concretized_asts=None,
        always_concretize=None,
        never_concretize=None,
        concretize_at=None,
        concretization_threshold_memory=None,
        concretization_threshold_registers=None,
        concretization_threshold_instruction=None,
        cooldown_symbolic_stop=2,
        cooldown_unsupported_stop=2,
        cooldown_nonunicorn_blocks=100,
        cooldown_stop_point=1,
        max_steps=1000000,
    ):
        """
        Initializes the Unicorn plugin for angr. This plugin handles communication with
        UnicornEngine.
        """

        SimStatePlugin.__init__(self)

        self._syscall_pc = None
        self.jumpkind = "Ijk_Boring"
        self.error = None
        self.errno = 0
        self.trap_ip = None

        self.cache_key = hash(self) if cache_key is None else cache_key

        # cooldowns to avoid thrashing in and out of unicorn
        # the countdown vars are the CURRENT counter that is counting down
        # when they hit zero execution will start
        # the cooldown vars are the settings for what the countdown should start at
        # the val is copied from cooldown to countdown on check fail
        self.cooldown_nonunicorn_blocks = cooldown_nonunicorn_blocks
        self.cooldown_symbolic_stop = cooldown_symbolic_stop
        self.cooldown_unsupported_stop = cooldown_unsupported_stop
        self.cooldown_stop_point = cooldown_stop_point
        self.countdown_nonunicorn_blocks = 0
        self.countdown_symbolic_stop = 0
        self.countdown_unsupported_stop = 0
        self.countdown_stop_point = 0

        # the default step limit
        self.max_steps = max_steps

        self.steps = 0
        self._mapped = 0
        self._uncache_regions = []
        self._symbolic_offsets = None
        self.gdt = None

        # following variables are used in python level hook
        # we cannot see native hooks from python
        self.syscall_hooks = {} if syscall_hooks is None else syscall_hooks

        # native state in libsimunicorn
        self._uc_state = None
        self.stop_reason = None
        self.stop_details = None
        self.stop_message = None

        # this is the counter for the unicorn count
        self._unicount = next(_unicounter) if unicount is None else unicount

        #
        # Selective concretization stuff
        #

        # this is the number of times specific symbolic variables have kicked us out of unicorn
        self.symbolic_var_counts = {} if symbolic_var_counts is None else symbolic_var_counts

        # this is the number of times we've been kept out of unicorn at given instructions
        self.symbolic_inst_counts = {} if symbolic_inst_counts is None else symbolic_inst_counts

        # these are threshold for the number of times that we tolerate being kept out of unicorn
        # before we start concretizing
        self.concretization_threshold_memory = concretization_threshold_memory
        self.concretization_threshold_registers = concretization_threshold_registers
        self.concretization_threshold_instruction = concretization_threshold_instruction

        # these are sets of names of variables that should either always or never
        # be concretized
        self.always_concretize = set() if always_concretize is None else always_concretize
        self.never_concretize = set() if never_concretize is None else never_concretize
        self.concretize_at = set() if concretize_at is None else concretize_at

        # this is a record of the ASTs for which we've added concretization constraints
        self._concretized_asts = set() if concretized_asts is None else concretized_asts

        # the address to use for concrete transmits
        self.cgc_transmit_addr = None

        # the address for CGC receive
        self.cgc_receive_addr = None

        # the address for CGC random
        self.cgc_random_addr = None

        self.time = None

        self._bullshit_cb = (
            ctypes.cast(
                unicorn.unicorn.UC_HOOK_MEM_INVALID_CB(self._hook_mem_unmapped), unicorn.unicorn.UC_HOOK_MEM_INVALID_CB
            )
            if unicorn is not None
            else None
        )

    @SimStatePlugin.memo
    def copy(self, _memo):
        u = Unicorn(
            syscall_hooks=dict(self.syscall_hooks),
            cache_key=self.cache_key,
            # unicount=self._unicount,
            symbolic_var_counts=dict(self.symbolic_var_counts),
            symbolic_inst_counts=dict(self.symbolic_inst_counts),
            concretized_asts=set(self._concretized_asts),
            always_concretize=set(self.always_concretize),
            never_concretize=set(self.never_concretize),
            concretize_at=set(self.concretize_at),
            concretization_threshold_memory=self.concretization_threshold_memory,
            concretization_threshold_registers=self.concretization_threshold_registers,
            concretization_threshold_instruction=self.concretization_threshold_instruction,
            cooldown_nonunicorn_blocks=self.cooldown_nonunicorn_blocks,
            cooldown_symbolic_stop=self.cooldown_symbolic_stop,
            cooldown_unsupported_stop=self.cooldown_unsupported_stop,
            max_steps=self.max_steps,
        )
        u.countdown_nonunicorn_blocks = self.countdown_nonunicorn_blocks
        u.countdown_symbolic_stop = self.countdown_symbolic_stop
        u.countdown_unsupported_stop = self.countdown_unsupported_stop
        u.countdown_stop_point = self.countdown_stop_point
        u.cgc_receive_addr = self.cgc_receive_addr
        u.cgc_random_addr = self.cgc_random_addr
        u.cgc_transmit_addr = self.cgc_transmit_addr
        u._uncache_regions = list(self._uncache_regions)
        u.gdt = self.gdt
        return u

    def merge(self, others, merge_conditions, common_ancestor=None):  # pylint: disable=unused-argument
        self.cooldown_nonunicorn_blocks = max(
            self.cooldown_nonunicorn_blocks, max(o.cooldown_nonunicorn_blocks for o in others)
        )
        self.cooldown_symbolic_stop = max(self.cooldown_symbolic_stop, max(o.cooldown_symbolic_stop for o in others))
        self.cooldown_unsupported_stop = max(
            self.cooldown_unsupported_stop, max(o.cooldown_unsupported_stop for o in others)
        )
        self.countdown_nonunicorn_blocks = max(
            self.countdown_nonunicorn_blocks, max(o.countdown_nonunicorn_blocks for o in others)
        )
        self.countdown_symbolic_stop = max(self.countdown_symbolic_stop, max(o.countdown_symbolic_stop for o in others))
        self.countdown_unsupported_stop = max(
            self.countdown_unsupported_stop, max(o.countdown_unsupported_stop for o in others)
        )
        self.countdown_stop_point = max(self.countdown_stop_point, max(o.countdown_stop_point for o in others))

        # get a fresh unicount, just in case
        self._unicount = next(_unicounter)

        # keep these guys, since merging them sounds like a pain
        # self.symbolic_var_counts
        # self.symbolic_inst_counts

        # these are threshold for the number of times that we tolerate being kept out of unicorn
        # before we start concretizing
        def merge_nullable_min(*args):
            nonnull = [a for a in args if a is not None]
            if not nonnull:
                return None
            return min(nonnull)

        self.concretization_threshold_memory = merge_nullable_min(
            self.concretization_threshold_memory, *(o.concretization_threshold_memory for o in others)
        )
        self.concretization_threshold_registers = merge_nullable_min(
            self.concretization_threshold_registers, *(o.concretization_threshold_registers for o in others)
        )
        self.concretization_threshold_instruction = merge_nullable_min(
            self.concretization_threshold_instruction, *(o.concretization_threshold_instruction for o in others)
        )

        # these are sets of names of variables that should either always or never
        # be concretized
        self.always_concretize.union(*[o.always_concretize for o in others])
        self.never_concretize.union(*[o.never_concretize for o in others])
        self.concretize_at.union(*[o.concretize_at for o in others])

        # intersect these so that we know to add future constraints properly
        self._concretized_asts.intersection(*[o._concretized_asts for o in others])

        # I guess always lie to the static analysis?
        return False

    def widen(self, others):  # pylint: disable=unused-argument
        l.warning("Can't widen the unicorn plugin!")

    def __getstate__(self):
        d = dict(self.__dict__)
        del d["_bullshit_cb"]
        del d["_uc_state"]
        del d["cache_key"]
        del d["_unicount"]
        return d

    def __setstate__(self, s):
        self.__dict__.update(s)
        self._bullshit_cb = (
            ctypes.cast(
                unicorn.unicorn.UC_HOOK_MEM_INVALID_CB(self._hook_mem_unmapped), unicorn.unicorn.UC_HOOK_MEM_INVALID_CB
            )
            if unicorn is not None
            else None
        )
        self._unicount = next(_unicounter)
        self._uc_state = None
        self.cache_key = hash(self)
        _unicorn_tls.uc = None

    def set_state(self, state):
        SimStatePlugin.set_state(self, state)
        if self._is_mips32:
            self._unicount = next(_unicounter)

    @property
    def _reuse_unicorn(self):
        return not self._is_mips32

    @property
    def uc(self):
        new_id = next(_unicounter)
        is_thumb = self.state.arch.qemu_name == "arm" and self.state.arch.is_thumb(self.state.addr)
        if (
            not hasattr(_unicorn_tls, "uc")
            or _unicorn_tls.uc is None
            or _unicorn_tls.uc.arch != self.state.arch
            or _unicorn_tls.uc.cache_key != self.cache_key
        ):
            _unicorn_tls.uc = Uniwrapper(self.state.arch, self.cache_key, thumb=is_thumb)
        elif _unicorn_tls.uc.id != self._unicount:
            if not self._reuse_unicorn:
                _unicorn_tls.uc = Uniwrapper(self.state.arch, self.cache_key, thumb=is_thumb)
            else:
                # l.debug("Reusing unicorn state!")
                _unicorn_tls.uc.reset()
        else:
            # l.debug("Reusing unicorn state!")
            pass

        _unicorn_tls.uc.id = new_id
        self._unicount = new_id
        return _unicorn_tls.uc

    @staticmethod
    def delete_uc():
        _unicorn_tls.uc = None

    @property
    def _uc_regs(self):
        return self.state.arch.uc_regs

    @property
    def _uc_prefix(self):
        return self.state.arch.uc_prefix

    @property
    def _uc_const(self):
        return self.state.arch.uc_const

    def _setup_unicorn(self):
        if self.state.arch.uc_mode is None:
            raise SimUnicornUnsupport(f"unsupported architecture {self.state.arch!r}")

    def set_last_block_details(self, details):
        _UC_NATIVE.set_last_block_details(self._uc_state, details["addr"], details["curr_count"], details["tot_count"])

    def set_stops(self, stop_points):
        _UC_NATIVE.set_stops(
            self._uc_state,
            ctypes.c_uint64(len(stop_points)),
            (ctypes.c_uint64 * len(stop_points))(*(ctypes.c_uint64(sp) for sp in stop_points)),
        )

    def set_tracking(self, track_bbls, track_stack):
        _UC_NATIVE.set_tracking(self._uc_state, track_bbls, track_stack)

    def hook(self):
        # l.debug('adding native hooks')
        _UC_NATIVE.hook(self._uc_state)  # prefer to use native hooks

        self.uc.hook_add(unicorn.UC_HOOK_MEM_UNMAPPED, self._hook_mem_unmapped, None, 1)

        arch = self.state.arch.qemu_name
        if arch == "x86_64":
            self.uc.hook_add(unicorn.UC_HOOK_INTR, self._hook_intr_x86, None, 1, 0)
            self.uc.hook_add(
                unicorn.UC_HOOK_INSN, self._hook_syscall_x86_64, None, arg1=self._uc_const.UC_X86_INS_SYSCALL
            )
        elif arch == "i386":
            self.uc.hook_add(unicorn.UC_HOOK_INTR, self._hook_intr_x86, None, 1, 0)
        elif arch == "mips" or arch == "mipsel":
            self.uc.hook_add(unicorn.UC_HOOK_INTR, self._hook_intr_mips, None, 1, 0)
        elif arch == "arm":
            # EDG says: Unicorn's ARM support has no concept of interrupts.
            # This is because interrupts are not a part of the ARM ISA per se, and interrupt controllers
            # are left to the vendor to provide.
            # TODO: This is not true for CortexM.  Revisit when Tobi's NVIC implementation gets upstreamed.
            pass
        else:
            raise SimUnicornUnsupport

    def _hook_intr_mips(self, uc, intno, user_data):
        self.trap_ip = self.uc.reg_read(unicorn.mips_const.UC_MIPS_REG_PC)

        if intno == 17:  # EXCP_SYSCALL
            sysno = uc.reg_read(self._uc_regs["v0"])
