Romain Malmain c96ea616fe
Qemu helpers & hooks refactoring (#2267)
* Helper is now called Module.

* Emulator now contains hooks state.

* Emulator is managed by QemuExecutor.

* QEMU hooks have been completely refactored on the rust side.

* Generics cleanup.
2024-07-17 11:46:42 +02:00

1059 lines
30 KiB
Rust

//! Low-level QEMU library
//!
//! This module exposes the low-level QEMU library through [`Qemu`].
//! To access higher-level features of QEMU, it is recommanded to use [`crate::Emulator`] instead.
use core::fmt;
use std::{
cmp::{Ordering, PartialOrd},
ffi::CString,
fmt::{Display, Formatter},
intrinsics::{copy_nonoverlapping, transmute},
mem::MaybeUninit,
ops::Range,
pin::Pin,
ptr,
};
use libafl_bolts::os::unix_signals::Signal;
#[cfg(emulation_mode = "systemmode")]
use libafl_qemu_sys::qemu_init;
#[cfg(emulation_mode = "usermode")]
use libafl_qemu_sys::{guest_base, qemu_user_init, VerifyAccess};
use libafl_qemu_sys::{
libafl_flush_jit, libafl_get_exit_reason, libafl_page_from_addr, libafl_qemu_add_gdb_cmd,
libafl_qemu_cpu_index, libafl_qemu_current_cpu, libafl_qemu_gdb_reply, libafl_qemu_get_cpu,
libafl_qemu_num_cpus, libafl_qemu_num_regs, libafl_qemu_read_reg,
libafl_qemu_remove_breakpoint, libafl_qemu_set_breakpoint, libafl_qemu_trigger_breakpoint,
libafl_qemu_write_reg, CPUArchState, CPUStatePtr, FatPtr, GuestAddr, GuestPhysAddr, GuestUsize,
GuestVirtAddr,
};
use num_traits::Num;
use strum::IntoEnumIterator;
use crate::{GuestAddrKind, GuestReg, Regs};
#[cfg(emulation_mode = "usermode")]
mod usermode;
#[cfg(emulation_mode = "usermode")]
pub use usermode::*;
#[cfg(emulation_mode = "systemmode")]
mod systemmode;
#[cfg(emulation_mode = "systemmode")]
#[allow(unused_imports)]
pub use systemmode::*;
mod hooks;
pub use hooks::*;
static mut QEMU_IS_INITIALIZED: bool = false;
#[derive(Debug)]
pub enum QemuInitError {
MultipleInstances,
EmptyArgs,
TooManyArgs(usize),
}
#[derive(Debug, Clone)]
pub enum QemuExitReason {
End(QemuShutdownCause), // QEMU ended for some reason.
Breakpoint(GuestAddr), // Breakpoint triggered. Contains the address of the trigger.
SyncExit, // Synchronous backdoor: The guest triggered a backdoor and should return to LibAFL.
}
#[derive(Debug, Clone)]
pub enum QemuExitError {
UnknownKind, // Exit reason was not NULL, but exit kind is unknown. Should never happen.
UnexpectedExit, // Qemu exited without going through an expected exit point. Can be caused by a crash for example.
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QemuSnapshotCheckResult {
nb_page_inconsistencies: u64,
}
#[derive(Debug, Clone)]
pub enum QemuRWErrorKind {
Read,
Write,
}
#[derive(Debug, Clone)]
pub enum QemuRWErrorCause {
WrongCallingConvention(CallingConvention, CallingConvention), // expected, given
WrongArgument(i32),
CurrentCpuNotFound,
Reg(i32),
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct QemuRWError {
kind: QemuRWErrorKind,
cause: QemuRWErrorCause,
cpu: Option<CPUStatePtr>, // Only makes sense when cause != CurrentCpuNotFound
}
impl QemuRWError {
#[must_use]
pub fn new(kind: QemuRWErrorKind, cause: QemuRWErrorCause, cpu: Option<CPUStatePtr>) -> Self {
Self { kind, cause, cpu }
}
#[must_use]
pub fn current_cpu_not_found(kind: QemuRWErrorKind) -> Self {
Self::new(kind, QemuRWErrorCause::CurrentCpuNotFound, None)
}
#[must_use]
pub fn new_argument_error(kind: QemuRWErrorKind, reg_id: i32) -> Self {
Self::new(kind, QemuRWErrorCause::WrongArgument(reg_id), None)
}
pub fn check_conv(
kind: QemuRWErrorKind,
expected_conv: CallingConvention,
given_conv: CallingConvention,
) -> Result<(), Self> {
if expected_conv != given_conv {
return Err(Self::new(
kind,
QemuRWErrorCause::WrongCallingConvention(expected_conv, given_conv),
None,
));
}
Ok(())
}
}
/// Represents a QEMU snapshot check result for which no error was detected
impl Default for QemuSnapshotCheckResult {
fn default() -> Self {
Self {
nb_page_inconsistencies: 0,
}
}
}
/// The thin wrapper around QEMU.
/// It is considered unsafe to use it directly.
/// Prefer using `Emulator` instead in case of doubt.
#[derive(Clone, Copy, Debug)]
pub struct Qemu {
_private: (),
}
#[derive(Debug, Clone)]
pub struct QemuMemoryChunk {
addr: GuestAddrKind,
size: GuestReg,
cpu: Option<CPU>,
}
#[allow(clippy::vec_box)]
static mut GDB_COMMANDS: Vec<Box<FatPtr>> = vec![];
extern "C" fn gdb_cmd(data: *const (), buf: *const u8, len: usize) -> i32 {
unsafe {
let closure = &mut *(data as *mut Box<dyn for<'r> FnMut(&Qemu, &'r str) -> bool>);
let cmd = std::str::from_utf8_unchecked(std::slice::from_raw_parts(buf, len));
let qemu = Qemu::get_unchecked();
i32::from(closure(&qemu, cmd))
}
}
#[derive(Debug, Clone)]
pub enum QemuShutdownCause {
None,
HostError,
HostQmpQuit,
HostQmpSystemReset,
HostSignal(Signal),
HostUi,
GuestShutdown,
GuestReset,
GuestPanic,
SubsystemReset,
SnapshotLoad,
}
#[repr(transparent)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct MemAccessInfo {
oi: libafl_qemu_sys::MemOpIdx,
}
#[derive(Debug, Clone, Copy)]
#[repr(transparent)]
pub struct CPU {
ptr: CPUStatePtr,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CallingConvention {
Cdecl,
}
pub trait HookId {
fn remove(&self, invalidate_block: bool) -> bool;
}
#[derive(Debug)]
pub struct HookData(u64);
impl std::error::Error for QemuInitError {}
impl Display for QemuInitError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
QemuInitError::MultipleInstances => {
write!(f, "Only one instance of the QEMU Emulator is permitted")
}
QemuInitError::EmptyArgs => {
write!(f, "QEMU emulator args cannot be empty")
}
QemuInitError::TooManyArgs(n) => {
write!(
f,
"Too many arguments passed to QEMU emulator ({n} > i32::MAX)"
)
}
}
}
}
impl From<QemuInitError> for libafl::Error {
fn from(err: QemuInitError) -> Self {
libafl::Error::unknown(format!("{err}"))
}
}
impl Display for QemuExitReason {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
QemuExitReason::End(shutdown_cause) => write!(f, "End: {shutdown_cause:?}"),
QemuExitReason::Breakpoint(bp) => write!(f, "Breakpoint: {bp}"),
QemuExitReason::SyncExit => write!(f, "Sync Exit"),
}
}
}
impl MemAccessInfo {
#[must_use]
pub fn memop(&self) -> libafl_qemu_sys::MemOp {
libafl_qemu_sys::MemOp(self.oi >> 4)
}
#[must_use]
pub fn memopidx(&self) -> libafl_qemu_sys::MemOpIdx {
self.oi
}
#[must_use]
pub fn mmu_index(&self) -> u32 {
self.oi & 15
}
#[must_use]
pub fn size(&self) -> usize {
libafl_qemu_sys::memop_size(self.memop()) as usize
}
#[must_use]
pub fn is_big_endian(&self) -> bool {
libafl_qemu_sys::memop_big_endian(self.memop())
}
#[must_use]
pub fn encode_with(&self, other: u32) -> u64 {
(u64::from(self.oi) << 32) | u64::from(other)
}
#[must_use]
pub fn decode_from(encoded: u64) -> (Self, u32) {
let low = (encoded & 0xFFFFFFFF) as u32;
let high = (encoded >> 32) as u32;
(Self { oi: high }, low)
}
#[must_use]
pub fn new(oi: libafl_qemu_sys::MemOpIdx) -> Self {
Self { oi }
}
}
impl From<libafl_qemu_sys::MemOpIdx> for MemAccessInfo {
fn from(oi: libafl_qemu_sys::MemOpIdx) -> Self {
Self { oi }
}
}
pub trait ArchExtras {
fn read_return_address<T>(&self) -> Result<T, QemuRWError>
where
T: From<GuestReg>;
fn write_return_address<T>(&self, val: T) -> Result<(), QemuRWError>
where
T: Into<GuestReg>;
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, QemuRWError>
where
T: From<GuestReg>;
fn write_function_argument<T>(
&self,
conv: CallingConvention,
idx: i32,
val: T,
) -> Result<(), QemuRWError>
where
T: Into<GuestReg>;
}
#[allow(clippy::unused_self)]
impl CPU {
#[must_use]
#[allow(clippy::cast_sign_loss)]
pub fn index(&self) -> usize {
unsafe { libafl_qemu_cpu_index(self.ptr) as usize }
}
pub fn trigger_breakpoint(&self) {
unsafe {
libafl_qemu_trigger_breakpoint(self.ptr);
}
}
#[cfg(emulation_mode = "usermode")]
#[must_use]
pub fn g2h<T>(&self, addr: GuestAddr) -> *mut T {
unsafe { (addr as usize + guest_base) as *mut T }
}
#[cfg(emulation_mode = "usermode")]
#[must_use]
pub fn h2g<T>(&self, addr: *const T) -> GuestAddr {
unsafe { (addr as usize - guest_base) as GuestAddr }
}
#[cfg(emulation_mode = "usermode")]
#[must_use]
pub fn access_ok(&self, kind: VerifyAccess, addr: GuestAddr, size: usize) -> bool {
unsafe {
// TODO add support for tagged GuestAddr
libafl_qemu_sys::page_check_range(addr, size as GuestAddr, kind.into())
}
}
// TODO expose tlb_set_dirty and tlb_reset_dirty
#[must_use]
pub fn num_regs(&self) -> i32 {
unsafe { libafl_qemu_num_regs(self.ptr) }
}
pub fn write_reg<R, T>(&self, reg: R, val: T) -> Result<(), QemuRWError>
where
R: Into<i32> + Clone,
T: Into<GuestReg>,
{
let reg_id = reg.clone().into();
#[cfg(feature = "be")]
let val = GuestReg::to_be(val.into());
#[cfg(not(feature = "be"))]
let val = GuestReg::to_le(val.into());
let success =
unsafe { libafl_qemu_write_reg(self.ptr, reg_id, ptr::addr_of!(val) as *const u8) };
if success == 0 {
Err(QemuRWError {
kind: QemuRWErrorKind::Write,
cause: QemuRWErrorCause::Reg(reg.into()),
cpu: Some(self.ptr),
})
} else {
Ok(())
}
}
pub fn read_reg<R, T>(&self, reg: R) -> Result<T, QemuRWError>
where
R: Into<i32> + Clone,
T: From<GuestReg>,
{
unsafe {
let reg_id = reg.clone().into();
let mut val = MaybeUninit::uninit();
let success = libafl_qemu_read_reg(self.ptr, reg_id, val.as_mut_ptr() as *mut u8);
if success == 0 {
Err(QemuRWError {
kind: QemuRWErrorKind::Write,
cause: QemuRWErrorCause::Reg(reg.into()),
cpu: Some(self.ptr),
})
} else {
#[cfg(feature = "be")]
return Ok(GuestReg::from_be(val.assume_init()).into());
#[cfg(not(feature = "be"))]
return Ok(GuestReg::from_le(val.assume_init()).into());
}
}
}
pub fn reset(&self) {
unsafe { libafl_qemu_sys::cpu_reset(self.ptr) };
}
#[must_use]
pub fn save_state(&self) -> CPUArchState {
unsafe {
let mut saved = MaybeUninit::<CPUArchState>::uninit();
copy_nonoverlapping(
libafl_qemu_sys::cpu_env(self.ptr.as_mut().unwrap()),
saved.as_mut_ptr(),
1,
);
saved.assume_init()
}
}
pub fn restore_state(&self, saved: &CPUArchState) {
unsafe {
copy_nonoverlapping(
saved,
libafl_qemu_sys::cpu_env(self.ptr.as_mut().unwrap()),
1,
);
}
}
#[must_use]
pub fn raw_ptr(&self) -> CPUStatePtr {
self.ptr
}
#[must_use]
pub fn display_context(&self) -> String {
let mut display = String::new();
let mut maxl = 0;
for r in Regs::iter() {
maxl = std::cmp::max(format!("{r:#?}").len(), maxl);
}
for (i, r) in Regs::iter().enumerate() {
let v: GuestAddr = self.read_reg(r).unwrap();
let sr = format!("{r:#?}");
display += &format!("{sr:>maxl$}: {v:#016x} ");
if (i + 1) % 4 == 0 {
display += "\n";
}
}
if !display.ends_with('\n') {
display += "\n";
}
display
}
}
impl<T> From<Pin<&mut T>> for HookData {
fn from(value: Pin<&mut T>) -> Self {
unsafe { HookData(transmute::<Pin<&mut T>, u64>(value)) }
}
}
impl<T> From<Pin<&T>> for HookData {
fn from(value: Pin<&T>) -> Self {
unsafe { HookData(transmute::<Pin<&T>, u64>(value)) }
}
}
impl<T> From<&'static mut T> for HookData {
fn from(value: &'static mut T) -> Self {
unsafe { HookData(transmute::<&mut T, u64>(value)) }
}
}
impl<T> From<&'static T> for HookData {
fn from(value: &'static T) -> Self {
unsafe { HookData(transmute::<&T, u64>(value)) }
}
}
impl<T> From<*mut T> for HookData {
fn from(value: *mut T) -> Self {
HookData(value as u64)
}
}
impl<T> From<*const T> for HookData {
fn from(value: *const T) -> Self {
HookData(value as u64)
}
}
impl From<u64> for HookData {
fn from(value: u64) -> Self {
HookData(value)
}
}
impl From<u32> for HookData {
fn from(value: u32) -> Self {
HookData(u64::from(value))
}
}
impl From<u16> for HookData {
fn from(value: u16) -> Self {
HookData(u64::from(value))
}
}
impl From<u8> for HookData {
fn from(value: u8) -> Self {
HookData(u64::from(value))
}
}
#[allow(clippy::unused_self)]
impl Qemu {
#[allow(clippy::must_use_candidate, clippy::similar_names)]
pub fn init(args: &[String], env: &[(String, String)]) -> Result<Self, QemuInitError> {
if args.is_empty() {
return Err(QemuInitError::EmptyArgs);
}
let argc = args.len();
if i32::try_from(argc).is_err() {
return Err(QemuInitError::TooManyArgs(argc));
}
unsafe {
if QEMU_IS_INITIALIZED {
return Err(QemuInitError::MultipleInstances);
}
QEMU_IS_INITIALIZED = true;
}
#[allow(clippy::cast_possible_wrap)]
let argc = argc as i32;
let args: Vec<CString> = args
.iter()
.map(|x| CString::new(x.clone()).unwrap())
.collect();
let mut argv: Vec<*const u8> = args.iter().map(|x| x.as_ptr() as *const u8).collect();
argv.push(ptr::null()); // argv is always null terminated.
let env_strs: Vec<String> = env
.iter()
.map(|(k, v)| format!("{}={}\0", &k, &v))
.collect();
let mut envp: Vec<*const u8> = env_strs.iter().map(|x| x.as_bytes().as_ptr()).collect();
envp.push(ptr::null());
unsafe {
#[cfg(emulation_mode = "usermode")]
qemu_user_init(argc, argv.as_ptr(), envp.as_ptr());
#[cfg(emulation_mode = "systemmode")]
{
qemu_init(argc, argv.as_ptr(), envp.as_ptr());
libc::atexit(qemu_cleanup_atexit);
libafl_qemu_sys::syx_snapshot_init(true);
}
}
Ok(Qemu { _private: () })
}
#[must_use]
pub fn hooks(&self) -> QemuHooks {
unsafe { QemuHooks::get_unchecked() }
}
/// Get a QEMU object.
/// Same as `Qemu::get`, but without checking whether QEMU has been correctly initialized.
/// Since Qemu is a ZST, this operation is free.
///
/// # Safety
///
/// Should not be used if `Qemu::init` has never been used before (otherwise QEMU will not be initialized, and a crash will occur).
/// Prefer `Qemu::get` for a safe version of this method.
#[must_use]
pub unsafe fn get_unchecked() -> Self {
Qemu { _private: () }
}
#[must_use]
pub fn get() -> Option<Self> {
unsafe {
if QEMU_IS_INITIALIZED {
Some(Qemu { _private: () })
} else {
None
}
}
}
/// This function will run the emulator until the next breakpoint / sync exit, or until finish.
/// It is a low-level function and simply kicks QEMU.
/// # Safety
///
/// Should, in general, be safe to call.
/// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system.
pub unsafe fn run(&self) -> Result<QemuExitReason, QemuExitError> {
self.run_inner();
let exit_reason = unsafe { libafl_get_exit_reason() };
if exit_reason.is_null() {
Err(QemuExitError::UnexpectedExit)
} else {
let exit_reason: &mut libafl_qemu_sys::libafl_exit_reason =
unsafe { transmute(&mut *exit_reason) };
Ok(match exit_reason.kind {
libafl_qemu_sys::libafl_exit_reason_kind_INTERNAL => unsafe {
let qemu_shutdown_cause: QemuShutdownCause =
match exit_reason.data.internal.cause {
libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_NONE => {
QemuShutdownCause::None
}
libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_ERROR => {
QemuShutdownCause::HostError
}
libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_QMP_QUIT => {
QemuShutdownCause::HostQmpQuit
}
libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_QMP_SYSTEM_RESET => {
QemuShutdownCause::HostQmpSystemReset
}
libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_SIGNAL => {
QemuShutdownCause::HostSignal(
Signal::try_from(exit_reason.data.internal.signal).unwrap(),
)
}
libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_UI => {
QemuShutdownCause::HostUi
}
libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_GUEST_SHUTDOWN => {
QemuShutdownCause::GuestShutdown
}
libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_GUEST_RESET => {
QemuShutdownCause::GuestReset
}
libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_GUEST_PANIC => {
QemuShutdownCause::GuestPanic
}
libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_SUBSYSTEM_RESET => {
QemuShutdownCause::SubsystemReset
}
libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_SNAPSHOT_LOAD => {
QemuShutdownCause::SnapshotLoad
}
_ => panic!("shutdown cause not handled."),
};
QemuExitReason::End(qemu_shutdown_cause)
},
libafl_qemu_sys::libafl_exit_reason_kind_BREAKPOINT => unsafe {
let bp_addr = exit_reason.data.breakpoint.addr;
QemuExitReason::Breakpoint(bp_addr)
},
libafl_qemu_sys::libafl_exit_reason_kind_SYNC_EXIT => QemuExitReason::SyncExit,
_ => return Err(QemuExitError::UnknownKind),
})
}
}
#[must_use]
#[allow(clippy::cast_possible_wrap)]
#[allow(clippy::cast_sign_loss)]
pub fn num_cpus(&self) -> usize {
unsafe { libafl_qemu_num_cpus() as usize }
}
#[must_use]
pub fn current_cpu(&self) -> Option<CPU> {
let ptr = unsafe { libafl_qemu_current_cpu() };
if ptr.is_null() {
None
} else {
Some(CPU { ptr })
}
}
#[must_use]
#[allow(clippy::cast_possible_wrap)]
pub fn cpu_from_index(&self, index: usize) -> CPU {
unsafe {
CPU {
ptr: libafl_qemu_get_cpu(index as i32),
}
}
}
#[must_use]
pub fn page_from_addr(&self, addr: GuestAddr) -> GuestAddr {
unsafe { libafl_page_from_addr(addr) }
}
//#[must_use]
/*pub fn page_size() -> GuestUsize {
unsafe { libafl_page_size }
}*/
pub unsafe fn write_mem(&self, addr: GuestAddr, buf: &[u8]) {
self.current_cpu()
.unwrap_or_else(|| self.cpu_from_index(0))
.write_mem(addr, buf);
}
pub unsafe fn read_mem(&self, addr: GuestAddr, buf: &mut [u8]) {
self.current_cpu()
.unwrap_or_else(|| self.cpu_from_index(0))
.read_mem(addr, buf);
}
#[must_use]
pub fn num_regs(&self) -> i32 {
self.current_cpu().unwrap().num_regs()
}
pub fn write_reg<R, T>(&self, reg: R, val: T) -> Result<(), QemuRWError>
where
T: Num + PartialOrd + Copy + Into<GuestReg>,
R: Into<i32> + Clone,
{
self.current_cpu()
.ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Write))?
.write_reg(reg, val)
}
pub fn read_reg<R, T>(&self, reg: R) -> Result<T, QemuRWError>
where
T: Num + PartialOrd + Copy + From<GuestReg>,
R: Into<i32> + Clone,
{
self.current_cpu()
.ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Read))?
.read_reg(reg)
}
pub fn set_breakpoint(&self, addr: GuestAddr) {
unsafe {
libafl_qemu_set_breakpoint(addr.into());
}
}
pub fn remove_breakpoint(&self, addr: GuestAddr) {
unsafe {
libafl_qemu_remove_breakpoint(addr.into());
}
}
pub fn entry_break(&self, addr: GuestAddr) {
self.set_breakpoint(addr);
unsafe {
match self.run() {
Ok(QemuExitReason::Breakpoint(_)) => {}
_ => panic!("Unexpected QEMU exit."),
}
}
self.remove_breakpoint(addr);
}
pub fn flush_jit(&self) {
unsafe {
libafl_flush_jit();
}
}
#[must_use]
pub fn remove_hook(&self, id: impl HookId, invalidate_block: bool) -> bool {
id.remove(invalidate_block)
}
#[allow(clippy::type_complexity)]
pub fn add_gdb_cmd(&self, callback: Box<dyn FnMut(&Self, &str) -> bool>) {
unsafe {
let fat: Box<FatPtr> = Box::new(transmute::<
Box<dyn for<'a, 'b> FnMut(&'a Qemu, &'b str) -> bool>,
FatPtr,
>(callback));
libafl_qemu_add_gdb_cmd(gdb_cmd, ptr::from_ref(&*fat) as *const ());
GDB_COMMANDS.push(fat);
}
}
pub fn gdb_reply(&self, output: &str) {
unsafe { libafl_qemu_gdb_reply(output.as_bytes().as_ptr(), output.len()) };
}
#[must_use]
pub fn host_page_size(&self) -> usize {
unsafe { libafl_qemu_sys::libafl_qemu_host_page_size() }
}
}
impl ArchExtras for Qemu {
fn read_return_address<T>(&self) -> Result<T, QemuRWError>
where
T: From<GuestReg>,
{
self.current_cpu()
.ok_or(QemuRWError {
kind: QemuRWErrorKind::Read,
cause: QemuRWErrorCause::CurrentCpuNotFound,
cpu: None,
})?
.read_return_address::<T>()
}
fn write_return_address<T>(&self, val: T) -> Result<(), QemuRWError>
where
T: Into<GuestReg>,
{
self.current_cpu()
.ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Write))?
.write_return_address::<T>(val)
}
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, QemuRWError>
where
T: From<GuestReg>,
{
self.current_cpu()
.ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Read))?
.read_function_argument::<T>(conv, idx)
}
fn write_function_argument<T>(
&self,
conv: CallingConvention,
idx: i32,
val: T,
) -> Result<(), QemuRWError>
where
T: Into<GuestReg>,
{
self.current_cpu()
.ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Write))?
.write_function_argument::<T>(conv, idx, val)
}
}
// TODO: maybe include QEMU in the memory chunk to enable address translation and a more accurate implementation
impl PartialEq<Self> for GuestAddrKind {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(GuestAddrKind::Physical(paddr_self), GuestAddrKind::Physical(paddr_other)) => {
paddr_self == paddr_other
}
(GuestAddrKind::Virtual(vaddr_self), GuestAddrKind::Virtual(vaddr_other)) => {
vaddr_self == vaddr_other
}
_ => false,
}
}
}
// TODO: Check PartialEq comment, same idea
impl PartialOrd for GuestAddrKind {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(GuestAddrKind::Physical(paddr_self), GuestAddrKind::Physical(paddr_other)) => {
paddr_self.partial_cmp(paddr_other)
}
(GuestAddrKind::Virtual(vaddr_self), GuestAddrKind::Virtual(vaddr_other)) => {
vaddr_self.partial_cmp(vaddr_other)
}
_ => None,
}
}
}
impl QemuMemoryChunk {
#[must_use]
pub fn addr(&self) -> GuestAddrKind {
self.addr
}
#[must_use]
pub fn size(&self) -> GuestReg {
self.size
}
#[must_use]
pub fn phys(addr: GuestPhysAddr, size: GuestReg, cpu: Option<CPU>) -> Self {
Self {
addr: GuestAddrKind::Physical(addr),
size,
cpu,
}
}
#[must_use]
pub fn virt(addr: GuestVirtAddr, size: GuestReg, cpu: CPU) -> Self {
Self {
addr: GuestAddrKind::Virtual(addr),
size,
cpu: Some(cpu),
}
}
#[must_use]
pub fn get_slice(&self, range: &Range<GuestAddr>) -> Option<QemuMemoryChunk> {
let new_addr = self.addr + range.start;
let slice_size = range.clone().count();
if new_addr + (slice_size as GuestUsize) >= self.addr + self.size.into() {
return None;
}
Some(Self {
addr: new_addr,
size: slice_size as GuestReg,
cpu: self.cpu,
})
}
/// Returns the number of bytes effectively written.
#[must_use]
pub fn write(&self, qemu: Qemu, input: &[u8]) -> GuestReg {
let max_len: usize = self.size.try_into().unwrap();
let input_sliced = if input.len() > max_len {
&input[0..max_len]
} else {
input
};
match self.addr {
GuestAddrKind::Physical(hwaddr) => unsafe {
#[cfg(emulation_mode = "usermode")]
{
// For now the default behaviour is to fall back to virtual addresses
qemu.write_mem(hwaddr.try_into().unwrap(), input_sliced);
}
#[cfg(emulation_mode = "systemmode")]
{
qemu.write_phys_mem(hwaddr, input_sliced);
}
},
GuestAddrKind::Virtual(vaddr) => unsafe {
self.cpu
.as_ref()
.unwrap()
.write_mem(vaddr.try_into().unwrap(), input_sliced);
},
};
input_sliced.len().try_into().unwrap()
}
}
#[cfg(feature = "python")]
pub mod pybind {
use pyo3::{exceptions::PyValueError, prelude::*};
use super::{GuestAddr, GuestUsize};
static mut PY_GENERIC_HOOKS: Vec<(GuestAddr, PyObject)> = vec![];
extern "C" fn py_generic_hook_wrapper(idx: u64, _pc: GuestAddr) {
let obj = unsafe { &PY_GENERIC_HOOKS[idx as usize].1 };
Python::with_gil(|py| {
obj.call0(py).expect("Error in the hook");
});
}
#[pyclass(unsendable)]
pub struct Qemu {
pub qemu: super::Qemu,
}
#[pymethods]
impl Qemu {
#[allow(clippy::needless_pass_by_value)]
#[new]
fn new(args: Vec<String>, env: Vec<(String, String)>) -> PyResult<Qemu> {
let qemu = super::Qemu::init(&args, &env)
.map_err(|e| PyValueError::new_err(format!("{e}")))?;
Ok(Qemu { qemu })
}
fn run(&self) {
unsafe {
self.qemu.run().unwrap();
}
}
fn write_mem(&self, addr: GuestAddr, buf: &[u8]) {
unsafe {
self.qemu.write_mem(addr, buf);
}
}
fn read_mem(&self, addr: GuestAddr, size: usize) -> Vec<u8> {
let mut buf = vec![0; size];
unsafe {
self.qemu.read_mem(addr, &mut buf);
}
buf
}
fn num_regs(&self) -> i32 {
self.qemu.num_regs()
}
fn write_reg(&self, reg: i32, val: GuestUsize) -> PyResult<()> {
self.qemu
.write_reg(reg, val)
.map_err(|_| PyValueError::new_err("write register error"))
}
fn read_reg(&self, reg: i32) -> PyResult<GuestUsize> {
self.qemu
.read_reg(reg)
.map_err(|_| PyValueError::new_err("read register error"))
}
fn set_breakpoint(&self, addr: GuestAddr) {
self.qemu.set_breakpoint(addr);
}
fn entry_break(&self, addr: GuestAddr) {
self.qemu.entry_break(addr);
}
fn remove_breakpoint(&self, addr: GuestAddr) {
self.qemu.remove_breakpoint(addr);
}
fn flush_jit(&self) {
self.qemu.flush_jit();
}
fn set_hook(&self, addr: GuestAddr, hook: PyObject) {
unsafe {
let idx = PY_GENERIC_HOOKS.len();
PY_GENERIC_HOOKS.push((addr, hook));
self.qemu.hooks().add_instruction_hooks(
idx as u64,
addr,
py_generic_hook_wrapper,
true,
);
}
}
fn remove_hooks_at(&self, addr: GuestAddr) -> usize {
unsafe {
PY_GENERIC_HOOKS.retain(|(a, _)| *a != addr);
}
self.qemu.hooks().remove_instruction_hooks_at(addr, true)
}
}
}