Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rust-tests: Some RISC-V emulation tests #1988

Draft
wants to merge 3 commits into
base: dev
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
361 changes: 359 additions & 2 deletions tests/rust-tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ extern crate alloc;
use alloc::rc::Rc;
use core::cell::RefCell;
use unicorn_engine::unicorn_const::{
uc_error, Arch, HookType, MemType, Mode, Permission, SECOND_SCALE, TlbEntry, TlbType
uc_error, Arch, HookType, MemType, Mode, Permission, TlbEntry, TlbType, SECOND_SCALE,
};
use unicorn_engine::{
InsnSysX86, RegisterARM, RegisterMIPS, RegisterPPC, RegisterRISCV, RegisterX86, Unicorn,
};
use unicorn_engine::{InsnSysX86, RegisterARM, RegisterMIPS, RegisterPPC, RegisterX86, Unicorn};

pub static X86_REGISTERS: [RegisterX86; 125] = [
RegisterX86::AH,
Expand Down Expand Up @@ -623,6 +625,361 @@ fn emulate_ppc() {
assert_eq!(emu.reg_read(RegisterPPC::R26), Ok(1379));
}

#[test]
fn emulate_riscv64() {
let riscv_code: Vec<u8> = vec![0x13, 0x05, 0xa5, 0x00]; // addi a0, a0, 10

let mut emu = unicorn_engine::Unicorn::new(Arch::RISCV, Mode::RISCV64)
.expect("failed to initialize unicorn instance");
assert_eq!(emu.reg_write(RegisterRISCV::A0, 123), Ok(()));
assert_eq!(emu.reg_read(RegisterRISCV::A0), Ok(123));

// Attempt to write to memory before mapping it.
assert_eq!(
emu.mem_write(0x1000, &riscv_code),
(Err(uc_error::WRITE_UNMAPPED))
);

assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(()));
assert_eq!(emu.mem_write(0x1000, &riscv_code), Ok(()));
assert_eq!(
emu.mem_read_as_vec(0x1000, riscv_code.len()),
Ok(riscv_code.clone())
);

assert_eq!(
emu.emu_start(
0x1000,
(0x1000 + riscv_code.len()) as u64,
10 * SECOND_SCALE,
1000
),
Ok(())
);
assert_eq!(emu.reg_read(RegisterRISCV::A0), Ok(123 + 10));
}

#[test]
fn emulate_riscv64_invalid_insn_hook() {
let riscv_code: Vec<u8> = vec![0x73, 0x10, 0x00, 0xc0]; // "unimp"

struct Data {
invalid_hook_called: bool,
}

let mut emu = unicorn_engine::Unicorn::new_with_data(
Arch::RISCV,
Mode::RISCV64,
Data {
invalid_hook_called: false,
},
)
.expect("failed to initialize unicorn instance");

// Attempt to write to memory before mapping it.
assert_eq!(
emu.mem_write(0x1000, &riscv_code),
(Err(uc_error::WRITE_UNMAPPED))
);

assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(()));
assert_eq!(emu.mem_write(0x1000, &riscv_code), Ok(()));
assert_eq!(
emu.mem_read_as_vec(0x1000, riscv_code.len()),
Ok(riscv_code.clone())
);

emu.add_insn_invalid_hook(|emu| {
let data = emu.get_data_mut();
data.invalid_hook_called = true;
emu.emu_stop().expect("failed to stop");
true
})
.expect("failed to add invalid instruction hook");

assert_eq!(
emu.emu_start(
0x1000,
(0x1000 + riscv_code.len()) as u64,
10 * SECOND_SCALE,
1000
),
Err(uc_error::EXCEPTION), // FIXME: Should return Ok(()) because the hook stopped emulation?
);

assert!(
emu.get_data().invalid_hook_called,
"invalid instruction hook was not called"
);
}

#[test]
fn emulate_riscv64_invalid_insn_interrupt() {
let riscv_code: Vec<u8> = vec![0x73, 0x10, 0x00, 0xc0]; // "unimp"

struct Data {
hook_calls: usize,
mcause: Option<u32>,
}

let mut emu = unicorn_engine::Unicorn::new_with_data(
Arch::RISCV,
Mode::RISCV64,
Data {
hook_calls: 0,
mcause: None,
},
)
.expect("failed to initialize unicorn instance");

// Attempt to write to memory before mapping it.
assert_eq!(
emu.mem_write(0x1000, &riscv_code),
(Err(uc_error::WRITE_UNMAPPED))
);

assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(()));
assert_eq!(emu.mem_write(0x1000, &riscv_code), Ok(()));
assert_eq!(
emu.mem_read_as_vec(0x1000, riscv_code.len()),
Ok(riscv_code.clone())
);

emu.add_intr_hook(|emu, mcause| {
let data = emu.get_data_mut();
data.hook_calls += 1;
data.mcause = Some(mcause);
emu.emu_stop().expect("failed to stop");
})
.expect("failed to add interrupt hook");

assert_eq!(
emu.emu_start(
0x1000,
(0x1000 + riscv_code.len()) as u64,
10 * SECOND_SCALE,
1000
),
Ok(())
);

assert_eq!(
emu.get_data().hook_calls,
1,
"interrupt hook should have been called exactly once"
);
assert_eq!(
emu.get_data().mcause,
Some(2_u32),
"wrong mcause value for illegal instruction"
);
}

#[test]
fn emulate_riscv64_mem_error_hook() {
let riscv_code: Vec<u8> = vec![
0x17, 0x45, 0x01, 0x00, // auipc a0,0x14
0x13, 0x05, 0x85, 0x00, // addi a0,a0,8 # 14008
0x03, 0x25, 0x05, 0x00, // lw a0,0(a0)
];

struct Data {
hook_calls: usize,
call: Option<HookCall>,
}
#[derive(Debug, PartialEq)]
struct HookCall {
typ: MemType,
addr: u64,
size: usize,
}

let mut emu = unicorn_engine::Unicorn::new_with_data(
Arch::RISCV,
Mode::RISCV64,
Data {
hook_calls: 0,
call: None,
},
)
.expect("failed to initialize unicorn instance");

// Attempt to write to memory before mapping it.
assert_eq!(
emu.mem_write(0x1000, &riscv_code),
(Err(uc_error::WRITE_UNMAPPED))
);

assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(()));
assert_eq!(emu.mem_write(0x1000, &riscv_code), Ok(()));
assert_eq!(
emu.mem_read_as_vec(0x1000, riscv_code.len()),
Ok(riscv_code.clone())
);

emu.add_mem_hook(HookType::MEM_INVALID, 0, !0, |emu, typ, addr, size, _| {
let data = emu.get_data_mut();
data.hook_calls += 1;
data.call = Some(HookCall { typ, addr, size });
emu.emu_stop().expect("failed to stop emulation");
true
})
.expect("failed to add memory hook");

assert_eq!(
emu.emu_start(
0x1000,
(0x1000 + riscv_code.len()) as u64,
10 * SECOND_SCALE,
1000
),
Err(uc_error::MAP), // FIXME: Should return Ok(()) because the hook stopped emulation?
);

assert_eq!(
emu.get_data().hook_calls,
1,
"interrupt hook should have been called exactly once"
);
assert_eq!(
emu.get_data().call,
Some(HookCall {
typ: MemType::READ_UNMAPPED,
addr: 0x15008,
size: 4,
}),
"wrong hook call for read from unmapped memory"
);
}

#[test]
fn emulate_riscv64_mem_error_interrupt() {
let riscv_code: Vec<u8> = vec![
0x17, 0x45, 0x01, 0x00, // auipc a0,0x14
0x13, 0x05, 0x85, 0x00, // addi a0,a0,8 # 14008
0x03, 0x25, 0x05, 0x00, // lw a0,0(a0)
];

struct Data {
hook_calls: usize,
mcause: Option<u32>,
}

let mut emu = unicorn_engine::Unicorn::new_with_data(
Arch::RISCV,
Mode::RISCV64,
Data {
hook_calls: 0,
mcause: None,
},
)
.expect("failed to initialize unicorn instance");

// Attempt to write to memory before mapping it.
assert_eq!(
emu.mem_write(0x1000, &riscv_code),
(Err(uc_error::WRITE_UNMAPPED))
);

assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(()));
assert_eq!(emu.mem_write(0x1000, &riscv_code), Ok(()));
assert_eq!(
emu.mem_read_as_vec(0x1000, riscv_code.len()),
Ok(riscv_code.clone())
);

emu.add_intr_hook(|emu, mcause| {
let data = emu.get_data_mut();
data.hook_calls += 1;
data.mcause = Some(mcause);
emu.emu_stop().expect("failed to stop");
})
.expect("failed to add interrupt hook");

assert_eq!(
emu.emu_start(
0x1000,
(0x1000 + riscv_code.len()) as u64,
10 * SECOND_SCALE,
1000
),
Err(uc_error::READ_UNMAPPED), // FIXME: Should return Ok(()) because the hook stopped emulation?
);

assert_eq!(
emu.get_data().hook_calls,
1,
"interrupt hook should have been called exactly once"
);
assert_eq!(
emu.get_data().mcause,
Some(13_u32),
"wrong mcause value for load page fault"
);
}

#[test]
fn emulate_riscv64_ecall_interrupt() {
let riscv_code: Vec<u8> = vec![0x73, 0x00, 0x00, 0x00]; // ecall

struct Data {
hook_calls: usize,
mcause: Option<u32>,
}

let mut emu = unicorn_engine::Unicorn::new_with_data(
Arch::RISCV,
Mode::RISCV64,
Data {
hook_calls: 0,
mcause: None,
},
)
.expect("failed to initialize unicorn instance");

// Attempt to write to memory before mapping it.
assert_eq!(
emu.mem_write(0x1000, &riscv_code),
(Err(uc_error::WRITE_UNMAPPED))
);

assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(()));
assert_eq!(emu.mem_write(0x1000, &riscv_code), Ok(()));
assert_eq!(
emu.mem_read_as_vec(0x1000, riscv_code.len()),
Ok(riscv_code.clone())
);

emu.add_intr_hook(|emu, mcause| {
let data = emu.get_data_mut();
data.hook_calls += 1;
data.mcause = Some(mcause);
emu.emu_stop().expect("failed to stop");
})
.expect("failed to add interrupt hook");

assert_eq!(
emu.emu_start(
0x1000,
(0x1000 + riscv_code.len()) as u64,
10 * SECOND_SCALE,
1000
),
Ok(())
);

assert_eq!(
emu.get_data().hook_calls,
1,
"interrupt hook should have been called exactly once"
);
assert_eq!(
emu.get_data().mcause,
Some(8_u32),
"wrong mcause value for ecall from U-Mode"
);
}

#[test]
fn mem_unmapping() {
let mut emu = unicorn_engine::Unicorn::new(Arch::X86, Mode::MODE_32)
Expand Down
Loading