Basic implementation of uc_ctl

This commit is contained in:
lazymio 2021-11-01 00:39:36 +01:00
parent b07baede63
commit 3dd2e0f95d
No known key found for this signature in database
GPG Key ID: DFF27E34A47CB873
19 changed files with 459 additions and 52 deletions

View File

@ -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}

View File

@ -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;

View File

@ -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);

View File

@ -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: */

View File

@ -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;

View File

@ -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 */

View File

@ -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);

View File

@ -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;
}

View File

@ -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 {

View File

@ -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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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 {

View File

@ -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
View 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
View 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
View File

@ -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;
}