Files
favicon-trap/uc.c
farmdve 036763d6ae Fix memory leaks as reported by DrMemory and Valgrind.
ARM and probably the rest of the arches have significant memory leaks as
they have no release interface.

Additionally, DrMemory does not have 64-bit support and thus I can't
test the 64-bit version under Windows. Under Linux valgrind supports
both 32-bit and 64-bit but there are different macros and code for Linux
and Windows.
2016-01-08 01:42:56 +02:00

1120 lines
31 KiB
C

/* Unicorn Emulator Engine */
/* By Nguyen Anh Quynh <aquynh@gmail.com>, 2015 */
#if defined (WIN32) || defined (WIN64) || defined (_WIN32) || defined (_WIN64)
#pragma warning(disable:4996)
#endif
#if defined(UNICORN_HAS_OSXKERNEL)
#include <libkern/libkern.h>
#else
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#endif
#include <time.h> // nanosleep
#include <string.h>
#ifndef _WIN32
#include <sys/mman.h>
#endif
#include "uc_priv.h"
#include "hook.h"
// target specific headers
#include "qemu/target-m68k/unicorn.h"
#include "qemu/target-i386/unicorn.h"
#include "qemu/target-arm/unicorn.h"
#include "qemu/target-mips/unicorn.h"
#include "qemu/target-sparc/unicorn.h"
#include "qemu/include/hw/boards.h"
UNICORN_EXPORT
unsigned int uc_version(unsigned int *major, unsigned int *minor)
{
if (major != NULL && minor != NULL) {
*major = UC_API_MAJOR;
*minor = UC_API_MINOR;
}
return (UC_API_MAJOR << 8) + UC_API_MINOR;
}
UNICORN_EXPORT
uc_err uc_errno(uc_engine *uc)
{
return uc->errnum;
}
UNICORN_EXPORT
const char *uc_strerror(uc_err code)
{
switch(code) {
default:
return "Unknown error code";
case UC_ERR_OK:
return "OK (UC_ERR_OK)";
case UC_ERR_NOMEM:
return "No memory available or memory not present (UC_ERR_NOMEM)";
case UC_ERR_ARCH:
return "Invalid/unsupported architecture (UC_ERR_ARCH)";
case UC_ERR_HANDLE:
return "Invalid handle (UC_ERR_HANDLE)";
case UC_ERR_MODE:
return "Invalid mode (UC_ERR_MODE)";
case UC_ERR_VERSION:
return "Different API version between core & binding (UC_ERR_VERSION)";
case UC_ERR_READ_UNMAPPED:
return "Invalid memory read (UC_ERR_READ_UNMAPPED)";
case UC_ERR_WRITE_UNMAPPED:
return "Invalid memory write (UC_ERR_WRITE_UNMAPPED)";
case UC_ERR_FETCH_UNMAPPED:
return "Invalid memory fetch (UC_ERR_FETCH_UNMAPPED)";
case UC_ERR_HOOK:
return "Invalid hook type (UC_ERR_HOOK)";
case UC_ERR_INSN_INVALID:
return "Invalid instruction (UC_ERR_INSN_INVALID)";
case UC_ERR_MAP:
return "Invalid memory mapping (UC_ERR_MAP)";
case UC_ERR_WRITE_PROT:
return "Write to write-protected memory (UC_ERR_WRITE_PROT)";
case UC_ERR_READ_PROT:
return "Read from non-readable memory (UC_ERR_READ_PROT)";
case UC_ERR_FETCH_PROT:
return "Fetch from non-executable memory (UC_ERR_FETCH_PROT)";
case UC_ERR_ARG:
return "Invalid argument (UC_ERR_ARG)";
case UC_ERR_READ_UNALIGNED:
return "Read from unaligned memory (UC_ERR_READ_UNALIGNED)";
case UC_ERR_WRITE_UNALIGNED:
return "Write to unaligned memory (UC_ERR_WRITE_UNALIGNED)";
case UC_ERR_FETCH_UNALIGNED:
return "Fetch from unaligned memory (UC_ERR_FETCH_UNALIGNED)";
case UC_ERR_HOOK_EXIST:
return "Hook for this type event already exists (UC_ERR_HOOK_EXIST)";
case UC_ERR_RESOURCE:
return "Insufficient resource (UC_ERR_RESOURCE)";
}
}
UNICORN_EXPORT
bool uc_arch_supported(uc_arch arch)
{
switch (arch) {
#ifdef UNICORN_HAS_ARM
case UC_ARCH_ARM: return true;
#endif
#ifdef UNICORN_HAS_ARM64
case UC_ARCH_ARM64: return true;
#endif
#ifdef UNICORN_HAS_M68K
case UC_ARCH_M68K: return true;
#endif
#ifdef UNICORN_HAS_MIPS
case UC_ARCH_MIPS: return true;
#endif
#ifdef UNICORN_HAS_PPC
case UC_ARCH_PPC: return true;
#endif
#ifdef UNICORN_HAS_SPARC
case UC_ARCH_SPARC: return true;
#endif
#ifdef UNICORN_HAS_X86
case UC_ARCH_X86: return true;
#endif
/* Invalid or disabled arch */
default: return false;
}
}
UNICORN_EXPORT
uc_err uc_open(uc_arch arch, uc_mode mode, uc_engine **result)
{
struct uc_struct *uc;
if (arch < UC_ARCH_MAX) {
uc = calloc(1, sizeof(*uc));
if (!uc) {
// memory insufficient
return UC_ERR_NOMEM;
}
uc->errnum = UC_ERR_OK;
uc->arch = arch;
uc->mode = mode;
// uc->cpus = QTAILQ_HEAD_INITIALIZER(uc->cpus);
uc->cpus.tqh_first = NULL;
uc->cpus.tqh_last = &(uc->cpus.tqh_first);
// uc->ram_list = { .blocks = QTAILQ_HEAD_INITIALIZER(ram_list.blocks) };
uc->ram_list.blocks.tqh_first = NULL;
uc->ram_list.blocks.tqh_last = &(uc->ram_list.blocks.tqh_first);
uc->x86_global_cpu_lock = SPIN_LOCK_UNLOCKED;
uc->memory_listeners.tqh_first = NULL;
uc->memory_listeners.tqh_last = &uc->memory_listeners.tqh_first;
uc->address_spaces.tqh_first = NULL;
uc->address_spaces.tqh_last = &uc->address_spaces.tqh_first;
switch(arch) {
default:
break;
#ifdef UNICORN_HAS_M68K
case UC_ARCH_M68K:
uc->init_arch = m68k_uc_init;
break;
#endif
#ifdef UNICORN_HAS_X86
case UC_ARCH_X86:
uc->init_arch = x86_uc_init;
break;
#endif
#ifdef UNICORN_HAS_ARM
case UC_ARCH_ARM:
uc->init_arch = arm_uc_init;
// verify mode
if (mode != UC_MODE_ARM && mode != UC_MODE_THUMB) {
free(uc);
return UC_ERR_MODE;
}
if (mode == UC_MODE_THUMB)
uc->thumb = 1;
break;
#endif
#ifdef UNICORN_HAS_ARM64
case UC_ARCH_ARM64:
uc->init_arch = arm64_uc_init;
break;
#endif
#if defined(UNICORN_HAS_MIPS) || defined(UNICORN_HAS_MIPSEL) || defined(UNICORN_HAS_MIPS64) || defined(UNICORN_HAS_MIPS64EL)
case UC_ARCH_MIPS:
if (mode & UC_MODE_BIG_ENDIAN) {
#ifdef UNICORN_HAS_MIPS
if (mode & UC_MODE_MIPS32)
uc->init_arch = mips_uc_init;
#endif
#ifdef UNICORN_HAS_MIPS64
if (mode & UC_MODE_MIPS64)
uc->init_arch = mips64_uc_init;
#endif
} else { // little endian
#ifdef UNICORN_HAS_MIPSEL
if (mode & UC_MODE_MIPS32)
uc->init_arch = mipsel_uc_init;
#endif
#ifdef UNICORN_HAS_MIPS64EL
if (mode & UC_MODE_MIPS64)
uc->init_arch = mips64el_uc_init;
#endif
}
break;
#endif
#ifdef UNICORN_HAS_SPARC
case UC_ARCH_SPARC:
if (mode & UC_MODE_64)
uc->init_arch = sparc64_uc_init;
else
uc->init_arch = sparc_uc_init;
break;
#endif
}
if (uc->init_arch == NULL) {
return UC_ERR_ARCH;
}
if (machine_initialize(uc))
return UC_ERR_RESOURCE;
*result = uc;
if (uc->reg_reset)
uc->reg_reset(uc);
uc->hook_size = HOOK_SIZE;
uc->hook_callbacks = calloc(1, sizeof(uc->hook_callbacks[0]) * HOOK_SIZE);
return UC_ERR_OK;
} else {
return UC_ERR_ARCH;
}
}
UNICORN_EXPORT
uc_err uc_close(uc_engine *uc)
{
int i;
if (uc->release)
uc->release(uc->tcg_ctx);
free(uc->l1_map);
if (uc->bounce.buffer) {
free(uc->bounce.buffer);
}
g_free(uc->tcg_ctx);
g_hash_table_destroy(uc->type_table);
for (i = 0; i < DIRTY_MEMORY_NUM; i++) {
free(uc->ram_list.dirty_memory[i]);
}
// TODO: remove uc->root (created with object_new())
//uc->root->free(uc->root);
free(uc->hook_callbacks);
free(uc->mapped_blocks);
// finally, free uc itself.
memset(uc, 0, sizeof(*uc));
free(uc);
return UC_ERR_OK;
}
UNICORN_EXPORT
uc_err uc_reg_read(uc_engine *uc, int regid, void *value)
{
if (uc->reg_read)
uc->reg_read(uc, regid, value);
else
return -1; // FIXME: need a proper uc_err
return UC_ERR_OK;
}
UNICORN_EXPORT
uc_err uc_reg_write(uc_engine *uc, int regid, const void *value)
{
if (uc->reg_write)
uc->reg_write(uc, regid, value);
else
return -1; // FIXME: need a proper uc_err
return UC_ERR_OK;
}
// check if a memory area is mapped
// this is complicated because an area can overlap adjacent blocks
static bool check_mem_area(uc_engine *uc, uint64_t address, size_t size)
{
size_t count = 0, len;
while(count < size) {
MemoryRegion *mr = memory_mapping(uc, address);
if (mr) {
len = MIN(size - count, mr->end - address);
count += len;
address += len;
} else // this address is not mapped in yet
break;
}
return (count == size);
}
UNICORN_EXPORT
uc_err uc_mem_read(uc_engine *uc, uint64_t address, void *_bytes, size_t size)
{
size_t count = 0, len;
uint8_t *bytes = _bytes;
if (uc->mem_redirect) {
address = uc->mem_redirect(address);
}
if (!check_mem_area(uc, address, size))
return UC_ERR_READ_UNMAPPED;
// memory area can overlap adjacent memory blocks
while(count < size) {
MemoryRegion *mr = memory_mapping(uc, address);
if (mr) {
len = MIN(size - count, mr->end - address);
if (uc->read_mem(&uc->as, address, bytes, len) == false)
break;
count += len;
address += len;
bytes += len;
} else // this address is not mapped in yet
break;
}
if (count == size)
return UC_ERR_OK;
else
return UC_ERR_READ_UNMAPPED;
}
UNICORN_EXPORT
uc_err uc_mem_write(uc_engine *uc, uint64_t address, const void *_bytes, size_t size)
{
size_t count = 0, len;
const uint8_t *bytes = _bytes;
if (uc->mem_redirect) {
address = uc->mem_redirect(address);
}
if (!check_mem_area(uc, address, size))
return UC_ERR_WRITE_UNMAPPED;
// memory area can overlap adjacent memory blocks
while(count < size) {
MemoryRegion *mr = memory_mapping(uc, address);
if (mr) {
uint32_t operms = mr->perms;
if (!(operms & UC_PROT_WRITE)) // write protected
// but this is not the program accessing memory, so temporarily mark writable
uc->readonly_mem(mr, false);
len = MIN(size - count, mr->end - address);
if (uc->write_mem(&uc->as, address, bytes, len) == false)
break;
if (!(operms & UC_PROT_WRITE)) // write protected
// now write protect it again
uc->readonly_mem(mr, true);
count += len;
address += len;
bytes += len;
} else // this address is not mapped in yet
break;
}
if (count == size)
return UC_ERR_OK;
else
return UC_ERR_WRITE_UNMAPPED;
}
#define TIMEOUT_STEP 2 // microseconds
static void *_timeout_fn(void *arg)
{
struct uc_struct *uc = arg;
int64_t current_time = get_clock();
do {
usleep(TIMEOUT_STEP);
// perhaps emulation is even done before timeout?
if (uc->emulation_done)
break;
} while(get_clock() - current_time < uc->timeout);
// timeout before emulation is done?
if (!uc->emulation_done) {
// force emulation to stop
uc_emu_stop(uc);
}
return NULL;
}
static void enable_emu_timer(uc_engine *uc, uint64_t timeout)
{
uc->timeout = timeout;
qemu_thread_create(uc, &uc->timer, "timeout", _timeout_fn,
uc, QEMU_THREAD_JOINABLE);
}
UNICORN_EXPORT
uc_err uc_emu_start(uc_engine* uc, uint64_t begin, uint64_t until, uint64_t timeout, size_t count)
{
// reset the counter
uc->emu_counter = 0;
uc->stop_request = false;
uc->invalid_error = UC_ERR_OK;
uc->block_full = false;
uc->emulation_done = false;
switch(uc->arch) {
default:
break;
case UC_ARCH_M68K:
uc_reg_write(uc, UC_M68K_REG_PC, &begin);
break;
case UC_ARCH_X86:
switch(uc->mode) {
default:
break;
case UC_MODE_16:
uc_reg_write(uc, UC_X86_REG_IP, &begin);
break;
case UC_MODE_32:
uc_reg_write(uc, UC_X86_REG_EIP, &begin);
break;
case UC_MODE_64:
uc_reg_write(uc, UC_X86_REG_RIP, &begin);
break;
}
break;
case UC_ARCH_ARM:
switch(uc->mode) {
default:
break;
case UC_MODE_THUMB:
case UC_MODE_ARM:
uc_reg_write(uc, UC_ARM_REG_R15, &begin);
break;
}
break;
case UC_ARCH_ARM64:
uc_reg_write(uc, UC_ARM64_REG_PC, &begin);
break;
case UC_ARCH_MIPS:
// TODO: MIPS32/MIPS64/BIGENDIAN etc
uc_reg_write(uc, UC_MIPS_REG_PC, &begin);
break;
case UC_ARCH_SPARC:
// TODO: Sparc/Sparc64
uc_reg_write(uc, UC_SPARC_REG_PC, &begin);
break;
}
uc->emu_count = count;
if (count > 0) {
uc->hook_insn = true;
}
uc->addr_end = until;
if (uc->vm_start(uc)) {
return UC_ERR_RESOURCE;
}
if (timeout)
enable_emu_timer(uc, timeout * 1000); // microseconds -> nanoseconds
uc->pause_all_vcpus(uc);
// emulation is done
uc->emulation_done = true;
if (timeout) {
// wait for the timer to finish
qemu_thread_join(uc, &uc->timer);
}
return uc->invalid_error;
}
UNICORN_EXPORT
uc_err uc_emu_stop(uc_engine *uc)
{
if (uc->emulation_done)
return UC_ERR_OK;
uc->stop_request = true;
if (uc->current_cpu) {
// exit the current TB
cpu_exit(uc->current_cpu);
}
return UC_ERR_OK;
}
static int _hook_code(uc_engine *uc, int type, uint64_t begin, uint64_t end,
void *callback, void *user_data, uc_hook *hh)
{
int i;
i = hook_add(uc, type, begin, end, callback, user_data);
if (i == 0)
return UC_ERR_NOMEM;
*hh = i;
return UC_ERR_OK;
}
static uc_err _hook_mem_access(uc_engine *uc, uc_hook_type type,
uint64_t begin, uint64_t end,
void *callback, void *user_data, uc_hook *hh)
{
int i;
i = hook_add(uc, type, begin, end, callback, user_data);
if (i == 0)
return UC_ERR_NOMEM;
*hh = i;
return UC_ERR_OK;
}
// find if a memory range overlaps with existing mapped regions
static bool memory_overlap(struct uc_struct *uc, uint64_t begin, size_t size)
{
unsigned int i;
uint64_t end = begin + size - 1;
for(i = 0; i < uc->mapped_block_count; i++) {
// begin address falls inside this region?
if (begin >= uc->mapped_blocks[i]->addr && begin <= uc->mapped_blocks[i]->end - 1)
return true;
// end address falls inside this region?
if (end >= uc->mapped_blocks[i]->addr && end <= uc->mapped_blocks[i]->end - 1)
return true;
// this region falls totally inside this range?
if (begin < uc->mapped_blocks[i]->addr && end > uc->mapped_blocks[i]->end - 1)
return true;
}
// not found
return false;
}
// common setup/error checking shared between uc_mem_map and uc_mem_map_ptr
static uc_err mem_map(uc_engine *uc, uint64_t address, size_t size, uint32_t perms, MemoryRegion *block)
{
MemoryRegion **regions;
if (size == 0)
// invalid memory mapping
return UC_ERR_ARG;
// address cannot wrapp around
if (address + size - 1 < address)
return UC_ERR_ARG;
// address must be aligned to uc->target_page_size
if ((address & uc->target_page_align) != 0)
return UC_ERR_ARG;
// size must be multiple of uc->target_page_size
if ((size & uc->target_page_align) != 0)
return UC_ERR_ARG;
// check for only valid permissions
if ((perms & ~UC_PROT_ALL) != 0)
return UC_ERR_ARG;
// this area overlaps existing mapped regions?
if (memory_overlap(uc, address, size))
return UC_ERR_MAP;
if (block == NULL)
return UC_ERR_NOMEM;
if ((uc->mapped_block_count & (MEM_BLOCK_INCR - 1)) == 0) { //time to grow
regions = (MemoryRegion**)realloc(uc->mapped_blocks,
sizeof(MemoryRegion*) * (uc->mapped_block_count + MEM_BLOCK_INCR));
if (regions == NULL) {
return UC_ERR_NOMEM;
}
uc->mapped_blocks = regions;
}
uc->mapped_blocks[uc->mapped_block_count] = block;
uc->mapped_block_count++;
return UC_ERR_OK;
}
UNICORN_EXPORT
uc_err uc_mem_map(uc_engine *uc, uint64_t address, size_t size, uint32_t perms)
{
if (uc->mem_redirect) {
address = uc->mem_redirect(address);
}
return mem_map(uc, address, size, perms, uc->memory_map(uc, address, size, perms));
}
UNICORN_EXPORT
uc_err uc_mem_map_ptr(uc_engine *uc, uint64_t address, size_t size, uint32_t perms, void *ptr)
{
if (ptr == NULL)
return UC_ERR_ARG;
if (uc->mem_redirect) {
address = uc->mem_redirect(address);
}
return mem_map(uc, address, size, UC_PROT_ALL, uc->memory_map_ptr(uc, address, size, perms, ptr));
}
// Create a backup copy of the indicated MemoryRegion.
// Generally used in prepartion for splitting a MemoryRegion.
static uint8_t *copy_region(struct uc_struct *uc, MemoryRegion *mr)
{
uint8_t *block = (uint8_t *)malloc(int128_get64(mr->size));
if (block != NULL) {
uc_err err = uc_mem_read(uc, mr->addr, block, int128_get64(mr->size));
if (err != UC_ERR_OK) {
free(block);
block = NULL;
}
}
return block;
}
/*
Split the given MemoryRegion at the indicated address for the indicated size
this may result in the create of up to 3 spanning sections. If the delete
parameter is true, the no new section will be created to replace the indicate
range. This functions exists to support uc_mem_protect and uc_mem_unmap.
This is a static function and callers have already done some preliminary
parameter validation.
The do_delete argument indicates that we are being called to support
uc_mem_unmap. In this case we save some time by choosing NOT to remap
the areas that are intended to get unmapped
*/
// TODO: investigate whether qemu region manipulation functions already offered
// this capability
static bool split_region(struct uc_struct *uc, MemoryRegion *mr, uint64_t address,
size_t size, bool do_delete)
{
uint8_t *backup;
uint32_t perms;
uint64_t begin, end, chunk_end;
size_t l_size, m_size, r_size;
chunk_end = address + size;
// if this region belongs to area [address, address+size],
// then there is no work to do.
if (address <= mr->addr && chunk_end >= mr->end)
return true;
if (size == 0)
// trivial case
return true;
if (address >= mr->end || chunk_end <= mr->addr)
// impossible case
return false;
backup = copy_region(uc, mr);
if (backup == NULL)
return false;
// save the essential information required for the split before mr gets deleted
perms = mr->perms;
begin = mr->addr;
end = mr->end;
// unmap this region first, then do split it later
if (uc_mem_unmap(uc, mr->addr, int128_get64(mr->size)) != UC_ERR_OK)
goto error;
/* overlapping cases
* |------mr------|
* case 1 |---size--|
* case 2 |--size--|
* case 3 |---size--|
*/
// adjust some things
if (address < begin)
address = begin;
if (chunk_end > end)
chunk_end = end;
// compute sub region sizes
l_size = (size_t)(address - begin);
r_size = (size_t)(end - chunk_end);
m_size = (size_t)(chunk_end - address);
// If there are error in any of the below operations, things are too far gone
// at that point to recover. Could try to remap orignal region, but these smaller
// allocation just failed so no guarantee that we can recover the original
// allocation at this point
if (l_size > 0) {
if (uc_mem_map(uc, begin, l_size, perms) != UC_ERR_OK)
goto error;
if (uc_mem_write(uc, begin, backup, l_size) != UC_ERR_OK)
goto error;
}
if (m_size > 0 && !do_delete) {
if (uc_mem_map(uc, address, m_size, perms) != UC_ERR_OK)
goto error;
if (uc_mem_write(uc, address, backup + l_size, m_size) != UC_ERR_OK)
goto error;
}
if (r_size > 0) {
if (uc_mem_map(uc, chunk_end, r_size, perms) != UC_ERR_OK)
goto error;
if (uc_mem_write(uc, chunk_end, backup + l_size + m_size, r_size) != UC_ERR_OK)
goto error;
}
return true;
error:
free(backup);
return false;
}
UNICORN_EXPORT
uc_err uc_mem_protect(struct uc_struct *uc, uint64_t address, size_t size, uint32_t perms)
{
MemoryRegion *mr;
uint64_t addr = address;
size_t count, len;
if (size == 0)
// trivial case, no change
return UC_ERR_OK;
// address must be aligned to uc->target_page_size
if ((address & uc->target_page_align) != 0)
return UC_ERR_ARG;
// size must be multiple of uc->target_page_size
if ((size & uc->target_page_align) != 0)
return UC_ERR_ARG;
// check for only valid permissions
if ((perms & ~UC_PROT_ALL) != 0)
return UC_ERR_ARG;
if (uc->mem_redirect) {
address = uc->mem_redirect(address);
}
// check that user's entire requested block is mapped
if (!check_mem_area(uc, address, size))
return UC_ERR_NOMEM;
// Now we know entire region is mapped, so change permissions
// We may need to split regions if this area spans adjacent regions
addr = address;
count = 0;
while(count < size) {
mr = memory_mapping(uc, addr);
len = MIN(size - count, mr->end - addr);
if (!split_region(uc, mr, addr, len, false))
return UC_ERR_NOMEM;
mr = memory_mapping(uc, addr);
mr->perms = perms;
uc->readonly_mem(mr, (perms & UC_PROT_WRITE) == 0);
count += len;
addr += len;
}
return UC_ERR_OK;
}
UNICORN_EXPORT
uc_err uc_mem_unmap(struct uc_struct *uc, uint64_t address, size_t size)
{
MemoryRegion *mr;
uint64_t addr;
size_t count, len;
if (size == 0)
// nothing to unmap
return UC_ERR_OK;
// address must be aligned to uc->target_page_size
if ((address & uc->target_page_align) != 0)
return UC_ERR_ARG;
// size must be multiple of uc->target_page_size
if ((size & uc->target_page_align) != 0)
return UC_ERR_MAP;
if (uc->mem_redirect) {
address = uc->mem_redirect(address);
}
// check that user's entire requested block is mapped
if (!check_mem_area(uc, address, size))
return UC_ERR_NOMEM;
// Now we know entire region is mapped, so do the unmap
// We may need to split regions if this area spans adjacent regions
addr = address;
count = 0;
while(count < size) {
mr = memory_mapping(uc, addr);
len = MIN(size - count, mr->end - addr);
if (!split_region(uc, mr, addr, len, true))
return UC_ERR_NOMEM;
// if we can retrieve the mapping, then no splitting took place
// so unmap here
mr = memory_mapping(uc, addr);
if (mr != NULL)
uc->memory_unmap(uc, mr);
count += len;
addr += len;
}
return UC_ERR_OK;
}
// find the memory region of this address
MemoryRegion *memory_mapping(struct uc_struct* uc, uint64_t address)
{
unsigned int i;
if (uc->mapped_block_count == 0)
return NULL;
if (uc->mem_redirect) {
address = uc->mem_redirect(address);
}
// try with the cache index first
i = uc->mapped_block_cache_index;
if (i < uc->mapped_block_count && address >= uc->mapped_blocks[i]->addr && address < uc->mapped_blocks[i]->end)
return uc->mapped_blocks[i];
for(i = 0; i < uc->mapped_block_count; i++) {
if (address >= uc->mapped_blocks[i]->addr && address <= uc->mapped_blocks[i]->end - 1) {
// cache this index for the next query
uc->mapped_block_cache_index = i;
return uc->mapped_blocks[i];
}
}
// not found
return NULL;
}
static uc_err _hook_mem_invalid(struct uc_struct* uc, int type, uc_cb_eventmem_t callback,
void *user_data, uc_hook *evh)
{
size_t i;
// only one event handler at the same time
if ((type & UC_HOOK_MEM_READ_UNMAPPED) != 0 && (uc->hook_mem_read_idx != 0))
return UC_ERR_HOOK_EXIST;
if ((type & UC_HOOK_MEM_READ_PROT) != 0 && (uc->hook_mem_read_prot_idx != 0))
return UC_ERR_HOOK_EXIST;
if ((type & UC_HOOK_MEM_WRITE_UNMAPPED) != 0 && (uc->hook_mem_write_idx != 0))
return UC_ERR_HOOK_EXIST;
if ((type & UC_HOOK_MEM_WRITE_PROT) != 0 && (uc->hook_mem_write_prot_idx != 0))
return UC_ERR_HOOK_EXIST;
if ((type & UC_HOOK_MEM_FETCH_UNMAPPED) != 0 && (uc->hook_mem_fetch_idx != 0))
return UC_ERR_HOOK_EXIST;
if ((type & UC_HOOK_MEM_FETCH_PROT) != 0 && (uc->hook_mem_fetch_prot_idx != 0))
return UC_ERR_HOOK_EXIST;
i = hook_find_new(uc);
if (i) {
uc->hook_callbacks[i].callback = callback;
uc->hook_callbacks[i].user_data = user_data;
*evh = i;
if (type & UC_HOOK_MEM_READ_UNMAPPED)
uc->hook_mem_read_idx = i;
if (type & UC_HOOK_MEM_READ_PROT)
uc->hook_mem_read_prot_idx = i;
if (type & UC_HOOK_MEM_WRITE_UNMAPPED)
uc->hook_mem_write_idx = i;
if (type & UC_HOOK_MEM_WRITE_PROT)
uc->hook_mem_write_prot_idx = i;
if (type & UC_HOOK_MEM_FETCH_UNMAPPED)
uc->hook_mem_fetch_idx = i;
if (type & UC_HOOK_MEM_FETCH_PROT)
uc->hook_mem_fetch_prot_idx = i;
return UC_ERR_OK;
} else
return UC_ERR_NOMEM;
}
static uc_err _hook_intr(struct uc_struct* uc, void *callback,
void *user_data, uc_hook *evh)
{
size_t i;
// only one event handler at the same time
if (uc->hook_intr_idx)
return UC_ERR_HOOK_EXIST;
i = hook_find_new(uc);
if (i) {
uc->hook_callbacks[i].callback = callback;
uc->hook_callbacks[i].user_data = user_data;
*evh = i;
uc->hook_intr_idx = i;
return UC_ERR_OK;
} else
return UC_ERR_NOMEM;
}
static uc_err _hook_insn(struct uc_struct *uc, unsigned int insn_id, void *callback,
void *user_data, uc_hook *evh)
{
size_t i;
switch(uc->arch) {
default: break;
case UC_ARCH_X86:
switch(insn_id) {
default: break;
case UC_X86_INS_OUT:
// only one event handler at the same time
if (uc->hook_out_idx)
return UC_ERR_HOOK_EXIST;
i = hook_find_new(uc);
if (i) {
uc->hook_callbacks[i].callback = callback;
uc->hook_callbacks[i].user_data = user_data;
*evh = i;
uc->hook_out_idx = i;
return UC_ERR_OK;
} else
return UC_ERR_NOMEM;
case UC_X86_INS_IN:
// only one event handler at the same time
if (uc->hook_in_idx)
return UC_ERR_HOOK_EXIST;
i = hook_find_new(uc);
if (i) {
uc->hook_callbacks[i].callback = callback;
uc->hook_callbacks[i].user_data = user_data;
*evh = i;
uc->hook_in_idx = i;
return UC_ERR_OK;
} else
return UC_ERR_NOMEM;
case UC_X86_INS_SYSCALL:
case UC_X86_INS_SYSENTER:
// only one event handler at the same time
if (uc->hook_syscall_idx)
return UC_ERR_HOOK_EXIST;
i = hook_find_new(uc);
if (i) {
uc->hook_callbacks[i].callback = callback;
uc->hook_callbacks[i].user_data = user_data;
*evh = i;
uc->hook_syscall_idx = i;
return UC_ERR_OK;
} else
return UC_ERR_NOMEM;
}
break;
}
return UC_ERR_OK;
}
UNICORN_EXPORT
uc_err uc_hook_add(uc_engine *uc, uc_hook *hh, int type, void *callback, void *user_data, ...)
{
va_list valist;
int ret = UC_ERR_OK;
int id;
uint64_t begin, end;
va_start(valist, user_data);
if (type & UC_HOOK_MEM_READ_UNMAPPED)
ret = _hook_mem_invalid(uc, UC_HOOK_MEM_READ_UNMAPPED, callback, user_data, hh);
if (type & UC_HOOK_MEM_WRITE_UNMAPPED)
ret = _hook_mem_invalid(uc, UC_HOOK_MEM_WRITE_UNMAPPED, callback, user_data, hh);
if (type & UC_HOOK_MEM_FETCH_UNMAPPED)
ret = _hook_mem_invalid(uc, UC_HOOK_MEM_FETCH_UNMAPPED, callback, user_data, hh);
if (type & UC_HOOK_MEM_READ_PROT)
ret = _hook_mem_invalid(uc, UC_HOOK_MEM_READ_PROT, callback, user_data, hh);
if (type & UC_HOOK_MEM_WRITE_PROT)
ret = _hook_mem_invalid(uc, UC_HOOK_MEM_WRITE_PROT, callback, user_data, hh);
if (type & UC_HOOK_MEM_FETCH_PROT)
ret = _hook_mem_invalid(uc, UC_HOOK_MEM_FETCH_PROT, callback, user_data, hh);
switch(type) {
default:
break;
case UC_HOOK_INTR:
ret = _hook_intr(uc, callback, user_data, hh);
break;
case UC_HOOK_INSN:
id = va_arg(valist, int);
ret = _hook_insn(uc, id, callback, user_data, hh);
break;
case UC_HOOK_CODE:
begin = va_arg(valist, uint64_t);
end = va_arg(valist, uint64_t);
ret = _hook_code(uc, UC_HOOK_CODE, begin, end, callback, user_data, hh);
break;
case UC_HOOK_BLOCK:
begin = va_arg(valist, uint64_t);
end = va_arg(valist, uint64_t);
ret = _hook_code(uc, UC_HOOK_BLOCK, begin, end, callback, user_data, hh);
break;
case UC_HOOK_MEM_READ:
begin = va_arg(valist, uint64_t);
end = va_arg(valist, uint64_t);
ret = _hook_mem_access(uc, UC_HOOK_MEM_READ, begin, end, callback, user_data, hh);
break;
case UC_HOOK_MEM_WRITE:
begin = va_arg(valist, uint64_t);
end = va_arg(valist, uint64_t);
ret = _hook_mem_access(uc, UC_HOOK_MEM_WRITE, begin, end, callback, user_data, hh);
break;
case UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE:
begin = va_arg(valist, uint64_t);
end = va_arg(valist, uint64_t);
ret = _hook_mem_access(uc, UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, begin, end, callback, user_data, hh);
break;
}
va_end(valist);
return ret;
}
UNICORN_EXPORT
uc_err uc_hook_del(uc_engine *uc, uc_hook hh)
{
return hook_del(uc, hh);
}