mirror of
https://github.com/icicle-emu/icicle-python
synced 2026-06-21 13:53:41 +00:00
Implement running and exceptions
This commit is contained in:
+73
-6
@@ -23,11 +23,70 @@ class MemoryErrorCode(Enum):
|
||||
AddressOverflow = ...
|
||||
Unknown = ...
|
||||
|
||||
class RunStatus(Enum):
|
||||
Running = ...
|
||||
InstructionLimit = ...
|
||||
Breakpoint = ...
|
||||
Interrupted = ...
|
||||
Halt = ...
|
||||
Killed = ...
|
||||
Deadlock = ...
|
||||
OutOfMemory = ...
|
||||
Unimplemented = ...
|
||||
UnhandledException = ...
|
||||
|
||||
class ExceptionCode(Enum):
|
||||
NoException = ...
|
||||
InstructionLimit = ...
|
||||
Halt = ...
|
||||
Sleep = ...
|
||||
Syscall = ...
|
||||
CpuStateChanged = ...
|
||||
DivideByZero = ...
|
||||
ReadUnmapped = ...
|
||||
ReadPerm = ...
|
||||
ReadUnaligned = ...
|
||||
ReadWatch = ...
|
||||
ReadUninitialized = ...
|
||||
WriteUnmapped = ...
|
||||
WritePerm = ...
|
||||
WriteWatch = ...
|
||||
WriteUnaligned = ...
|
||||
ExecViolation = ...
|
||||
SelfModifyingCode = ...
|
||||
OutOfMemory = ...
|
||||
AddressOverflow = ...
|
||||
InvalidInstruction = ...
|
||||
UnknownInterrupt = ...
|
||||
UnknownCpuID = ...
|
||||
InvalidOpSize = ...
|
||||
InvalidFloatSize = ...
|
||||
CodeNotTranslated = ...
|
||||
ShadowStackOverflow = ...
|
||||
ShadowStackInvalid = ...
|
||||
InvalidTarget = ...
|
||||
UnimplementedOp = ...
|
||||
ExternalAddr = ...
|
||||
Environment = ...
|
||||
JitError = ...
|
||||
InternalError = ...
|
||||
UnknownError = ...
|
||||
|
||||
class MemoryError(Exception):
|
||||
code: MemoryErrorCode
|
||||
|
||||
class Icicle:
|
||||
def __init__(self, architecture: str) -> None: ...
|
||||
def __init__(self, architecture: str, *, jit=False, recompilation=False) -> None: ...
|
||||
|
||||
@property
|
||||
def exception_code(self) -> ExceptionCode: ...
|
||||
|
||||
@property
|
||||
def exception_value(self) -> int: ...
|
||||
|
||||
icount: int
|
||||
|
||||
icount_limit: int
|
||||
|
||||
def mem_map(self, address: int, size: int, protection: MemoryProtection): ...
|
||||
|
||||
@@ -47,8 +106,16 @@ class Icicle:
|
||||
|
||||
def reg_write(self, name: str, value: int) -> None: ...
|
||||
|
||||
def architectures() -> List[str]:
|
||||
"""
|
||||
List all the available architectures.
|
||||
"""
|
||||
...
|
||||
def reset(self): ...
|
||||
|
||||
def run(self) -> RunStatus: ...
|
||||
|
||||
def run_until(self, address: int) -> RunStatus: ...
|
||||
|
||||
def step(self, count: int) -> RunStatus: ...
|
||||
|
||||
def add_breakpoint(self, address: int) -> bool: ...
|
||||
|
||||
def remove_breakpoint(self, address: int) -> bool: ...
|
||||
|
||||
def architectures() -> List[str]: ...
|
||||
|
||||
+3
-1
@@ -1 +1,3 @@
|
||||
maturin==1.0.1
|
||||
maturin==1.0.1
|
||||
keystone-engine==0.9.2
|
||||
capstone==4.0.2
|
||||
+209
-5
@@ -5,11 +5,12 @@ use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::fmt::{format, Formatter};
|
||||
use icicle_cpu::mem::{Mapping, MemError, perm};
|
||||
use icicle_cpu::{ExceptionCode, VmExit};
|
||||
use pyo3::prelude::*;
|
||||
use icicle_vm;
|
||||
use icicle_vm::{BuildError, Vm};
|
||||
use icicle_vm::linux::LinuxCpu;
|
||||
use pyo3::types::PyList;
|
||||
use pyo3::types::{PyList, PyTuple};
|
||||
use pyo3::{create_exception, import_exception};
|
||||
use pyo3::exceptions::*;
|
||||
use target_lexicon;
|
||||
@@ -30,6 +31,40 @@ enum MemoryProtection {
|
||||
ExecuteReadWrite,
|
||||
}
|
||||
|
||||
#[pyclass(module = "icicle")]
|
||||
#[derive(Clone)]
|
||||
enum RunStatus {
|
||||
/// The VM is still running.
|
||||
Running,
|
||||
|
||||
/// The VM exited because it reached instruction count limit.
|
||||
InstructionLimit,
|
||||
|
||||
/// The VM exited because it reached a breakpoint.
|
||||
Breakpoint,
|
||||
|
||||
/// The VM exited because the interrupt flag was set.
|
||||
Interrupted,
|
||||
|
||||
/// The VM has halted.
|
||||
Halt,
|
||||
|
||||
/// Killed by an environment specific mechanism.
|
||||
Killed,
|
||||
|
||||
/// A deadlock was detected.
|
||||
Deadlock,
|
||||
|
||||
/// MMU was unable to allocate memory for an operation.
|
||||
OutOfMemory,
|
||||
|
||||
/// Internal error where the emulator reached unimplemented code.
|
||||
Unimplemented,
|
||||
|
||||
/// The VM exited due to a unhandled exception.
|
||||
UnhandledException,
|
||||
}
|
||||
|
||||
#[pyclass(module = "icicle")]
|
||||
enum MemoryErrorCode {
|
||||
Unallocated,
|
||||
@@ -67,6 +102,97 @@ impl From<MemError> for MemoryErrorCode {
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(module = "icicle", name = "ExceptionCode")]
|
||||
#[derive(Clone)]
|
||||
enum ExceptionCodePy {
|
||||
NoException = 0x0000,
|
||||
|
||||
InstructionLimit = 0x0001,
|
||||
Halt = 0x0002,
|
||||
Sleep = 0x0003,
|
||||
|
||||
Syscall = 0x0101,
|
||||
CpuStateChanged = 0x0102,
|
||||
DivideByZero = 0x0103,
|
||||
|
||||
ReadUnmapped = 0x0201,
|
||||
ReadPerm = 0x0202,
|
||||
ReadUnaligned = 0x0203,
|
||||
ReadWatch = 0x0204,
|
||||
ReadUninitialized = 0x0205,
|
||||
|
||||
WriteUnmapped = 0x0301,
|
||||
WritePerm = 0x0302,
|
||||
WriteWatch = 0x0303,
|
||||
WriteUnaligned = 0x0304,
|
||||
|
||||
ExecViolation = 0x0401,
|
||||
SelfModifyingCode = 0x0402,
|
||||
OutOfMemory = 0x0501,
|
||||
AddressOverflow = 0x0502,
|
||||
|
||||
InvalidInstruction = 0x1001,
|
||||
UnknownInterrupt = 0x1002,
|
||||
UnknownCpuID = 0x1003,
|
||||
InvalidOpSize = 0x1004,
|
||||
InvalidFloatSize = 0x1005,
|
||||
CodeNotTranslated = 0x1006,
|
||||
ShadowStackOverflow = 0x1007,
|
||||
ShadowStackInvalid = 0x1008,
|
||||
InvalidTarget = 0x1009,
|
||||
UnimplementedOp = 0x100a,
|
||||
|
||||
ExternalAddr = 0x2001,
|
||||
Environment = 0x2002,
|
||||
|
||||
JitError = 0x3001,
|
||||
InternalError = 0x3002,
|
||||
|
||||
UnknownError,
|
||||
}
|
||||
|
||||
impl From<ExceptionCode> for ExceptionCodePy {
|
||||
fn from(value: ExceptionCode) -> Self {
|
||||
match value {
|
||||
ExceptionCode::None => ExceptionCodePy::NoException,
|
||||
ExceptionCode::InstructionLimit => ExceptionCodePy::InstructionLimit,
|
||||
ExceptionCode::Halt => ExceptionCodePy::Halt,
|
||||
ExceptionCode::Sleep => ExceptionCodePy::Sleep,
|
||||
ExceptionCode::Syscall => ExceptionCodePy::Syscall,
|
||||
ExceptionCode::CpuStateChanged => ExceptionCodePy::CpuStateChanged,
|
||||
ExceptionCode::DivideByZero => ExceptionCodePy::DivideByZero,
|
||||
ExceptionCode::ReadUnmapped => ExceptionCodePy::ReadUnmapped,
|
||||
ExceptionCode::ReadPerm => ExceptionCodePy::ReadPerm,
|
||||
ExceptionCode::ReadUnaligned => ExceptionCodePy::ReadUnaligned,
|
||||
ExceptionCode::ReadWatch => ExceptionCodePy::ReadWatch,
|
||||
ExceptionCode::ReadUninitialized => ExceptionCodePy::ReadUninitialized,
|
||||
ExceptionCode::WriteUnmapped => ExceptionCodePy::WriteUnmapped,
|
||||
ExceptionCode::WritePerm => ExceptionCodePy::WritePerm,
|
||||
ExceptionCode::WriteWatch => ExceptionCodePy::WriteWatch,
|
||||
ExceptionCode::WriteUnaligned => ExceptionCodePy::WriteUnaligned,
|
||||
ExceptionCode::ExecViolation => ExceptionCodePy::ExecViolation,
|
||||
ExceptionCode::SelfModifyingCode => ExceptionCodePy::SelfModifyingCode,
|
||||
ExceptionCode::OutOfMemory => ExceptionCodePy::OutOfMemory,
|
||||
ExceptionCode::AddressOverflow => ExceptionCodePy::AddressOverflow,
|
||||
ExceptionCode::InvalidInstruction => ExceptionCodePy::InvalidInstruction,
|
||||
ExceptionCode::UnknownInterrupt => ExceptionCodePy::UnknownInterrupt,
|
||||
ExceptionCode::UnknownCpuID => ExceptionCodePy::UnknownCpuID,
|
||||
ExceptionCode::InvalidOpSize => ExceptionCodePy::InvalidOpSize,
|
||||
ExceptionCode::InvalidFloatSize => ExceptionCodePy::InvalidFloatSize,
|
||||
ExceptionCode::CodeNotTranslated => ExceptionCodePy::CodeNotTranslated,
|
||||
ExceptionCode::ShadowStackOverflow => ExceptionCodePy::ShadowStackOverflow,
|
||||
ExceptionCode::ShadowStackInvalid => ExceptionCodePy::ShadowStackInvalid,
|
||||
ExceptionCode::InvalidTarget => ExceptionCodePy::InvalidTarget,
|
||||
ExceptionCode::UnimplementedOp => ExceptionCodePy::UnimplementedOp,
|
||||
ExceptionCode::ExternalAddr => ExceptionCodePy::ExternalAddr,
|
||||
ExceptionCode::Environment => ExceptionCodePy::Environment,
|
||||
ExceptionCode::JitError => ExceptionCodePy::JitError,
|
||||
ExceptionCode::InternalError => ExceptionCodePy::InternalError,
|
||||
ExceptionCode::UnknownError => ExceptionCodePy::UnknownError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn raise_MemoryError(message: String, e: MemError) -> PyErr {
|
||||
Python::with_gil(|py| {
|
||||
@@ -109,8 +235,39 @@ fn reg_find<'a>(i: &'a Icicle, name: &str) -> PyResult<&'a NamedRegister> {
|
||||
|
||||
#[pymethods]
|
||||
impl Icicle {
|
||||
#[getter]
|
||||
fn get_icount_limit(&mut self) -> u64 {
|
||||
self.vm.icount_limit
|
||||
}
|
||||
|
||||
#[setter]
|
||||
fn set_icount_limit(&mut self, value: u64) {
|
||||
self.vm.icount_limit = value;
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn get_icount(&mut self) -> u64 {
|
||||
return self.vm.cpu.icount;
|
||||
}
|
||||
|
||||
#[setter]
|
||||
fn set_icount(&mut self, value: u64) {
|
||||
self.vm.cpu.icount = value;
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn get_exception_code(&self) -> ExceptionCodePy {
|
||||
ExceptionCode::from_u32(self.vm.cpu.exception.code).into()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn get_exception_value(&self) -> u64 {
|
||||
self.vm.cpu.exception.value
|
||||
}
|
||||
|
||||
#[new]
|
||||
fn new(architecture: String) -> PyResult<Self> {
|
||||
#[args(architecture, jit = false, recompilation = false)]
|
||||
fn new(architecture: String, jit: bool, recompilation: bool) -> PyResult<Self> {
|
||||
if architecture.split("-").count() != 1 {
|
||||
return Err(
|
||||
PyException::new_err(format!("Bad architecture format: {architecture}"))
|
||||
@@ -126,9 +283,8 @@ impl Icicle {
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: allow these to be customized
|
||||
cpu_config.enable_jit = false;
|
||||
cpu_config.enable_recompilation = false;
|
||||
cpu_config.enable_jit = jit;
|
||||
cpu_config.enable_recompilation = recompilation;
|
||||
|
||||
let mut vm = icicle_vm::build(&cpu_config)
|
||||
.map_err(|e| {
|
||||
@@ -239,6 +395,52 @@ impl Icicle {
|
||||
fn reg_write(&mut self, name: &str, value: u64) -> PyResult<()> {
|
||||
Ok(self.vm.cpu.write_reg(reg_find(self, name)?.var, value))
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.vm.reset();
|
||||
}
|
||||
|
||||
fn run(&mut self) -> RunStatus {
|
||||
// TODO: do we have to clear the exception?
|
||||
|
||||
match self.vm.run() {
|
||||
VmExit::Running => RunStatus::Running,
|
||||
VmExit::InstructionLimit => RunStatus::InstructionLimit,
|
||||
VmExit::Breakpoint => RunStatus::Breakpoint,
|
||||
VmExit::Interrupted => RunStatus::Interrupted,
|
||||
VmExit::Halt => RunStatus::Halt,
|
||||
VmExit::Killed => RunStatus::Killed,
|
||||
VmExit::Deadlock => RunStatus::Deadlock,
|
||||
VmExit::OutOfMemory => RunStatus::OutOfMemory,
|
||||
VmExit::Unimplemented => RunStatus::Unimplemented,
|
||||
VmExit::UnhandledException(_) => RunStatus::UnhandledException,
|
||||
}
|
||||
}
|
||||
|
||||
fn run_until(&mut self, address: u64) -> RunStatus {
|
||||
let breakpoint_added = self.vm.add_breakpoint(address);
|
||||
let status = self.run();
|
||||
if breakpoint_added {
|
||||
self.vm.remove_breakpoint(address);
|
||||
}
|
||||
status
|
||||
}
|
||||
|
||||
fn step(&mut self, count: u64) -> RunStatus {
|
||||
let old_limit = self.vm.icount_limit;
|
||||
self.vm.icount_limit = self.vm.cpu.icount.saturating_add(count);
|
||||
let status = self.run();
|
||||
self.vm.icount_limit = old_limit;
|
||||
status
|
||||
}
|
||||
|
||||
fn add_breakpoint(&mut self, address: u64) -> bool {
|
||||
self.vm.add_breakpoint(address)
|
||||
}
|
||||
|
||||
fn remove_breakpoint(&mut self, address: u64) -> bool {
|
||||
self.vm.remove_breakpoint(address)
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
@@ -259,6 +461,8 @@ fn icicle(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_class::<Icicle>()?;
|
||||
m.add_class::<MemoryProtection>()?;
|
||||
m.add_class::<MemoryErrorCode>()?;
|
||||
m.add_class::<RunStatus>()?;
|
||||
m.add_class::<ExceptionCodePy>()?;
|
||||
PyModule::from_code(py, r#"
|
||||
class MemoryError(Exception):
|
||||
def __init__(self, message, code):
|
||||
|
||||
+58
-2
@@ -1,11 +1,40 @@
|
||||
import icicle
|
||||
import keystone
|
||||
import capstone
|
||||
|
||||
def main():
|
||||
def assemble(code: str, addr: int = 0) -> bytes:
|
||||
ks = keystone.Ks(keystone.KS_ARCH_X86, keystone.KS_MODE_64)
|
||||
encoding, count = ks.asm(code, addr)
|
||||
if encoding is None:
|
||||
raise keystone.KsError("no encoding")
|
||||
return bytes(encoding)
|
||||
|
||||
def disassemble(code: bytes, addr: int = 0) -> str:
|
||||
result = ""
|
||||
cs = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64)
|
||||
while True:
|
||||
for address, size, mnemonic, op_str in cs.disasm_lite(code, addr, 1):
|
||||
if len(result) > 0:
|
||||
result += "\n"
|
||||
result += hex(address) + "|"
|
||||
result += mnemonic
|
||||
if op_str:
|
||||
result += " "
|
||||
result += op_str
|
||||
code = code[size:]
|
||||
if len(code) == 0:
|
||||
return result
|
||||
addr += size
|
||||
|
||||
def old_test():
|
||||
buf = assemble("mov eax, ebx\nnop")
|
||||
print(disassemble(buf, 0))
|
||||
print(assemble("mov eax, ebx").hex())
|
||||
print("Hello world!")
|
||||
print(dir(icicle))
|
||||
print(icicle.MemoryProtection.ReadOnly)
|
||||
print(f"Architectures: {icicle.architectures()}")
|
||||
vm = icicle.Icicle("i686")
|
||||
vm = icicle.Icicle("x86_64")
|
||||
|
||||
for name, (offset, size) in vm.reg_list().items():
|
||||
value = vm.reg_read(name.lower())
|
||||
@@ -27,5 +56,32 @@ def main():
|
||||
|
||||
print(vm)
|
||||
|
||||
def main():
|
||||
vm = icicle.Icicle("x86_64", jit=True)
|
||||
print(vm)
|
||||
|
||||
addr = 0x10000
|
||||
#vm.mem_map(0, 0x1000, icicle.MemoryProtection.ReadWrite)
|
||||
vm.mem_map(addr, 0x1000, icicle.MemoryProtection.ExecuteRead)
|
||||
code = assemble("""
|
||||
mov eax, 42
|
||||
mov qword ptr [rax], 0x5
|
||||
nop
|
||||
""", addr)
|
||||
print("code:")
|
||||
print(disassemble(code, addr))
|
||||
vm.mem_write(addr, code)
|
||||
data = vm.mem_read(addr, len(code))
|
||||
assert data == code
|
||||
|
||||
vm.reg_write("rip", addr)
|
||||
status = vm.run_until(addr + len(code))
|
||||
print(status)
|
||||
if status == icicle.RunStatus.UnhandledException:
|
||||
print(f"code: {vm.exception_code}, value: {hex(vm.exception_value)}")
|
||||
print(f"rip: {hex(vm.reg_read('rip'))}")
|
||||
else:
|
||||
print(f"rax: {hex(vm.reg_read('rax'))}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user