Basic implementation of uc_ctl
This commit is contained in:
parent
b07baede63
commit
3dd2e0f95d
@ -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}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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: */
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
76
samples/sample_ctl.c
Normal file
76
samples/sample_ctl.c
Normal file
@ -0,0 +1,76 @@
|
||||
/* Unicorn Emulator Engine */
|
||||
/* By Lazymio(@wtdcode), 2021 */
|
||||
|
||||
/* Sample code to demonstrate how to use uc_ctl */
|
||||
|
||||
#include <unicorn/unicorn.h>
|
||||
#include <string.h>
|
||||
|
||||
// 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;
|
||||
}
|
25
tests/unit/test_ctl.c
Normal file
25
tests/unit/test_ctl.c
Normal file
@ -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}};
|
183
uc.c
183
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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user