From 7cbc604956ea746f2429672802ab143565867b87 Mon Sep 17 00:00:00 2001 From: KeyCode17 Date: Tue, 8 Sep 2026 12:06:13 +0700 Subject: [PATCH] test: port upstream x86 unit tests Ports tests/unit/test_x86.c from the unicorn submodule (pin 681bd32) to crates/unicorn/src/tests/x86.rs, following the existing per-arch test modules. x86 was the only architecture whose upstream test file had no Rust counterpart, despite being the largest one (71 KB of C). 50 of the 56 upstream tests are ported. The remaining 6 need bindings that do not exist yet: test_x86_hook_cpuid, test_x86_hook_insn_rdtsc, test_x86_hook_insn_rdtscp need an instruction hook whose callback returns a value (uc_cb_insn_cpuid_t); add_insn_sys_hook takes FnMut(..) -> (). test_x86_mmu, test_x86_read_virtual need uc_x86_msr read/write; MSR is not covered by value_size_x86 and reg_read only returns u64. test_x86_segmentation needs a uc_x86_mmr accessor to write GDTR. test_x86_unaligned_access and test_x86_64_unaligned_access are gated on the same host-arch condition upstream uses (TARGET_READ_INLINED is set for aarch64 and ppc targets). Signed-off-by: KeyCode17 --- crates/unicorn/src/tests/mod.rs | 2 + crates/unicorn/src/tests/x86.rs | 1376 +++++++++++++++++++++++++++++++ 2 files changed, 1378 insertions(+) create mode 100644 crates/unicorn/src/tests/x86.rs diff --git a/crates/unicorn/src/tests/mod.rs b/crates/unicorn/src/tests/mod.rs index 4637767..e2e552a 100644 --- a/crates/unicorn/src/tests/mod.rs +++ b/crates/unicorn/src/tests/mod.rs @@ -16,6 +16,8 @@ mod ppc; mod riscv; #[cfg(feature = "arch_s390x")] mod s390x; +#[cfg(feature = "arch_x86")] +mod x86; use crate::{Arch, HookType, Mode, Prot, TlbEntry, TlbType, Unicorn, uc_error}; diff --git a/crates/unicorn/src/tests/x86.rs b/crates/unicorn/src/tests/x86.rs new file mode 100644 index 0000000..0b1a8d9 --- /dev/null +++ b/crates/unicorn/src/tests/x86.rs @@ -0,0 +1,1376 @@ +use super::*; +use crate::{MemType, RegisterX86, TcgOpCode, TcgOpFlag, X86CpuModel, X86Insn}; + +const MEM_BASE: u64 = 0x4000_0000; +const MEM_SIZE: u64 = 1024 * 1024; +const MEM_STACK: u64 = MEM_BASE + (MEM_SIZE / 2); +const MEM_TEXT: u64 = MEM_STACK + 4096; + +struct QuickTest<'a> { + mode: Mode, + code: &'a [u8], + in_regs: &'a [(RegisterX86, u64)], + out_regs: &'a [(RegisterX86, u64)], +} + +impl QuickTest<'_> { + fn run(&self) { + let is_64 = self.mode == Mode::MODE_64; + let mut uc = Unicorn::new(Arch::X86, self.mode).unwrap(); + + uc.mem_map(MEM_BASE, MEM_SIZE, Prot::ALL).unwrap(); + uc.mem_write(MEM_TEXT, self.code).unwrap(); + + let stack_reg = if is_64 { + RegisterX86::RSP + } else { + RegisterX86::ESP + }; + uc.reg_write(stack_reg, MEM_STACK).unwrap(); + + for &(reg, value) in self.in_regs { + let value = if is_64 { value } else { value & 0xFFFF_FFFF }; + uc.reg_write(reg, value).unwrap(); + } + + uc.emu_start(MEM_TEXT, MEM_TEXT + self.code.len() as u64, 0, 0) + .unwrap(); + + for &(reg, expected) in self.out_regs { + let expected = if is_64 { + expected + } else { + expected & 0xFFFF_FFFF + }; + assert_eq!(uc.reg_read(reg).unwrap(), expected); + } + + uc.mem_unmap(MEM_BASE, MEM_SIZE).unwrap(); + } +} + +#[test] +fn test_x86_in() { + let code = b"\xe5\x10"; // IN eax, 0x10 + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, (0u32, 0usize)); + let hook = uc + .add_insn_in_hook(|uc, port, size| { + let eip = uc.reg_read(RegisterX86::EIP).unwrap(); + assert_eq!(eip, CODE_START); + *uc.get_data_mut() = (port, size); + 0 + }) + .unwrap(); + + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); + + let (port, size) = *uc.get_data(); + assert_eq!(port, 0x10); + assert_eq!(size, 4); + + uc.remove_hook(hook).unwrap(); +} + +#[test] +fn test_x86_out() { + let code = b"\xb0\x32\xe6\x46"; // MOV al, 0x32; OUT 0x46, al + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, (0u32, 0usize, 0u32)); + let hook = uc + .add_insn_out_hook(|uc, port, size, value| { + *uc.get_data_mut() = (port, size, value); + }) + .unwrap(); + + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); + + let (port, size, value) = *uc.get_data(); + assert_eq!(port, 0x46); + assert_eq!(size, 1); + assert_eq!(value, 0x32); + + uc.remove_hook(hook).unwrap(); +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +struct MemHookResult { + mem_type: MemType, + address: u64, + size: usize, + value: i64, +} + +#[test] +fn test_x86_mem_hook_all() { + #[rustfmt::skip] + let code = &[ + 0xb8, 0xef, 0xbe, 0xad, 0xde, // mov eax, 0xdeadbeef + 0xa3, 0x00, 0x80, 0x00, 0x00, // mov [0x8000], eax + 0xa1, 0x00, 0x00, 0x01, 0x00, // mov eax, [0x10000] + ]; + let expects = [ + MemHookResult { + mem_type: MemType::WRITE, + address: 0x8000, + size: 4, + value: 0xdead_beef, + }, + MemHookResult { + mem_type: MemType::READ_UNMAPPED, + address: 0x10000, + size: 4, + value: 0, + }, + MemHookResult { + mem_type: MemType::READ, + address: 0x10000, + size: 4, + value: 0, + }, + ]; + + let mut uc = uc_common_setup( + Arch::X86, + Mode::MODE_32, + None, + code, + (0usize, [None::; 16]), + ); + uc.mem_map(0x8000, 0x1000, Prot::ALL).unwrap(); + + let hook = uc + .add_mem_hook( + HookType::MEM_VALID | HookType::MEM_INVALID, + 1, + 0, + |uc, mem_type, address, size, value| { + let (count, results) = uc.get_data_mut(); + assert!(*count < results.len()); + results[*count] = Some(MemHookResult { + mem_type, + address, + size, + value, + }); + *count += 1; + + if mem_type == MemType::READ_UNMAPPED { + uc.mem_map(address, 0x1000, Prot::ALL).unwrap(); + } + + true + }, + ) + .unwrap(); + + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); + + let (count, results) = uc.get_data(); + assert_eq!(*count, 3); + for (expect, result) in expects.iter().zip(results.iter()) { + assert_eq!(Some(*expect), *result); + } + + uc.remove_hook(hook).unwrap(); +} + +#[test] +fn test_x86_inc_dec_pxor() { + #[rustfmt::skip] + let code = &[ + 0x41, // inc ecx + 0x4a, // dec edx + 0x66, 0x0f, 0xef, 0xc1, // pxor xmm0, xmm1 + ]; + let r_xmm0 = [0x0809_0a0b_0c0d_0e0fu64, 0x0001_0203_0405_0607u64]; + let r_xmm1 = [0x8090_a0b0_c0d0_e0f0u64, 0x0010_2030_4050_6070u64]; + + let mut uc = Unicorn::new(Arch::X86, Mode::MODE_32).unwrap(); + uc.ctl_set_cpu_model(X86CpuModel::HASWELL as i32).unwrap(); + uc.mem_map(CODE_START, CODE_LEN, Prot::ALL).unwrap(); + uc.mem_write(CODE_START, code).unwrap(); + + uc.reg_write(RegisterX86::ECX, 0x1234).unwrap(); + uc.reg_write(RegisterX86::EDX, 0x7890).unwrap(); + uc.reg_write_long(RegisterX86::XMM0, &xmm_bytes(r_xmm0)) + .unwrap(); + uc.reg_write_long(RegisterX86::XMM1, &xmm_bytes(r_xmm1)) + .unwrap(); + + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); + + assert_eq!(uc.reg_read(RegisterX86::ECX).unwrap(), 0x1235); + assert_eq!(uc.reg_read(RegisterX86::EDX).unwrap(), 0x788f); + + let xmm0 = xmm_words(&uc.reg_read_long(RegisterX86::XMM0).unwrap()); + assert_eq!(xmm0[0], 0x8899_aabb_ccdd_eeff); + assert_eq!(xmm0[1], 0x0011_2233_4455_6677); +} + +fn xmm_bytes(words: [u64; 2]) -> [u8; 16] { + let mut bytes = [0u8; 16]; + bytes[..8].copy_from_slice(&words[0].to_le_bytes()); + bytes[8..].copy_from_slice(&words[1].to_le_bytes()); + bytes +} + +fn xmm_words(bytes: &[u8]) -> [u64; 2] { + [ + u64::from_le_bytes(bytes[..8].try_into().unwrap()), + u64::from_le_bytes(bytes[8..16].try_into().unwrap()), + ] +} + +#[test] +fn test_x86_relative_jump() { + // jmp 4; nop; nop; nop; nop; nop; nop + let code = b"\xeb\x02\x90\x90\x90\x90\x90\x90"; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, ()); + + uc.emu_start(CODE_START, CODE_START + 4, 0, 0).unwrap(); + + assert_eq!(uc.reg_read(RegisterX86::EIP).unwrap(), CODE_START + 4); +} + +#[test] +fn test_x86_loop() { + let code = b"\x41\x4a\xeb\xfe"; // inc ecx; dec edx; jmp $ + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, ()); + uc.reg_write(RegisterX86::ECX, 0x1234).unwrap(); + uc.reg_write(RegisterX86::EDX, 0x7890).unwrap(); + + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 1_000_000, 0) + .unwrap(); + + assert_eq!(uc.reg_read(RegisterX86::ECX).unwrap(), 0x1235); + assert_eq!(uc.reg_read(RegisterX86::EDX).unwrap(), 0x788f); +} + +#[test] +fn test_x86_invalid_mem_read() { + let code = b"\x8b\x0d\xaa\xaa\xaa\xaa"; // mov ecx, [0xAAAAAAAA] + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, ()); + + let err = uc + .emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap_err(); + assert_eq!(err, uc_error::READ_UNMAPPED); +} + +#[test] +fn test_x86_invalid_mem_write() { + let code = b"\x89\x0d\xaa\xaa\xaa\xaa"; // mov [0xAAAAAAAA], ecx + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, ()); + + let err = uc + .emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap_err(); + assert_eq!(err, uc_error::WRITE_UNMAPPED); +} + +#[test] +fn test_x86_invalid_jump() { + let code = b"\xe9\xe9\xee\xee\xee"; // jmp 0xEEEEEEEE + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, ()); + + let err = uc + .emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap_err(); + assert_eq!(err, uc_error::FETCH_UNMAPPED); +} + +#[test] +fn test_x86_64_syscall() { + let code = b"\x0f\x05"; // syscall + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_64, None, code, false); + uc.reg_write(RegisterX86::RAX, 0x100).unwrap(); + let hook = uc + .add_insn_sys_hook(X86Insn::SYSCALL, 1, 0, |uc| { + assert_eq!(uc.reg_read(RegisterX86::RAX).unwrap(), 0x100); + *uc.get_data_mut() = true; + }) + .unwrap(); + + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); + + assert!(*uc.get_data()); + + uc.remove_hook(hook).unwrap(); +} + +#[test] +fn test_x86_16_add() { + let code = b"\x00\x00"; // add byte ptr [bx + si], al + let r_bx = 5; + let r_si = 6; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_16, None, code, ()); + uc.mem_map(0, 0x1000, Prot::ALL).unwrap(); + uc.reg_write(RegisterX86::AX, 7).unwrap(); + uc.reg_write(RegisterX86::BX, r_bx).unwrap(); + uc.reg_write(RegisterX86::SI, r_si).unwrap(); + + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); + + let mut result = [0u8; 1]; + uc.mem_read(r_bx + r_si, &mut result).unwrap(); + assert_eq!(result[0], 7); +} + +#[test] +fn test_x86_reg_save() { + let code = b"\x40"; // inc eax + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, ()); + uc.reg_write(RegisterX86::EAX, 1).unwrap(); + + let mut ctx = uc.context_alloc().unwrap(); + uc.context_save(&mut ctx).unwrap(); + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); + + assert_eq!(uc.reg_read(RegisterX86::EAX).unwrap(), 2); + + uc.context_restore(&ctx).unwrap(); + + assert_eq!(uc.reg_read(RegisterX86::EAX).unwrap(), 1); +} + +#[test] +fn test_x86_invalid_mem_read_stop_in_cb() { + // inc eax; mov ebx, [0x100000]; inc edx + let code = b"\x40\x8b\x1d\x00\x00\x10\x00\x42"; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, ()); + // False indicates that we fail to handle this ERROR and let the emulation stop. + uc.add_mem_hook(HookType::MEM_READ, 1, 0, |_, _, _, _, _| false) + .unwrap(); + uc.reg_write(RegisterX86::EAX, 0x1234).unwrap(); + uc.reg_write(RegisterX86::EDX, 0x5678).unwrap(); + + let err = uc + .emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap_err(); + assert_eq!(err, uc_error::READ_UNMAPPED); + + // The state of Unicorn should be correct at this time. + assert_eq!(uc.reg_read(RegisterX86::EIP).unwrap(), CODE_START + 1); + assert_eq!(uc.reg_read(RegisterX86::EAX).unwrap(), 0x1235); + assert_eq!(uc.reg_read(RegisterX86::EDX).unwrap(), 0x5678); +} + +#[test] +fn test_x86_x87_fnstenv() { + // fnop; fnstenv [eax]; fld dword ptr [eax]; fnstenv [eax] + let code = b"\xd9\xd0\xd9\x30\xd9\x00\xd9\x30"; + let base = CODE_START + 3 * CODE_LEN; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, 0u32); + uc.mem_map(base, CODE_LEN, Prot::ALL).unwrap(); + uc.reg_write(RegisterX86::EAX, base).unwrap(); + + uc.add_code_hook(1, 0, move |uc, address, _| { + if address == CODE_START + 4 { + // The first fnstenv executed: save the address of the fld. + let eip = uc.reg_read(RegisterX86::EIP).unwrap(); + *uc.get_data_mut() = eip as u32; + + let eax = uc.reg_read(RegisterX86::EAX).unwrap(); + // Don't update FCS:FIP for fnop. + assert_eq!(read_fnstenv(uc, eax)[3], 0); + } + }) + .unwrap(); + + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); + + // But update FCS:FIP for fld. + let last_eip = *uc.get_data(); + assert_eq!(read_fnstenv(&uc, base)[3], last_eip); +} + +fn read_fnstenv(uc: &Unicorn, address: u64) -> [u32; 7] { + let mut buf = [0u8; 28]; + uc.mem_read(address, &mut buf).unwrap(); + let mut fnstenv = [0u32; 7]; + for (word, chunk) in fnstenv.iter_mut().zip(buf.chunks_exact(4)) { + *word = u32::from_le_bytes(chunk.try_into().unwrap()); + } + fnstenv +} + +#[test] +fn test_x86_mmio() { + // mov [0x20004], ecx; mov ecx, [0x20004] + let code = b"\x89\x0d\x04\x00\x02\x00\x8b\x0d\x04\x00\x02\x00"; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, ()); + uc.reg_write(RegisterX86::ECX, 0xdead_beef).unwrap(); + uc.mmio_map( + 0x20000, + 0x1000, + Some(|_: &mut Unicorn<()>, offset, size| { + assert_eq!(offset, 4); + assert_eq!(size, 4); + 0x1926_0817 + }), + Some(|_: &mut Unicorn<()>, offset, size, value| { + assert_eq!(offset, 4); + assert_eq!(size, 4); + assert_eq!(value, 0xdead_beef); + }), + ) + .unwrap(); + + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); + + assert_eq!(uc.reg_read(RegisterX86::ECX).unwrap(), 0x1926_0817); +} + +#[test] +fn test_x86_missing_code() { + // Don't write any code by design. + let mut uc = Unicorn::new(Arch::X86, Mode::MODE_32).unwrap(); + uc.reg_write(RegisterX86::ECX, 0x1234).unwrap(); + uc.reg_write(RegisterX86::EDX, 0x7890).unwrap(); + uc.add_mem_hook(HookType::MEM_UNMAPPED, 1, 0, |uc, _, address, size, _| { + let code = b"\x41\x4a"; // inc ecx; dec edx + let aligned_address = address & !0xfffu64; + let aligned_size = ((size / 0x1000) + 1) * 0x1000; + + uc.mem_map(aligned_address, aligned_size as u64, Prot::ALL) + .unwrap(); + uc.mem_write(aligned_address, code).unwrap(); + + true + }) + .unwrap(); + + uc.emu_start(CODE_START, CODE_START + 2, 0, 0).unwrap(); + + assert_eq!(uc.reg_read(RegisterX86::ECX).unwrap(), 0x1235); + assert_eq!(uc.reg_read(RegisterX86::EDX).unwrap(), 0x788f); +} + +#[test] +fn test_x86_smc_xor() { + // 0x1000 xor dword ptr [edi+0x3], eax ; edi=0x1000, eax=0xbc4177e6 + // 0x1003 dw 0x3ea98b13 + let code = b"\x31\x47\x03\x13\x8b\xa9\x3e"; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, ()); + uc.reg_write(RegisterX86::EDI, CODE_START).unwrap(); + uc.reg_write(RegisterX86::EAX, 0xbc41_77e6).unwrap(); + + uc.emu_start(CODE_START, CODE_START + 3, 0, 0).unwrap(); + + let mut result = [0u8; 4]; + uc.mem_read(CODE_START + 3, &mut result).unwrap(); + + assert_eq!(u32::from_le_bytes(result), 0x3ea9_8b13 ^ 0xbc41_77e6); +} + +#[test] +fn test_x86_smc_add() { + // mov qword ptr [rip+0x10], rax + // mov word ptr [rip], 0x0548 + // [orig] mov eax, dword ptr [rax + 0x12345678]; [after SMC] add rax, 0x12345678 + // hlt + let code = b"\x48\x89\x05\x10\x00\x00\x00\x66\xc7\x05\x00\x00\x00\x00\x48\ + \x05\x8b\x80\x78\x56\x34\x12\xf4"; + let stack_base = 0x20000; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_64, None, code, ()); + uc.mem_map(stack_base, 0x2000, Prot::ALL).unwrap(); + uc.reg_write(RegisterX86::RSP, stack_base + 0x1800).unwrap(); + + uc.emu_start(CODE_START, u64::MAX, 0, 0).unwrap(); +} + +#[test] +fn test_x86_smc_mem_hook() { + // mov qword ptr [rip+0x29], rax + // mov word ptr [rip], 0x0548 + // [orig] mov eax, dword ptr [rax + 0x12345678]; [after SMC] add rax, 0x12345678 + // nop; nop; nop + // mov qword ptr [rip-0x08], rax + // mov word ptr [rip], 0x0548 + // [orig] mov eax, dword ptr [rax + 0x12345678]; [after SMC] add rax, 0x12345678 + // hlt + let code = b"\x48\x89\x05\x29\x00\x00\x00\x66\xC7\x05\x00\x00\x00\x00\x48\x05\x8B\ + \x80\x78\x56\x34\x12\x90\x90\x90\x48\x89\x05\xF8\xFF\xFF\xFF\x66\xC7\ + \x05\x00\x00\x00\x00\x48\x05\x8B\x80\x78\x56\x34\x12\xF4"; + let stack_base = 0x20000; + let write_addresses = [0x1030u64, 0x1010, 0x1010, 0x1018, 0x1018, 0x1029, 0x1029]; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_64, None, code, 0usize); + uc.add_mem_hook(HookType::MEM_WRITE, 1, 0, move |uc, _, addr, _, _| { + let i = uc.get_data_mut(); + assert!(*i < write_addresses.len()); + assert_eq!(write_addresses[*i], addr); + *i += 1; + true + }) + .unwrap(); + uc.mem_map(stack_base, 0x2000, Prot::ALL).unwrap(); + uc.reg_write(RegisterX86::RSP, stack_base + 0x1800).unwrap(); + + uc.emu_start(CODE_START, u64::MAX, 0, 0).unwrap(); +} + +#[test] +fn test_x86_mmio_uc_mem_rw() { + let mut uc = Unicorn::new(Arch::X86, Mode::MODE_32).unwrap(); + + uc.mmio_map( + 0x20000, + 0x1000, + Some(|_: &mut Unicorn<()>, offset, size| { + assert_eq!(offset, 8); + assert_eq!(size, 4); + 0x1926_0817 + }), + Some(|_: &mut Unicorn<()>, offset, size, value| { + assert_eq!(offset, 4); + assert_eq!(size, 4); + assert_eq!(value, 0xdead_beef); + }), + ) + .unwrap(); + + uc.mem_write(0x20004, &0xdead_beefu32.to_le_bytes()) + .unwrap(); + + let mut data = [0u8; 4]; + uc.mem_read(0x20008, &mut data).unwrap(); + + assert_eq!(u32::from_le_bytes(data), 0x1926_0817); +} + +#[test] +fn test_x86_sysenter() { + let code = b"\x0F\x34"; // sysenter + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, 0u32); + uc.add_insn_sys_hook(X86Insn::SYSENTER, 1, 0, |uc| { + *uc.get_data_mut() += 1; + }) + .unwrap(); + + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); + + assert_eq!(*uc.get_data(), 1); +} + +#[test] +fn test_x86_486_cpuid() { + let code = &[0x31, 0xC0, 0x0F, 0xA2]; // xor eax, eax; cpuid + + let mut uc = Unicorn::new(Arch::X86, Mode::MODE_32).unwrap(); + uc.ctl_set_cpu_model(X86CpuModel::Model_486 as i32).unwrap(); + uc.mem_map(0, 4 * 1024, Prot::ALL).unwrap(); + uc.mem_write(0, code).unwrap(); + uc.emu_start(0, code.len() as u64, 0, 0).unwrap(); + + // Read eax after emulation + assert_ne!(uc.reg_read(RegisterX86::EAX).unwrap(), 0); + // magic string "Genu" for intel cpu + assert_eq!(uc.reg_read(RegisterX86::EBX).unwrap(), 0x756e_6547); +} + +// This is a regression bug. +#[test] +fn test_x86_clear_tb_cache() { + let code = b"\x83\xc1\x01\x4a\x00"; // add ecx, 1; dec edx + let code_start = 0x1240; // Choose this address by design + let code_len = 0x1000; + + let mut uc = Unicorn::new(Arch::X86, Mode::MODE_32).unwrap(); + uc.mem_map(code_start & (1 << 12), code_len, Prot::ALL) + .unwrap(); + uc.mem_write(code_start, code).unwrap(); + uc.reg_write(RegisterX86::ECX, 0x1234).unwrap(); + uc.reg_write(RegisterX86::EDX, 0x7890).unwrap(); + + // This emulation should take no effect at all. + uc.emu_start(code_start, code_start, 0, 0).unwrap(); + + // Emulate ADD ecx, 1. + uc.emu_start(code_start, code_start + 3, 0, 0).unwrap(); + + // If tb cache is not cleared, edx would be still 0x7890 + uc.emu_start(code_start, code_start + code.len() as u64 - 1, 0, 0) + .unwrap(); + + assert_eq!(uc.reg_read(RegisterX86::ECX).unwrap(), 0x1236); + assert_eq!(uc.reg_read(RegisterX86::EDX).unwrap(), 0x788f); +} + +#[test] +fn test_x86_clear_count_cache() { + // uc_emu_start will clear last TB when exiting so generating a tb at last by design + // add ecx, 1; dec edx; jmp t; t: add ebx, 1 + let code = b"\x83\xc1\x01\x4a\xeb\x00\x83\xc3\x01"; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, ()); + uc.reg_write(RegisterX86::ECX, 0x1234).unwrap(); + uc.reg_write(RegisterX86::EDX, 0x7890).unwrap(); + + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 2) + .unwrap(); + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); + + assert_eq!(uc.reg_read(RegisterX86::ECX).unwrap(), 0x1236); + assert_eq!(uc.reg_read(RegisterX86::EDX).unwrap(), 0x788e); +} + +// This is a regression bug. +#[test] +fn test_x86_clear_empty_tb() { + // lb: + // add ecx, 1; + // cmp ecx, 0; + // jz lb; + // dec edx; + let code = b"\x83\xc1\x01\x83\xf9\x00\x74\xf8\x4a\x00"; + let code_start = 0x1240; // Choose this address by design + let code_len = 0x1000; + + let mut uc = Unicorn::new(Arch::X86, Mode::MODE_32).unwrap(); + uc.mem_map(code_start & (1 << 12), code_len, Prot::ALL) + .unwrap(); + uc.mem_write(code_start, code).unwrap(); + uc.reg_write(RegisterX86::EDX, 0x7890).unwrap(); + + // Make sure we generate an empty tb at the exit address by stopping at dec edx. + uc.emu_start(code_start, code_start + 8, 0, 0).unwrap(); + + // If tb cache is not cleared, edx would be still 0x7890 + uc.emu_start(code_start, code_start + code.len() as u64 - 1, 0, 0) + .unwrap(); + + assert_eq!(uc.reg_read(RegisterX86::EDX).unwrap(), 0x788f); +} + +#[test] +fn test_x86_hook_tcg_op() { + #[rustfmt::skip] + let code = &[ + 0x2b, 0x35, 0x00, 0x10, 0x00, 0x00, // sub esi, [0x1000] + 0x29, 0xd8, // sub eax, ebx + 0x83, 0xe8, 0x01, // sub eax, 1 + 0x83, 0xf8, 0x00, // cmp eax, 0 + 0x39, 0xd3, // cmp ebx, edx + 0x3b, 0x35, 0x00, 0x10, 0x00, 0x00, // cmp esi, [0x1000] + ]; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, 0usize); + uc.reg_write(RegisterX86::EAX, 0x1234).unwrap(); + uc.reg_write(RegisterX86::EBX, 2).unwrap(); + + for (flag, expected) in [ + (TcgOpFlag(0), 6), + (TcgOpFlag::DIRECT, 3), + (TcgOpFlag::CMP, 3), + ] { + *uc.get_data_mut() = 0; + let hook = uc + .add_tcg_hook(TcgOpCode::SUB, flag, 0, u64::MAX, |uc, _, _, _, _| { + *uc.get_data_mut() += 1; + }) + .unwrap(); + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); + uc.remove_hook(hook).unwrap(); + + assert_eq!(*uc.get_data(), expected); + } +} + +#[test] +fn test_x86_cmpxchg() { + let code = b"\x0F\xC7\x0D\xE0\xBE\xAD\xDE"; // cmpxchg8b [0xdeadbee0] + let r_aaaa = 0x4141_4141; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, 0u8); + uc.mem_map(0xdeadb000, 0x1000, Prot::ALL).unwrap(); + uc.add_mem_hook( + HookType::MEM_READ | HookType::MEM_WRITE, + 1, + 0, + |uc, mem_type, _, _, _| { + *uc.get_data_mut() |= if mem_type == MemType::READ { 1 } else { 2 }; + true + }, + ) + .unwrap(); + + uc.reg_write(RegisterX86::EDX, 0).unwrap(); + uc.reg_write(RegisterX86::EAX, 0).unwrap(); + uc.reg_write(RegisterX86::ECX, r_aaaa).unwrap(); + uc.reg_write(RegisterX86::EBX, r_aaaa).unwrap(); + + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); + + let mut mem = [0u8; 8]; + uc.mem_read(0xdeadbee0, &mut mem).unwrap(); + + assert_eq!(u64::from_le_bytes(mem), 0x4141_4141_4141_4141); + + // Both read and write happened. + assert_eq!(*uc.get_data(), 3); +} + +#[test] +fn test_x86_nested_emu_start() { + let code = b"\x41\x4a"; // inc ecx; dec edx + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, ()); + uc.reg_write(RegisterX86::ECX, 0x1234).unwrap(); + uc.reg_write(RegisterX86::EDX, 0x7890).unwrap(); + // Emulate DEC in the nested hook. + uc.add_code_hook(CODE_START, CODE_START, |uc, _, _| { + uc.emu_start(CODE_START + 1, CODE_START + 2, 0, 0).unwrap(); + }) + .unwrap(); + + // Emulate INC + uc.emu_start(CODE_START, CODE_START + 1, 0, 0).unwrap(); + + assert_eq!(uc.reg_read(RegisterX86::ECX).unwrap(), 0x1235); + assert_eq!(uc.reg_read(RegisterX86::EDX).unwrap(), 0x788f); +} + +#[test] +fn test_x86_nested_emu_stop() { + let code = b"\x41\x4a\x4a"; // inc ecx; dec edx; dec edx + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, ()); + uc.reg_write(RegisterX86::ECX, 0x1234).unwrap(); + uc.reg_write(RegisterX86::EDX, 0x7890).unwrap(); + // Emulate DEC in the nested hook. + uc.add_code_hook(CODE_START, CODE_START, |uc, _, _| { + uc.emu_start(CODE_START + 1, CODE_START + 2, 0, 0).unwrap(); + // ecx shouldn't be changed! + uc.emu_stop().unwrap(); + }) + .unwrap(); + + uc.emu_start(CODE_START, CODE_START + 3, 0, 0).unwrap(); + + assert_eq!(uc.reg_read(RegisterX86::ECX).unwrap(), 0x1234); + assert_eq!(uc.reg_read(RegisterX86::EDX).unwrap(), 0x788f); +} + +#[test] +fn test_x86_64_nested_emu_start_error() { + // nop; nop; mov rax, [0x10000] + let code = b"\x90\x90\x48\xa1\x00\x00\x01\x00\x00\x00\x00\x00"; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_64, None, code, ()); + uc.add_code_hook(CODE_START, CODE_START, |uc, _, _| { + let err = uc.emu_start(CODE_START + 2, 0, 0, 0).unwrap_err(); + assert_eq!(err, uc_error::READ_UNMAPPED); + }) + .unwrap(); + + // This call shouldn't fail! + uc.emu_start(CODE_START, CODE_START + 2, 0, 0).unwrap(); +} + +#[test] +fn test_x86_eflags_reserved_bit() { + let mut uc = Unicorn::new(Arch::X86, Mode::MODE_32).unwrap(); + + let r_eflags = uc.reg_read(RegisterX86::EFLAGS).unwrap(); + assert_ne!(r_eflags & 2, 0); + + uc.reg_write(RegisterX86::EFLAGS, r_eflags).unwrap(); + + let r_eflags = uc.reg_read(RegisterX86::EFLAGS).unwrap(); + assert_ne!(r_eflags & 2, 0); +} + +#[test] +fn test_x86_nested_uc_emu_start_exits() { + // cmp eax, 0 + // jnz t + // nop <-- nested emu_start + // t:mov dword ptr [eax], 0 + let code = b"\x83\xf8\x00\x75\x01\x90\xc7\x00\x00\x00\x00\x00"; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, ()); + uc.add_code_hook(CODE_START, CODE_START, |uc, _, _| { + uc.emu_start(CODE_START + 5, CODE_START + 6, 0, 0).unwrap(); + }) + .unwrap(); + + uc.emu_start(CODE_START, CODE_START + 5, 0, 0).unwrap(); + + assert_eq!(uc.reg_read(RegisterX86::EIP).unwrap(), CODE_START + 5); +} + +#[test] +fn test_x86_correct_address_in_small_jump_hook() { + // movabs $0x7F00, %rax; jmp *%rax + let code = b"\x48\xb8\x00\x7F\x00\x00\x00\x00\x00\x00\xff\xe0"; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_64, None, code, ()); + uc.add_mem_hook(HookType::MEM_UNMAPPED, 1, 0, |uc, _, address, _, _| { + // Check registers + assert_eq!(uc.reg_read(RegisterX86::RAX).unwrap(), 0x7F00); + assert_eq!(uc.reg_read(RegisterX86::RIP).unwrap(), 0x7F00); + // Check address + assert_eq!(address, 0x7F00); + + false + }) + .unwrap(); + + let err = uc + .emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap_err(); + assert_eq!(err, uc_error::FETCH_UNMAPPED); + + assert_eq!(uc.reg_read(RegisterX86::RAX).unwrap(), 0x7F00); + assert_eq!(uc.reg_read(RegisterX86::RIP).unwrap(), 0x7F00); +} + +#[test] +fn test_x86_correct_address_in_long_jump_hook() { + // movabs $0x7FFFFFFFFFFFFF00, %rax; jmp *%rax + let code = b"\x48\xb8\x00\xff\xff\xff\xff\xff\xff\x7f\xff\xe0"; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_64, None, code, ()); + uc.ctl_set_tlb_type(TlbType::VIRTUAL).unwrap(); + uc.add_mem_hook(HookType::MEM_UNMAPPED, 1, 0, |uc, _, address, _, _| { + // Check registers + assert_eq!( + uc.reg_read(RegisterX86::RAX).unwrap(), + 0x7FFF_FFFF_FFFF_FF00 + ); + assert_eq!( + uc.reg_read(RegisterX86::RIP).unwrap(), + 0x7FFF_FFFF_FFFF_FF00 + ); + // Check address + assert_eq!(address, 0x7FFF_FFFF_FFFF_FF00); + + false + }) + .unwrap(); + + let err = uc + .emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap_err(); + assert_eq!(err, uc_error::FETCH_UNMAPPED); + + assert_eq!( + uc.reg_read(RegisterX86::RAX).unwrap(), + 0x7FFF_FFFF_FFFF_FF00 + ); + assert_eq!( + uc.reg_read(RegisterX86::RIP).unwrap(), + 0x7FFF_FFFF_FFFF_FF00 + ); +} + +#[test] +fn test_x86_invalid_vex_l() { + let code = &[0xC5, 0xFE, 0x6F, 0x09]; // vmovdqu ymm1, [rcx] + + // initialize memory and run emulation + let mut uc = Unicorn::new(Arch::X86, Mode::MODE_64).unwrap(); + uc.mem_map(0, 2 * 1024 * 1024, Prot::ALL).unwrap(); + uc.mem_write(0, code).unwrap(); + + let err = uc.emu_start(0, code.len() as u64, 0, 0).unwrap_err(); + assert_eq!(err, uc_error::INSN_INVALID); +} + +// AARCH64 inlines the read while s390x won't split the access. Mirrors the +// `!defined(TARGET_READ_INLINED) && defined(BOOST_LITTLE_ENDIAN)` guard in +// tests/unit/test_x86.c: upstream sets TARGET_READ_INLINED for aarch64 and ppc hosts. +#[cfg(all( + target_endian = "little", + not(any( + target_arch = "aarch64", + target_arch = "powerpc", + target_arch = "powerpc64" + )) +))] +mod unaligned { + use super::*; + + type WriteLog = [(u64, usize); 10]; + + fn log_access(log: &mut WriteLog, address: u64, size: usize) { + assert_ne!(size, 0); + for entry in log.iter_mut() { + if entry.1 == 0 { + *entry = (address, size); + return; + } + } + panic!("write log overflow"); + } + + #[test] + fn test_x86_unaligned_access() { + // mov dword ptr [0x200001], eax; mov eax, dword ptr [0x200001] + let code = b"\xa3\x01\x00\x20\x00\xa1\x01\x00\x20\x00"; + + let mut uc = uc_common_setup( + Arch::X86, + Mode::MODE_32, + None, + code, + ([(0u64, 0usize); 10], [(0u64, 0usize); 10]), + ); + uc.mem_map(0x200000, 0x1000, Prot::ALL).unwrap(); + uc.add_mem_hook(HookType::MEM_WRITE, 1, 0, |uc, _, address, size, _| { + log_access(&mut uc.get_data_mut().0, address, size); + true + }) + .unwrap(); + uc.add_mem_hook(HookType::MEM_READ, 1, 0, |uc, _, address, size, _| { + log_access(&mut uc.get_data_mut().1, address, size); + true + }) + .unwrap(); + + uc.reg_write(RegisterX86::EAX, 0x4142_4344).unwrap(); + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); + + let (write_log, read_log) = uc.get_data(); + assert_eq!(write_log[0], (0x200001, 4)); + assert_eq!(write_log[1].1, 0); + assert_eq!(read_log[0], (0x200001, 4)); + assert_eq!(read_log[1].1, 0); + + for (offset, expected) in [(1, 0x44), (2, 0x43), (3, 0x42), (4, 0x41)] { + let mut b = [0u8; 1]; + uc.mem_read(0x200000 + offset, &mut b).unwrap(); + assert_eq!(b[0], expected); + } + } + + #[test] + fn test_x86_64_unaligned_access() { + #[rustfmt::skip] + let code = &[ + 0x48, 0x89, 0x01, // mov qword ptr [rcx], rax + 0x48, 0x8b, 0x00, // mov rax, qword ptr [rax] + 0xcc, // int3 + ]; + + let mut uc = uc_common_setup( + Arch::X86, + Mode::MODE_64, + None, + code, + ([(0u64, 0usize); 10], [(0u64, 0usize); 10]), + ); + uc.mem_map(0x200000, 0x200000, Prot::ALL).unwrap(); + uc.add_mem_hook(HookType::MEM_WRITE, 1, 0, |uc, _, address, size, _| { + log_access(&mut uc.get_data_mut().0, address, size); + true + }) + .unwrap(); + uc.add_mem_hook(HookType::MEM_READ, 1, 0, |uc, _, address, size, _| { + log_access(&mut uc.get_data_mut().1, address, size); + true + }) + .unwrap(); + + uc.reg_write(RegisterX86::RAX, 0x2fffff).unwrap(); + uc.reg_write(RegisterX86::RCX, 0x2fffff).unwrap(); + + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 2) + .unwrap(); + + let (write_log, read_log) = uc.get_data(); + assert_eq!(write_log[0], (0x2fffff, 8)); + assert_eq!(write_log[1].1, 0); + assert_eq!(read_log[0], (0x2fffff, 8)); + assert_eq!(read_log[1].1, 0); + + let mut b = [0u8; 8]; + uc.mem_read(0x2fffff, &mut b).unwrap(); + assert_eq!(u64::from_le_bytes(b), 0x2fffff); + } +} + +#[test] +fn test_x86_lazy_mapping() { + let mut uc = Unicorn::new_with_data(Arch::X86, Mode::MODE_32, 0usize).unwrap(); + uc.add_mem_hook(HookType::MEM_FETCH_UNMAPPED, 1, 0, |uc, _, _, _, _| { + uc.mem_map(0x1000, 0x1000, Prot::ALL).unwrap(); + uc.mem_write(0x1000, b"\x90\x90").unwrap(); // nop; nop + + // Handled! + true + }) + .unwrap(); + uc.add_block_hook(1, 0, |uc, _, _| { + *uc.get_data_mut() += 1; + }) + .unwrap(); + + uc.emu_start(0x1000, 0x1002, 0, 0).unwrap(); + + assert_eq!(*uc.get_data(), 1); +} + +#[test] +fn test_x86_16_incorrect_ip() { + let code = b"\x41"; // inc cx + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_16, None, code, ()); + uc.add_block_hook(1, 0, check_16_bit_ip).unwrap(); + uc.add_code_hook(1, 0, check_16_bit_ip).unwrap(); + + uc.reg_write(RegisterX86::CS, 0x20).unwrap(); + + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); +} + +fn check_16_bit_ip(uc: &mut Unicorn<()>, address: u64, _: u32) { + let cs = uc.reg_read(RegisterX86::CS).unwrap(); + let ip = uc.reg_read(RegisterX86::IP).unwrap(); + + assert_eq!(cs, 0x20); + assert_eq!(address, (cs << 4) + ip); +} + +#[test] +fn test_x86_vtlb() { + // jmp 4; nop; nop; nop; nop; nop; nop + let code = b"\xeb\x02\x90\x90\x90\x90\x90\x90"; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, ()); + + uc.ctl_set_tlb_type(TlbType::VIRTUAL).unwrap(); + uc.add_tlb_hook(1, 0, |_, addr, _| { + Some(TlbEntry { + paddr: addr, + perms: Prot::ALL, + }) + }) + .unwrap(); + + uc.emu_start(CODE_START, CODE_START + 4, 0, 0).unwrap(); + + assert_eq!(uc.reg_read(RegisterX86::EIP).unwrap(), CODE_START + 4); +} + +// This aborts prior to a7a5d187e77f7853755eff4768658daf8095c3b7 +#[test] +fn test_x86_0xff_lcall() { + // Taken from unicorn#1842 + // 0: b8 01 00 00 00 mov eax,0x1 + // 5: bb 01 00 00 00 mov ebx,0x1 + // a: b9 01 00 00 00 mov ecx,0x1 + // f: ff (bad) + // 10: dd ba 01 00 00 00 fnstsw WORD PTR [edx+0x1] + // 16: b8 02 00 00 00 mov eax,0x2 + // 1b: bb 02 00 00 00 mov ebx,0x2 + let code = b"\xB8\x01\x00\x00\x00\xBB\x01\x00\x00\x00\xB9\x01\x00\x00\x00\xFF\xDD\ + \xBA\x01\x00\x00\x00\xB8\x02\x00\x00\x00\xBB\x02\x00\x00\x00"; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, ()); + uc.add_code_hook(1, 0, |_, _, _| {}).unwrap(); + + let err = uc + .emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap_err(); + assert_eq!(err, uc_error::INSN_INVALID); +} + +// https://github.com/unicorn-engine/unicorn/issues/1717 +// https://github.com/unicorn-engine/unicorn/issues/1862 +#[test] +fn test_x86_64_not_overwriting_tmp0_for_pc_update() { + // 0x1000: movabs rcx, 0xffffffffffffffff + // 0x100a: mov qword ptr [rsp], rcx + // 0x100e: shl qword ptr [rsp], cl ; (Shift to CF=1) + // 0x1012: jae 0xd ; this jump should not be taken! (CF=1 but jae expects CF=0) + let code = b"\x48\xb9\xff\xff\xff\xff\xff\xff\xff\xff\x48\x89\x0c\ + \x24\x48\xd3\x24\x24\x73\x0a"; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_64, None, code, ()); + uc.add_mem_hook( + HookType::MEM_READ | HookType::MEM_WRITE, + 1, + 0, + |_, _, _, _, _| true, + ) + .unwrap(); + + uc.reg_write(RegisterX86::RSP, 0x2000).unwrap(); + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 4) + .unwrap(); + + assert_eq!(uc.reg_read(RegisterX86::RIP).unwrap(), 0x1014); + assert_eq!(uc.reg_read(RegisterX86::EFLAGS).unwrap() & 0x1, 1); +} + +#[test] +fn test_fxsave_fpip_x86() { + const X86_NOP_OFFSET: u64 = 4; + + // note: fxsave was introduced in Pentium II + #[rustfmt::skip] + let code = &[ + // help testing through NOP offset [disassembly in at&t syntax] + 0x90, 0x90, 0x90, 0x90, // nop nop nop nop + // run a floating point instruction + 0xdb, 0xc9, // fcmovne %st(1), %st + // fxsave needs 512 bytes of storage space + 0x81, 0xec, 0x00, 0x02, 0x00, 0x00, // subl $512, %esp + // fxsave needs a 16-byte aligned address for storage + 0x83, 0xe4, 0xf0, // andl $0xfffffff0, %esp + // store fxsave data on the stack + 0x0f, 0xae, 0x04, 0x24, // fxsave (%esp) + // fxsave stores FPIP at an 8-byte offset, move FPIP to eax register + 0x8b, 0x44, 0x24, 0x08, // movl 0x8(%esp), %eax + ]; + + // initialize emulator in X86-32bit mode + let mut uc = Unicorn::new(Arch::X86, Mode::MODE_32).unwrap(); + + // map 1MB of memory for this emulation + uc.mem_map(MEM_BASE, MEM_SIZE, Prot::ALL).unwrap(); + uc.mem_write(MEM_TEXT, code).unwrap(); + uc.reg_write(RegisterX86::ESP, MEM_STACK).unwrap(); + uc.emu_start(MEM_TEXT, MEM_TEXT + code.len() as u64, 0, 0) + .unwrap(); + + assert_eq!( + uc.reg_read(RegisterX86::EAX).unwrap(), + MEM_TEXT + X86_NOP_OFFSET + ); + + uc.mem_unmap(MEM_BASE, MEM_SIZE).unwrap(); +} + +#[test] +fn test_fxsave_fpip_x64() { + const X64_NOP_OFFSET: u64 = 8; + + #[rustfmt::skip] + let code = &[ + // help testing through NOP offset [disassembly in at&t] + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // nops + // run a floating point instruction + 0xdb, 0xc9, // fcmovne %st(1), %st + // fxsave64 needs 512 bytes of storage space + 0x48, 0x81, 0xec, 0x00, 0x02, 0x00, 0x00, // subq $512, %rsp + // fxsave needs a 16-byte aligned address for storage + 0x48, 0x83, 0xe4, 0xf0, // andq 0xfffffffffffffff0, %rsp + // store fxsave64 data on the stack + 0x48, 0x0f, 0xae, 0x04, 0x24, // fxsave64 (%rsp) + // fxsave64 stores FPIP at an 8-byte offset, move FPIP to rax register + 0x48, 0x8b, 0x44, 0x24, 0x08, // movq 0x8(%rsp), %rax + ]; + + let mut uc = Unicorn::new(Arch::X86, Mode::MODE_64).unwrap(); + + // map 1MB of memory for this emulation + uc.mem_map(MEM_BASE, MEM_SIZE, Prot::ALL).unwrap(); + uc.mem_write(MEM_TEXT, code).unwrap(); + uc.reg_write(RegisterX86::RSP, MEM_STACK).unwrap(); + uc.emu_start(MEM_TEXT, MEM_TEXT + code.len() as u64, 0, 0) + .unwrap(); + + assert_eq!( + uc.reg_read(RegisterX86::RAX).unwrap(), + MEM_TEXT + X64_NOP_OFFSET + ); + + uc.mem_unmap(MEM_BASE, MEM_SIZE).unwrap(); +} + +// References: +// - https://gynvael.coldwind.pl/?id=268 +// - https://github.com/JonathanSalwan/Triton/issues/1131 +#[test] +fn test_bswap_ax() { + QuickTest { + mode: Mode::MODE_32, + code: &[0x66, 0x0F, 0xC8], // bswap ax + in_regs: &[(RegisterX86::EAX, 0x4433_2211)], + out_regs: &[(RegisterX86::EAX, 0x4433_0000)], + } + .run(); + + QuickTest { + mode: Mode::MODE_64, + code: &[0x66, 0x0F, 0xC8], // bswap ax + in_regs: &[(RegisterX86::RAX, 0x8877_6655_4433_2211)], + out_regs: &[(RegisterX86::RAX, 0x8877_6655_4433_0000)], + } + .run(); + + QuickTest { + mode: Mode::MODE_64, + code: &[0x66, 0x48, 0x0F, 0xC8], // bswap rax (66h ignored) + in_regs: &[(RegisterX86::RAX, 0x8877_6655_4433_2211)], + out_regs: &[(RegisterX86::RAX, 0x1122_3344_5566_7788)], + } + .run(); + + QuickTest { + mode: Mode::MODE_64, + code: &[0x48, 0x66, 0x0F, 0xC8], // bswap ax (rex ignored) + in_regs: &[(RegisterX86::RAX, 0x8877_6655_4433_2211)], + out_regs: &[(RegisterX86::RAX, 0x8877_6655_4433_0000)], + } + .run(); + + QuickTest { + mode: Mode::MODE_32, + code: &[0x0F, 0xC8], // bswap eax + in_regs: &[(RegisterX86::EAX, 0x4433_2211)], + out_regs: &[(RegisterX86::EAX, 0x1122_3344)], + } + .run(); + + QuickTest { + mode: Mode::MODE_64, + code: &[0x0F, 0xC8], // bswap eax + in_regs: &[(RegisterX86::RAX, 0x8877_6655_4433_2211)], + out_regs: &[(RegisterX86::RAX, 0x0000_0000_1122_3344)], + } + .run(); +} + +#[test] +fn test_rex_x64() { + QuickTest { + mode: Mode::MODE_64, + code: &[0x48, 0x66, 0x89, 0xD8], // mov ax, bx (rex.w ignored) + in_regs: &[ + (RegisterX86::RAX, 0x8877_6655_4433_2211), + (RegisterX86::RBX, 0x1122_3344_5566_7788), + ], + out_regs: &[(RegisterX86::RAX, 0x8877_6655_4433_7788)], + } + .run(); + + QuickTest { + mode: Mode::MODE_64, + code: &[0x66, 0x48, 0x89, 0xD8], // mov rax, rbx (66h ignored) + in_regs: &[ + (RegisterX86::RAX, 0x8877_6655_4433_2211), + (RegisterX86::RBX, 0x1122_3344_5566_7788), + ], + out_regs: &[(RegisterX86::RAX, 0x1122_3344_5566_7788)], + } + .run(); + + QuickTest { + mode: Mode::MODE_64, + code: &[0x66, 0x89, 0xD8], // mov ax, bx (expected encoding) + in_regs: &[ + (RegisterX86::RAX, 0x8877_6655_4433_2211), + (RegisterX86::RBX, 0x1122_3344_5566_7788), + ], + out_regs: &[(RegisterX86::RAX, 0x8877_6655_4433_7788)], + } + .run(); +} + +#[test] +fn test_x86_ro_segfault() { + // mov eax, [0x1000] + // mov eax, [0x1000] + let code = b"\xA1\x00\x10\x00\x00\xA1\x00\x10\x00\x00"; + + let mut uc = Unicorn::new(Arch::X86, Mode::MODE_32).unwrap(); + uc.mem_map(0, 0x1000, Prot::ALL).unwrap(); + uc.mem_write(0, code).unwrap(); + uc.mem_map(0x1000, 0x1000, Prot::READ).unwrap(); + + uc.add_mem_hook(HookType::MEM_READ, 1, 0, move |uc, _, address, _, _| { + uc.mem_write(address, code).unwrap(); + true + }) + .unwrap(); + uc.emu_start(0, code.len() as u64, 0, 0).unwrap(); + + assert_eq!(uc.reg_read(RegisterX86::EAX).unwrap(), 0x0010_00a1); +} + +#[test] +fn test_x86_dr7() { + // mov rax, 0x10005; mov dr7, rax + let code = b"\x48\xC7\xC0\x05\x00\x01\x00\x0F\x23\xF8"; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_64, None, code, ()); + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); +} + +#[test] +fn test_x86_hook_block() { + // jmp 4; nop; nop; nop; nop; nop; nop + let code = b"\xeb\x02\x90\x90\x90\x90\x90\x90"; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, 0u64); + uc.add_block_hook(1, 0, |uc, address, _| { + let pc = uc.reg_read(RegisterX86::EIP).unwrap(); + assert_eq!(pc, address); + *uc.get_data_mut() += 1; + }) + .unwrap(); + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); + + assert_eq!(*uc.get_data(), 2); +} + +#[test] +fn test_x86_mem_hooks_pc_guarantee() { + // bs, _ = ks.asm("inc edx; t: mov eax, [ebx]; inc ebx; cmp ebx, ecx; jnz t;") + let code = b"\x42\x8b\x03\x43\x39\xcb\x75\xf9"; + let ebx = CODE_START + CODE_LEN; + let ecx = CODE_START + CODE_LEN + 0x10; + + let mut uc = uc_common_setup(Arch::X86, Mode::MODE_32, None, code, ()); + + uc.mem_map(CODE_START + CODE_LEN, 0x1000, Prot::ALL) + .unwrap(); + uc.add_mem_hook(HookType::MEM_READ, 1, 0, |uc, _, addr, _, _| { + if addr >= CODE_START + CODE_LEN { + let eip = uc.reg_read(RegisterX86::EIP).unwrap(); + assert_eq!(eip, CODE_START + 1); + } + true + }) + .unwrap(); + uc.reg_write(RegisterX86::EBX, ebx).unwrap(); + uc.reg_write(RegisterX86::ECX, ecx).unwrap(); + uc.emu_start(CODE_START, CODE_START + code.len() as u64, 0, 0) + .unwrap(); +}