/* Unicorn Emulator Engine */
/* By Nguyen Anh Quynh <aquynh@gmail.com>, 2015 */
/* Modified for Unicorn Engine by Chen Huitao<chenhuitao@hfmrit.com>, 2020 */

#ifndef UC_PRIV_H
#define UC_PRIV_H

#include "unicorn/platform.h"
#include <stdio.h>

#include "qemu.h"
#include "qemu/xxhash.h"
#include "unicorn/unicorn.h"
#include "list.h"

// The max recursive nested uc_emu_start levels
#define UC_MAX_NESTED_LEVEL (64)

// These are masks of supported modes for each cpu/arch.
// They should be updated when changes are made to the uc_mode enum typedef.
#define UC_MODE_ARM_MASK                                                       \
    (UC_MODE_ARM | UC_MODE_THUMB | UC_MODE_LITTLE_ENDIAN | UC_MODE_MCLASS |    \
     UC_MODE_ARM926 | UC_MODE_ARM946 | UC_MODE_ARM1176 | UC_MODE_BIG_ENDIAN |  \
     UC_MODE_ARMBE8)
#define UC_MODE_MIPS_MASK                                                      \
    (UC_MODE_MIPS32 | UC_MODE_MIPS64 | UC_MODE_LITTLE_ENDIAN |                 \
     UC_MODE_BIG_ENDIAN)
#define UC_MODE_X86_MASK                                                       \
    (UC_MODE_16 | UC_MODE_32 | UC_MODE_64 | UC_MODE_LITTLE_ENDIAN)
#define UC_MODE_PPC_MASK (UC_MODE_PPC32 | UC_MODE_PPC64 | UC_MODE_BIG_ENDIAN)
#define UC_MODE_SPARC_MASK                                                     \
    (UC_MODE_SPARC32 | UC_MODE_SPARC64 | UC_MODE_BIG_ENDIAN)
#define UC_MODE_M68K_MASK (UC_MODE_BIG_ENDIAN)
#define UC_MODE_RISCV_MASK                                                     \
    (UC_MODE_RISCV32 | UC_MODE_RISCV64 | UC_MODE_LITTLE_ENDIAN)
#define UC_MODE_S390X_MASK (UC_MODE_BIG_ENDIAN)
#define UC_MODE_TRICORE_MASK (UC_MODE_LITTLE_ENDIAN)

#define ARR_SIZE(a) (sizeof(a) / sizeof(a[0]))

#define READ_QWORD(x) ((uint64_t)x)
#define READ_DWORD(x) (x & 0xffffffff)
#define READ_WORD(x) (x & 0xffff)
#define READ_BYTE_H(x) ((x & 0xffff) >> 8)
#define READ_BYTE_L(x) (x & 0xff)
#define WRITE_DWORD(x, w) (x = (x & ~0xffffffffLL) | (w & 0xffffffff))
#define WRITE_WORD(x, w) (x = (x & ~0xffff) | (w & 0xffff))
#define WRITE_BYTE_H(x, b) (x = (x & ~0xff00) | ((b & 0xff) << 8))
#define WRITE_BYTE_L(x, b) (x = (x & ~0xff) | (b & 0xff))

struct TranslationBlock;

// Place the struct here since we need it in uc.c
typedef struct _mmio_cbs {
    uc_cb_mmio_read_t read;
    void *user_data_read;
    uc_cb_mmio_write_t write;
    void *user_data_write;
    MemoryRegionOps ops;
} mmio_cbs;

typedef uc_err (*query_t)(struct uc_struct *uc, uc_query_type type,
                          size_t *result);

typedef uc_err (*reg_read_t)(void *env, int mode, unsigned int regid,
                             void *value, size_t *size);
typedef uc_err (*reg_write_t)(void *env, int mode, unsigned int regid,
                              const void *value, size_t *size, int *setpc);

typedef struct {
    reg_read_t read;
    reg_write_t write;
} context_reg_rw_t;

typedef void (*reg_reset_t)(struct uc_struct *uc);

typedef bool (*uc_write_mem_t)(AddressSpace *as, hwaddr addr,
                               const uint8_t *buf, hwaddr len);

typedef bool (*uc_read_mem_t)(AddressSpace *as, hwaddr addr, uint8_t *buf,
                              hwaddr len);

typedef bool (*uc_read_mem_virtual_t)(struct uc_struct *uc, vaddr addr,
                                      uint32_t prot, uint8_t *buf, int len);

typedef bool (*uc_virtual_to_physical_t)(struct uc_struct *uc, vaddr addr,
                                      uint32_t prot, uint64_t *res);

typedef MemoryRegion *(*uc_mem_cow_t)(struct uc_struct *uc,
                                      MemoryRegion *current, hwaddr begin,
                                      size_t size);

typedef void (*uc_args_void_t)(void *);

typedef void (*uc_args_uc_t)(struct uc_struct *);
typedef void (*uc_args_int_uc_t)(struct uc_struct *);

typedef void (*uc_args_uc_long_t)(struct uc_struct *, uint32_t);

typedef void (*uc_args_uc_u64_t)(struct uc_struct *, uint64_t addr);

typedef uint64_t (*uc_get_pc_t)(struct uc_struct *);

typedef MemoryRegion *(*uc_args_uc_ram_size_t)(struct uc_struct *, hwaddr begin,
                                               size_t size, uint32_t perms);

typedef MemoryRegion *(*uc_args_uc_ram_size_ptr_t)(struct uc_struct *,
                                                   hwaddr begin, size_t size,
                                                   uint32_t perms, void *ptr);

typedef void (*uc_mem_unmap_t)(struct uc_struct *, MemoryRegion *mr);

typedef MemoryRegion *(*uc_memory_mapping_t)(struct uc_struct *, hwaddr addr);

typedef void (*uc_memory_filter_t)(MemoryRegion *, int32_t);

typedef bool (*uc_flatview_copy_t)(struct uc_struct *, FlatView *, FlatView *,
                                   bool);

typedef void (*uc_readonly_mem_t)(MemoryRegion *mr, bool readonly);

typedef int (*uc_cpus_init)(struct uc_struct *, const char *);

typedef MemoryRegion *(*uc_memory_map_io_t)(struct uc_struct *uc,
                                            ram_addr_t begin, size_t size,
                                            uc_cb_mmio_read_t read_cb,
                                            uc_cb_mmio_write_t write_cb,
                                            void *user_data_read,
                                            void *user_data_write);

// which interrupt should make emulation stop?
typedef bool (*uc_args_int_t)(struct uc_struct *uc, int intno);

// validate if Unicorn supports hooking a given instruction
typedef bool (*uc_insn_hook_validate)(uint32_t insn_enum);

typedef bool (*uc_opcode_hook_validate_t)(uint32_t op, uint32_t flags);

// init target page
typedef void (*uc_target_page_init)(struct uc_struct *);

// soft float init
typedef void (*uc_softfloat_initialize)(void);

// tcg flush softmmu tlb
typedef void (*uc_tcg_flush_tlb)(struct uc_struct *uc);

// Invalidate the TB at given address
typedef void (*uc_invalidate_tb_t)(struct uc_struct *uc, uint64_t start,
                                   size_t len);

// Request generating TB at given address
typedef uc_err (*uc_gen_tb_t)(struct uc_struct *uc, uint64_t pc, uc_tb *out_tb);

// tb flush
typedef uc_tcg_flush_tlb uc_tb_flush_t;

typedef uc_err (*uc_set_tlb_t)(struct uc_struct *uc, int mode);

struct hook {
    int type;       // UC_HOOK_*
    int insn;       // instruction for HOOK_INSN
    int refs;       // reference count to free hook stored in multiple lists
    int op;         // opcode for HOOK_TCG_OPCODE
    int op_flags;   // opcode flags for HOOK_TCG_OPCODE
    bool to_delete; // set to true when the hook is deleted by the user. The
                    // destruction of the hook is delayed.
    uint64_t begin, end; // only trigger if PC or memory access is in this
                         // address (depends on hook type)
    void *callback;      // a uc_cb_* type
    void *user_data;
    GHashTable *hooked_regions; // The regions this hook instrumented on
};

// Add an inline hook to helper_table
typedef void (*uc_add_inline_hook_t)(struct uc_struct *uc, struct hook *hk,
                                     void **args, int args_len);

// Delete a hook from helper_table
typedef void (*uc_del_inline_hook_t)(struct uc_struct *uc, struct hook *hk);

// Return the size of a CPU context
typedef size_t (*uc_context_size_t)(struct uc_struct *uc);

// Generate a CPU context
typedef uc_err (*uc_context_save_t)(struct uc_struct *uc, uc_context *context);

// Restore a CPU context
typedef uc_err (*uc_context_restore_t)(struct uc_struct *uc,
                                       uc_context *context);

// hook list offsets
//
// The lowest 6 bits are used for hook type index while the others
// are used for hook flags.
//
// mirrors the order of uc_hook_type from include/unicorn/unicorn.h
typedef enum uc_hook_idx {
    UC_HOOK_INTR_IDX,
    UC_HOOK_INSN_IDX,
    UC_HOOK_CODE_IDX,
    UC_HOOK_BLOCK_IDX,
    UC_HOOK_MEM_READ_UNMAPPED_IDX,
    UC_HOOK_MEM_WRITE_UNMAPPED_IDX,
    UC_HOOK_MEM_FETCH_UNMAPPED_IDX,
    UC_HOOK_MEM_READ_PROT_IDX,
    UC_HOOK_MEM_WRITE_PROT_IDX,
    UC_HOOK_MEM_FETCH_PROT_IDX,
    UC_HOOK_MEM_READ_IDX,
    UC_HOOK_MEM_WRITE_IDX,
    UC_HOOK_MEM_FETCH_IDX,
    UC_HOOK_MEM_READ_AFTER_IDX,
    UC_HOOK_INSN_INVALID_IDX,
    UC_HOOK_EDGE_GENERATED_IDX,
    UC_HOOK_TCG_OPCODE_IDX,
    UC_HOOK_TLB_FILL_IDX,

    UC_HOOK_MAX,
} uc_hook_idx;

// Copy the essential information from TranslationBlock
#define UC_TB_COPY(uc_tb, tb)                                                  \
    do {                                                                       \
        (uc_tb)->pc = tb->pc;                                                  \
        (uc_tb)->icount = tb->icount;                                          \
        (uc_tb)->size = tb->size;                                              \
    } while (0)

// The lowest 6 bits are used for hook type index.
#define UC_HOOK_IDX_MASK ((1 << 6) - 1)

// hook flags
#define UC_HOOK_FLAG_NO_STOP                                                   \
    (1 << 6) // Don't stop emulation in this uc_tracecode.

// The rest of bits are reserved for hook flags.
#define UC_HOOK_FLAG_MASK (~(UC_HOOK_IDX_MASK))

#define HOOK_FOREACH_VAR_DECLARE struct list_item *cur

// for loop macro to loop over hook lists
#define HOOK_FOREACH(uc, hh, idx)                                              \
    for (cur = (uc)->hook[idx##_IDX].head;                                     \
         cur != NULL && ((hh) = (struct hook *)cur->data); cur = cur->next)

// if statement to check hook bounds
#define HOOK_BOUND_CHECK(hh, addr)                                             \
    ((((addr) >= (hh)->begin && (addr) <= (hh)->end) ||                        \
      (hh)->begin > (hh)->end) &&                                              \
     !((hh)->to_delete))

#define HOOK_EXISTS(uc, idx) ((uc)->hook[idx##_IDX].head != NULL)
#define HOOK_EXISTS_BOUNDED(uc, idx, addr)                                     \
    _hook_exists_bounded((uc)->hook[idx##_IDX].head, addr)

static inline bool _hook_exists_bounded(struct list_item *cur, uint64_t addr)
{
    while (cur != NULL) {
        if (HOOK_BOUND_CHECK((struct hook *)cur->data, addr))
            return true;
        cur = cur->next;
    }
    return false;
}

// relloc increment, KEEP THIS A POWER OF 2!
#define MEM_BLOCK_INCR 32

typedef struct TargetPageBits TargetPageBits;
typedef struct TCGContext TCGContext;

struct uc_struct {
    uc_arch arch;
    uc_mode mode;
    uc_err errnum; // qemu/cpu-exec.c
    AddressSpace address_space_memory;
    AddressSpace address_space_io;
    query_t query;
    reg_read_t reg_read;
    reg_write_t reg_write;
    reg_reset_t reg_reset;

    uc_write_mem_t write_mem;
    uc_read_mem_t read_mem;
    uc_read_mem_virtual_t read_mem_virtual;
    uc_virtual_to_physical_t virtual_to_physical;
    uc_mem_cow_t memory_cow;
    uc_args_void_t release;  // release resource when uc_close()
    uc_args_uc_u64_t set_pc; // set PC for tracecode
    uc_get_pc_t get_pc;
    uc_args_int_t
        stop_interrupt; // check if the interrupt should stop emulation
    uc_memory_map_io_t memory_map_io;

    uc_args_uc_t init_arch, cpu_exec_init_all;
    uc_args_int_uc_t vm_start;
    uc_args_uc_long_t tcg_exec_init;
    uc_args_uc_ram_size_t memory_map;
    uc_args_uc_ram_size_ptr_t memory_map_ptr;
    uc_memory_mapping_t memory_mapping;
    uc_memory_filter_t memory_filter_subregions;
    uc_flatview_copy_t flatview_copy;
    uc_mem_unmap_t memory_unmap;
    uc_mem_unmap_t memory_moveout;
    uc_mem_unmap_t memory_movein;
    uc_readonly_mem_t readonly_mem;
    uc_cpus_init cpus_init;
    uc_target_page_init target_page;
    uc_softfloat_initialize softfloat_initialize;
    uc_tcg_flush_tlb tcg_flush_tlb;
    uc_invalidate_tb_t uc_invalidate_tb;
    uc_gen_tb_t uc_gen_tb;
    uc_tb_flush_t tb_flush;
    uc_add_inline_hook_t add_inline_hook;
    uc_del_inline_hook_t del_inline_hook;

    uc_context_size_t context_size;
    uc_context_save_t context_save;
    uc_context_restore_t context_restore;

    /*  only 1 cpu in unicorn,
        do not need current_cpu to handle current running cpu. */
    CPUState *cpu;

    uc_insn_hook_validate insn_hook_validate;
    uc_opcode_hook_validate_t opcode_hook_invalidate;

    MemoryRegion *system_memory;    // qemu/exec.c
    MemoryRegion *system_io;        // qemu/exec.c
    MemoryRegion io_mem_unassigned; // qemu/exec.c
    RAMList ram_list;               // qemu/exec.c
    /* qemu/exec.c */
    unsigned int alloc_hint;
    /* qemu/exec-vary.c */
    TargetPageBits *init_target_page;
    int target_bits; // User defined page bits by uc_ctl
    int cpu_model;
    BounceBuffer bounce;                // qemu/cpu-exec.c
    volatile sig_atomic_t exit_request; // qemu/cpu-exec.c
    /* qemu/accel/tcg/cpu-exec-common.c */
    /* always be true after call tcg_exec_init(). */
    bool tcg_allowed;
    /* This is a multi-level map on the virtual address space.
       The bottom level has pointers to PageDesc.  */
    void **l1_map; // qemu/accel/tcg/translate-all.c
    size_t l1_map_size;
    /* qemu/accel/tcg/translate-all.c */
    int v_l1_size;
    int v_l1_shift;
    int v_l2_levels;
    /* code generation context */
    TCGContext *tcg_ctx;
    /* memory.c */
    QTAILQ_HEAD(memory_listeners, MemoryListener) memory_listeners;
    QTAILQ_HEAD(, AddressSpace) address_spaces;
    GHashTable *flat_views;
    bool memory_region_update_pending;

    uc_set_tlb_t set_tlb;

    // linked lists containing hooks per type
    struct list hook[UC_HOOK_MAX];
    struct list hooks_to_del;
    int hooks_count[UC_HOOK_MAX];

    // hook to count number of instructions for uc_emu_start()
    uc_hook count_hook;

    size_t emu_counter; // current counter of uc_emu_start()
    size_t emu_count;   // save counter of uc_emu_start()

    int size_recur_mem; // size for mem access when in a recursive call

    bool init_tcg;       // already initialized local TCGv variables?
    bool stop_request;   // request to immediately stop emulation - for
                         // uc_emu_stop()
    bool quit_request;   // request to quit the current TB, but continue to
                         // emulate - for uc_mem_protect()
    bool emulation_done; // emulation is done by uc_emu_start()
    bool timed_out;      // emulation timed out, that can retrieve via
                         // uc_query(UC_QUERY_TIMEOUT)
    QemuThread timer;    // timer for emulation timeout
    uint64_t timeout;    // timeout for uc_emu_start()

    uint64_t invalid_addr; // invalid address to be accessed
    int invalid_error;     // invalid memory code: 1 = READ, 2 = WRITE, 3 = CODE

    int use_exits;
    uint64_t exits[UC_MAX_NESTED_LEVEL]; // When multiple exits is not enabled.
    GTree *ctl_exits; // addresses where emulation stops (@until param of
                      // uc_emu_start()) Also see UC_CTL_USE_EXITS for more
                      // details.

    int thumb; // thumb mode for ARM
    MemoryRegion **mapped_blocks;
    uint32_t mapped_block_count;
    uint32_t mapped_block_cache_index;
    void *qemu_thread_data; // to support cross compile to Windows
                            // (qemu-thread-win32.c)
    uint32_t target_page_size;
    uint32_t target_page_align;
    uint64_t qemu_host_page_size;
    uint64_t qemu_real_host_page_size;
    int qemu_icache_linesize;
    /* ARCH_REGS_STORAGE_SIZE */
    uc_context_content context_content;
    int cpu_context_size;
    uint64_t next_pc; // save next PC for some special cases
    bool hook_insert; // insert new hook at begin of the hook list (append by
                      // default)
    bool first_tb; // is this the first Translation-Block ever generated since
                   // uc_emu_start()?
    bool no_exit_request; // Disable check_exit_request temporarily. A
                          // workaround to treat the IT block as a whole block.
    bool init_done;       // Whether the initialization is done.

    sigjmp_buf jmp_bufs[UC_MAX_NESTED_LEVEL]; // To support nested uc_emu_start
    int nested_level;                         // Current nested_level

    struct TranslationBlock *last_tb; // The real last tb we executed.

    FlatView *empty_view; // Static function variable moved from flatviews_init

    uint32_t tcg_buffer_size; // The buffer size we are going to use
#ifdef WIN32
    PVOID seh_handle;
    void *seh_closure;
#endif
    GArray *unmapped_regions;
    int32_t snapshot_level;
    uint64_t nested; // the nested level of all exposed API
    bool thread_executable_entry;
    bool current_executable;
    bool skip_sync_pc_on_exit;
};

// Metadata stub for the variable-size cpu context used with uc_context_*()
struct uc_context {
    size_t context_size;  // size of the real internal context structure
    uc_mode mode;         // the mode of this context
    uc_arch arch;         // the arch of this context
    int snapshot_level;   // the memory snapshot level to restore
    bool ramblock_freed;  // wheter there was a some ramblock freed
    RAMBlock *last_block; // The last element of the ramblock list
    FlatView *fv;         // The current flatview of the memory
    char data[0];         // context
};

// We have to support 32bit system so we can't hold uint64_t on void*
static inline void uc_add_exit(uc_engine *uc, uint64_t addr)
{
    uint64_t *new_exit = g_malloc(sizeof(uint64_t));
    *new_exit = addr;
    g_tree_insert(uc->ctl_exits, (gpointer)new_exit, (gpointer)1);
}

// This function has to exist since we would like to accept uint32_t or
// it's complex to achieve so.
static inline int uc_addr_is_exit(uc_engine *uc, uint64_t addr)
{
    if (uc->use_exits) {
        return g_tree_lookup(uc->ctl_exits, (gpointer)(&addr)) == (gpointer)1;
    } else {
        return uc->exits[uc->nested_level - 1] == addr;
    }
}

typedef struct HookedRegion {
    uint64_t start;
    uint64_t length;
} HookedRegion;

// hooked_regions related functions
static inline guint hooked_regions_hash(const void *p)
{
    HookedRegion *region = (HookedRegion *)p;

    return qemu_xxhash4(region->start, region->length);
}

static inline gboolean hooked_regions_equal(const void *lhs, const void *rhs)
{
    HookedRegion *l = (HookedRegion *)lhs;
    HookedRegion *r = (HookedRegion *)rhs;

    return l->start == r->start && l->length == r->length;
}

static inline void hooked_regions_add(struct hook *h, uint64_t start,
                                      uint64_t length)
{
    HookedRegion tmp;
    tmp.start = start;
    tmp.length = length;

    if (!g_hash_table_lookup(h->hooked_regions, (void *)&tmp)) {
        HookedRegion *r = malloc(sizeof(HookedRegion));
        r->start = start;
        r->length = length;
        g_hash_table_insert(h->hooked_regions, (void *)r, (void *)1);
    }
}

static inline void hooked_regions_check_single(struct list_item *cur,
                                               uint64_t start, uint64_t length)
{
    while (cur != NULL) {
        if (HOOK_BOUND_CHECK((struct hook *)cur->data, start)) {
            hooked_regions_add((struct hook *)cur->data, start, length);
        }
        cur = cur->next;
    }
}

static inline void hooked_regions_check(uc_engine *uc, uint64_t start,
                                        uint64_t length)
{
    // Only UC_HOOK_BLOCK and UC_HOOK_CODE might be wrongle cached!
    hooked_regions_check_single(uc->hook[UC_HOOK_CODE_IDX].head, start, length);
    hooked_regions_check_single(uc->hook[UC_HOOK_BLOCK_IDX].head, start,
                                length);
}

/*
 break translation loop:
 This is done in two cases:
 1. the user wants to stop the emulation.
 2. the user has set it IP. This requires to restart the internal
 CPU emulation and rebuild some translation blocks
*/
static inline uc_err break_translation_loop(uc_engine *uc)
{
    if (uc->emulation_done) {
        return UC_ERR_OK;
    }

    // TODO: make this atomic somehow?
    if (uc->cpu) {
        // exit the current TB
        cpu_exit(uc->cpu);
    }

    return UC_ERR_OK;
}

static inline void revert_uc_emu_stop(uc_engine *uc)
{
    uc->stop_request = 0;
    uc->cpu->exit_request = 0;
    uc->cpu->tcg_exit_req = 0;
    uc->cpu->icount_decr_ptr->u16.high = 0;
}

#ifdef UNICORN_TRACER
#define UC_TRACE_START(loc) trace_start(get_tracer(), loc)
#define UC_TRACE_END(loc, fmt, ...)                                            \
    trace_end(get_tracer(), loc, fmt, __VA_ARGS__)

typedef enum trace_loc {
    UC_TRACE_TB_EXEC = 0,
    UC_TRACE_TB_TRANS,
    UC_TRACER_MAX
} trace_loc;

typedef struct uc_tracer {
    int64_t starts[UC_TRACER_MAX];
} uc_tracer;

uc_tracer *get_tracer();

void trace_start(uc_tracer *tracer, trace_loc loc);

void trace_end(uc_tracer *tracer, trace_loc loc, const char *fmt, ...);

#else
#define UC_TRACE_START(loc)
#define UC_TRACE_END(loc, fmt, ...)
#endif

#endif
/* vim: set ts=4 noet:  */
