Rework of libafl_qemu configuration (#2054)
* LibAFL QEMU can now be dynamically linked * LibAFL QEMU reconfiguration happens less frequently (now using a signature check) * Possibility to have custom rpath in QEMU
This commit is contained in:
parent
da6118e61e
commit
bc3ef5952b
@ -12,6 +12,8 @@ classic = [] # The classic way to interact with LibAFL QEMU, with direct calls t
|
|||||||
breakpoint = [] # Uses the command system, with breakpoints
|
breakpoint = [] # Uses the command system, with breakpoints
|
||||||
sync_exit = [] # Uses the command system, with sync exit.
|
sync_exit = [] # Uses the command system, with sync exit.
|
||||||
|
|
||||||
|
shared = ["libafl_qemu/shared"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
incremental = true
|
incremental = true
|
||||||
debug = true
|
debug = true
|
||||||
@ -24,3 +26,6 @@ libafl_bolts = { path = "../../libafl_bolts/" }
|
|||||||
libafl_qemu = { path = "../../libafl_qemu/", features = ["arm", "systemmode"] }
|
libafl_qemu = { path = "../../libafl_qemu/", features = ["arm", "systemmode"] }
|
||||||
libafl_qemu_sys = { path = "../../libafl_qemu/libafl_qemu_sys", features = ["arm", "systemmode"] }
|
libafl_qemu_sys = { path = "../../libafl_qemu/libafl_qemu_sys", features = ["arm", "systemmode"] }
|
||||||
env_logger = "*"
|
env_logger = "*"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
libafl_qemu_build = { path = "../../libafl_qemu/libafl_qemu_build" }
|
||||||
|
@ -45,7 +45,6 @@ args = [
|
|||||||
"--no-default-features",
|
"--no-default-features",
|
||||||
"--features", "std,${FEATURE}",
|
"--features", "std,${FEATURE}",
|
||||||
"--target-dir", "${TARGET_DIR}",
|
"--target-dir", "${TARGET_DIR}",
|
||||||
"-vv",
|
|
||||||
]
|
]
|
||||||
dependencies = ["image"]
|
dependencies = ["image"]
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use libafl_qemu_build::build_libafl_qemu;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! assert_unique_feature {
|
macro_rules! assert_unique_feature {
|
||||||
() => {};
|
() => {};
|
||||||
@ -12,4 +14,6 @@ macro_rules! assert_unique_feature {
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
assert_unique_feature!("classic", "breakpoint", "sync_exit");
|
assert_unique_feature!("classic", "breakpoint", "sync_exit");
|
||||||
|
|
||||||
|
build_libafl_qemu();
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ use libafl::{
|
|||||||
inputs::BytesInput,
|
inputs::BytesInput,
|
||||||
monitors::MultiMonitor,
|
monitors::MultiMonitor,
|
||||||
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
|
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
|
||||||
observers::{HitcountsMapObserver, TimeObserver, TrackingHinted, VariableMapObserver},
|
observers::{CanTrack, HitcountsMapObserver, TimeObserver, VariableMapObserver},
|
||||||
schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler},
|
schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler},
|
||||||
stages::{CalibrationStage, StdMutationalStage},
|
stages::{CalibrationStage, StdMutationalStage},
|
||||||
state::{HasCorpus, StdState},
|
state::{HasCorpus, StdState},
|
||||||
|
@ -13,7 +13,7 @@ use libafl::{
|
|||||||
inputs::{BytesInput, HasTargetBytes},
|
inputs::{BytesInput, HasTargetBytes},
|
||||||
monitors::MultiMonitor,
|
monitors::MultiMonitor,
|
||||||
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
|
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
|
||||||
observers::{HitcountsMapObserver, TimeObserver, TrackingHinted, VariableMapObserver},
|
observers::{CanTrack, HitcountsMapObserver, TimeObserver, VariableMapObserver},
|
||||||
schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler},
|
schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler},
|
||||||
stages::StdMutationalStage,
|
stages::StdMutationalStage,
|
||||||
state::{HasCorpus, StdState},
|
state::{HasCorpus, StdState},
|
||||||
|
@ -13,7 +13,7 @@ use libafl::{
|
|||||||
inputs::BytesInput,
|
inputs::BytesInput,
|
||||||
monitors::MultiMonitor,
|
monitors::MultiMonitor,
|
||||||
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
|
mutators::scheduled::{havoc_mutations, StdScheduledMutator},
|
||||||
observers::{HitcountsMapObserver, TimeObserver, TrackingHinted, VariableMapObserver},
|
observers::{CanTrack, HitcountsMapObserver, TimeObserver, VariableMapObserver},
|
||||||
schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler},
|
schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler},
|
||||||
stages::{CalibrationStage, StdMutationalStage},
|
stages::{CalibrationStage, StdMutationalStage},
|
||||||
state::{HasCorpus, StdState},
|
state::{HasCorpus, StdState},
|
||||||
|
@ -58,8 +58,8 @@ serdeany_autoreg = ["libafl_bolts/serdeany_autoreg"]
|
|||||||
|
|
||||||
slirp = [ "systemmode", "libafl_qemu_sys/slirp" ] # build qemu with host libslirp (for user networking)
|
slirp = [ "systemmode", "libafl_qemu_sys/slirp" ] # build qemu with host libslirp (for user networking)
|
||||||
|
|
||||||
# disabled atm, enabled when fixed with dynamic list
|
# Requires the binary's build.rs to call `build_libafl_qemu`
|
||||||
# shared = [ "libafl_qemu_sys/shared" ]
|
shared = [ "libafl_qemu_sys/shared" ]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libafl = { path = "../libafl", version = "0.12.0", default-features = false, features = ["std", "derive", "regex"] }
|
libafl = { path = "../libafl", version = "0.12.0", default-features = false, features = ["std", "derive", "regex"] }
|
||||||
|
@ -6,9 +6,11 @@ use std::{
|
|||||||
|
|
||||||
use which::which;
|
use which::which;
|
||||||
|
|
||||||
|
use crate::cargo_add_rpath;
|
||||||
|
|
||||||
const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
|
const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
|
||||||
const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
|
const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
|
||||||
const QEMU_REVISION: &str = "50b0c90e0aab07643ccb58cfbbef742bcfb8b7d1";
|
const QEMU_REVISION: &str = "c9519ee8b6cb1ba54b7df1001f7f39f07218d514";
|
||||||
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
#[allow(clippy::module_name_repetitions)]
|
||||||
pub struct BuildResult {
|
pub struct BuildResult {
|
||||||
@ -22,138 +24,55 @@ fn build_dep_check(tools: &[&str]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines, clippy::missing_panics_doc)]
|
fn get_config_signature(config_cmd: &Command) -> String {
|
||||||
#[must_use]
|
let mut signature_string = String::new();
|
||||||
pub fn build(
|
|
||||||
cpu_target: &str,
|
// Command env
|
||||||
is_big_endian: bool,
|
let config_env: String = config_cmd
|
||||||
|
.get_envs()
|
||||||
|
.map(|(key, value)| {
|
||||||
|
format!(
|
||||||
|
"\t{}={}",
|
||||||
|
key.to_str().expect("Couldn't convert OsStr to str"),
|
||||||
|
if let Some(v) = value {
|
||||||
|
v.to_str().expect("Could't convert OsStr to str")
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.reduce(|acc, elt| format!("{acc}\n{elt}"))
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
signature_string += format!("Environment:\n{config_env}").as_str();
|
||||||
|
|
||||||
|
// Command args
|
||||||
|
let config_args: String = config_cmd
|
||||||
|
.get_args()
|
||||||
|
.map(|arg| format!("\t{}", arg.to_str().expect("Couldn't convert OsStr to str")))
|
||||||
|
.reduce(|acc, arg| format!("{acc}\n{arg}"))
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
signature_string += format!("\n\nArguments:\n{config_args}").as_str();
|
||||||
|
|
||||||
|
signature_string
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
fn configure_qemu(
|
||||||
|
cc_compiler: &cc::Tool,
|
||||||
|
cpp_compiler: &cc::Tool,
|
||||||
|
qemu_path: &PathBuf,
|
||||||
|
build_dir: &Path,
|
||||||
is_usermode: bool,
|
is_usermode: bool,
|
||||||
jobs: Option<u32>,
|
cpu_target: &String,
|
||||||
) -> BuildResult {
|
target_suffix: &String,
|
||||||
let mut cpu_target = cpu_target.to_string();
|
) -> Command {
|
||||||
// qemu-system-arm supports both big and little endian configurations and so
|
|
||||||
// therefore the "be" feature should ignored in this configuration. Also
|
|
||||||
// ignore the feature if we are running in clippy which enables all the
|
|
||||||
// features at once (disabling the check for mutually exclusive options)
|
|
||||||
// resulting in cpu_target being set to 'x86_64' above which obviously
|
|
||||||
// doesn't support BE.
|
|
||||||
if is_big_endian && cpu_target == "arm" && is_usermode && !cfg!(feature = "clippy") {
|
|
||||||
// We have told rustc which CPU target to use above (it doesn't need
|
|
||||||
// to make any changes for endianness), however, we need QEMU to be
|
|
||||||
// built for the right endian-ness, so we update the cpu_target for
|
|
||||||
// here on down
|
|
||||||
cpu_target += "eb";
|
|
||||||
}
|
|
||||||
|
|
||||||
if !is_big_endian && cpu_target == "mips" && !cfg!(feature = "clippy") {
|
|
||||||
cpu_target += "el";
|
|
||||||
}
|
|
||||||
|
|
||||||
let custom_qemu_dir = env::var_os("CUSTOM_QEMU_DIR").map(|x| x.to_string_lossy().to_string());
|
|
||||||
let custom_qemu_no_build = env::var("CUSTOM_QEMU_NO_BUILD").is_ok();
|
|
||||||
let custom_qemu_no_configure = env::var("CUSTOM_QEMU_NO_CONFIGURE").is_ok();
|
|
||||||
println!("cargo:rerun-if-env-changed=CUSTOM_QEMU_DIR");
|
|
||||||
println!("cargo:rerun-if-env-changed=CUSTOM_QEMU_NO_BUILD");
|
|
||||||
println!("cargo:rerun-if-env-changed=CUSTOM_QEMU_NO_CONFIGURE");
|
|
||||||
|
|
||||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
|
||||||
let out_dir = out_dir.to_string_lossy().to_string();
|
|
||||||
let out_dir_path = Path::new(&out_dir);
|
|
||||||
let mut target_dir = out_dir_path.to_path_buf();
|
|
||||||
target_dir.pop();
|
|
||||||
target_dir.pop();
|
|
||||||
target_dir.pop();
|
|
||||||
|
|
||||||
build_dep_check(&["git", "make"]);
|
|
||||||
|
|
||||||
let cc_compiler = cc::Build::new().cpp(false).get_compiler();
|
|
||||||
let cpp_compiler = cc::Build::new().cpp(true).get_compiler();
|
|
||||||
|
|
||||||
let qemu_path = if let Some(qemu_dir) = custom_qemu_dir.as_ref() {
|
|
||||||
Path::new(&qemu_dir).to_path_buf()
|
|
||||||
} else {
|
|
||||||
let qemu_path = target_dir.join(QEMU_DIRNAME);
|
|
||||||
|
|
||||||
let qemu_rev = target_dir.join("QEMU_REVISION");
|
|
||||||
if qemu_rev.exists()
|
|
||||||
&& fs::read_to_string(&qemu_rev).expect("Failed to read QEMU_REVISION") != QEMU_REVISION
|
|
||||||
{
|
|
||||||
drop(fs::remove_dir_all(&qemu_path));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !qemu_path.is_dir() {
|
|
||||||
println!("cargo:warning=Qemu not found, cloning with git ({QEMU_REVISION})...");
|
|
||||||
fs::create_dir_all(&qemu_path).unwrap();
|
|
||||||
assert!(Command::new("git")
|
|
||||||
.current_dir(&qemu_path)
|
|
||||||
.arg("init")
|
|
||||||
.status()
|
|
||||||
.unwrap()
|
|
||||||
.success());
|
|
||||||
assert!(Command::new("git")
|
|
||||||
.current_dir(&qemu_path)
|
|
||||||
.arg("remote")
|
|
||||||
.arg("add")
|
|
||||||
.arg("origin")
|
|
||||||
.arg(QEMU_URL)
|
|
||||||
.status()
|
|
||||||
.unwrap()
|
|
||||||
.success());
|
|
||||||
assert!(Command::new("git")
|
|
||||||
.current_dir(&qemu_path)
|
|
||||||
.arg("fetch")
|
|
||||||
.arg("--depth")
|
|
||||||
.arg("1")
|
|
||||||
.arg("origin")
|
|
||||||
.arg(QEMU_REVISION)
|
|
||||||
.status()
|
|
||||||
.unwrap()
|
|
||||||
.success());
|
|
||||||
assert!(Command::new("git")
|
|
||||||
.current_dir(&qemu_path)
|
|
||||||
.arg("checkout")
|
|
||||||
.arg("FETCH_HEAD")
|
|
||||||
.status()
|
|
||||||
.unwrap()
|
|
||||||
.success());
|
|
||||||
fs::write(&qemu_rev, QEMU_REVISION).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_path
|
|
||||||
};
|
|
||||||
|
|
||||||
let build_dir = qemu_path.join("build");
|
|
||||||
|
|
||||||
let target_suffix = if is_usermode {
|
|
||||||
"linux-user".to_string()
|
|
||||||
} else {
|
|
||||||
"softmmu".to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
let (output_lib, output_lib_link) = if is_usermode {
|
|
||||||
(
|
|
||||||
build_dir.join(format!("libqemu-{cpu_target}.so")),
|
|
||||||
format!("qemu-{cpu_target}"),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
build_dir.join(format!("libqemu-system-{cpu_target}.so")),
|
|
||||||
format!("qemu-system-{cpu_target}"),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
// println!("cargo:rerun-if-changed={}", output_lib.to_string_lossy());
|
|
||||||
|
|
||||||
if !output_lib.is_file() || (custom_qemu_dir.is_some() && !custom_qemu_no_build) {
|
|
||||||
/*drop(
|
|
||||||
Command::new("make")
|
|
||||||
.current_dir(&qemu_path)
|
|
||||||
.arg("distclean")
|
|
||||||
.status(),
|
|
||||||
);*/
|
|
||||||
let mut cmd = Command::new("./configure");
|
let mut cmd = Command::new("./configure");
|
||||||
cmd.current_dir(&qemu_path)
|
|
||||||
//.arg("--as-static-lib")
|
// Set common options for usermode and systemmode
|
||||||
|
cmd.current_dir(qemu_path)
|
||||||
|
.env("__LIBAFL_QEMU_CONFIGURE", "")
|
||||||
.env("__LIBAFL_QEMU_BUILD_OUT", build_dir.join("linkinfo.json"))
|
.env("__LIBAFL_QEMU_BUILD_OUT", build_dir.join("linkinfo.json"))
|
||||||
.env("__LIBAFL_QEMU_BUILD_CC", cc_compiler.path())
|
.env("__LIBAFL_QEMU_BUILD_CC", cc_compiler.path())
|
||||||
.env("__LIBAFL_QEMU_BUILD_CXX", cpp_compiler.path())
|
.env("__LIBAFL_QEMU_BUILD_CXX", cpp_compiler.path())
|
||||||
@ -166,18 +85,20 @@ pub fn build(
|
|||||||
qemu_path.join("linker_interceptor++.py").display()
|
qemu_path.join("linker_interceptor++.py").display()
|
||||||
))
|
))
|
||||||
.arg("--as-shared-lib")
|
.arg("--as-shared-lib")
|
||||||
.arg(&format!("--target-list={cpu_target}-{target_suffix}"));
|
.arg(&format!("--target-list={cpu_target}-{target_suffix}"))
|
||||||
if cfg!(feature = "debug_assertions") {
|
.arg("--disable-bsd-user")
|
||||||
cmd.arg("--enable-debug");
|
.arg("--disable-capstone");
|
||||||
|
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
// cmd.arg("--enable-debug");
|
||||||
|
// .arg("--enable-debug-tcg");
|
||||||
}
|
}
|
||||||
if is_usermode && !custom_qemu_no_configure {
|
|
||||||
cmd.args([
|
if is_usermode {
|
||||||
"--disable-bsd-user",
|
// Usermode options
|
||||||
"--disable-fdt",
|
cmd.args(["--disable-fdt", "--disable-system"]);
|
||||||
"--disable-system",
|
} else {
|
||||||
"--disable-capstone",
|
// Systemmode options
|
||||||
]);
|
|
||||||
} else if !custom_qemu_no_configure {
|
|
||||||
cmd.arg(if cfg!(feature = "slirp") {
|
cmd.arg(if cfg!(feature = "slirp") {
|
||||||
"--enable-slirp"
|
"--enable-slirp"
|
||||||
} else {
|
} else {
|
||||||
@ -193,9 +114,7 @@ pub fn build(
|
|||||||
.arg("--disable-bochs")
|
.arg("--disable-bochs")
|
||||||
.arg("--disable-bpf")
|
.arg("--disable-bpf")
|
||||||
.arg("--disable-brlapi")
|
.arg("--disable-brlapi")
|
||||||
.arg("--disable-bsd-user")
|
|
||||||
.arg("--disable-bzip2")
|
.arg("--disable-bzip2")
|
||||||
.arg("--disable-capstone")
|
|
||||||
.arg("--disable-cap-ng")
|
.arg("--disable-cap-ng")
|
||||||
.arg("--disable-canokey")
|
.arg("--disable-canokey")
|
||||||
.arg("--disable-cloop")
|
.arg("--disable-cloop")
|
||||||
@ -294,12 +213,19 @@ pub fn build(
|
|||||||
.arg("--disable-tests");
|
.arg("--disable-tests");
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(
|
cmd
|
||||||
cmd.status().expect("Invoking Configure failed").success(),
|
}
|
||||||
"Configure didn't finish successfully"
|
|
||||||
);
|
fn build_qemu(
|
||||||
|
cc_compiler: &cc::Tool,
|
||||||
|
cpp_compiler: &cc::Tool,
|
||||||
|
build_dir: &PathBuf,
|
||||||
|
jobs: Option<u32>,
|
||||||
|
) -> Command {
|
||||||
let mut cmd = Command::new("make");
|
let mut cmd = Command::new("make");
|
||||||
cmd.current_dir(&build_dir)
|
|
||||||
|
cmd.current_dir(build_dir)
|
||||||
|
.env("__LIBAFL_QEMU_CONFIGURE", "")
|
||||||
.env("__LIBAFL_QEMU_BUILD_OUT", build_dir.join("linkinfo.json"))
|
.env("__LIBAFL_QEMU_BUILD_OUT", build_dir.join("linkinfo.json"))
|
||||||
.env("__LIBAFL_QEMU_BUILD_CC", cc_compiler.path())
|
.env("__LIBAFL_QEMU_BUILD_CC", cc_compiler.path())
|
||||||
.env("__LIBAFL_QEMU_BUILD_CXX", cpp_compiler.path())
|
.env("__LIBAFL_QEMU_BUILD_CXX", cpp_compiler.path())
|
||||||
@ -308,12 +234,187 @@ pub fn build(
|
|||||||
if let Some(j) = jobs {
|
if let Some(j) = jobs {
|
||||||
cmd.arg(&format!("{j}")).env("V", "1");
|
cmd.arg(&format!("{j}")).env("V", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines, clippy::missing_panics_doc)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn build(
|
||||||
|
cpu_target: &str,
|
||||||
|
is_big_endian: bool,
|
||||||
|
is_usermode: bool,
|
||||||
|
jobs: Option<u32>,
|
||||||
|
) -> BuildResult {
|
||||||
|
let mut cpu_target = cpu_target.to_string();
|
||||||
|
// qemu-system-arm supports both big and little endian configurations and so
|
||||||
|
// the "be" feature should be ignored in this configuration. Also
|
||||||
|
// ignore the feature if we are running in clippy which enables all the
|
||||||
|
// features at once (disabling the check for mutually exclusive options)
|
||||||
|
// resulting in cpu_target being set to 'x86_64' above which obviously
|
||||||
|
// doesn't support BE.
|
||||||
|
if is_big_endian && cpu_target == "arm" && is_usermode && !cfg!(feature = "clippy") {
|
||||||
|
// We have told rustc which CPU target to use above (it doesn't need
|
||||||
|
// to make any changes for endianness), however, we need QEMU to be
|
||||||
|
// built for the right endian-ness, so we update the cpu_target for
|
||||||
|
// here on down
|
||||||
|
cpu_target += "eb";
|
||||||
|
}
|
||||||
|
|
||||||
|
if !is_big_endian && cpu_target == "mips" && !cfg!(feature = "clippy") {
|
||||||
|
cpu_target += "el";
|
||||||
|
}
|
||||||
|
|
||||||
|
let libafl_qemu_dir = env::var_os("LIBAFL_QEMU_DIR").map(|x| x.to_string_lossy().to_string());
|
||||||
|
let libafl_qemu_force_configure = env::var("LIBAFL_QEMU_FORCE_CONFIGURE").is_ok();
|
||||||
|
let libafl_qemu_no_build = env::var("LIBAFL_QEMU_NO_BUILD").is_ok();
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-env-changed=LIBAFL_QEMU_DIR");
|
||||||
|
println!("cargo:rerun-if-env-changed=LIBAFL_QEMU_FORCE_CONFIGURE");
|
||||||
|
println!("cargo:rerun-if-env-changed=LIBAFL_QEMU_NO_BUILD");
|
||||||
|
|
||||||
|
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||||
|
let out_dir = out_dir.to_string_lossy().to_string();
|
||||||
|
let out_dir_path = Path::new(&out_dir);
|
||||||
|
let mut target_dir = out_dir_path.to_path_buf();
|
||||||
|
target_dir.pop();
|
||||||
|
target_dir.pop();
|
||||||
|
target_dir.pop();
|
||||||
|
|
||||||
|
build_dep_check(&["git", "make"]);
|
||||||
|
|
||||||
|
let cc_compiler = cc::Build::new().cpp(false).get_compiler();
|
||||||
|
let cpp_compiler = cc::Build::new().cpp(true).get_compiler();
|
||||||
|
|
||||||
|
let qemu_path = if let Some(qemu_dir) = libafl_qemu_dir.as_ref() {
|
||||||
|
Path::new(&qemu_dir).to_path_buf()
|
||||||
|
} else {
|
||||||
|
let qemu_path = target_dir.join(QEMU_DIRNAME);
|
||||||
|
|
||||||
|
let qemu_rev = target_dir.join("QEMU_REVISION");
|
||||||
|
if qemu_rev.exists()
|
||||||
|
&& fs::read_to_string(&qemu_rev).expect("Failed to read QEMU_REVISION") != QEMU_REVISION
|
||||||
|
{
|
||||||
|
drop(fs::remove_dir_all(&qemu_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !qemu_path.is_dir() {
|
||||||
|
println!("cargo:warning=Qemu not found, cloning with git ({QEMU_REVISION})...");
|
||||||
|
fs::create_dir_all(&qemu_path).unwrap();
|
||||||
|
assert!(Command::new("git")
|
||||||
|
.current_dir(&qemu_path)
|
||||||
|
.arg("init")
|
||||||
|
.status()
|
||||||
|
.unwrap()
|
||||||
|
.success());
|
||||||
|
assert!(Command::new("git")
|
||||||
|
.current_dir(&qemu_path)
|
||||||
|
.arg("remote")
|
||||||
|
.arg("add")
|
||||||
|
.arg("origin")
|
||||||
|
.arg(QEMU_URL)
|
||||||
|
.status()
|
||||||
|
.unwrap()
|
||||||
|
.success());
|
||||||
|
assert!(Command::new("git")
|
||||||
|
.current_dir(&qemu_path)
|
||||||
|
.arg("fetch")
|
||||||
|
.arg("--depth")
|
||||||
|
.arg("1")
|
||||||
|
.arg("origin")
|
||||||
|
.arg(QEMU_REVISION)
|
||||||
|
.status()
|
||||||
|
.unwrap()
|
||||||
|
.success());
|
||||||
|
assert!(Command::new("git")
|
||||||
|
.current_dir(&qemu_path)
|
||||||
|
.arg("checkout")
|
||||||
|
.arg("FETCH_HEAD")
|
||||||
|
.status()
|
||||||
|
.unwrap()
|
||||||
|
.success());
|
||||||
|
fs::write(&qemu_rev, QEMU_REVISION).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
qemu_path
|
||||||
|
};
|
||||||
|
|
||||||
|
let qemu_build_dir = qemu_path.join("build");
|
||||||
|
let config_signature_path = qemu_build_dir.join("libafl_config");
|
||||||
|
|
||||||
|
let target_suffix = if is_usermode {
|
||||||
|
"linux-user".to_string()
|
||||||
|
} else {
|
||||||
|
"softmmu".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (output_lib, output_lib_link) = if is_usermode {
|
||||||
|
(
|
||||||
|
qemu_build_dir.join(format!("libqemu-{cpu_target}.so")),
|
||||||
|
format!("qemu-{cpu_target}"),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
qemu_build_dir.join(format!("libqemu-system-{cpu_target}.so")),
|
||||||
|
format!("qemu-system-{cpu_target}"),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let libafl_config_old_signature = fs::read_to_string(&config_signature_path);
|
||||||
|
|
||||||
|
let mut config_cmd = configure_qemu(
|
||||||
|
&cc_compiler,
|
||||||
|
&cpp_compiler,
|
||||||
|
&qemu_path,
|
||||||
|
&qemu_build_dir,
|
||||||
|
is_usermode,
|
||||||
|
&cpu_target,
|
||||||
|
&target_suffix,
|
||||||
|
);
|
||||||
|
|
||||||
|
let current_config_signature = get_config_signature(&config_cmd);
|
||||||
|
let must_reconfigure = if libafl_qemu_force_configure {
|
||||||
|
// If the user asked to reconfigure, do so
|
||||||
|
true
|
||||||
|
} else if let Ok(libafl_config_old_signature) = libafl_config_old_signature {
|
||||||
|
if libafl_config_old_signature == current_config_signature {
|
||||||
|
// Signature match, do not reconfigure
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
println!("cargo:warning=QEMU configuration is outdated. Reconfiguring...");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// In worst scenario, reconfigure
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
if must_reconfigure {
|
||||||
assert!(
|
assert!(
|
||||||
cmd.status().expect("Invoking Make Failed").success(),
|
config_cmd
|
||||||
|
.status()
|
||||||
|
.expect("Invoking Configure failed")
|
||||||
|
.success(),
|
||||||
|
"Configure didn't finish successfully"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Config succeeded at this point, (over)write the signature file
|
||||||
|
fs::write(config_signature_path, current_config_signature)
|
||||||
|
.expect("Couldn't write config signature file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always build by default, make will detect if it is necessary to rebuild qemu
|
||||||
|
if !libafl_qemu_no_build {
|
||||||
|
let mut build_cmd = build_qemu(&cc_compiler, &cpp_compiler, &qemu_build_dir, jobs);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
build_cmd.status().expect("Invoking Make Failed").success(),
|
||||||
"Make didn't finish successfully"
|
"Make didn't finish successfully"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert!(output_lib.is_file()); // Sanity check
|
||||||
|
|
||||||
/*
|
/*
|
||||||
let mut objects = vec![];
|
let mut objects = vec![];
|
||||||
for dir in &[
|
for dir in &[
|
||||||
@ -338,13 +439,12 @@ pub fn build(
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
if cfg!(feature = "shared") {
|
if cfg!(feature = "shared") {
|
||||||
println!(
|
let qemu_build_dir_str = qemu_build_dir.to_str().expect("Could not convert to str");
|
||||||
"cargo:rustc-link-search=native={}",
|
println!("cargo:rustc-link-search=native={qemu_build_dir_str}");
|
||||||
build_dir.to_string_lossy()
|
|
||||||
);
|
|
||||||
println!("cargo:rustc-link-lib=dylib={output_lib_link}");
|
println!("cargo:rustc-link-lib=dylib={output_lib_link}");
|
||||||
|
cargo_add_rpath(qemu_build_dir_str);
|
||||||
} else {
|
} else {
|
||||||
let compile_commands_string = &fs::read_to_string(build_dir.join("linkinfo.json"))
|
let compile_commands_string = &fs::read_to_string(qemu_build_dir.join("linkinfo.json"))
|
||||||
.expect("Failed to read linkinfo.json");
|
.expect("Failed to read linkinfo.json");
|
||||||
|
|
||||||
let linkinfo = json::parse(compile_commands_string).expect("Failed to parse linkinfo.json");
|
let linkinfo = json::parse(compile_commands_string).expect("Failed to parse linkinfo.json");
|
||||||
@ -357,16 +457,27 @@ pub fn build(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(cpp_compiler
|
let mut link_command = cpp_compiler.to_command();
|
||||||
.to_command()
|
|
||||||
.current_dir(&build_dir)
|
link_command
|
||||||
|
.current_dir(&qemu_build_dir)
|
||||||
.arg("-o")
|
.arg("-o")
|
||||||
.arg("libqemu-partially-linked.o")
|
.arg("libqemu-partially-linked.o")
|
||||||
.arg("-r")
|
.arg("-r")
|
||||||
.args(cmd)
|
.args(cmd);
|
||||||
.status()
|
|
||||||
.expect("Partial linked failure")
|
let link_str = format!("{link_command:?}");
|
||||||
.success());
|
|
||||||
|
let output = link_command.output().expect("Partial linked failure");
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
fs::write(qemu_build_dir.join("link.command"), link_str).expect("Link command failed.");
|
||||||
|
fs::write(qemu_build_dir.join("link.stdout"), &output.stdout)
|
||||||
|
.expect("Link stdout failed.");
|
||||||
|
fs::write(qemu_build_dir.join("link.stderr"), &output.stderr)
|
||||||
|
.expect("Link stderr failed.");
|
||||||
|
panic!("Linking failed.");
|
||||||
|
}
|
||||||
|
|
||||||
/* // Old manual linking, kept here for reference and debugging
|
/* // Old manual linking, kept here for reference and debugging
|
||||||
if is_usermode {
|
if is_usermode {
|
||||||
@ -456,7 +567,7 @@ pub fn build(
|
|||||||
.current_dir(out_dir_path)
|
.current_dir(out_dir_path)
|
||||||
.arg("crs")
|
.arg("crs")
|
||||||
.arg("libqemu-partially-linked.a")
|
.arg("libqemu-partially-linked.a")
|
||||||
.arg(build_dir.join("libqemu-partially-linked.o"))
|
.arg(qemu_build_dir.join("libqemu-partially-linked.o"))
|
||||||
.status()
|
.status()
|
||||||
.expect("Ar creation");
|
.expect("Ar creation");
|
||||||
|
|
||||||
@ -476,6 +587,21 @@ pub fn build(
|
|||||||
.expect("linkinfo.json `libs` values must be strings");
|
.expect("linkinfo.json `libs` values must be strings");
|
||||||
println!("cargo:rustc-link-lib={val}");
|
println!("cargo:rustc-link-lib={val}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for arg in linkinfo["rpath"].members() {
|
||||||
|
let val = arg
|
||||||
|
.as_str()
|
||||||
|
.expect("linkinfo.json `libs` values must be strings")
|
||||||
|
.to_string()
|
||||||
|
.replace(
|
||||||
|
"$ORIGIN",
|
||||||
|
qemu_build_dir
|
||||||
|
.as_os_str()
|
||||||
|
.to_str()
|
||||||
|
.expect("Could not convert OsStr to str"),
|
||||||
|
);
|
||||||
|
cargo_add_rpath(&val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -497,7 +623,7 @@ pub fn build(
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
fs::create_dir_all(target_dir.join("pc-bios")).unwrap();
|
fs::create_dir_all(target_dir.join("pc-bios")).unwrap();
|
||||||
for path in fs::read_dir(build_dir.join("pc-bios")).unwrap() {
|
for path in fs::read_dir(qemu_build_dir.join("pc-bios")).unwrap() {
|
||||||
let path = path.unwrap().path();
|
let path = path.unwrap().path();
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
if let Some(name) = path.file_name() {
|
if let Some(name) = path.file_name() {
|
||||||
@ -510,6 +636,6 @@ pub fn build(
|
|||||||
|
|
||||||
BuildResult {
|
BuildResult {
|
||||||
qemu_path,
|
qemu_path,
|
||||||
build_dir,
|
build_dir: qemu_build_dir,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ use std::{
|
|||||||
env, fs,
|
env, fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::Command,
|
process::Command,
|
||||||
|
ptr::addr_of_mut,
|
||||||
};
|
};
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
@ -15,6 +16,43 @@ pub use build::build;
|
|||||||
|
|
||||||
const LLVM_VERSION_MAX: i32 = 33;
|
const LLVM_VERSION_MAX: i32 = 33;
|
||||||
|
|
||||||
|
static mut CARGO_RPATH: Option<Vec<String>> = None;
|
||||||
|
static CARGO_RPATH_SEPARATOR: &str = "|";
|
||||||
|
|
||||||
|
pub fn cargo_add_rpath(rpath: &str) {
|
||||||
|
unsafe {
|
||||||
|
if let Some(rpaths) = &mut *addr_of_mut!(CARGO_RPATH) {
|
||||||
|
rpaths.push(rpath.to_string());
|
||||||
|
} else {
|
||||||
|
CARGO_RPATH = Some(vec![rpath.to_string()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cargo_propagate_rpath() {
|
||||||
|
unsafe {
|
||||||
|
if let Some(cargo_cmds) = &mut *addr_of_mut!(CARGO_RPATH) {
|
||||||
|
let rpath = cargo_cmds.join(CARGO_RPATH_SEPARATOR);
|
||||||
|
println!("cargo:rpath={rpath}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Must be called from final binary crates
|
||||||
|
pub fn build_libafl_qemu() {
|
||||||
|
// Add rpath if there are some
|
||||||
|
if let Some(rpaths) = env::var_os("DEP_QEMU_RPATH") {
|
||||||
|
let rpaths: Vec<&str> = rpaths
|
||||||
|
.to_str()
|
||||||
|
.expect("Cannot convert OsString to str")
|
||||||
|
.split(CARGO_RPATH_SEPARATOR)
|
||||||
|
.collect();
|
||||||
|
for rpath in rpaths {
|
||||||
|
println!("cargo:rustc-link-arg-bins=-Wl,-rpath,{rpath}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build_with_bindings(
|
pub fn build_with_bindings(
|
||||||
cpu_target: &str,
|
cpu_target: &str,
|
||||||
is_big_endian: bool,
|
is_big_endian: bool,
|
||||||
@ -42,6 +80,8 @@ pub fn build_with_bindings(
|
|||||||
let re = Regex::new("(Option<\\s*)unsafe( extern \"C\" fn\\(data: u64)").unwrap();
|
let re = Regex::new("(Option<\\s*)unsafe( extern \"C\" fn\\(data: u64)").unwrap();
|
||||||
let replaced = re.replace_all(&contents, "$1$2");
|
let replaced = re.replace_all(&contents, "$1$2");
|
||||||
fs::write(bindings_file, replaced.as_bytes()).expect("Unable to write file");
|
fs::write(bindings_file, replaced.as_bytes()).expect("Unable to write file");
|
||||||
|
|
||||||
|
cargo_propagate_rpath();
|
||||||
}
|
}
|
||||||
|
|
||||||
// For bindgen, the llvm version must be >= of the rust llvm version
|
// For bindgen, the llvm version must be >= of the rust llvm version
|
||||||
|
@ -10,6 +10,7 @@ license = "MIT OR Apache-2.0"
|
|||||||
keywords = ["fuzzing", "qemu", "instrumentation"]
|
keywords = ["fuzzing", "qemu", "instrumentation"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
categories = ["development-tools::testing", "emulators", "embedded", "os", "no-std"]
|
categories = ["development-tools::testing", "emulators", "embedded", "os", "no-std"]
|
||||||
|
links = "qemu"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["x86_64", "usermode"]
|
features = ["x86_64", "usermode"]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user