saibotk 83c87acd5b
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>
2024-10-30 10:33:03 +01:00

120 lines
4.4 KiB
Rust

use std::{env, fs::copy, path::PathBuf};
use libafl_qemu_build::{build_with_bindings, maybe_generate_stub_bindings};
#[macro_export]
macro_rules! assert_unique_feature {
() => {};
($first:tt $(,$rest:tt)*) => {
$(
#[cfg(all(not(any(docsrs, feature = "clippy")), feature = $first, feature = $rest))]
compile_error!(concat!("features \"", $first, "\" and \"", $rest, "\" cannot be used together"));
)*
assert_unique_feature!($($rest),*);
}
}
#[macro_export]
macro_rules! assert_at_least_one_feature {
($($feature:literal),+) => {
#[cfg(not(any($(feature = $feature),+)))]
compile_error!(concat!("At least one of the following features must be enabled:", $(" ", $feature),+));
};
}
pub fn build() {
// Make sure that at most one qemu mode is set
assert_unique_feature!("usermode", "systemmode");
// Make sure that at least one qemu mode is set
assert_at_least_one_feature!("usermode", "systemmode");
let emulation_mode = if cfg!(feature = "usermode") {
"usermode"
} else if cfg!(feature = "systemmode") {
"systemmode"
} else {
unreachable!(
"The above macros, `assert_unique_feature` and `assert_at_least_one_feature`, should \
panic before this code is reached."
);
};
// 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", "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", "riscv32", "riscv64");
let cpu_target = if cfg!(feature = "x86_64") {
"x86_64".to_string()
} else if cfg!(feature = "arm") {
"arm".to_string()
} else if cfg!(feature = "aarch64") {
"aarch64".to_string()
} else if cfg!(feature = "i386") {
"i386".to_string()
} else if cfg!(feature = "mips") {
"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, riscv32, riscv64, x86_64 - defaulting to x86_64"
);
"x86_64".to_string()
})
};
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\", \"riscv32\", \"riscv64\"))");
let jobs = env::var("NUM_JOBS")
.ok()
.map(|x| str::parse::<u32>(&x).expect("The number of jobs is not a valid integer!"));
let out_dir = env::var("OUT_DIR").unwrap();
let out_dir = PathBuf::from(out_dir);
let bindings_file = out_dir.join("bindings.rs");
let src_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let src_dir = PathBuf::from(src_dir);
let stub_bindings_file = src_dir.join("src/bindings/x86_64_stub_bindings.rs");
if env::var("DOCS_RS").is_ok() || cfg!(feature = "clippy") {
// Only build when we're not generating docs and not in clippy
copy(stub_bindings_file, bindings_file).expect("Failed to copy the bindings stub");
return;
}
build_with_bindings(
&cpu_target,
cfg!(feature = "be"),
emulation_mode == "usermode",
jobs,
&bindings_file,
);
println!("cargo:rerun-if-changed={}", stub_bindings_file.display());
// If the bindings are built and differ from the current stub, replace it with the freshly generated bindings
maybe_generate_stub_bindings(
&cpu_target,
emulation_mode,
stub_bindings_file.as_path(),
bindings_file.as_path(),
);
}