unicorn/hook.c
2015-08-26 09:01:58 -04:00

258 lines
7.8 KiB
C

/* Unicorn Emulator Engine */
/* By Nguyen Anh Quynh <aquynh@gmail.com>, 2015 */
#include "uc_priv.h"
#include "hook.h"
// return index for a new hook entry in hook_callbacks[] array.
// this realloc memory if needed.
size_t hook_find_new(struct uc_struct *uc)
{
size_t i;
struct hook_struct *new;
// find the first free slot. skip slot 0, so index > 0
for(i = 1; i < uc->hook_size; i++) {
if (uc->hook_callbacks[i].callback == NULL) {
return i;
}
}
// not found, so the array is full.
// we have to realloc hook_callbacks[] to contain new hooks
new = realloc(uc->hook_callbacks,
(uc->hook_size + HOOK_SIZE) * sizeof(uc->hook_callbacks[0]));
if (!new) // OOM ?
return 0;
// reset the newly added slots
memset(new + uc->hook_size * sizeof(uc->hook_callbacks[0]), 0,
HOOK_SIZE * sizeof(uc->hook_callbacks[0]));
uc->hook_callbacks = new;
uc->hook_size += HOOK_SIZE;
// return the first newly allocated slot
return uc->hook_size - HOOK_SIZE;
}
// return -1 on failure, index to hook_callbacks[] on success.
size_t hook_add(struct uc_struct *uc, int type, uint64_t begin, uint64_t end, void *callback, void *user_data)
{
int i;
// find the first free slot. skip slot 0, so index > 0
i = hook_find_new(uc);
if (i) {
uc->hook_callbacks[i].hook_type = type;
uc->hook_callbacks[i].begin = begin;
uc->hook_callbacks[i].end = end;
uc->hook_callbacks[i].callback = callback;
uc->hook_callbacks[i].user_data = user_data;
switch(type) {
default: break;
case UC_HOOK_BLOCK:
uc->hook_block = true;
if (begin > end)
uc->hook_block_idx = i;
break;
case UC_HOOK_CODE:
uc->hook_insn = true;
if (begin > end)
uc->hook_insn_idx = i;
break;
case UC_MEM_READ:
uc->hook_mem_read = true;
if (begin > end)
uc->hook_read_idx = i;
break;
case UC_MEM_WRITE:
uc->hook_mem_write = true;
if (begin > end)
uc->hook_write_idx = i;
break;
case UC_MEM_READ_WRITE:
uc->hook_mem_read = true;
uc->hook_mem_write = true;
if (begin > end) {
uc->hook_read_idx = i;
uc->hook_write_idx = i;
}
break;
}
return i;
}
// not found
return 0;
}
// return 0 on success, -1 on failure
uc_err hook_del(struct uc_struct *uc, uch *h2)
{
if (*h2 == uc->hook_block_idx) {
uc->hook_block_idx = 0;
}
if (*h2 == uc->hook_insn_idx) {
uc->hook_insn_idx = 0;
}
if (*h2 == uc->hook_read_idx) {
uc->hook_read_idx = 0;
}
if (*h2 == uc->hook_write_idx) {
uc->hook_write_idx = 0;
}
if (*h2 == uc->hook_mem_idx) {
uc->hook_mem_idx = 0;
}
if (*h2 == uc->hook_intr_idx) {
uc->hook_intr_idx = 0;
}
if (*h2 == uc->hook_out_idx) {
uc->hook_out_idx = 0;
}
if (*h2 == uc->hook_in_idx) {
uc->hook_in_idx = 0;
}
uc->hook_callbacks[*h2].callback = NULL;
uc->hook_callbacks[*h2].user_data = NULL;
uc->hook_callbacks[*h2].hook_type = 0;
uc->hook_callbacks[*h2].begin = 0;
uc->hook_callbacks[*h2].end = 0;
*h2 = 0;
return UC_ERR_OK;
}
// return NULL on failure
static struct hook_struct *_hook_find(struct uc_struct *uc, int type, uint64_t address)
{
int i;
switch(type) {
default: break;
case UC_HOOK_BLOCK:
// already hooked all blocks?
if (uc->hook_block_idx)
return &uc->hook_callbacks[uc->hook_block_idx];
break;
case UC_HOOK_CODE:
// already hooked all the code?
if (uc->hook_insn_idx)
return &uc->hook_callbacks[uc->hook_insn_idx];
break;
case UC_MEM_READ:
// already hooked all memory read?
if (uc->hook_read_idx)
return &uc->hook_callbacks[uc->hook_read_idx];
break;
case UC_MEM_WRITE:
// already hooked all memory write?
if (uc->hook_write_idx)
return &uc->hook_callbacks[uc->hook_write_idx];
break;
}
// no trace-all callback
for(i = 1; i < uc->hook_size; i++) {
switch(type) {
default: break;
case UC_HOOK_BLOCK:
case UC_HOOK_CODE:
if (uc->hook_callbacks[i].hook_type == type) {
if (uc->hook_callbacks[i].begin <= address && address <= uc->hook_callbacks[i].end)
return &uc->hook_callbacks[i];
}
break;
case UC_MEM_READ:
if (uc->hook_callbacks[i].hook_type == UC_MEM_READ || uc->hook_callbacks[i].hook_type == UC_MEM_READ_WRITE) {
if (uc->hook_callbacks[i].begin <= address && address <= uc->hook_callbacks[i].end)
return &uc->hook_callbacks[i];
}
break;
case UC_MEM_WRITE:
if (uc->hook_callbacks[i].hook_type == UC_MEM_WRITE || uc->hook_callbacks[i].hook_type == UC_MEM_READ_WRITE) {
if (uc->hook_callbacks[i].begin <= address && address <= uc->hook_callbacks[i].end)
return &uc->hook_callbacks[i];
}
break;
}
}
// not found
return NULL;
}
static void hook_count_cb(struct uc_struct *uc, uint64_t address, uint32_t size, void *user_data)
{
// count this instruction
uc->emu_counter++;
if (uc->emu_counter > uc->emu_count)
uc_emu_stop(uc);
else if (uc->hook_count_callback)
uc->hook_count_callback(uc, address, size, user_data);
}
struct hook_struct *hook_find(struct uc_struct *uc, int type, uint64_t address)
{
// stop executing callbacks if we already got stop request
if (uc->stop_request)
return NULL;
// UC_HOOK_CODE is special because we may need to count instructions
if (type == UC_HOOK_CODE && uc->emu_count > 0) {
struct hook_struct *st = _hook_find(uc, type, address);
if (st) {
// prepare this struct to pass back to caller
uc->hook_count.hook_type = UC_HOOK_CODE;
uc->hook_count.begin = st->begin;
uc->hook_count.end = st->end;
uc->hook_count.callback = hook_count_cb;
uc->hook_count.user_data = st->user_data;
// save this hook callback so we can call it later
uc->hook_count_callback = st->callback;
} else {
// there is no callback, but we still need to
// handle instruction count
uc->hook_count.hook_type = UC_HOOK_CODE;
uc->hook_count.begin = 1;
uc->hook_count.end = 0;
uc->hook_count.callback = hook_count_cb;
uc->hook_count.user_data = NULL;
uc->hook_count_callback = NULL; // no callback
}
return &(uc->hook_count);
} else
return _hook_find(uc, type, address);
}
// TCG helper
void helper_uc_tracecode(int32_t size, void *callback, void *handle, int64_t address, void *user_data);
void helper_uc_tracecode(int32_t size, void *callback, void *handle, int64_t address, void *user_data)
{
struct uc_struct *uc = handle;
// sync PC in CPUArchState with address
if (uc->set_pc) {
uc->set_pc(uc, address);
}
((uc_cb_hookcode_t)callback)(uc, address, size, user_data);
}