The Build Script
To pass arguments to the compilation of a specific dependency, we can use the
cargo rustc subprogram. This lets us pass arguments to rustc, the Rust compiler, as
well as to LLVM, which rustc uses for final code generation and
additional optimization. We'll use std::process::Command, which is similar to using
subprocess.Popen in Python or system in C, although much more flexible.
We'll walk through the cargo rustc arguments that we'll add in addition to the
SanitizerCoverage flags.
-p first-target-solution: Tellrustcto just build the target with these arguments, rather than pass these arguments to all the code we're compiling.--target-dir target/first-target/: Tellrustcto build in a subdirectory of the defaulttargetdirectory, to avoid conflicting with the currently running build process' lock on thetargetdirectory.link-dead-code: Don't prune any unused code from the compilation result.lto=no: Don't do link-time optimization, because it might remove some code or instrumentation.--emit=dep-info,link: Emit linking and dependency information.opt-level={}: We just pass the opt level we are compiling the fuzzer at to the target as well. This allows us to use--release.
use std::{env::var, process::Command}; fn main() { let status = Command::new("cargo") .arg("rustc") .arg("-p") .arg("first-target") .arg("--target-dir") .arg("target/first-target/") .arg("--") .arg("-C") .arg(format!( "opt-level={}", var("OPT_LEVEL").expect("No OPT_LEVEL variable set") )) .arg("-C") .arg("prefer-dynamic") .arg("-C") .arg("passes=sancov-module") .arg("-C") .arg("llvm-args=-sanitizer-coverage-level=3") .arg("-C") .arg("llvm-args=-sanitizer-coverage-inline-8bit-counters") .arg("-C") .arg("llvm-args=-sanitizer-coverage-prune-blocks=0") .arg("-C") .arg("link-dead-code") .arg("-C") .arg("lto=no") .arg("--emit=dep-info,link") .status() .expect("Failed to spawn Cargo"); assert!(status.success(), "Target build command failed");
We'll also use some specially formatted print statements in our build script to instruct
cargo to link with the library. Note the env! macro, which grabs the value of an
environment variable at compile time (and is a compile error if the variable isn't
present). The CARGO_MANIFEST_DIR environment variable is set automatically by cargo
during build script compilation and execution. You can check the full list of these
environment variables
here. You can
also check out the full list of special print messages
here.
#![allow(unused)] fn main() { println!( "cargo:rustc-link-search={}/target/first-target/debug/", env!("CARGO_MANIFEST_DIR") ); println!("cargo:rustc-link-lib=static=first_target"); } }