diff --git a/CMakeLists.txt b/CMakeLists.txt index 1eca217e..0c22f03d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1102,6 +1102,8 @@ endif() # Extra tests set(UNICORN_TEST_FILE ${UNICORN_TEST_FILE} test_mem) +set(UNICORN_TEST_FILE ${UNICORN_TEST_FILE} test_ctl) +set(UNICORN_SAMPLE_FILE ${UNICORN_SAMPLE_FILE} sample_ctl) target_compile_options(unicorn PRIVATE ${UNICORN_COMPILE_OPTIONS} diff --git a/glib_compat/gtree.c b/glib_compat/gtree.c index a790d568..b2617a3f 100644 --- a/glib_compat/gtree.c +++ b/glib_compat/gtree.c @@ -260,7 +260,7 @@ static inline GTreeNode *g_tree_node_next (GTreeNode *node) return tmp; } -static void g_tree_remove_all (GTree *tree) +void g_tree_remove_all (GTree *tree) { GTreeNode *node; GTreeNode *next; diff --git a/glib_compat/gtree.h b/glib_compat/gtree.h index f62dbe08..47b6c880 100644 --- a/glib_compat/gtree.h +++ b/glib_compat/gtree.h @@ -44,6 +44,8 @@ void g_tree_destroy (GTree *tree); void g_tree_insert (GTree *tree, gpointer key, gpointer value); +void g_tree_remove_all (GTree *tree); + gboolean g_tree_remove (GTree *tree, gconstpointer key); gpointer g_tree_lookup (GTree *tree, gconstpointer key); diff --git a/include/uc_priv.h b/include/uc_priv.h index bcdf9059..19dbecda 100644 --- a/include/uc_priv.h +++ b/include/uc_priv.h @@ -41,6 +41,8 @@ #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; + typedef uc_err (*query_t)(struct uc_struct *uc, uc_query_type type, size_t *result); @@ -114,6 +116,14 @@ 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 struct TranslationBlock *(*uc_gen_tb_t)(struct uc_struct *uc, + uint64_t pc); + struct hook { int type; // UC_HOOK_* int insn; // instruction for HOOK_INSN @@ -226,6 +236,8 @@ struct uc_struct { 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; /* only 1 cpu in unicorn, do not need current_cpu to handle current running cpu. */ @@ -288,8 +300,9 @@ struct uc_struct { uint64_t invalid_addr; // invalid address to be accessed int invalid_error; // invalid memory code: 1 = READ, 2 = WRITE, 3 = CODE - uint64_t addr_end; // address where emulation stops (@end param of - // uc_emu_start()) + int use_exit; + GTree *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; @@ -328,5 +341,20 @@ struct uc_context { // check if this address is mapped in (via uc_mem_map()) MemoryRegion *memory_mapping(struct uc_struct *uc, uint64_t address); +// 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->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) +{ + return g_tree_lookup(uc->exits, (gpointer)(&addr)) == (gpointer)1; +} + #endif /* vim: set ts=4 noet: */ diff --git a/include/unicorn/unicorn.h b/include/unicorn/unicorn.h index 51dc212c..a9f49650 100644 --- a/include/unicorn/unicorn.h +++ b/include/unicorn/unicorn.h @@ -427,40 +427,43 @@ typedef enum uc_query_type { // If a control don't have `Set` or `Get` for @args, it means it's r/o or w/o. typedef enum uc_control_type { // Current mode. - // Read: @args = (*int) + // Read: @args = (int*) UC_CTL_UC_MODE = 0, // Curent page size. - // Write: @args = (int) - // Read: @args = (*int) + // Write: @args = (uint32_t) + // Read: @args = (uint32_t*) UC_CTL_UC_PAGE_SIZE, // Current arch. - // Read: @args = (*int) + // Read: @args = (int*) UC_CTL_UC_ARCH, // Current timeout. - // Read: @args = (*uint64_t) + // Read: @args = (uint64_t*) UC_CTL_UC_TIMEOUT, + // Enable multiple exists. + // Without this control, reading/setting exists won't work. + // This is for API backward compatibility. + // Write: @args = (int) + UC_CTL_UC_USE_EXITS, // The number of current exists. - // Read: @args = (*size_t) + // Read: @args = (size_t*) UC_CTL_UC_EXITS_CNT, // Current exists. - // Write: @args = (*uint64_t exists, size_t len) + // Write: @args = (uint64_t* exists, size_t len) // @len = UC_CTL_UC_EXITS_CNT - // Read: @args = (*uint64_t exists, size_t len) + // Read: @args = (uint64_t* exists, size_t len) // @len = UC_CTL_UC_EXITS_CNT UC_CTL_UC_EXITS, + // Set the cpu model of uc. // Note this option can only be set before any Unicorn // API is called except for uc_open. // Write: @args = (int) // Read: @args = (int) UC_CTL_CPU_MODEL, - // Request the edge of two TBs. - // Read: @args = (uint64_t, uint64_t, *uint64_t) - UC_CTL_TB_EDGE, // Request a tb cache at a specific address // Read: @args = (uint64_t) UC_CTL_TB_REQUEST_CACHE, - // Remove a tb cache at a specific address + // Invalidate a tb cache at a specific address // Read: @args = (uint64_t) UC_CTL_TB_REMOVE_CACHE @@ -469,13 +472,15 @@ typedef enum uc_control_type { #define uc_ctl_get_mode(uc, mode) \ uc_ctl(uc, UC_CTL_READ(UC_CTL_UC_MODE, 1), (mode)) #define uc_ctl_get_page_size(uc, ptr) \ - uc_ctl(uc, UC_CTL_READ(UC_CTL_UC_PAGE_SIZE, 1, (ptr)) + uc_ctl(uc, UC_CTL_READ(UC_CTL_UC_PAGE_SIZE, 1), (ptr)) #define uc_ctl_set_page_size(uc, page_size) \ uc_ctl(uc, UC_CTL_WRITE(UC_CTL_UC_PAGE_SIZE, 1), (page_size)) #define uc_ctl_get_arch(uc, arch) \ uc_ctl(uc, UC_CTL_READ(UC_CTL_UC_ARCH, 1), (arch)) #define uc_ctl_get_timeout(uc, ptr) \ uc_ctl(uc, UC_CTL_READ(UC_CTL_UC_TIMEOUT, 1), (ptr)) +#define uc_ctl_exits_enabled(uc, enabled) \ + uc_ctl(uc, UC_CTL_WRITE(UC_CTL_UC_USE_EXITS, 1), (enabled)) #define uc_ctl_get_exists_cnt(uc, ptr) \ uc_ctl(uc, UC_CTL_READ(UC_CTL_UC_EXITS_CNT, 1), (ptr)) #define uc_ctl_get_exists(uc, buffer, len) \ @@ -487,11 +492,9 @@ typedef enum uc_control_type { #define uc_ctl_set_cpu_model(uc, model) \ uc_ctl(uc, UC_CTL_WRITE(UC_CTL_CPU_MODEL, 1), (model)) #define uc_ctl_remove_cache(uc, address) \ - uc_ctl(uc, UC_CTL_WRITE(UC_CTL_TB_REMOVE_CACHE, 1), (address)) + uc_ctl(uc, UC_CTL_READ(UC_CTL_TB_REMOVE_CACHE, 1), (address)) #define uc_ctl_request_cache(uc, address) \ - uc_ctl(uc, UC_CTL_WRITE(UC_CTL_TB_REQUEST_CACHE, 1), (address)) -#define uc_ctl_get_edge(uc, addr1, addr2, ptr) \ - uc_ctl(uc, UC_CTL_READ_WRITE(UC_CTL_TB_EDGE, 3), (addr1), (addr2), (ptr)) + uc_ctl(uc, UC_CTL_READ(UC_CTL_TB_REQUEST_CACHE, 1), (address)) // Opaque storage for CPU context, used with uc_context_*() struct uc_context; diff --git a/qemu/accel/tcg/translate-all.c b/qemu/accel/tcg/translate-all.c index d59b9604..a7b94d99 100644 --- a/qemu/accel/tcg/translate-all.c +++ b/qemu/accel/tcg/translate-all.c @@ -980,6 +980,99 @@ static void tb_htable_init(struct uc_struct *uc) qht_init(&uc->tcg_ctx->tb_ctx.htable, tb_cmp, CODE_GEN_HTABLE_SIZE, mode); } +// GVA to GPA (GPA -> HVA via page_find, HVA->HPA via host mmu) +// Unicorn: Why addr - 1? +// 0: INC ecx +// 1: DEC edx <--- We put exit here, then the range of TB is [0, 1) +// +// While tb_invalidate_phys_range invalides [start, end) +// +// This function is designed to used with g_tree_foreach +static inline gboolean uc_exit_invalidate_iter(gpointer key, gpointer val, gpointer data) { + uint64_t exit = *((uint64_t*)key); + uc_engine* uc = (uc_engine*)data; + tb_page_addr_t start, end; + + if (exit != 0) { + end = get_page_addr_code(uc->cpu->env_ptr, exit); + + start = (end-1) ; + end = end & (target_ulong)(-1); + + tb_invalidate_phys_range(uc, start, end); + } + + return false; +} + +static void uc_invalidate_tb(struct uc_struct *uc, uint64_t start_addr, size_t len) { + tb_page_addr_t start, end; + + // GVA to GPA (GPA -> HVA via page_find, HVA->HPA via host mmu) + start = get_page_addr_code(uc->cpu->env_ptr, start_addr) & (target_ulong)(-1); + + // For 32bit target. + end = (start + len) & (target_ulong)(-1); + + // We get a wrap? + if (start > end) { + return; + } + + tb_invalidate_phys_range(uc, start, end); +} + +static TranslationBlock* uc_gen_tb(struct uc_struct *uc, uint64_t addr) { + TranslationBlock *tb; + target_ulong cs_base, pc; + CPUState *cpu = uc->cpu; + CPUArchState *env = (CPUArchState *)cpu->env_ptr; + uint32_t flags; + uint32_t hash; + uint32_t cflags = cpu->cflags_next_tb; + + if (cflags == -1) { + cflags = curr_cflags(); + } + + cpu_get_tb_cpu_state(env, &pc, &cs_base, &flags); + + // Unicorn: Our hack here. + pc = addr; + + hash = tb_jmp_cache_hash_func(env->uc, pc); + tb = cpu->tb_jmp_cache[hash]; + + cflags &= ~CF_CLUSTER_MASK; + cflags |= cpu->cluster_index << CF_CLUSTER_SHIFT; + + if (likely(tb && + tb->pc == pc && + tb->cs_base == cs_base && + tb->flags == flags && + tb->trace_vcpu_dstate == *cpu->trace_dstate && + (tb_cflags(tb) & (CF_HASH_MASK | CF_INVALID)) == cflags)) { + return tb; + } + + tb = tb_htable_lookup(cpu, pc, cs_base, flags, cflags); + cpu->tb_jmp_cache[hash] = tb; + + if (tb != NULL) { + return tb; + } + + if (tb == NULL) { + mmap_lock(); + tb = tb_gen_code(cpu, pc, cs_base, flags, cflags); + mmap_unlock(); + /* We add the TB in the virtual pc hash table for the fast lookup */ + cpu->tb_jmp_cache[hash] = tb; + } + + return tb; +} + /* Must be called before using the QEMU cpus. 'tb_size' is the size (in bytes) allocated to the translation buffer. Zero means default size. */ @@ -1000,6 +1093,9 @@ void tcg_exec_init(struct uc_struct *uc, unsigned long tb_size) tcg_prologue_init(uc->tcg_ctx); /* cpu_interrupt_handler is not used in uc1 */ uc->l1_map = g_malloc0(sizeof(void *) * V_L1_MAX_SIZE); + /* Invalidate / Cache TBs */ + uc->uc_invalidate_tb = uc_invalidate_tb; + uc->uc_gen_tb = uc_gen_tb; } /* call with @p->lock held */ diff --git a/qemu/accel/tcg/translator.c b/qemu/accel/tcg/translator.c index 0fca28a9..9ed272fd 100644 --- a/qemu/accel/tcg/translator.c +++ b/qemu/accel/tcg/translator.c @@ -58,7 +58,7 @@ void translator_loop(const TranslatorOps *ops, DisasContextBase *db, /* Unicorn: early check to see if the address of this block is * the "run until" address. */ - if (tb->pc == cpu->uc->addr_end) { + if (uc_addr_is_exit(uc, tb->pc)) { // This should catch that instruction is at the end // and generate appropriate halting code. gen_tb_start(tcg_ctx, db->tb); diff --git a/qemu/softmmu/cpus.c b/qemu/softmmu/cpus.c index 12cb84e4..c318b855 100644 --- a/qemu/softmmu/cpus.c +++ b/qemu/softmmu/cpus.c @@ -171,10 +171,28 @@ void cpu_stop_current(struct uc_struct *uc) } } + +// Unicorn: Why addr - 1? +// 0: INC ecx +// 1: DEC edx <--- We put exit here, then the range of TB is [0, 1) +// +// While tb_invalidate_phys_range invalides [start, end) +// +// This function is designed to used with g_tree_foreach +static inline gboolean uc_exit_invalidate_iter(gpointer key, gpointer val, gpointer data) { + uint64_t exit = *((uint64_t*)key); + uc_engine* uc = (uc_engine*)data; + + if (exit != 0) { + uc->uc_invalidate_tb(uc, exit - 1, 1); + } + + return false; +} + void resume_all_vcpus(struct uc_struct* uc) { CPUState *cpu = uc->cpu; - tb_page_addr_t start, end; cpu->halted = 0; cpu->exit_request = 0; cpu->exception_index = -1; @@ -187,23 +205,11 @@ void resume_all_vcpus(struct uc_struct* uc) } } - // clear the cache of the addr_end address, since the generated code + // clear the cache of the exits address, since the generated code // at that address is to exit emulation, but not for the instruction there. // if we dont do this, next time we cannot emulate at that address - if (uc->addr_end != 0) { - // GVA to GPA (GPA -> HVA via page_find, HVA->HPA via host mmu) - end = get_page_addr_code(uc->cpu->env_ptr, uc->addr_end); - // For 32bit target. - start = (end - 1) & (target_ulong)(-1); - end = end & (target_ulong)(-1); - // Unicorn: Why start - 1? - // 0: INC ecx - // 1: DEC edx <--- We put exit here, then the range of TB is [0, 1) - // - // While tb_invalidate_phys_range invalides [start, end) - tb_invalidate_phys_range(uc, start, end); - } + g_tree_foreach(uc->exits, uc_exit_invalidate_iter, (void*)uc); cpu->created = false; } diff --git a/qemu/target/arm/translate-a64.c b/qemu/target/arm/translate-a64.c index a9eb1ca0..914b382b 100644 --- a/qemu/target/arm/translate-a64.c +++ b/qemu/target/arm/translate-a64.c @@ -14637,7 +14637,7 @@ static void aarch64_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) CPUARMState *env = cpu->env_ptr; // Unicorn: end address tells us to stop emulation - if (dcbase->pc_next == dc->uc->addr_end) { + if (uc_addr_is_exit(dc->uc, dcbase->pc_next)) { // imitate WFI instruction to halt emulation dcbase->is_jmp = DISAS_WFI; } else { diff --git a/qemu/target/arm/translate.c b/qemu/target/arm/translate.c index 742a55fd..efdfc4b4 100644 --- a/qemu/target/arm/translate.c +++ b/qemu/target/arm/translate.c @@ -11421,7 +11421,7 @@ static void arm_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) } // Unicorn: end address tells us to stop emulation - if (dcbase->pc_next == dc->uc->addr_end) { + if (uc_addr_is_exit(dc->uc, dcbase->pc_next)) { // imitate WFI instruction to halt emulation dcbase->is_jmp = DISAS_WFI; } else { @@ -11499,7 +11499,7 @@ static void thumb_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) } // Unicorn: end address tells us to stop emulation - if (dcbase->pc_next == uc->addr_end) { + if (uc_addr_is_exit(uc, dcbase->pc_next)) { // imitate WFI instruction to halt emulation dcbase->is_jmp = DISAS_WFI; return; diff --git a/qemu/target/i386/translate.c b/qemu/target/i386/translate.c index cf148c4b..8616b12f 100644 --- a/qemu/target/i386/translate.c +++ b/qemu/target/i386/translate.c @@ -4765,7 +4765,7 @@ static target_ulong disas_insn(DisasContext *s, CPUState *cpu) s->uc = env->uc; // Unicorn: end address tells us to stop emulation - if (s->pc == s->uc->addr_end) { + if (uc_addr_is_exit(env->uc, s->pc)) { // imitate the HLT instruction gen_update_cc_op(s); gen_jmp_im(s, pc_start - s->cs_base); diff --git a/qemu/target/m68k/translate.c b/qemu/target/m68k/translate.c index 323eff44..c447a905 100644 --- a/qemu/target/m68k/translate.c +++ b/qemu/target/m68k/translate.c @@ -6326,7 +6326,7 @@ static void m68k_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) uint16_t insn; // Unicorn: end address tells us to stop emulation - if (dc->pc == uc->addr_end) { + if (uc_addr_is_exit(uc, dc->pc)) { gen_exception(dc, dc->pc, EXCP_HLT); return; } diff --git a/qemu/target/mips/translate.c b/qemu/target/mips/translate.c index 97e680a3..e9fceaf9 100644 --- a/qemu/target/mips/translate.c +++ b/qemu/target/mips/translate.c @@ -30932,7 +30932,7 @@ static void mips_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) is_slot = ctx->hflags & MIPS_HFLAG_BMASK; // Unicorn: end address tells us to stop emulation - if (ctx->base.pc_next == uc->addr_end) { + if (uc_addr_is_exit(uc, ctx->base.pc_next)) { // raise a special interrupt to quit gen_helper_wait(tcg_ctx, tcg_ctx->cpu_env); ctx->base.is_jmp = DISAS_NORETURN; diff --git a/qemu/target/ppc/translate.c b/qemu/target/ppc/translate.c index 8cfebab2..fec05d19 100644 --- a/qemu/target/ppc/translate.c +++ b/qemu/target/ppc/translate.c @@ -7626,7 +7626,7 @@ static void ppc_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) ctx->base.pc_next, ctx->mem_idx, (int)msr_ir); // Unicorn: end address tells us to stop emulation - if (ctx->base.pc_next == uc->addr_end) { + if (uc_addr_is_exit(uc, ctx->base.pc_next)) { gen_wait(ctx); return; } diff --git a/qemu/target/riscv/translate.c b/qemu/target/riscv/translate.c index 9898093d..e256958f 100644 --- a/qemu/target/riscv/translate.c +++ b/qemu/target/riscv/translate.c @@ -850,7 +850,7 @@ static void riscv_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) bool insn_hook = false; // Unicorn: end address tells us to stop emulation - if (ctx->base.pc_next == ctx->uc->addr_end) { + if (uc_addr_is_exit(uc, ctx->base.pc_next)) { // Unicorn: We have to exit current execution here. dcbase->is_jmp = DISAS_UC_EXIT; } else { diff --git a/qemu/target/sparc/translate.c b/qemu/target/sparc/translate.c index c6f3d9cd..8c2a8b40 100644 --- a/qemu/target/sparc/translate.c +++ b/qemu/target/sparc/translate.c @@ -5951,7 +5951,7 @@ static void sparc_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) unsigned int insn; // Unicorn: end address tells us to stop emulation - if (dc->pc == uc->addr_end) { + if (uc_addr_is_exit(uc, dc->pc)) { #ifndef TARGET_SPARC64 gen_helper_power_down(tcg_ctx, tcg_ctx->cpu_env); #endif diff --git a/samples/sample_ctl.c b/samples/sample_ctl.c new file mode 100644 index 00000000..543a6e6a --- /dev/null +++ b/samples/sample_ctl.c @@ -0,0 +1,76 @@ +/* Unicorn Emulator Engine */ +/* By Lazymio(@wtdcode), 2021 */ + +/* Sample code to demonstrate how to use uc_ctl */ + +#include +#include + +// code to be emulated +// code to be emulated +#define X86_CODE32 "\x41\x4a" // INC ecx; DEC edx; PXOR xmm0, xmm1 + +// memory address where emulation starts +#define ADDRESS 0x10000 + +static void test_uc_ctl_read(void) +{ + uc_engine *uc; + uc_err err; + uint32_t tmp; + uc_hook trace1, trace2; + int mode, arch; + uint32_t pagesize; + uint64_t timeout; + + int r_ecx = 0x1234; // ECX register + int r_edx = 0x7890; // EDX register + + printf("Reading some properties by uc_ctl.\n"); + + // Initialize emulator in X86-32bit mode + err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc); + if (err) { + printf("Failed on uc_open() with error returned: %u\n", err); + return; + } + + // Let's query some properties by uc_ctl. + // Note uc_ctl_* is just tiny macro wrappers for uc_ctl(). + err = uc_ctl_get_mode(uc, &mode); + if (err) { + printf("Failed on uc_ctl() with error returned: %u\n", err); + return; + } + + err = uc_ctl_get_arch(uc, &arch); + if (err) { + printf("Failed on uc_ctl() with error returned: %u\n", err); + return; + } + + err = uc_ctl_get_timeout(uc, &timeout); + if (err) { + printf("Failed on uc_ctl() with error returned: %u\n", err); + return; + } + + err = uc_ctl_get_page_size(uc, &pagesize); + if (err) { + printf("Failed on uc_ctl() with error returned: %u\n", err); + return; + } + + printf(">>> mode = %d, arch = %d, timeout=%" PRIu64 ", pagesize=%" PRIu32 + "\n", + mode, arch, timeout, pagesize); + + uc_close(uc); +} + +int main(int argc, char **argv, char **envp) +{ + test_uc_ctl_read(); + + return 0; +} diff --git a/tests/unit/test_ctl.c b/tests/unit/test_ctl.c new file mode 100644 index 00000000..f2f66625 --- /dev/null +++ b/tests/unit/test_ctl.c @@ -0,0 +1,25 @@ +#include "unicorn_test.h" + +const uint64_t code_start = 0x1000; +const uint64_t code_len = 0x4000; + +#define GEN_SIMPLE_READ_TEST(field, ctl_type, arg_type, expected) \ + static void test_uc_ctl_##field() \ + { \ + uc_engine *uc; \ + arg_type arg; \ + OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc)); \ + OK(uc_ctl(uc, UC_CTL_READ(ctl_type, 1), &arg)); \ + TEST_CHECK(arg == expected); \ + } + +GEN_SIMPLE_READ_TEST(mode, UC_CTL_UC_MODE, int, 4) +GEN_SIMPLE_READ_TEST(arch, UC_CTL_UC_ARCH, int, 4) +GEN_SIMPLE_READ_TEST(page_size, UC_CTL_UC_PAGE_SIZE, uint32_t, 4096) +GEN_SIMPLE_READ_TEST(time_out, UC_CTL_UC_TIMEOUT, uint64_t, 0) + +TEST_LIST = {{"test_uc_ctl_mode", test_uc_ctl_mode}, + {"test_uc_ctl_page_size", test_uc_ctl_page_size}, + {"test_uc_ctl_arch", test_uc_ctl_arch}, + {"test_uc_ctl_time_out", test_uc_ctl_time_out}, + {NULL, NULL}}; \ No newline at end of file diff --git a/uc.c b/uc.c index 8dd778cd..3d765be4 100644 --- a/uc.c +++ b/uc.c @@ -136,6 +136,20 @@ bool uc_arch_supported(uc_arch arch) } } +static gint uc_exits_cmp(gconstpointer a, gconstpointer b, gpointer user_data) +{ + uint64_t lhs = *((uint64_t *)a); + uint64_t rhs = *((uint64_t *)b); + + if (lhs < rhs) { + return -1; + } else if (lhs == rhs) { + return 0; + } else { + return 1; + } +} + UNICORN_EXPORT uc_err uc_open(uc_arch arch, uc_mode mode, uc_engine **result) { @@ -161,6 +175,8 @@ uc_err uc_open(uc_arch arch, uc_mode mode, uc_engine **result) QTAILQ_INIT(&uc->address_spaces); + uc->exits = g_tree_new_full(uc_exits_cmp, NULL, g_free, NULL); + switch (arch) { default: break; @@ -396,6 +412,8 @@ uc_err uc_close(uc_engine *uc) } list_clear(&uc->saved_contexts); + g_tree_destroy(uc->exits); + // finally, free uc itself. memset(uc, 0, sizeof(*uc)); free(uc); @@ -735,7 +753,10 @@ uc_err uc_emu_start(uc_engine *uc, uint64_t begin, uint64_t until, } } - uc->addr_end = until; + // This is low efficiency for compatibility. + // Consider a new uc_ctl to set not updating uc->exists in uc_emu_start? + g_tree_remove_all(uc->exits); + uc_add_exit(uc, until); if (timeout) { enable_emu_timer(uc, timeout * 1000); // microseconds -> nanoseconds @@ -1525,12 +1546,6 @@ uc_err uc_query(uc_engine *uc, uc_query_type type, size_t *result) return UC_ERR_OK; } -UNICORN_EXPORT -uc_err uc_ctl(uc_engine *uc, uc_control_type option, ...) -{ - return UC_ERR_ARG; -} - UNICORN_EXPORT uc_err uc_context_alloc(uc_engine *uc, uc_context **context) { @@ -1762,3 +1777,157 @@ uc_err uc_context_free(uc_context *context) } return uc_free(context); } + +typedef struct _uc_ctl_exit_request { + uint64_t *array; + size_t len; +} uc_ctl_exit_request; + +static inline gboolean uc_read_exit_iter(gpointer key, gpointer val, + gpointer data) +{ + uc_ctl_exit_request *req = (uc_ctl_exit_request *)data; + + req->array[req->len++] = *(uint64_t *)key; + + return false; +} + +UNICORN_EXPORT +uc_err uc_ctl(uc_engine *uc, uc_control_type control, ...) +{ + int rw, type; + uc_err err = UC_ERR_OK; + va_list args; + + rw = control >> 30; + type = (control & ((1 << 16) - 1)); + va_start(args, control); + + switch (type) { + case UC_CTL_UC_MODE: { + if (rw == UC_CTL_IO_READ) { + int *pmode = va_arg(args, int *); + *pmode = uc->mode; + } else { + err = UC_ERR_ARG; + } + break; + } + + case UC_CTL_UC_ARCH: { + if (rw == UC_CTL_IO_READ) { + int *arch = va_arg(args, int *); + *arch = uc->arch; + } else { + err = UC_ERR_ARG; + } + break; + } + + case UC_CTL_UC_TIMEOUT: { + if (rw == UC_CTL_IO_READ) { + size_t *arch = va_arg(args, size_t *); + *arch = uc->timeout; + } else { + err = UC_ERR_ARG; + } + break; + } + + case UC_CTL_UC_PAGE_SIZE: { + if (rw == UC_CTL_IO_READ) { + uint32_t *page_size = va_arg(args, uint32_t *); + *page_size = uc->target_page_size; + } else { + // Not implemented. + err = UC_ERR_ARG; + } + break; + } + case UC_CTL_UC_USE_EXITS: { + if (rw == UC_CTL_IO_WRITE) { + int use_exit = va_arg(args, int); + uc->use_exit = use_exit; + } else { + err = UC_ERR_ARG; + } + break; + } + + case UC_CTL_UC_EXITS_CNT: { + if (!uc->use_exit) { + err = UC_ERR_ARG; + } else if (rw == UC_CTL_IO_READ) { + size_t *exits_cnt = va_arg(args, size_t *); + *exits_cnt = g_tree_nnodes(uc->exits); + } else { + err = UC_ERR_ARG; + } + break; + } + + case UC_CTL_UC_EXITS: { + if (!uc->use_exit) { + err = UC_ERR_ARG; + } else if (rw == UC_CTL_IO_READ) { + uint64_t *exits = va_arg(args, uint64_t *); + size_t cnt = va_arg(args, size_t); + if (cnt < g_tree_nnodes(uc->exits)) { + err = UC_ERR_ARG; + } else { + uc_ctl_exit_request req; + req.array = exits; + req.len = 0; + + g_tree_foreach(uc->exits, uc_read_exit_iter, (void *)&req); + } + } else if (rw == UC_CTL_IO_WRITE) { + uint64_t *exits = va_arg(args, uint64_t *); + size_t cnt = va_arg(args, size_t); + + g_tree_remove_all(uc->exits); + + for (size_t i = 0; i < cnt; i++) { + uc_add_exit(uc, exits[i]); + } + } else { + err = UC_ERR_ARG; + } + break; + } + + case UC_CTL_CPU_MODEL: + // Not implemented. + err = UC_ERR_ARG; + break; + + case UC_CTL_TB_REQUEST_CACHE: { + if (rw == UC_CTL_IO_READ) { + uint64_t addr = va_arg(args, uint64_t); + uc->uc_gen_tb(uc, addr); + } else { + err = UC_ERR_ARG; + } + break; + } + + case UC_CTL_TB_REMOVE_CACHE: { + if (rw == UC_CTL_IO_READ) { + uint64_t addr = va_arg(args, uint64_t); + uc->uc_invalidate_tb(uc, addr, 1); + } else { + err = UC_ERR_ARG; + } + break; + } + + default: + err = UC_ERR_ARG; + break; + } + + va_end(args); + + return err; +} \ No newline at end of file