Improved calling convention support for x86, x86_64, arm and aarch64 for libafl-qemu (#3013)
* Improved calling convention support for x86, x86_64, arm and aarch64 * fix * fix write_function_arguments * fix argument_error * follow clippy advice * last change? * fix guestaddr issue * add code block for cargo-fmt * default to default convention * fix mistake * add pub * fix * changes after review * last change? --------- Co-authored-by: celian <cglenaz>
This commit is contained in:
parent
7c83be2408
commit
40c0a8c57c
@ -30,9 +30,8 @@ use libafl_bolts::{
|
||||
#[cfg(feature = "fork")]
|
||||
use libafl_qemu::QemuForkExecutor;
|
||||
use libafl_qemu::{
|
||||
elf::EasyElf, modules::edges::StdEdgeCoverageChildModule, ArchExtras, CallingConvention,
|
||||
Emulator, GuestAddr, GuestReg, MmapPerms, QemuExitError, QemuExitReason, QemuShutdownCause,
|
||||
Regs,
|
||||
elf::EasyElf, modules::edges::StdEdgeCoverageChildModule, ArchExtras, Emulator, GuestAddr,
|
||||
GuestReg, MmapPerms, QemuExitError, QemuExitReason, QemuShutdownCause, Regs,
|
||||
};
|
||||
#[cfg(feature = "snapshot")]
|
||||
use libafl_qemu::{modules::SnapshotModule, QemuExecutor};
|
||||
@ -232,10 +231,8 @@ pub fn fuzz() -> Result<(), Error> {
|
||||
qemu.write_reg(Regs::Pc, test_one_input_ptr).unwrap();
|
||||
qemu.write_reg(Regs::Sp, stack_ptr).unwrap();
|
||||
qemu.write_return_address(ret_addr).unwrap();
|
||||
qemu.write_function_argument(CallingConvention::Cdecl, 0, input_addr)
|
||||
.unwrap();
|
||||
qemu.write_function_argument(CallingConvention::Cdecl, 1, len)
|
||||
.unwrap();
|
||||
qemu.write_function_argument(0, input_addr).unwrap();
|
||||
qemu.write_function_argument(1, len).unwrap();
|
||||
|
||||
match qemu.run() {
|
||||
Ok(QemuExitReason::Breakpoint(_)) => {}
|
||||
@ -268,10 +265,8 @@ pub fn fuzz() -> Result<(), Error> {
|
||||
qemu.write_reg(Regs::Pc, test_one_input_ptr).unwrap();
|
||||
qemu.write_reg(Regs::Sp, stack_ptr).unwrap();
|
||||
qemu.write_return_address(ret_addr).unwrap();
|
||||
qemu.write_function_argument(CallingConvention::Cdecl, 0, input_addr)
|
||||
.unwrap();
|
||||
qemu.write_function_argument(CallingConvention::Cdecl, 1, len)
|
||||
.unwrap();
|
||||
qemu.write_function_argument(0, input_addr).unwrap();
|
||||
qemu.write_function_argument(1, len).unwrap();
|
||||
|
||||
match qemu.run() {
|
||||
Ok(QemuExitReason::Breakpoint(_)) => {}
|
||||
|
@ -29,8 +29,8 @@ use libafl_bolts::{
|
||||
use libafl_qemu::{
|
||||
elf::EasyElf,
|
||||
modules::{drcov::DrCovModule, SnapshotModule},
|
||||
ArchExtras, CallingConvention, Emulator, GuestAddr, GuestReg, MmapPerms, Qemu, QemuExecutor,
|
||||
QemuExitReason, QemuMappingsViewer, QemuRWError, QemuShutdownCause, Regs,
|
||||
ArchExtras, Emulator, GuestAddr, GuestReg, MmapPerms, Qemu, QemuExecutor, QemuExitReason,
|
||||
QemuMappingsViewer, QemuRWError, QemuShutdownCause, Regs,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
@ -179,8 +179,8 @@ pub fn fuzz() {
|
||||
qemu.write_reg(Regs::Pc, test_one_input_ptr)?;
|
||||
qemu.write_reg(Regs::Sp, stack_ptr)?;
|
||||
qemu.write_return_address(ret_addr)?;
|
||||
qemu.write_function_argument(CallingConvention::Cdecl, 0, input_addr)?;
|
||||
qemu.write_function_argument(CallingConvention::Cdecl, 1, len)?;
|
||||
qemu.write_function_argument(0, input_addr)?;
|
||||
qemu.write_function_argument(1, len)?;
|
||||
|
||||
match qemu.run() {
|
||||
Ok(QemuExitReason::Breakpoint(_)) => {}
|
||||
|
@ -4,9 +4,7 @@ use libafl::{
|
||||
Error,
|
||||
};
|
||||
use libafl_bolts::AsSlice;
|
||||
use libafl_qemu::{
|
||||
elf::EasyElf, ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, Qemu, Regs,
|
||||
};
|
||||
use libafl_qemu::{elf::EasyElf, ArchExtras, GuestAddr, GuestReg, MmapPerms, Qemu, Regs};
|
||||
|
||||
pub struct Harness {
|
||||
qemu: Qemu,
|
||||
@ -118,11 +116,11 @@ impl Harness {
|
||||
.map_err(|e| Error::unknown(format!("Failed to write return address: {e:?}")))?;
|
||||
|
||||
self.qemu
|
||||
.write_function_argument(CallingConvention::Cdecl, 0, self.input_addr)
|
||||
.write_function_argument(0, self.input_addr)
|
||||
.map_err(|e| Error::unknown(format!("Failed to write argument 0: {e:?}")))?;
|
||||
|
||||
self.qemu
|
||||
.write_function_argument(CallingConvention::Cdecl, 1, len)
|
||||
.write_function_argument(1, len)
|
||||
.map_err(|e| Error::unknown(format!("Failed to write argument 1: {e:?}")))?;
|
||||
unsafe {
|
||||
let _ = self.qemu.run();
|
||||
|
@ -8,7 +8,12 @@ use pyo3::prelude::*;
|
||||
pub use strum_macros::EnumIter;
|
||||
pub use syscall_numbers::aarch64::*;
|
||||
|
||||
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
|
||||
use crate::{sync_exit::ExitArgs, CallingConvention, GuestAddr, QemuRWError, QemuRWErrorKind};
|
||||
|
||||
#[expect(non_upper_case_globals)]
|
||||
impl CallingConvention {
|
||||
pub const Default: CallingConvention = CallingConvention::Aapcs64;
|
||||
}
|
||||
|
||||
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
|
||||
#[repr(i32)]
|
||||
@ -94,47 +99,83 @@ impl crate::ArchExtras for crate::CPU {
|
||||
self.write_reg(Regs::Lr, val)
|
||||
}
|
||||
|
||||
fn read_function_argument(
|
||||
fn read_function_argument_with_cc(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: u8,
|
||||
) -> Result<GuestReg, QemuRWError> {
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
|
||||
|
||||
let reg_id = match idx {
|
||||
0 => Regs::X0,
|
||||
1 => Regs::X1,
|
||||
2 => Regs::X2,
|
||||
3 => Regs::X3,
|
||||
4 => Regs::X4,
|
||||
5 => Regs::X5,
|
||||
r => {
|
||||
return Err(QemuRWError::new_argument_error(
|
||||
QemuRWErrorKind::Read,
|
||||
i32::from(r),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
self.read_reg(reg_id)
|
||||
}
|
||||
|
||||
fn write_function_argument<T>(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: i32,
|
||||
) -> Result<GuestReg, QemuRWError> {
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Aapcs64, conv)?;
|
||||
|
||||
match idx {
|
||||
0 => self.read_reg(Regs::X0),
|
||||
1 => self.read_reg(Regs::X1),
|
||||
2 => self.read_reg(Regs::X2),
|
||||
3 => self.read_reg(Regs::X3),
|
||||
4 => self.read_reg(Regs::X4),
|
||||
5 => self.read_reg(Regs::X5),
|
||||
6 => self.read_reg(Regs::X6),
|
||||
7 => self.read_reg(Regs::X7),
|
||||
_ => {
|
||||
const SIZE: usize = size_of::<GuestReg>();
|
||||
let stack_ptr: GuestAddr = self.read_reg(Regs::Rsp)?;
|
||||
/*
|
||||
* Stack is full and descending. SP points to return address, arguments
|
||||
* are in reverse order above that. 8th argument is at SP + 8.
|
||||
*/
|
||||
|
||||
let offset = (SIZE as GuestAddr) * (GuestAddr::from(idx) - 7);
|
||||
let mut buf = [0; SIZE];
|
||||
self.read_mem(stack_ptr + offset, &mut buf)?;
|
||||
|
||||
#[cfg(feature = "be")]
|
||||
{
|
||||
Ok(GuestReg::from_le_bytes(buf).into())
|
||||
}
|
||||
#[cfg(not(feature = "be"))]
|
||||
Ok(GuestReg::from_le_bytes(buf).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_function_argument_with_cc<T>(
|
||||
&self,
|
||||
idx: u8,
|
||||
val: T,
|
||||
conv: CallingConvention,
|
||||
) -> Result<(), QemuRWError>
|
||||
where
|
||||
T: Into<GuestReg>,
|
||||
{
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Aapcs64, conv)?;
|
||||
|
||||
let val: GuestReg = val.into();
|
||||
match idx {
|
||||
0 => self.write_reg(Regs::X0, val),
|
||||
1 => self.write_reg(Regs::X1, val),
|
||||
r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
|
||||
2 => self.write_reg(Regs::X2, val),
|
||||
3 => self.write_reg(Regs::X3, val),
|
||||
4 => self.write_reg(Regs::X4, val),
|
||||
5 => self.write_reg(Regs::X5, val),
|
||||
6 => self.write_reg(Regs::X6, val),
|
||||
7 => self.write_reg(Regs::X7, val),
|
||||
_ => {
|
||||
let val: GuestReg = val.into();
|
||||
let stack_ptr: GuestAddr = self.read_reg(Regs::Sp)?;
|
||||
/*
|
||||
* Stack is full and descending. SP points to return address, arguments
|
||||
* are in reverse order above that. 8th argument is at SP + 8.
|
||||
*/
|
||||
let size: GuestAddr = size_of::<GuestReg>() as GuestAddr;
|
||||
let offset = size * (GuestAddr::from(idx) - 7);
|
||||
#[cfg(feature = "be")]
|
||||
let arg = val.to_be_bytes();
|
||||
|
||||
#[cfg(not(feature = "be"))]
|
||||
let arg = val.to_le_bytes();
|
||||
self.write_mem(stack_ptr + offset, &arg)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,12 @@ use pyo3::prelude::*;
|
||||
pub use strum_macros::EnumIter;
|
||||
pub use syscall_numbers::arm::*;
|
||||
|
||||
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
|
||||
use crate::{sync_exit::ExitArgs, CallingConvention, GuestAddr, QemuRWError, QemuRWErrorKind};
|
||||
|
||||
#[expect(non_upper_case_globals)]
|
||||
impl CallingConvention {
|
||||
pub const Default: CallingConvention = CallingConvention::Aapcs;
|
||||
}
|
||||
|
||||
/// Registers for the ARM instruction set.
|
||||
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
|
||||
@ -91,46 +96,77 @@ impl crate::ArchExtras for crate::CPU {
|
||||
self.write_reg(Regs::Lr, val)
|
||||
}
|
||||
|
||||
fn read_function_argument(
|
||||
fn read_function_argument_with_cc(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: u8,
|
||||
) -> Result<GuestReg, QemuRWError> {
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
|
||||
|
||||
let reg_id = match idx {
|
||||
0 => Regs::R0,
|
||||
1 => Regs::R1,
|
||||
2 => Regs::R2,
|
||||
3 => Regs::R3,
|
||||
// 4.. would be on the stack, let's not do this for now
|
||||
r => {
|
||||
return Err(QemuRWError::new_argument_error(
|
||||
QemuRWErrorKind::Read,
|
||||
i32::from(r),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
self.read_reg(reg_id)
|
||||
}
|
||||
|
||||
fn write_function_argument<T>(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: i32,
|
||||
) -> Result<GuestReg, QemuRWError> {
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Aapcs, conv)?;
|
||||
|
||||
match idx {
|
||||
0 => self.read_reg(Regs::R0),
|
||||
1 => self.read_reg(Regs::R1),
|
||||
2 => self.read_reg(Regs::R2),
|
||||
3 => self.read_reg(Regs::R3),
|
||||
_ => {
|
||||
const SIZE: usize = size_of::<GuestReg>();
|
||||
let stack_ptr: GuestAddr = self.read_reg(Regs::Sp)?;
|
||||
/*
|
||||
* Stack is full and descending. SP points to return address, arguments
|
||||
* are in reverse order above that. 4th argument is at SP + 8.
|
||||
*/
|
||||
|
||||
let offset = (SIZE as GuestAddr) * (GuestAddr::from(idx) - 3);
|
||||
let mut buf = [0; SIZE];
|
||||
self.read_mem(stack_ptr + offset, &mut buf)?;
|
||||
|
||||
#[cfg(feature = "be")]
|
||||
{
|
||||
Ok(GuestReg::from_le_bytes(buf).into())
|
||||
}
|
||||
#[cfg(not(feature = "be"))]
|
||||
Ok(GuestReg::from_le_bytes(buf).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_function_argument_with_cc<T>(
|
||||
&self,
|
||||
idx: u8,
|
||||
val: T,
|
||||
conv: CallingConvention,
|
||||
) -> Result<(), QemuRWError>
|
||||
where
|
||||
T: Into<GuestReg>,
|
||||
{
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Aapcs, conv)?;
|
||||
|
||||
let val: GuestReg = val.into();
|
||||
match idx {
|
||||
0 => self.write_reg(Regs::R0, val),
|
||||
1 => self.write_reg(Regs::R1, val),
|
||||
r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
|
||||
2 => self.write_reg(Regs::R2, val),
|
||||
3 => self.write_reg(Regs::R3, val),
|
||||
_ => {
|
||||
let val: GuestReg = val.into();
|
||||
let stack_ptr: GuestAddr = self.read_reg(Regs::Sp)?;
|
||||
/*
|
||||
* Stack is full and descending. SP points to return address, arguments
|
||||
* are in reverse order above that. 4th argument is at SP + 4.
|
||||
*/
|
||||
let size: GuestAddr = size_of::<GuestReg>() as GuestAddr;
|
||||
let offset = size * (GuestAddr::from(idx) - 3);
|
||||
|
||||
#[cfg(feature = "be")]
|
||||
let arg = val.to_be_bytes();
|
||||
|
||||
#[cfg(not(feature = "be"))]
|
||||
let arg = val.to_le_bytes();
|
||||
|
||||
self.write_mem(stack_ptr + offset, &arg)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,11 @@ pub use strum_macros::EnumIter;
|
||||
|
||||
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
|
||||
|
||||
#[expect(non_upper_case_globals)]
|
||||
impl CallingConvention {
|
||||
pub const Default: CallingConvention = CallingConvention::Hexagon;
|
||||
}
|
||||
|
||||
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
|
||||
#[repr(i32)]
|
||||
pub enum Regs {
|
||||
@ -103,12 +108,12 @@ impl crate::ArchExtras for crate::CPU {
|
||||
self.write_reg(Regs::Lr, val)
|
||||
}
|
||||
|
||||
fn read_function_argument(
|
||||
fn read_function_argument_with_cc(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: u8,
|
||||
conv: CallingConvention,
|
||||
) -> Result<GuestReg, QemuRWError> {
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Hexagon, conv)?;
|
||||
|
||||
// Note that 64 bit values may be passed in two registers (and may have padding), then this mapping is off.
|
||||
let reg_id = match idx {
|
||||
@ -118,29 +123,33 @@ impl crate::ArchExtras for crate::CPU {
|
||||
3 => Regs::R3,
|
||||
4 => Regs::R4,
|
||||
5 => Regs::R5,
|
||||
r => {
|
||||
return Err(QemuRWError::new_argument_error(
|
||||
QemuRWErrorKind::Read,
|
||||
i32::from(r),
|
||||
))
|
||||
}
|
||||
r => return Err(QemuRWError::new_argument_error(QemuRWErrorKind::Read, r)),
|
||||
};
|
||||
|
||||
self.read_reg(reg_id)
|
||||
}
|
||||
|
||||
fn write_function_argument<T>(
|
||||
fn write_function_argument_with_cc<T>(
|
||||
&self,
|
||||
idx: u8,
|
||||
val: T,
|
||||
conv: CallingConvention,
|
||||
idx: i32,
|
||||
_val: T,
|
||||
) -> Result<(), QemuRWError>
|
||||
where
|
||||
T: Into<GuestReg>,
|
||||
{
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Hexagon, conv)?;
|
||||
|
||||
// TODO
|
||||
Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, idx))
|
||||
// Note that 64 bit values may be passed in two registers (and may have padding), then this mapping is off.
|
||||
let val: GuestReg = val.into();
|
||||
match idx {
|
||||
0 => self.write_reg(Regs::R0, val),
|
||||
1 => self.write_reg(Regs::R1, val),
|
||||
2 => self.write_reg(Regs::R2, val),
|
||||
3 => self.write_reg(Regs::R3, val),
|
||||
4 => self.write_reg(Regs::R4, val),
|
||||
5 => self.write_reg(Regs::R5, val),
|
||||
r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,11 @@ pub use syscall_numbers::x86::*;
|
||||
|
||||
use crate::{sync_exit::ExitArgs, CallingConvention, GuestAddr, QemuRWError, QemuRWErrorKind};
|
||||
|
||||
#[expect(non_upper_case_globals)]
|
||||
impl CallingConvention {
|
||||
pub const Default: CallingConvention = CallingConvention::Cdecl;
|
||||
}
|
||||
|
||||
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
|
||||
#[repr(i32)]
|
||||
pub enum Regs {
|
||||
@ -77,43 +82,37 @@ impl crate::ArchExtras for crate::CPU {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_function_argument(
|
||||
fn read_function_argument_with_cc(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: u8,
|
||||
conv: CallingConvention,
|
||||
) -> Result<GuestReg, QemuRWError> {
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
|
||||
|
||||
match idx {
|
||||
0..=1 => {
|
||||
_ => {
|
||||
const SIZE: usize = size_of::<GuestReg>();
|
||||
let stack_ptr: GuestAddr = self.read_reg(Regs::Sp)?;
|
||||
/*
|
||||
* Stack is full and descending. SP points to return address, arguments
|
||||
* are in reverse order above that.
|
||||
*/
|
||||
let size: GuestAddr = size_of::<GuestReg>() as GuestAddr;
|
||||
let offset = size * (idx as GuestAddr + 1);
|
||||
|
||||
let mut val = [0u8; size_of::<GuestReg>()];
|
||||
let offset = (SIZE as GuestAddr) * (GuestAddr::from(idx) + 1);
|
||||
let mut val = [0u8; SIZE];
|
||||
unsafe {
|
||||
self.read_mem(stack_ptr + offset, &mut val);
|
||||
}
|
||||
Ok(GuestReg::from_le_bytes(val).into())
|
||||
}
|
||||
r => {
|
||||
return Err(QemuRWError::new_argument_error(
|
||||
QemuRWErrorKind::Read,
|
||||
i32::from(r),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_function_argument<T>(
|
||||
fn write_function_argument_with_cc<T>(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: i32,
|
||||
idx: u8,
|
||||
val: T,
|
||||
conv: CallingConvention,
|
||||
) -> Result<(), QemuRWError>
|
||||
where
|
||||
T: Into<GuestReg>,
|
||||
@ -121,7 +120,7 @@ impl crate::ArchExtras for crate::CPU {
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
|
||||
|
||||
match idx {
|
||||
0..=1 => {
|
||||
_ => {
|
||||
let val: GuestReg = val.into();
|
||||
let stack_ptr: GuestAddr = self.read_reg(Regs::Sp)?;
|
||||
/*
|
||||
@ -129,7 +128,7 @@ impl crate::ArchExtras for crate::CPU {
|
||||
* are in reverse order above that.
|
||||
*/
|
||||
let size: GuestAddr = size_of::<GuestReg>() as GuestAddr;
|
||||
let offset = size * (idx as GuestAddr + 1);
|
||||
let offset = size * (GuestAddr::from(idx) + 1);
|
||||
|
||||
let arg = val.to_le_bytes();
|
||||
unsafe {
|
||||
|
@ -9,6 +9,11 @@ pub use syscall_numbers::mips::*;
|
||||
|
||||
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
|
||||
|
||||
#[expect(non_upper_case_globals)]
|
||||
impl CallingConvention {
|
||||
pub const Default: CallingConvention = CallingConvention::MipsO32;
|
||||
}
|
||||
|
||||
/// Registers for the MIPS instruction set.
|
||||
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
|
||||
#[repr(i32)]
|
||||
@ -91,12 +96,12 @@ impl crate::ArchExtras for crate::CPU {
|
||||
self.write_reg(Regs::Ra, val)
|
||||
}
|
||||
|
||||
fn read_function_argument(
|
||||
fn read_function_argument_with_cc(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: u8,
|
||||
conv: CallingConvention,
|
||||
) -> Result<GuestReg, QemuRWError> {
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::MipsO32, conv)?;
|
||||
|
||||
let reg_id = match idx {
|
||||
0 => Regs::A0,
|
||||
@ -104,32 +109,30 @@ impl crate::ArchExtras for crate::CPU {
|
||||
2 => Regs::A2,
|
||||
3 => Regs::A3,
|
||||
// 4.. would be on the stack, let's not do this for now
|
||||
r => {
|
||||
return Err(QemuRWError::new_argument_error(
|
||||
QemuRWErrorKind::Read,
|
||||
i32::from(r),
|
||||
))
|
||||
}
|
||||
r => return Err(QemuRWError::new_argument_error(QemuRWErrorKind::Read, r)),
|
||||
};
|
||||
|
||||
self.read_reg(reg_id)
|
||||
}
|
||||
|
||||
fn write_function_argument<T>(
|
||||
fn write_function_argument_with_cc<T>(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: i32,
|
||||
idx: u8,
|
||||
val: T,
|
||||
conv: CallingConvention,
|
||||
) -> Result<(), QemuRWError>
|
||||
where
|
||||
T: Into<GuestReg>,
|
||||
{
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::MipsO32, conv)?;
|
||||
|
||||
let val: GuestReg = val.into();
|
||||
match idx {
|
||||
0 => self.write_reg(Regs::A0, val),
|
||||
1 => self.write_reg(Regs::A1, val),
|
||||
2 => self.write_reg(Regs::A2, val),
|
||||
3 => self.write_reg(Regs::A3, val),
|
||||
// 4.. would be on the stack, let's not do this for now
|
||||
r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,11 @@ pub use syscall_numbers::powerpc::*;
|
||||
|
||||
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
|
||||
|
||||
#[expect(non_upper_case_globals)]
|
||||
impl CallingConvention {
|
||||
pub const Default: CallingConvention = CallingConvention::Ppc32;
|
||||
}
|
||||
|
||||
/// Registers for the MIPS instruction set.
|
||||
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
|
||||
#[repr(i32)]
|
||||
@ -131,12 +136,12 @@ impl crate::ArchExtras for crate::CPU {
|
||||
self.write_reg(Regs::Lr, val)
|
||||
}
|
||||
|
||||
fn read_function_argument(
|
||||
fn read_function_argument_with_cc(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: u8,
|
||||
conv: CallingConvention,
|
||||
) -> Result<GuestReg, QemuRWError> {
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Ppc32, conv)?;
|
||||
|
||||
let reg_id = match idx {
|
||||
0 => Regs::R3,
|
||||
@ -145,27 +150,22 @@ impl crate::ArchExtras for crate::CPU {
|
||||
3 => Regs::R6,
|
||||
4 => Regs::R7,
|
||||
5 => Regs::R8,
|
||||
r => {
|
||||
return Err(QemuRWError::new_argument_error(
|
||||
QemuRWErrorKind::Read,
|
||||
i32::from(r),
|
||||
))
|
||||
}
|
||||
r => return Err(QemuRWError::new_argument_error(QemuRWErrorKind::Read, r)),
|
||||
};
|
||||
|
||||
self.read_reg(reg_id)
|
||||
}
|
||||
|
||||
fn write_function_argument<T>(
|
||||
fn write_function_argument_with_cc<T>(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: i32,
|
||||
idx: u8,
|
||||
val: T,
|
||||
conv: CallingConvention,
|
||||
) -> Result<(), QemuRWError>
|
||||
where
|
||||
T: Into<GuestReg>,
|
||||
{
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Ppc32, conv)?;
|
||||
|
||||
let val: GuestReg = val.into();
|
||||
match idx {
|
||||
|
@ -22,6 +22,11 @@ pub const SYS_riscv_hwprobe: c_long = SYS_arch_specific_syscall + 14;
|
||||
|
||||
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
|
||||
|
||||
#[expect(non_upper_case_globals)]
|
||||
impl CallingConvention {
|
||||
pub const Default: CallingConvention = CallingConvention::RiscVilp32;
|
||||
}
|
||||
|
||||
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
|
||||
#[repr(i32)]
|
||||
pub enum Regs {
|
||||
@ -106,12 +111,12 @@ impl crate::ArchExtras for crate::CPU {
|
||||
self.write_reg(Regs::Ra, val)
|
||||
}
|
||||
|
||||
fn read_function_argument(
|
||||
fn read_function_argument_with_cc(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: u8,
|
||||
conv: CallingConvention,
|
||||
) -> Result<GuestReg, QemuRWError> {
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::RiscVilp32, conv)?;
|
||||
|
||||
// Note that 64 bit values may be passed in two registers (and are even-odd eg. A0, A2 and A3 where A1 is empty), then this mapping is off.
|
||||
// Note: This does not consider the floating point registers.
|
||||
@ -125,27 +130,22 @@ impl crate::ArchExtras for crate::CPU {
|
||||
5 => Regs::A5, // argument value
|
||||
6 => Regs::A6, // argument value
|
||||
7 => Regs::A7, // argument value
|
||||
r => {
|
||||
return Err(QemuRWError::new_argument_error(
|
||||
QemuRWErrorKind::Read,
|
||||
i32::from(r),
|
||||
))
|
||||
}
|
||||
r => return Err(QemuRWError::new_argument_error(QemuRWErrorKind::Read, r)),
|
||||
};
|
||||
|
||||
self.read_reg(reg_id)
|
||||
}
|
||||
|
||||
fn write_function_argument<T>(
|
||||
fn write_function_argument_with_cc<T>(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: i32,
|
||||
idx: u8,
|
||||
val: T,
|
||||
conv: CallingConvention,
|
||||
) -> Result<(), QemuRWError>
|
||||
where
|
||||
T: Into<GuestReg>,
|
||||
{
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::RiscVilp32, conv)?;
|
||||
|
||||
let val: GuestReg = val.into();
|
||||
match idx {
|
||||
|
@ -6,7 +6,12 @@ use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
pub use strum_macros::EnumIter;
|
||||
pub use syscall_numbers::x86_64::*;
|
||||
|
||||
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
|
||||
use crate::{sync_exit::ExitArgs, CallingConvention, GuestAddr, QemuRWError, QemuRWErrorKind};
|
||||
|
||||
#[expect(non_upper_case_globals)]
|
||||
impl CallingConvention {
|
||||
pub const Default: CallingConvention = CallingConvention::SystemV;
|
||||
}
|
||||
|
||||
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
|
||||
#[repr(i32)]
|
||||
@ -84,47 +89,68 @@ impl crate::ArchExtras for crate::CPU {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_function_argument(
|
||||
fn read_function_argument_with_cc(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: u8,
|
||||
) -> Result<GuestReg, QemuRWError> {
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, conv)?;
|
||||
|
||||
let reg_id = match idx {
|
||||
0 => Regs::Rdi,
|
||||
1 => Regs::Rsi,
|
||||
2 => Regs::Rdx,
|
||||
3 => Regs::Rcx,
|
||||
4 => Regs::R8,
|
||||
5 => Regs::R9,
|
||||
r => {
|
||||
return Err(QemuRWError::new_argument_error(
|
||||
QemuRWErrorKind::Read,
|
||||
i32::from(r),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
self.read_reg(reg_id)
|
||||
}
|
||||
|
||||
fn write_function_argument<T>(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: i32,
|
||||
) -> Result<GuestReg, QemuRWError> {
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::SystemV, conv)?;
|
||||
|
||||
match idx {
|
||||
0 => self.read_reg(Regs::Rdi),
|
||||
1 => self.read_reg(Regs::Rsi),
|
||||
2 => self.read_reg(Regs::Rdx),
|
||||
3 => self.read_reg(Regs::Rcx),
|
||||
4 => self.read_reg(Regs::R8),
|
||||
5 => self.read_reg(Regs::R9),
|
||||
_ => {
|
||||
const SIZE: usize = size_of::<GuestReg>();
|
||||
let stack_ptr: GuestAddr = self.read_reg(Regs::Sp)?;
|
||||
/*
|
||||
* Stack is full and descending. SP points to return address, arguments
|
||||
* are in reverse order above that. 6th argument is at SP + 8.
|
||||
*/
|
||||
|
||||
let offset = (SIZE as GuestAddr) * (GuestAddr::from(idx) - 5);
|
||||
let mut buf = [0; SIZE];
|
||||
self.read_mem(stack_ptr + offset, &mut buf)?;
|
||||
|
||||
Ok(GuestAddr::from_le_bytes(buf))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_function_argument_with_cc<T>(
|
||||
&self,
|
||||
idx: u8,
|
||||
val: T,
|
||||
conv: CallingConvention,
|
||||
) -> Result<(), QemuRWError>
|
||||
where
|
||||
T: Into<GuestReg>,
|
||||
{
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::SystemV, conv)?;
|
||||
|
||||
let val: GuestReg = val.into();
|
||||
match idx {
|
||||
0 => self.write_reg(Regs::Rdi, val),
|
||||
1 => self.write_reg(Regs::Rsi, val),
|
||||
r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
|
||||
2 => self.write_reg(Regs::Rdx, val),
|
||||
3 => self.write_reg(Regs::Rcx, val),
|
||||
4 => self.write_reg(Regs::R8, val),
|
||||
5 => self.write_reg(Regs::R9, val),
|
||||
_ => {
|
||||
let val: GuestReg = val.into();
|
||||
let stack_ptr: GuestAddr = self.read_reg(Regs::Rsp)?;
|
||||
/*
|
||||
* Stack is full and descending. SP points to return address, arguments
|
||||
* are in reverse order above that. 6th argument is at SP + 8.
|
||||
*/
|
||||
let size: GuestAddr = size_of::<GuestReg>() as GuestAddr;
|
||||
let offset = size * (GuestAddr::from(idx) - 5);
|
||||
let arg = val.to_le_bytes();
|
||||
self.write_mem(stack_ptr + offset, &arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,10 +12,10 @@ pub use libafl_targets::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "usermode")]
|
||||
use crate::capstone;
|
||||
#[cfg(feature = "systemmode")]
|
||||
use crate::modules::utils::filters::{NopPageFilter, NOP_PAGE_FILTER};
|
||||
#[cfg(feature = "usermode")]
|
||||
use crate::{capstone, qemu::ArchExtras, CallingConvention};
|
||||
use crate::{
|
||||
emu::EmulatorModules,
|
||||
modules::{
|
||||
@ -301,12 +301,8 @@ impl CmpLogRoutinesModule {
|
||||
|
||||
let qemu = Qemu::get().unwrap();
|
||||
|
||||
let a0: GuestAddr = qemu
|
||||
.read_function_argument(CallingConvention::Cdecl, 0)
|
||||
.unwrap_or(0);
|
||||
let a1: GuestAddr = qemu
|
||||
.read_function_argument(CallingConvention::Cdecl, 1)
|
||||
.unwrap_or(0);
|
||||
let a0: GuestAddr = qemu.read_function_argument(0).unwrap_or(0);
|
||||
let a1: GuestAddr = qemu.read_function_argument(1).unwrap_or(0);
|
||||
|
||||
if a0 == 0 || a1 == 0 {
|
||||
return;
|
||||
|
@ -228,7 +228,7 @@ impl InjectionModule {
|
||||
let reg: GuestAddr = qemu
|
||||
.current_cpu()
|
||||
.unwrap()
|
||||
.read_function_argument(CallingConvention::Cdecl, parameter)
|
||||
.read_function_argument_with_cc(parameter, CallingConvention::Default)
|
||||
.unwrap_or_default();
|
||||
|
||||
let module = emulator_modules.get_mut::<Self>().unwrap();
|
||||
|
@ -36,7 +36,7 @@ pub enum QemuRWErrorKind {
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum QemuRWErrorCause {
|
||||
WrongCallingConvention(CallingConvention, CallingConvention), // expected, given
|
||||
WrongArgument(i32),
|
||||
WrongArgument(u8),
|
||||
CurrentCpuNotFound,
|
||||
Reg(i32),
|
||||
WrongMemoryLocation(GuestAddr, usize), // addr, size
|
||||
@ -121,8 +121,8 @@ impl QemuRWError {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn new_argument_error(kind: QemuRWErrorKind, reg_id: i32) -> Self {
|
||||
Self::new(kind, QemuRWErrorCause::WrongArgument(reg_id), None)
|
||||
pub fn new_argument_error(kind: QemuRWErrorKind, arg_id: u8) -> Self {
|
||||
Self::new(kind, QemuRWErrorCause::WrongArgument(arg_id), None)
|
||||
}
|
||||
|
||||
pub fn check_conv(
|
||||
|
@ -74,16 +74,17 @@ pub trait ArchExtras {
|
||||
fn write_return_address<T>(&self, val: T) -> Result<(), QemuRWError>
|
||||
where
|
||||
T: Into<GuestReg>;
|
||||
fn read_function_argument(
|
||||
fn read_function_argument_with_cc(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: u8,
|
||||
) -> Result<GuestReg, QemuRWError>;
|
||||
fn write_function_argument<T>(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: i32,
|
||||
) -> Result<GuestReg, QemuRWError>;
|
||||
|
||||
fn write_function_argument_with_cc<T>(
|
||||
&self,
|
||||
idx: u8,
|
||||
val: T,
|
||||
conv: CallingConvention,
|
||||
) -> Result<(), QemuRWError>
|
||||
where
|
||||
T: Into<GuestReg>;
|
||||
@ -155,7 +156,14 @@ pub struct CPU {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum CallingConvention {
|
||||
SystemV,
|
||||
Cdecl,
|
||||
Aapcs64,
|
||||
Aapcs,
|
||||
Hexagon,
|
||||
MipsO32,
|
||||
Ppc32,
|
||||
RiscVilp32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -937,6 +945,65 @@ impl Qemu {
|
||||
pub fn is_running(&self) -> bool {
|
||||
unsafe { QEMU_IS_RUNNING }
|
||||
}
|
||||
|
||||
/// Write the function arguments by following default calling convention.
|
||||
/// Assume that every arguments has integer/pointer type, otherwise the value
|
||||
/// may be stored at wrong place because of different rules for complex types.
|
||||
/// Note that the stack pointer register must point the top of the stack at the start
|
||||
/// of the called function, in case the argument is written in the stack.
|
||||
/// Support downward-growing stack only.
|
||||
/// If you need to specify a calling convention, use [`Self::write_function_arguments_with_cc`].
|
||||
pub fn write_function_arguments<T>(&mut self, val: &[T]) -> Result<(), QemuRWError>
|
||||
where
|
||||
T: Into<GuestReg> + Copy,
|
||||
{
|
||||
self.write_function_arguments_with_cc(val, &CallingConvention::Default)
|
||||
}
|
||||
|
||||
/// Write the function arguments by following calling convention `conv`.
|
||||
/// Assume that every arguments has integer/pointer type, otherwise the value
|
||||
/// may be stored at wrong place because of different rules for complex types.
|
||||
/// Note that the stack pointer register must point the top of the stack at the start
|
||||
/// of the called function, in case the argument is written in the stack.
|
||||
/// Support downward-growing stack only.
|
||||
pub fn write_function_arguments_with_cc<T>(
|
||||
&mut self,
|
||||
val: &[T],
|
||||
conv: &CallingConvention,
|
||||
) -> Result<(), QemuRWError>
|
||||
where
|
||||
T: Into<GuestReg> + Copy,
|
||||
{
|
||||
for (idx, elem) in val.iter().enumerate() {
|
||||
self.write_function_argument_with_cc(idx as u8, elem.to_owned(), conv.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read the function `idx` argument by following default calling convention.
|
||||
/// Assume that this argument and every prior arguments has integer/pointer type, otherwise
|
||||
/// it may return a wrong value because of different rules for complex types.
|
||||
/// Note that the stack pointer register must point the top of the stack at the start
|
||||
/// of the called function, in case the value is in the stack.
|
||||
/// Support downward-growing stack only.
|
||||
/// If you need to specify a calling convention, use [`Self::read_function_argument_with_cc`].
|
||||
pub fn read_function_argument(&self, idx: u8) -> Result<GuestReg, QemuRWError> {
|
||||
self.read_function_argument_with_cc(idx, CallingConvention::Default)
|
||||
}
|
||||
|
||||
/// Write the function `val` into `idx` argument by following default calling convention.
|
||||
/// Assume that `val` and every prior arguments has integer/pointer type, otherwise the value
|
||||
/// may be stored at wrong place because of different rules for complex types.
|
||||
/// Note that the stack pointer register must point the top of the stack at the start
|
||||
/// of the called function, in case the argument is written in the stack.
|
||||
/// Support downward-growing stack only.
|
||||
/// If you need to specify a calling convention, use [`Self::write_function_argument_with_cc`].
|
||||
pub fn write_function_argument<T>(&self, idx: u8, val: T) -> Result<(), QemuRWError>
|
||||
where
|
||||
T: Into<GuestReg>,
|
||||
{
|
||||
self.write_function_argument_with_cc(idx, val, CallingConvention::Default)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchExtras for Qemu {
|
||||
@ -955,28 +1022,40 @@ impl ArchExtras for Qemu {
|
||||
.write_return_address::<T>(val)
|
||||
}
|
||||
|
||||
fn read_function_argument(
|
||||
/// Read the function `idx` argument by following calling convention `conv`.
|
||||
/// Assume that this argument and every prior arguments has integer/pointer type, otherwise
|
||||
/// it may return a wrong value because of different rules for complex types.
|
||||
/// Note that the stack pointer register must point the top of the stack at the start
|
||||
/// of the called function, in case the value is in the stack.
|
||||
/// Support downward-growing stack only.
|
||||
fn read_function_argument_with_cc(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: u8,
|
||||
conv: CallingConvention,
|
||||
) -> Result<GuestReg, QemuRWError> {
|
||||
self.current_cpu()
|
||||
.ok_or(QemuRWError::current_cpu_not_found(QemuRWErrorKind::Read))?
|
||||
.read_function_argument(conv, idx)
|
||||
.read_function_argument_with_cc(idx, conv)
|
||||
}
|
||||
|
||||
fn write_function_argument<T>(
|
||||
/// Write the function `val` into `idx` argument by following calling convention `conv`.
|
||||
/// Assume that `val` and every prior arguments has integer/pointer type, otherwise the value
|
||||
/// may be stored at wrong place because of different rules for complex types.
|
||||
/// Note that the stack pointer register must point the top of the stack at the start
|
||||
/// of the called function, in case the argument is written in the stack.
|
||||
/// Support downward-growing stack only.
|
||||
fn write_function_argument_with_cc<T>(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: i32,
|
||||
idx: u8,
|
||||
val: T,
|
||||
conv: CallingConvention,
|
||||
) -> 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)
|
||||
.write_function_argument_with_cc::<T>(idx, val, conv)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user