Skip to main content

Overview

The CPU module provides ARM64 CPU emulation using the Unicorn Engine. It supports both single-core and multi-core configurations with shared memory.

UnicornCPU

Safe wrapper for the Unicorn CPU emulator that provides ARM64 emulation with thread-safe access.

Structure

pub struct UnicornCPU {
    emu: Arc<Mutex<Unicorn<'static, ()>>>,
    pub core_id: u32,
}
emu
Arc<Mutex<Unicorn<'static, ()>>>
Thread-safe reference to the Unicorn emulator instance
core_id
u32
Unique identifier for this CPU core (0-7 in multi-core mode)

Constructor Methods

new()

Create a new Unicorn instance with 8MB of memory for legacy/test mode.
pub fn new() -> Option<Self>
return
Option<UnicornCPU>
Returns Some(UnicornCPU) on success, None if initialization fails
Example:
use oboromi_core::cpu::UnicornCPU;

let cpu = UnicornCPU::new().expect("Failed to create CPU");
cpu.set_pc(0x1000);
cpu.set_x(0, 42);

new_with_shared_mem()

Create a new Unicorn instance with shared memory across multiple cores.
pub unsafe fn new_with_shared_mem(
    core_id: u32,
    memory_ptr: *mut u8,
    memory_size: u64
) -> Option<Self>
core_id
u32
required
Unique identifier for this core (used for stack allocation)
memory_ptr
*mut u8
required
Pointer to shared memory region
memory_size
u64
required
Size of the memory region in bytes
Safety: The caller must ensure memory_ptr is valid for the lifetime of this CPU and has at least memory_size bytes.
Example:
use oboromi_core::cpu::UnicornCPU;

let memory = vec![0u8; 12 * 1024 * 1024 * 1024].into_boxed_slice();
let memory_ptr = memory.as_ptr() as *mut u8;

unsafe {
    let cpu = UnicornCPU::new_with_shared_mem(0, memory_ptr, memory.len() as u64)
        .expect("Failed to create CPU with shared memory");
}

Execution Control

run()

Run the core until halt or breakpoint (BRK instruction).
pub fn run(&self) -> u64
return
u64
Returns 1 on success (normal completion or BRK), 0 on emulation error
Example:
cpu.set_pc(0x1000);
cpu.write_u32(0x1000, 0xD503201F); // NOP
cpu.write_u32(0x1004, 0xD4200000); // BRK #0

let result = cpu.run();
assert_eq!(result, 1); // Success

step()

Execute a single instruction.
pub fn step(&self) -> u64
return
u64
Returns 0 on success, 1 on error

halt()

Halt execution immediately.
pub fn halt(&self)

Register Access

get_x() / set_x()

Read or write general-purpose registers X0-X30.
pub fn get_x(&self, reg_index: u32) -> u64
pub fn set_x(&self, reg_index: u32, value: u64)
reg_index
u32
required
Register index (0-30). Returns 0 if index is invalid.
value
u64
Value to write to the register
Example:
cpu.set_x(0, 100);
cpu.set_x(1, 50);
// Execute: ADD X0, X0, X1
let result = cpu.get_x(0); // 150

get_sp() / set_sp()

Read or write the stack pointer.
pub fn get_sp(&self) -> u64
pub fn set_sp(&self, value: u64)
Example:
cpu.set_sp(0x8000);
let sp = cpu.get_sp();

get_pc() / set_pc()

Read or write the program counter.
pub fn get_pc(&self) -> u64
pub fn set_pc(&self, value: u64)
Example:
cpu.set_pc(0x1000); // Jump to address 0x1000
cpu.run();
let final_pc = cpu.get_pc();

Memory Access

write_u32() / read_u32()

Write or read 32-bit values from emulated memory.
pub fn write_u32(&self, vaddr: u64, value: u32)
pub fn read_u32(&self, vaddr: u64) -> u32
vaddr
u64
required
Virtual address to access
value
u32
32-bit value to write (little-endian)
Example:
cpu.write_u32(0x2000, 0xDEADBEEF);
let value = cpu.read_u32(0x2000);
assert_eq!(value, 0xDEADBEEF);

write_u64() / read_u64()

Write or read 64-bit values from emulated memory.
pub fn write_u64(&self, vaddr: u64, value: u64)
pub fn read_u64(&self, vaddr: u64) -> u64
vaddr
u64
required
Virtual address to access
value
u64
64-bit value to write (little-endian)
Example:
cpu.write_u64(0x3000, 0xCAFEBABEDEADBEEF);
let value = cpu.read_u64(0x3000);
assert_eq!(value, 0xCAFEBABEDEADBEEF);

CpuManager

Manages multiple CPU cores with shared memory for multi-core emulation.

Structure

pub struct CpuManager {
    pub cores: Vec<UnicornCPU>,
    pub shared_memory: Pin<Box<[u8]>>,
}
cores
Vec<UnicornCPU>
Vector of 8 CPU cores sharing the same memory
shared_memory
Pin<Box<[u8]>>
Pinned 12GB shared memory region (prevents reallocation)

Constants

pub const CORE_COUNT: usize = 8;
pub const MEMORY_SIZE: u64 = 12 * 1024 * 1024 * 1024; // 12GB
pub const MEMORY_BASE: u64 = 0x0;

Methods

new()

Create a new CPU manager with 8 cores and 12GB of shared memory.
pub fn new() -> Self
Memory is lazily allocated by modern operating systems using virtual memory, so physical RAM is only consumed when written to.
Example:
use oboromi_core::cpu::cpu_manager::CpuManager;

let manager = CpuManager::new();
assert_eq!(manager.cores.len(), 8);

get_core()

Get a reference to a specific CPU core.
pub fn get_core(&self, id: usize) -> Option<&UnicornCPU>
id
usize
required
Core ID (0-7)
Example:
let manager = CpuManager::new();
let core0 = manager.get_core(0).unwrap();
let core1 = manager.get_core(1).unwrap();

// Write with core 0
core0.write_u32(0x1000, 0xDEADBEEF);

// Read with core 1 (shared memory!)
let value = core1.read_u32(0x1000);
assert_eq!(value, 0xDEADBEEF);

run_all()

Execute one step on all cores sequentially (round-robin scheduling).
pub fn run_all(&self)
Currently runs cores sequentially. Future versions will support threaded execution.

Thread Safety

UnicornCPU implements Send and Sync, making it safe to share across threads:
unsafe impl Send for UnicornCPU {}
unsafe impl Sync for UnicornCPU {}
This allows multiple cores to be executed in parallel while sharing the same memory space.

Complete Example

use oboromi_core::cpu::cpu_manager::CpuManager;

fn main() {
    // Initialize 8-core system with 12GB RAM
    let manager = CpuManager::new();
    
    // Get two cores
    let core0 = manager.get_core(0).unwrap();
    let core1 = manager.get_core(1).unwrap();
    
    // ARM64 Instructions
    let nop = 0xD503201F;       // NOP
    let add = 0x91000400;       // ADD X0, X0, #1
    let brk = 0xD4200000;       // BRK #0
    
    // Program for Core 0
    let mut addr = 0x1000;
    core0.write_u32(addr, add);
    addr += 4;
    core0.write_u32(addr, add);
    addr += 4;
    core0.write_u32(addr, brk);
    
    // Set up Core 0
    core0.set_pc(0x1000);
    core0.set_sp(0x8000);
    core0.set_x(0, 100);
    
    // Execute on Core 0
    core0.run();
    
    // Check result
    let result = core0.get_x(0);
    println!("Core 0: X0 = {}", result); // 102
    
    // Core 1 can see memory written by Core 0
    let shared_val = core1.read_u32(0x1000);
    println!("Core 1 sees instruction: {:#010x}", shared_val);
}

See Also