Skip to content

Commit

Permalink
allow to change PC during callback. this solves issue #210
Browse files Browse the repository at this point in the history
  • Loading branch information
aquynh committed Jan 28, 2016
1 parent e750a4e commit 5a04bcb
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ rw_hookstack
hook_extrainvoke
sysenter_hook_x86
test_tb_x86
test_multihook
test_pc_change


#################
Expand Down
2 changes: 2 additions & 0 deletions qemu/cpu-exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ int cpu_exec(struct uc_struct *uc, CPUArchState *env) // qq
uintptr_t next_tb;
struct hook *hook;


/* This must be volatile so it is not trashed by longjmp() */
volatile bool have_tb_lock = false;

Expand Down Expand Up @@ -100,6 +101,7 @@ int cpu_exec(struct uc_struct *uc, CPUArchState *env) // qq
if (sigsetjmp(cpu->jmp_env, 0) == 0) {
if (uc->stop_request || uc->invalid_error)
break;

/* if an exception is pending, we execute it here */
if (cpu->exception_index >= 0) {
//printf(">>> GOT INTERRUPT. exception idx = %x\n", cpu->exception_index); // qq
Expand Down
3 changes: 3 additions & 0 deletions qemu/target-arm/unicorn_aarch64.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ int arm64_reg_write(struct uc_struct *uc, unsigned int regid, const void *value)
break;
case UC_ARM64_REG_PC:
ARM_CPU(uc, mycpu)->env.pc = *(uint64_t *)value;
// force to quit execution and flush TB
uc->quit_request = true;
uc_emu_stop(uc);
break;
case UC_ARM64_REG_SP:
ARM_CPU(uc, mycpu)->env.xregs[31] = *(uint64_t *)value;
Expand Down
3 changes: 3 additions & 0 deletions qemu/target-arm/unicorn_arm.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ int arm_reg_write(struct uc_struct *uc, unsigned int regid, const void *value)
//case UC_ARM_REG_PC:
case UC_ARM_REG_R15:
ARM_CPU(uc, mycpu)->env.regs[15] = *(uint32_t *)value;
// force to quit execution and flush TB
uc->quit_request = true;
uc_emu_stop(uc);
break;
}
}
Expand Down
15 changes: 15 additions & 0 deletions qemu/target-i386/unicorn.c
Original file line number Diff line number Diff line change
Expand Up @@ -656,9 +656,15 @@ int x86_reg_write(struct uc_struct *uc, unsigned int regid, const void *value)
break;
case UC_X86_REG_EIP:
X86_CPU(uc, mycpu)->env.eip = *(uint32_t *)value;
// force to quit execution and flush TB
uc->quit_request = true;
uc_emu_stop(uc);
break;
case UC_X86_REG_IP:
WRITE_WORD(X86_CPU(uc, mycpu)->env.eip, *(uint16_t *)value);
// force to quit execution and flush TB
uc->quit_request = true;
uc_emu_stop(uc);
break;
case UC_X86_REG_CS:
X86_CPU(uc, mycpu)->env.segs[R_CS].base = *(uint32_t *)value;
Expand Down Expand Up @@ -806,12 +812,21 @@ int x86_reg_write(struct uc_struct *uc, unsigned int regid, const void *value)
break;
case UC_X86_REG_RIP:
X86_CPU(uc, mycpu)->env.eip = *(uint64_t *)value;
// force to quit execution and flush TB
uc->quit_request = true;
uc_emu_stop(uc);
break;
case UC_X86_REG_EIP:
WRITE_DWORD(X86_CPU(uc, mycpu)->env.eip, *(uint32_t *)value);
// force to quit execution and flush TB
uc->quit_request = true;
uc_emu_stop(uc);
break;
case UC_X86_REG_IP:
WRITE_WORD(X86_CPU(uc, mycpu)->env.eip, *(uint16_t *)value);
// force to quit execution and flush TB
uc->quit_request = true;
uc_emu_stop(uc);
break;
case UC_X86_REG_CS:
X86_CPU(uc, mycpu)->env.segs[R_CS].base = *(uint64_t *)value;
Expand Down
3 changes: 3 additions & 0 deletions qemu/target-m68k/unicorn.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ int m68k_reg_write(struct uc_struct *uc, unsigned int regid, const void *value)
default: break;
case UC_M68K_REG_PC:
M68K_CPU(uc, mycpu)->env.pc = *(uint32_t *)value;
// force to quit execution and flush TB
uc->quit_request = true;
uc_emu_stop(uc);
break;
}
}
Expand Down
3 changes: 3 additions & 0 deletions qemu/target-mips/unicorn.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ int mips_reg_write(struct uc_struct *uc, unsigned int regid, const void *value)
default: break;
case UC_MIPS_REG_PC:
MIPS_CPU(uc, mycpu)->env.active_tc.PC = *(uint32_t *)value;
// force to quit execution and flush TB
uc->quit_request = true;
uc_emu_stop(uc);
break;
}
}
Expand Down
3 changes: 3 additions & 0 deletions qemu/target-sparc/unicorn.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ int sparc_reg_write(struct uc_struct *uc, unsigned int regid, const void *value)
case UC_SPARC_REG_PC:
SPARC_CPU(uc, mycpu)->env.pc = *(uint32_t *)value;
SPARC_CPU(uc, mycpu)->env.npc = *(uint32_t *)value + 4;
// force to quit execution and flush TB
uc->quit_request = true;
uc_emu_stop(uc);
break;
}
}
Expand Down
4 changes: 3 additions & 1 deletion tests/unit/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ CFLAGS += -lcmocka -lunicorn
CFLAGS += -I ../../include

ALL_TESTS = test_sanity test_x86 test_mem_map test_mem_high test_mem_map_ptr \
test_tb_x86 test_multihook
test_tb_x86 test_multihook test_pc_change

.PHONY: all
all: ${ALL_TESTS}
Expand All @@ -24,6 +24,7 @@ test: ${ALL_TESTS}
./test_mem_high
./test_tb_x86
./test_multihook
./test_pc_change

test_sanity: test_sanity.c
test_x86: test_x86.c
Expand All @@ -32,6 +33,7 @@ test_mem_map_ptr: test_mem_map_ptr.c
test_mem_high: test_mem_high.c
test_tb_x86: test_tb_x86.c
test_multihook: test_multihook.c
test_pc_change: test_pc_change.c

${ALL_TESTS}:
${CC} ${CFLAGS} -o $@ $^
104 changes: 104 additions & 0 deletions tests/unit/test_pc_change.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Test PC change during the callback. by Nguyen Anh Quynh, 2016
#include "unicorn_test.h"
#include <inttypes.h>

#define OK(x) uc_assert_success(x)

/* Called before every test to set up a new instance */
static int setup32(void **state)
{
uc_engine *uc;

OK(uc_open(UC_ARCH_X86, UC_MODE_32, &uc));

*state = uc;
return 0;
}

/* Called after every test to clean up */
static int teardown(void **state)
{
uc_engine *uc = *state;

OK(uc_close(uc));

*state = NULL;
return 0;
}

/******************************************************************************/

static void test_code_hook(uc_engine *uc, uint64_t address, uint32_t size, void *user_data)
{
uint8_t tmp[256];
int32_t r_eip = 0x1000006;
printf("instruction at 0x%"PRIx64": ", address);

if (!uc_mem_read(uc, address, tmp, size)) {
uint32_t i;

for (i = 0; i < size; i++) {
printf("0x%x ", tmp[i]);
}
printf("\n");
}

if (address == 0x1000003) {
// change the PC to "inc EDX"
uc_reg_write(uc, UC_X86_REG_EIP, &r_eip);
}
}

static void test_pc_change(void **state)
{
uc_engine *uc = *state;
uc_hook trace1;
int32_t r_ecx = 3, r_edx = 15;

#define BASEADDR 0x1000000

uint64_t address = BASEADDR;
const uint8_t code[] = {
0x41, // inc ECX @0x1000000
0x41, // inc ECX
0x41, // inc ECX
0x41, // inc ECX @0x1000003
0x41, // inc ECX
0x41, // inc ECX

0x42, // inc EDX @0x1000006
0x42, // inc EDX
};

#undef BASEADDR

// map 2MB memory for this emulation
OK(uc_mem_map(uc, address, 2 * 1024 * 1024, UC_PROT_ALL));

// write machine code to be emulated to memory
OK(uc_mem_write(uc, address, code, sizeof(code)));

uc_reg_write(uc, UC_X86_REG_ECX, &r_ecx);
uc_reg_write(uc, UC_X86_REG_EDX, &r_edx);
printf("ECX = %u, EDX = %u\n", r_ecx, r_edx);

// trace all instructions
OK(uc_hook_add(uc, &trace1, UC_HOOK_CODE, test_code_hook, NULL, (uint64_t)1, (uint64_t)0));

OK(uc_emu_start(uc, address, address+sizeof(code), 0, 0));

uc_reg_read(uc, UC_X86_REG_ECX, &r_ecx);
uc_reg_read(uc, UC_X86_REG_EDX, &r_edx);

printf("ECX = %u, EDX = %u\n", r_ecx, r_edx);
assert_int_equal(r_ecx, 6);
assert_int_equal(r_edx, 17);
}

int main(void)
{
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(test_pc_change, setup32, teardown),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}
3 changes: 2 additions & 1 deletion uc.c
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,6 @@ uc_err uc_emu_start(uc_engine* uc, uint64_t begin, uint64_t until, uint64_t time
{
// 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;
Expand Down Expand Up @@ -542,6 +541,8 @@ uc_err uc_emu_start(uc_engine* uc, uint64_t begin, uint64_t until, uint64_t time
break;
}

uc->stop_request = false;

uc->emu_count = count;
// remove count hook if counting isn't necessary
if (count <= 0 && uc->count_hook != 0) {
Expand Down

2 comments on commit 5a04bcb

@farmdve
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, I never thought this could be implemented in so few lines of code.

@aquynh
Copy link
Member Author

@aquynh aquynh commented on 5a04bcb Jan 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks trivial but actually it is not, as it was tricky to come up with a clean solution like this :-)

Please sign in to comment.