libafl_qemu 0.7.0

QEMU user backend library for LibAFL
use std::{env, fs, path::Path, process::Command};
use which::which;

const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge";
const QEMU_DIRNAME: &str = "qemu-libafl-bridge";
const QEMU_REVISION: &str = "e97deaae59c1825823037c2d549f8697a05d157c";

fn build_dep_check(tools: &[&str]) {
    for tool in tools {
        which(tool).unwrap_or_else(|_| panic!("Build tool {} not found", tool));
    }
}

#[macro_export]
macro_rules! assert_unique_feature {
    () => {};
    ($first:tt $(,$rest:tt)*) => {
        $(
            #[cfg(not(feature = "clippy"))] // ignore multiple definition for clippy
            #[cfg(all(feature = $first, feature = $rest))]
            compile_error!(concat!("features \"", $first, "\" and \"", $rest, "\" cannot be used together"));
        )*
        assert_unique_feature!($($rest),*);
    }
}

#[allow(clippy::too_many_lines)]
fn main() {
    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed=src/asan-giovese.c");
    println!("cargo:rerun-if-changed=src/asan-giovese.h");
    println!("cargo:rerun-if-env-changed=CROSS_CC");

    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
    if target_os != "linux" {
        return;
    }

    // Make sure we have at least and at most one architecutre feature set
    // Else, we default to `x86_64` - having a default makes CI easier :)
    assert_unique_feature!("arm", "aarch64", "i386", "i86_64");
    #[cfg(not(any(
        feature = "arm",
        feature = "aarch64",
        feature = "i386",
        feature = "x86_64"
    )))]
    println!(
        "cargo:warning=No architecture feature enabled for libafl_qemu, supported: arm, aarch64, i386, x86_64 - defaulting to x86_64"
    );

    let cpu_target = if cfg!(feature = "clippy") {
        // assume x86_64 for clippy
        "x86_64"
    } else if cfg!(feature = "arm") {
        "arm"
    } else if cfg!(feature = "aarch64") {
        "aarch64"
    } else if cfg!(feature = "i386") {
        "368"
    } else {
        // if cfg!(feature = "x86_64") {
        "x86_64"
        /*} else {
        panic!("No architecture feture enabled for libafl_qemu");
        */
    };

    let jobs = env::var("CARGO_BUILD_JOBS");

    let cross_cc = env::var("CROSS_CC").unwrap_or_else(|_| {
        println!("cargo:warning=CROSS_CC is not set, default to cc (things can go wrong if the selected cpu target ({}) is not the host arch ({}))", cpu_target, env::consts::ARCH);
        "cc".to_owned()
    });

    println!("cargo:rustc-cfg=cpu_target=\"{}\"", cpu_target);

    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();
    let qasan_dir = Path::new("libqasan");
    let qasan_dir = fs::canonicalize(&qasan_dir).unwrap();
    let src_dir = Path::new("src");
    //let cwd = env::current_dir().unwrap().to_string_lossy().to_string();

    println!("cargo:rerun-if-changed={}/libqasan.so", qasan_dir.display());
    println!(
        "cargo:rerun-if-changed={}/libqasan.so",
        target_dir.display()
    );

    build_dep_check(&["git", "make"]);

    let qemu_rev = out_dir_path.join("QEMU_REVISION");
    let qemu_path = out_dir_path.join(QEMU_DIRNAME);

    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
        );
        Command::new("git")
            .current_dir(&out_dir_path)
            .arg("clone")
            .arg(QEMU_URL)
            .status()
            .unwrap();
        Command::new("git")
            .current_dir(&qemu_path)
            .arg("checkout")
            .arg(QEMU_REVISION)
            .status()
            .unwrap();
        fs::write(&qemu_rev, QEMU_REVISION).unwrap();
    }

    let build_dir = qemu_path.join("build");
    let output_lib = build_dir.join(&format!("libqemu-{}.so", cpu_target));
    if !output_lib.is_file() {
        drop(
            Command::new("make")
                .current_dir(&qemu_path)
                .arg("distclean")
                .status(),
        );
        Command::new("./configure")
            .current_dir(&qemu_path)
            //.arg("--as-static-lib")
            .arg("--as-shared-lib")
            .arg(&format!("--target-list={}-linux-user", cpu_target))
            .args(&[
                "--audio-drv-list=",
                "--disable-blobs",
                "--disable-bochs",
                "--disable-brlapi",
                "--disable-bsd-user",
                "--disable-bzip2",
                "--disable-cap-ng",
                "--disable-cloop",
                "--disable-curl",
                "--disable-curses",
                "--disable-dmg",
                "--disable-fdt",
                "--disable-gcrypt",
                "--disable-glusterfs",
                "--disable-gnutls",
                "--disable-gtk",
                "--disable-guest-agent",
                "--disable-iconv",
                "--disable-libiscsi",
                "--disable-libnfs",
                "--disable-libssh",
                "--disable-libusb",
                "--disable-linux-aio",
                "--disable-live-block-migration",
                "--disable-lzo",
                "--disable-nettle",
                "--disable-numa",
                "--disable-opengl",
                "--disable-parallels",
                "--disable-plugins",
                "--disable-qcow1",
                "--disable-qed",
                "--disable-rbd",
                "--disable-rdma",
                "--disable-replication",
                "--disable-sdl",
                "--disable-seccomp",
                "--disable-smartcard",
                "--disable-snappy",
                "--disable-spice",
                "--disable-system",
                "--disable-tools",
                "--disable-tpm",
                "--disable-usb-redir",
                "--disable-vde",
                "--disable-vdi",
                "--disable-vhost-crypto",
                "--disable-vhost-kernel",
                "--disable-vhost-net",
                "--disable-vhost-scsi",
                "--disable-vhost-user",
                "--disable-vhost-vdpa",
                "--disable-vhost-vsock",
                "--disable-virglrenderer",
                "--disable-virtfs",
                "--disable-vnc",
                "--disable-vnc-jpeg",
                "--disable-vnc-png",
                "--disable-vnc-sasl",
                "--disable-vte",
                "--disable-vvfat",
                "--disable-xen",
                "--disable-xen-pci-passthrough",
                "--disable-xfsctl",
            ])
            .status()
            .expect("Configure failed");
        if let Ok(j) = jobs {
            Command::new("make")
                .current_dir(&qemu_path)
                .arg("-j")
                .arg(&j)
                .status()
                .expect("Make failed");
        } else {
            Command::new("make")
                .current_dir(&qemu_path)
                .arg("-j")
                .status()
                .expect("Make failed");
        }
        //let _ = remove_file(build_dir.join(&format!("libqemu-{}.so", cpu_target)));
    }

    #[cfg(feature = "python")]
    {
        let mut objects = vec![];
        for dir in &[
            build_dir.join("libcommon.fa.p"),
            build_dir.join(&format!("libqemu-{}-linux-user.fa.p", cpu_target)),
            //build_dir.join("libqemuutil.a.p"),
            //build_dir.join("libqom.fa.p"),
            //build_dir.join("libhwcore.fa.p"),
            //build_dir.join("libcapstone.a.p"),
        ] {
            for path in fs::read_dir(dir).unwrap() {
                let path = path.unwrap().path();
                if path.is_file() {
                    if let Some(name) = path.file_name() {
                        if name.to_string_lossy().starts_with("stubs") {
                            continue;
                        } else if let Some(ext) = path.extension() {
                            if ext == "o" {
                                objects.push(path);
                            }
                        }
                    }
                }
            }
        }

        for obj in &objects {
            println!("cargo:rustc-cdylib-link-arg={}", obj.display());
        }

        println!("cargo:rustc-cdylib-link-arg=-Wl,--start-group");

        println!("cargo:rustc-cdylib-link-arg=-Wl,--whole-archive");
        println!(
            "cargo:rustc-cdylib-link-arg={}/libhwcore.fa",
            build_dir.display()
        );
        println!(
            "cargo:rustc-cdylib-link-arg={}/libqom.fa",
            build_dir.display()
        );
        println!("cargo:rustc-cdylib-link-arg=-Wl,--no-whole-archive");
        println!(
            "cargo:rustc-cdylib-link-arg={}/libcapstone.a",
            build_dir.display()
        );
        println!(
            "cargo:rustc-cdylib-link-arg={}/libqemuutil.a",
            build_dir.display()
        );
        println!(
            "cargo:rustc-cdylib-link-arg={}/libhwcore.fa",
            build_dir.display()
        );
        println!(
            "cargo:rustc-cdylib-link-arg={}/libqom.fa",
            build_dir.display()
        );

        println!("cargo:rustc-cdylib-link-arg=-lrt");
        println!("cargo:rustc-cdylib-link-arg=-lutil");
        println!("cargo:rustc-cdylib-link-arg=-lgthread-2.0");
        println!("cargo:rustc-cdylib-link-arg=-lglib-2.0");
        println!("cargo:rustc-cdylib-link-arg=-lstdc++");

        println!("cargo:rustc-cdylib-link-arg=-Wl,--end-group");
    }

    #[cfg(not(feature = "python"))]
    {
        fs::copy(
            build_dir.join(&format!("libqemu-{}.so", cpu_target)),
            target_dir.join(&format!("libqemu-{}.so", cpu_target)),
        )
        .expect("Failed to copy the QEMU shared object");

        println!(
            "cargo:rustc-link-search=native={}",
            &target_dir.to_string_lossy().to_string()
        );
        println!("cargo:rustc-link-lib=qemu-{}", cpu_target);

        println!("cargo:rustc-env=LD_LIBRARY_PATH={}", target_dir.display());
    }

    drop(
        Command::new("make")
            .current_dir(&out_dir_path)
            .env("CC", &cross_cc)
            .env("OUT_DIR", &target_dir)
            .arg("-C")
            .arg(&qasan_dir)
            .arg("clean")
            .status(),
    );
    drop(
        Command::new("make")
            .current_dir(&out_dir_path)
            .env("CC", &cross_cc)
            .env("OUT_DIR", &target_dir)
            .arg("-C")
            .arg(&qasan_dir)
            .status(),
    );

    cc::Build::new()
        .warnings(false)
        .file(src_dir.join("asan-giovese.c"))
        .compile("asan_giovese");
}

/*
    // Build a static library
    let mut objects = vec![];
    for dir in &[
        build_dir.join("libcommon.fa.p"),
        build_dir.join(&format!("libqemu-{}-linux-user.fa.p", cpu_target)),
        build_dir.join("libqemuutil.a.p"),
        build_dir.join("libqom.fa.p"),
        build_dir.join("libhwcore.fa.p"),
        build_dir.join("libcapstone.a.p"),
    ] {
        for path in read_dir(dir).unwrap() {
            let path = path.unwrap().path();
            if path.is_file() {
                if let Some(name) = path.file_name() {
                    if name.to_string_lossy().starts_with("stubs") {
                        continue;
                    }
                    else if let Some(ext) = path.extension() {
                        if ext == "o" {
                            objects.push(path);
                        }
                    }
                }
            }
        }
    }


    Command::new("ar")
        .current_dir(&out_dir_path)
        .arg("crus")
        .arg("libqemu-bridge.a")
        .args(&objects)
        .status()
        .expect("Ar failed");

    println!("cargo:rustc-link-search=native={}", &out_dir);
    println!("cargo:rustc-link-lib=static=qemu-bridge");

    println!("cargo:rustc-link-lib=rt");
    println!("cargo:rustc-link-lib=util");
    println!("cargo:rustc-link-lib=gthread-2.0");
    println!("cargo:rustc-link-lib=glib-2.0");
    println!("cargo:rustc-link-lib=stdc++");

}
*/