libafl_qemu: Add RISCV support (#2367)
* libafl_qemu: Add RISCV support Adds the following targets (as features): - riscv32 - riscv64 Added `RISCVCPU` and `CPURISCVState` to the bindings allow list. Added riscv.rs to the arch module, with all necessary functions and registers implemented and mapped. The registers are the same as the ones found in qemus gdbstub xml found after a build. Additionally we added all syscall numbers for riscv 64 bit (already supported by the `syscall_numbers` crate) and also added the missing ones for riscv 32 bit. We compared both lists and their differences / equalities with a simple python script and generated a list of the missing ones, to be complete. We might PR those to the `syscall_numbers` crate later on. --------- Co-authored-by: Romain Malmain <romain.malmain@pm.me>
This commit is contained in:
parent
6eb2dafd34
commit
83c87acd5b
@ -59,6 +59,8 @@ mips = [
|
||||
] # build qemu for mips (el, use with the 'be' feature of mips be)
|
||||
ppc = ["libafl_qemu_sys/ppc"] # build qemu for powerpc
|
||||
hexagon = ["libafl_qemu_sys/hexagon"] # build qemu for hexagon
|
||||
riscv32 = ["libafl_qemu_sys/riscv32"] # build qemu for riscv 32bit
|
||||
riscv64 = ["libafl_qemu_sys/riscv64"] # build qemu for riscv 64bit
|
||||
|
||||
## Big Endian mode
|
||||
be = ["libafl_qemu_sys/be"]
|
||||
|
@ -17,7 +17,7 @@ void __libafl_qemu_testfile() {}
|
||||
pub fn build() {
|
||||
// Note: Unique features are checked in libafl_qemu_sys
|
||||
println!(
|
||||
r#"cargo::rustc-check-cfg=cfg(cpu_target, values("arm", "aarch64", "hexagon", "i386", "mips", "ppc", "x86_64"))"#
|
||||
r#"cargo::rustc-check-cfg=cfg(cpu_target, values("arm", "aarch64", "hexagon", "i386", "mips", "ppc", "riscv32", "riscv64", "x86_64"))"#
|
||||
);
|
||||
|
||||
let emulation_mode = if cfg!(feature = "usermode") {
|
||||
@ -92,6 +92,10 @@ pub fn build() {
|
||||
"mips".to_string()
|
||||
} else if cfg!(feature = "ppc") {
|
||||
"ppc".to_string()
|
||||
} else if cfg!(feature = "riscv32") {
|
||||
"riscv32".to_string()
|
||||
} else if cfg!(feature = "riscv64") {
|
||||
"riscv64".to_string()
|
||||
} else if cfg!(feature = "hexagon") {
|
||||
"hexagon".to_string()
|
||||
} else {
|
||||
@ -99,7 +103,7 @@ pub fn build() {
|
||||
};
|
||||
println!("cargo:rerun-if-env-changed=CPU_TARGET");
|
||||
println!("cargo:rustc-cfg=cpu_target=\"{cpu_target}\"");
|
||||
println!("cargo::rustc-check-cfg=cfg(cpu_target, values(\"x86_64\", \"arm\", \"aarch64\", \"i386\", \"mips\", \"ppc\", \"hexagon\"))");
|
||||
println!("cargo::rustc-check-cfg=cfg(cpu_target, values(\"x86_64\", \"arm\", \"aarch64\", \"i386\", \"mips\", \"ppc\", \"hexagon\", \"riscv32\", \"riscv64\"))");
|
||||
|
||||
let cross_cc = if cfg!(feature = "usermode") && (qemu_asan || qemu_asan_guest) {
|
||||
// TODO try to autodetect a cross compiler with the arch name (e.g. aarch64-linux-gnu-gcc)
|
||||
|
@ -194,6 +194,10 @@ pub fn generate(
|
||||
bindings
|
||||
.allowlist_type("ARMCPU")
|
||||
.allowlist_type("ARMv7MState")
|
||||
} else if cpu_target == "riscv32" || cpu_target == "riscv64" {
|
||||
bindings
|
||||
.allowlist_type("RISCVCPU")
|
||||
.allowlist_type("CPURISCVState")
|
||||
} else {
|
||||
bindings
|
||||
};
|
||||
|
@ -11,7 +11,7 @@ use crate::cargo_add_rpath;
|
||||
|
||||
pub const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
|
||||
pub const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
|
||||
pub const QEMU_REVISION: &str = "805b14ffc44999952562e8f219d81c21a4fa50b9";
|
||||
pub const QEMU_REVISION: &str = "c3c9c2128566ff325aa1a2bdcedde717f7d86e2c";
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct BuildResult {
|
||||
|
@ -223,6 +223,7 @@ fn qemu_bindgen_clang_args(
|
||||
let target_arch_dir = match cpu_target {
|
||||
"x86_64" => format!("-I{}/target/i386", qemu_dir.display()),
|
||||
"aarch64" => format!("-I{}/target/arm", qemu_dir.display()),
|
||||
"riscv32" | "riscv64" => format!("-I{}/target/riscv", qemu_dir.display()),
|
||||
_ => format!("-I{}/target/{cpu_target}", qemu_dir.display()),
|
||||
};
|
||||
|
||||
|
@ -33,6 +33,8 @@ aarch64 = [] # build qemu for aarch64
|
||||
mips = [] # build qemu for mips (el, use with the 'be' feature of mips be)
|
||||
ppc = [] # build qemu for powerpc
|
||||
hexagon = [] # build qemu for hexagon
|
||||
riscv32 = [] # build qemu for riscv 32bit
|
||||
riscv64 = [] # build qemu for riscv 64bit
|
||||
|
||||
be = []
|
||||
|
||||
|
@ -41,12 +41,14 @@ pub fn build() {
|
||||
|
||||
// Make sure we have at most one architecutre feature set
|
||||
// Else, we default to `x86_64` - having a default makes CI easier :)
|
||||
assert_unique_feature!("arm", "aarch64", "i386", "x86_64", "mips", "ppc", "hexagon");
|
||||
assert_unique_feature!(
|
||||
"arm", "aarch64", "i386", "x86_64", "mips", "ppc", "hexagon", "riscv32", "riscv64"
|
||||
);
|
||||
|
||||
// Make sure that we don't have BE set for any architecture other than arm and mips
|
||||
// Sure aarch64 may support BE, but its not in common usage and we don't
|
||||
// need it yet and so haven't tested it
|
||||
assert_unique_feature!("be", "aarch64", "i386", "x86_64", "hexagon");
|
||||
assert_unique_feature!("be", "aarch64", "i386", "x86_64", "hexagon", "riscv32", "riscv64");
|
||||
|
||||
let cpu_target = if cfg!(feature = "x86_64") {
|
||||
"x86_64".to_string()
|
||||
@ -60,12 +62,16 @@ pub fn build() {
|
||||
"mips".to_string()
|
||||
} else if cfg!(feature = "ppc") {
|
||||
"ppc".to_string()
|
||||
} else if cfg!(feature = "riscv32") {
|
||||
"riscv32".to_string()
|
||||
} else if cfg!(feature = "riscv64") {
|
||||
"riscv64".to_string()
|
||||
} else if cfg!(feature = "hexagon") {
|
||||
"hexagon".to_string()
|
||||
} else {
|
||||
env::var("CPU_TARGET").unwrap_or_else(|_| {
|
||||
println!(
|
||||
"cargo:warning=No architecture feature enabled or CPU_TARGET env specified for libafl_qemu, supported: arm, aarch64, hexagon, i386, mips, ppc, x86_64 - defaulting to x86_64"
|
||||
"cargo:warning=No architecture feature enabled or CPU_TARGET env specified for libafl_qemu, supported: arm, aarch64, hexagon, i386, mips, ppc, riscv32, riscv64, x86_64 - defaulting to x86_64"
|
||||
);
|
||||
"x86_64".to_string()
|
||||
})
|
||||
@ -73,7 +79,7 @@ pub fn build() {
|
||||
println!("cargo:rerun-if-env-changed=CPU_TARGET");
|
||||
println!("cargo:rerun-if-env-changed=LIBAFL_QEMU_GEN_STUBS");
|
||||
println!("cargo:rustc-cfg=cpu_target=\"{cpu_target}\"");
|
||||
println!("cargo::rustc-check-cfg=cfg(cpu_target, values(\"x86_64\", \"arm\", \"aarch64\", \"i386\", \"mips\", \"ppc\", \"hexagon\"))");
|
||||
println!("cargo::rustc-check-cfg=cfg(cpu_target, values(\"x86_64\", \"arm\", \"aarch64\", \"i386\", \"mips\", \"ppc\", \"hexagon\", \"riscv32\", \"riscv64\"))");
|
||||
|
||||
let jobs = env::var("NUM_JOBS")
|
||||
.ok()
|
||||
|
@ -32,3 +32,8 @@ pub use ppc::*;
|
||||
pub mod hexagon;
|
||||
#[cfg(cpu_target = "hexagon")]
|
||||
pub use hexagon::*;
|
||||
|
||||
#[cfg(any(cpu_target = "riscv32", cpu_target = "riscv64"))]
|
||||
pub mod riscv;
|
||||
#[cfg(any(cpu_target = "riscv32", cpu_target = "riscv64"))]
|
||||
pub use riscv::*;
|
||||
|
159
libafl_qemu/src/arch/riscv.rs
Normal file
159
libafl_qemu/src/arch/riscv.rs
Normal file
@ -0,0 +1,159 @@
|
||||
use core::ffi::c_long;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use capstone::arch::BuildsCapstone;
|
||||
use enum_map::{enum_map, EnumMap};
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
#[cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
pub use strum_macros::EnumIter;
|
||||
#[cfg(feature = "riscv32")]
|
||||
pub use syscall_numbers::riscv32::*;
|
||||
#[cfg(feature = "riscv64")]
|
||||
pub use syscall_numbers::riscv64::*;
|
||||
|
||||
// QEMU specific
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const SYS_syscalls: c_long = 447;
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const SYS_riscv_flush_icache: c_long = SYS_arch_specific_syscall + 15;
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const SYS_riscv_hwprobe: c_long = SYS_arch_specific_syscall + 14;
|
||||
|
||||
use crate::{sync_exit::ExitArgs, CallingConvention, QemuRWError, QemuRWErrorKind};
|
||||
|
||||
#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)]
|
||||
#[repr(i32)]
|
||||
pub enum Regs {
|
||||
Zero = 0, // x0: Hardwired zero
|
||||
Ra = 1, // x1: Return address
|
||||
Sp = 2, // x2: Stack pointer
|
||||
Gp = 3, // x3: Global pointer
|
||||
Tp = 4, // x4: Thread pointer
|
||||
T0 = 5, // x5: Temporary register
|
||||
T1 = 6, // x6: Temporary register
|
||||
T2 = 7, // x7: Temporary register
|
||||
FP = 8, // x8: Saved register / frame pointer
|
||||
S1 = 9, // x9: Saved register
|
||||
A0 = 10, // x10: Function argument / return value
|
||||
A1 = 11, // x11: Function argument / return value
|
||||
A2 = 12, // x12: Function argument
|
||||
A3 = 13, // x13: Function argument
|
||||
A4 = 14, // x14: Function argument
|
||||
A5 = 15, // x15: Function argument
|
||||
A6 = 16, // x16: Function argument
|
||||
A7 = 17, // x17: Function argument
|
||||
S2 = 18, // x18: Saved register
|
||||
S3 = 19, // x19: Saved register
|
||||
S4 = 20, // x20: Saved register
|
||||
S5 = 21, // x21: Saved register
|
||||
S6 = 22, // x22: Saved register
|
||||
S7 = 23, // x23: Saved register
|
||||
S8 = 24, // x24: Saved register
|
||||
S9 = 25, // x25: Saved register
|
||||
S10 = 26, // x26: Saved register
|
||||
S11 = 27, // x27: Saved register
|
||||
T3 = 28, // x28: Temporary register
|
||||
T4 = 29, // x29: Temporary register
|
||||
T5 = 30, // x30: Temporary register
|
||||
T6 = 31, // x31: Temporary register
|
||||
Pc = 32, // Program Counter (code pointer not actual register)
|
||||
}
|
||||
|
||||
static EXIT_ARCH_REGS: OnceLock<EnumMap<ExitArgs, Regs>> = OnceLock::new();
|
||||
|
||||
pub fn get_exit_arch_regs() -> &'static EnumMap<ExitArgs, Regs> {
|
||||
EXIT_ARCH_REGS.get_or_init(|| {
|
||||
enum_map! {
|
||||
ExitArgs::Ret => Regs::A0,
|
||||
ExitArgs::Cmd => Regs::A0,
|
||||
ExitArgs::Arg1 => Regs::A1,
|
||||
ExitArgs::Arg2 => Regs::A2,
|
||||
ExitArgs::Arg3 => Regs::A3,
|
||||
ExitArgs::Arg4 => Regs::A4,
|
||||
ExitArgs::Arg5 => Regs::A5,
|
||||
ExitArgs::Arg6 => Regs::A6,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "riscv64"))]
|
||||
pub type GuestReg = u32;
|
||||
#[cfg(feature = "riscv64")]
|
||||
pub type GuestReg = u64;
|
||||
|
||||
/// Return a RISCV ArchCapstoneBuilder
|
||||
pub fn capstone() -> capstone::arch::riscv::ArchCapstoneBuilder {
|
||||
#[cfg(not(feature = "riscv64"))]
|
||||
return capstone::Capstone::new()
|
||||
.riscv()
|
||||
.mode(capstone::arch::riscv::ArchMode::RiscV32);
|
||||
#[cfg(feature = "riscv64")]
|
||||
return capstone::Capstone::new()
|
||||
.riscv()
|
||||
.mode(capstone::arch::riscv::ArchMode::RiscV64);
|
||||
}
|
||||
|
||||
impl crate::ArchExtras for crate::CPU {
|
||||
fn read_return_address<T>(&self) -> Result<T, QemuRWError>
|
||||
where
|
||||
T: From<GuestReg>,
|
||||
{
|
||||
self.read_reg(Regs::Ra)
|
||||
}
|
||||
|
||||
fn write_return_address<T>(&self, val: T) -> Result<(), QemuRWError>
|
||||
where
|
||||
T: Into<GuestReg>,
|
||||
{
|
||||
self.write_reg(Regs::Ra, val)
|
||||
}
|
||||
|
||||
fn read_function_argument<T>(&self, conv: CallingConvention, idx: u8) -> Result<T, QemuRWError>
|
||||
where
|
||||
T: From<GuestReg>,
|
||||
{
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Read, CallingConvention::Cdecl, 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.
|
||||
// See https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
|
||||
let reg_id = match idx {
|
||||
0 => Regs::A0, // argument / return value
|
||||
1 => Regs::A1, // argument / return value
|
||||
2 => Regs::A2, // argument value
|
||||
3 => Regs::A3, // argument value
|
||||
4 => Regs::A4, // argument value
|
||||
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),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
self.read_reg(reg_id)
|
||||
}
|
||||
|
||||
fn write_function_argument<T>(
|
||||
&self,
|
||||
conv: CallingConvention,
|
||||
idx: i32,
|
||||
val: T,
|
||||
) -> Result<(), QemuRWError>
|
||||
where
|
||||
T: Into<GuestReg>,
|
||||
{
|
||||
QemuRWError::check_conv(QemuRWErrorKind::Write, CallingConvention::Cdecl, conv)?;
|
||||
|
||||
let val: GuestReg = val.into();
|
||||
match idx {
|
||||
0 => self.write_reg(Regs::A0, val), // argument / return value
|
||||
1 => self.write_reg(Regs::A1, val), // argument / return value
|
||||
r => Err(QemuRWError::new_argument_error(QemuRWErrorKind::Write, r)),
|
||||
}
|
||||
}
|
||||
}
|
@ -51,6 +51,10 @@ mips = ["libafl_qemu/mips"]
|
||||
ppc = ["libafl_qemu/ppc"]
|
||||
## build qemu for hexagon
|
||||
hexagon = ["libafl_qemu/hexagon"]
|
||||
## build qemu for riscv 32bit
|
||||
riscv32 = ["libafl_qemu/riscv32"]
|
||||
## build qemu for riscv 64bit
|
||||
riscv64 = ["libafl_qemu/riscv64"]
|
||||
|
||||
[build-dependencies]
|
||||
pyo3-build-config = { version = "0.22.3", optional = true }
|
||||
|
Loading…
x
Reference in New Issue
Block a user