From c1aafe3e98292cc904bd9e7f82d2b55be33d129f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandru=20Gean=C4=83?= <1569265+abgeana@users.noreply.github.com> Date: Fri, 12 Aug 2022 20:25:59 +0200 Subject: [PATCH] LLVM passes for Windows (#710) * libafl_cc fixes for windows * libafl_cc checks for llvm-config (again) * libafl_cc clang-format * libafl_cc fixes for macos * maintain libafl_cc pass manager selection logic * libafl_cc rustfmt --- libafl_cc/build.rs | 395 ++++++++++++++------------ libafl_cc/src/afl-coverage-pass.cc | 13 +- libafl_cc/src/autotokens-pass.cc | 16 +- libafl_cc/src/clang.rs | 35 ++- libafl_cc/src/cmplog-routines-pass.cc | 6 +- 5 files changed, 269 insertions(+), 196 deletions(-) diff --git a/libafl_cc/build.rs b/libafl_cc/build.rs index 44ef47a9f2..101fe7a4e8 100644 --- a/libafl_cc/build.rs +++ b/libafl_cc/build.rs @@ -4,7 +4,6 @@ use std::{env, fs::File, io::Write, path::Path, process::Command, str}; #[cfg(target_vendor = "apple")] use glob::glob; -#[cfg(not(target_vendor = "apple"))] use which::which; /// The max version of `LLVM` we're looking for @@ -18,7 +17,7 @@ const LLVM_VERSION_MIN: u32 = 6; /// Get the extension for a shared object fn dll_extension<'a>() -> &'a str { match env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() { - "windwos" => "dll", + "windows" => "dll", "macos" | "ios" => "dylib", _ => "so", } @@ -50,28 +49,44 @@ fn find_llvm_config_brew() -> Result { } } -fn find_llvm_config() -> String { - env::var("LLVM_CONFIG").unwrap_or_else(|_| { - // for Ghithub Actions, we check if we find llvm-config in brew. - #[cfg(target_vendor = "apple")] - match find_llvm_config_brew() { - Ok(llvm_dir) => llvm_dir.to_str().unwrap().to_string(), - Err(err) => { - println!("cargo:warning={}", err); - // falling back to system llvm-config - "llvm-config".to_string() - } +fn find_llvm_config() -> Result { + if let Ok(var) = env::var("LLVM_CONFIG") { + return Ok(var); + } + + // for Github Actions, we check if we find llvm-config in brew. + #[cfg(target_vendor = "apple")] + match find_llvm_config_brew() { + Ok(llvm_dir) => return Ok(llvm_dir.to_str().unwrap().to_string()), + Err(err) => { + println!("cargo:warning={}", err); } - #[cfg(not(target_vendor = "apple"))] - for version in (LLVM_VERSION_MIN..=LLVM_VERSION_MAX).rev() { - let llvm_config_name = format!("llvm-config-{}", version); - if which(&llvm_config_name).is_ok() { - return llvm_config_name; - } + }; + + #[cfg(not(target_vendor = "apple"))] + for version in (LLVM_VERSION_MIN..=LLVM_VERSION_MAX).rev() { + let llvm_config_name: String = format!("llvm-config-{}", version); + if which(&llvm_config_name).is_ok() { + return Ok(llvm_config_name); } - #[cfg(not(target_vendor = "apple"))] - "llvm-config".to_string() - }) + } + + if which("llvm-config").is_ok() { + return Ok("llvm-config".to_owned()); + } + + Err("could not find llvm-config".to_owned()) +} + +fn exec_llvm_config(args: &[&str]) -> String { + let llvm_config = find_llvm_config().expect("Unexpected error"); + match Command::new(&llvm_config).args(args).output() { + Ok(output) => String::from_utf8(output.stdout) + .expect("Unexpected llvm-config output") + .trim() + .to_string(), + Err(e) => panic!("Could not execute llvm-config: {}", e), + } } /// Use `xcrun` to get the path to the Xcode SDK tools library path, for linking @@ -86,165 +101,83 @@ fn find_macos_sdk_libs() -> String { ) } +fn find_llvm_version() -> Option { + let output = exec_llvm_config(&["--version"]); + if let Some(major) = output.split('.').collect::>().first() { + if let Ok(res) = major.parse::() { + return Some(res); + } + } + None +} + +fn build_pass( + bindir_path: &Path, + out_dir: &Path, + cxxflags: &Vec, + ldflags: &Vec<&str>, + src_dir: &Path, + src_file: &str, +) { + let dot_offset = src_file.rfind('.').unwrap(); + let src_stub = &src_file[..dot_offset]; + + println!("cargo:rerun-if-changed=src/{}", src_file); + if cfg!(unix) { + assert!(Command::new(bindir_path.join("clang++")) + .arg("-v") + .args(cxxflags) + .arg(src_dir.join(src_file)) + .args(ldflags) + .args(&["-o"]) + .arg(out_dir.join(format!("{}.{}", src_stub, dll_extension()))) + .status() + .unwrap_or_else(|_| panic!("Failed to compile {}", src_file)) + .success()); + } else if cfg!(windows) { + println!("{:?}", cxxflags); + assert!(Command::new(bindir_path.join("clang-cl")) + .arg("-v") + .args(cxxflags) + .arg(src_dir.join(src_file)) + .arg("/link") + .args(ldflags) + .arg(format!( + "/OUT:{}", + out_dir + .join(format!("{}.{}", src_stub, dll_extension())) + .display() + )) + .status() + .unwrap_or_else(|_| panic!("Failed to compile {}", src_file)) + .success()); + } +} + #[allow(clippy::too_many_lines)] fn main() { let out_dir = env::var_os("OUT_DIR").unwrap(); let out_dir = Path::new(&out_dir); let src_dir = Path::new("src"); - println!("cargo:rerun-if-env-changed=LLVM_CONFIG"); - println!("cargo:rerun-if-env-changed=LIBAFL_EDGES_MAP_SIZE"); - println!("cargo:rerun-if-env-changed=LIBAFL_ACCOUNTING_MAP_SIZE"); - - let mut custom_flags = vec![]; - let dest_path = Path::new(&out_dir).join("clang_constants.rs"); let mut clang_constants_file = File::create(&dest_path).expect("Could not create file"); - let edges_map_size: usize = option_env!("LIBAFL_EDGES_MAP_SIZE") - .map_or(Ok(65536), str::parse) - .expect("Could not parse LIBAFL_EDGES_MAP_SIZE"); - custom_flags.push(format!("-DLIBAFL_EDGES_MAP_SIZE={}", edges_map_size)); + println!("cargo:rerun-if-env-changed=LLVM_CONFIG"); + println!("cargo:rerun-if-env-changed=LIBAFL_EDGES_MAP_SIZE"); + println!("cargo:rerun-if-env-changed=LIBAFL_ACCOUNTING_MAP_SIZE"); + println!("cargo:rerun-if-changed=src/common-llvm.h"); + println!("cargo:rerun-if-changed=build.rs"); - let acc_map_size: usize = option_env!("LIBAFL_ACCOUNTING_MAP_SIZE") - .map_or(Ok(65536), str::parse) - .expect("Could not parse LIBAFL_ACCOUNTING_MAP_SIZE"); - custom_flags.push(format!("-DLIBAFL_ACCOUNTING_MAP_SIZE={}", acc_map_size)); - - let llvm_config = find_llvm_config(); - - // Get LLVM version. - let llvm_version = match llvm_config.split('-').collect::>().get(2) { - Some(ver) => ver.parse::().ok(), - None => None, - }; - - if let Some(ver) = llvm_version { - if ver >= 14 { - custom_flags.push("-DUSE_NEW_PM".to_string()); - } - } - - if let Ok(output) = Command::new(&llvm_config).args(&["--bindir"]).output() { - let llvm_bindir = Path::new( - str::from_utf8(&output.stdout) - .expect("Invalid llvm-config output") - .trim(), + // test if llvm-config is available and we can compile the passes + if find_llvm_config().is_err() { + println!( + "cargo:warning=Failed to find llvm-config, we will not build LLVM passes. If you need them, set the LLVM_CONFIG environment variable to a recent llvm-config." ); + write!( clang_constants_file, "// These constants are autogenerated by build.rs - - /// The path to the `clang` executable - pub const CLANG_PATH: &str = {:?}; - /// The path to the `clang++` executable - pub const CLANGXX_PATH: &str = {:?}; - - /// The size of the edges map - pub const EDGES_MAP_SIZE: usize = {}; - - /// The size of the accounting maps - pub const ACCOUNTING_MAP_SIZE: usize = {}; - - /// The llvm version used to build llvm passes - pub const LIBAFL_CC_LLVM_VERSION: Option = {:?}; - ", - llvm_bindir.join("clang"), - llvm_bindir.join("clang++"), - edges_map_size, - acc_map_size, - llvm_version, - ) - .expect("Could not write file"); - - let output = Command::new(&llvm_config) - .args(&["--cxxflags"]) - .output() - .expect("Failed to execute llvm-config"); - let cxxflags = str::from_utf8(&output.stdout).expect("Invalid llvm-config output"); - - let mut cmd = Command::new(&llvm_config); - - #[cfg(target_vendor = "apple")] - { - cmd.args(&["--libs"]); - } - - let output = cmd - .args(&["--ldflags"]) - .output() - .expect("Failed to execute llvm-config"); - let ldflags = str::from_utf8(&output.stdout).expect("Invalid llvm-config output"); - - let cxxflags: Vec<&str> = cxxflags.split_whitespace().collect(); - let mut ldflags: Vec<&str> = ldflags.split_whitespace().collect(); - - let sdk_path; - if env::var("CARGO_CFG_TARGET_VENDOR").unwrap().as_str() == "apple" { - // Needed on macos. - // Explanation at https://github.com/banach-space/llvm-tutor/blob/787b09ed31ff7f0e7bdd42ae20547d27e2991512/lib/CMakeLists.txt#L59 - ldflags.push("-undefined"); - ldflags.push("dynamic_lookup"); - - // In case the system is configured oddly, we may have trouble finding the SDK. Manually add the linker flag, just in case. - sdk_path = find_macos_sdk_libs(); - ldflags.push(&sdk_path); - }; - - println!("cargo:rerun-if-changed=src/common-llvm.h"); - println!("cargo:rerun-if-changed=src/cmplog-routines-pass.cc"); - println!("cargo:rerun-if-changed=src/afl-coverage-pass.cc"); - println!("cargo:rerun-if-changed=src/autotokens-pass.cc"); - println!("cargo:rerun-if-changed=src/coverage-accounting-pass.cc"); - - assert!(Command::new(llvm_bindir.join("clang++")) - .args(&cxxflags) - .args(&custom_flags) - .arg(src_dir.join("cmplog-routines-pass.cc")) - .args(&ldflags) - .args(&["-fPIC", "-shared", "-o"]) - .arg(out_dir.join(format!("cmplog-routines-pass.{}", dll_extension()))) - .status() - .expect("Failed to compile cmplog-routines-pass.cc") - .success()); - - assert!(Command::new(llvm_bindir.join("clang++")) - .args(&cxxflags) - .args(&custom_flags) - .arg(src_dir.join("afl-coverage-pass.cc")) - .args(&ldflags) - .args(&["-fPIC", "-shared", "-o"]) - .arg(out_dir.join(format!("afl-coverage-pass.{}", dll_extension()))) - .status() - .expect("Failed to compile afl-coverage-pass.cc") - .success()); - - assert!(Command::new(llvm_bindir.join("clang++")) - .args(&cxxflags) - .args(&custom_flags) - .arg(src_dir.join("autotokens-pass.cc")) - .args(&ldflags) - .args(&["-fPIC", "-shared", "-o"]) - .arg(out_dir.join(format!("autotokens-pass.{}", dll_extension()))) - .status() - .expect("Failed to compile autotokens-pass.cc") - .success()); - - assert!(Command::new(llvm_bindir.join("clang++")) - .args(&cxxflags) - .args(&custom_flags) - .arg(src_dir.join("coverage-accounting-pass.cc")) - .args(&ldflags) - .args(&["-fPIC", "-shared", "-o"]) - .arg(out_dir.join(format!("coverage-accounting-pass.{}", dll_extension()))) - .status() - .expect("Failed to compile coverage-accounting-pass.cc") - .success()); - } else { - write!( - clang_constants_file, - "// These constants are autogenerated by build.rs - /// The path to the `clang` executable pub const CLANG_PATH: &str = \"clang\"; /// The path to the `clang++` executable @@ -255,16 +188,130 @@ pub const LIBAFL_CC_LLVM_VERSION: Option = None; ) .expect("Could not write file"); - println!( - "cargo:warning=Failed to locate the LLVM path using {}, we will not build LLVM passes - (if you need them, set point the LLVM_CONFIG env to a recent llvm-config, or make sure {} is available)", - llvm_config, llvm_config - ); + return; + } + + let cxxflags = exec_llvm_config(&["--cxxflags"]); + let mut cxxflags: Vec = cxxflags.split_whitespace().map(String::from).collect(); + + let edges_map_size: usize = option_env!("LIBAFL_EDGES_MAP_SIZE") + .map_or(Ok(65536), str::parse) + .expect("Could not parse LIBAFL_EDGES_MAP_SIZE"); + cxxflags.push(format!("-DLIBAFL_EDGES_MAP_SIZE={}", edges_map_size)); + + let acc_map_size: usize = option_env!("LIBAFL_ACCOUNTING_MAP_SIZE") + .map_or(Ok(65536), str::parse) + .expect("Could not parse LIBAFL_ACCOUNTING_MAP_SIZE"); + cxxflags.push(format!("-DLIBAFL_ACCOUNTING_MAP_SIZE={}", acc_map_size)); + + let llvm_version = match find_llvm_config() + .unwrap() + .split('-') + .collect::>() + .get(2) + { + Some(ver) => ver.parse::().ok(), + None => None, + }; + // The approach below causes issues with arguments to optimization passes. + // An example is fuzzers/libfuzzer_libpng_accounting which passes -granularity=FUNC. + // In CI/CD, the new pass manager is not used. For now, maintain the same behavior. + //let llvm_version = find_llvm_version(); + + if let Some(ver) = llvm_version { + if ver >= 14 { + cxxflags.push(String::from("-DUSE_NEW_PM")); + } + } + + let llvm_bindir = exec_llvm_config(&["--bindir"]); + let bindir_path = Path::new(&llvm_bindir); + + write!( + clang_constants_file, + "// These constants are autogenerated by build.rs + + /// The path to the `clang` executable + pub const CLANG_PATH: &str = {:?}; + /// The path to the `clang++` executable + pub const CLANGXX_PATH: &str = {:?}; + + /// The size of the edges map + pub const EDGES_MAP_SIZE: usize = {}; + + /// The size of the accounting maps + pub const ACCOUNTING_MAP_SIZE: usize = {}; + + /// The llvm version used to build llvm passes + pub const LIBAFL_CC_LLVM_VERSION: Option = {:?}; + ", + bindir_path.join("clang"), + bindir_path.join("clang++"), + edges_map_size, + acc_map_size, + llvm_version, + ) + .expect("Could not write file"); + + let mut llvm_config_ld = vec![]; + if cfg!(target_vendor = "apple") { + llvm_config_ld.push("--libs"); + } + if cfg!(windows) { + llvm_config_ld.push("--libs"); + llvm_config_ld.push("--system-libs"); + } + llvm_config_ld.push("--ldflags"); + + let ldflags = exec_llvm_config(&llvm_config_ld); + let mut ldflags: Vec<&str> = ldflags.split_whitespace().collect(); + + if cfg!(unix) { + cxxflags.push(String::from("-shared")); + cxxflags.push(String::from("-fPIC")); + } + if cfg!(windows) { + cxxflags.push(String::from("-fuse-ld=lld")); + cxxflags.push(String::from("/LD")); + /* clang on windows links against the libcmt.lib runtime + * however, the distributed binaries are compiled against msvcrt.lib + * we need to also use msvcrt.lib instead of libcmt.lib when building the optimization passes + * first, we tell clang-cl (and indirectly link) to ignore libcmt.lib via -nodefaultlib:libcmt + * second, we pass the /MD flag to clang-cl to use the msvcrt.lib runtime instead when generating the object file + */ + ldflags.push("-nodefaultlib:libcmt"); + cxxflags.push(String::from("/MD")); + /* the include directories are not always added correctly when running --cxxflags or --includedir on windows + * this is somehow related to where/how llvm was compiled (vm, docker container, host) + * add the option of setting additional flags via the LLVM_CXXFLAGS variable + */ + if let Some(env_cxxflags) = option_env!("LLVM_CXXFLAGS") { + cxxflags.append(&mut env_cxxflags.split_whitespace().map(String::from).collect()); + } + } + + let sdk_path; + if env::var("CARGO_CFG_TARGET_VENDOR").unwrap().as_str() == "apple" { + // Needed on macos. + // Explanation at https://github.com/banach-space/llvm-tutor/blob/787b09ed31ff7f0e7bdd42ae20547d27e2991512/lib/CMakeLists.txt#L59 + ldflags.push("-undefined"); + ldflags.push("dynamic_lookup"); + + // In case the system is configured oddly, we may have trouble finding the SDK. Manually add the linker flag, just in case. + sdk_path = find_macos_sdk_libs(); + ldflags.push(&sdk_path); + }; + + for pass in &[ + "cmplog-routines-pass.cc", + "afl-coverage-pass.cc", + "autotokens-pass.cc", + "coverage-accounting-pass.cc", + ] { + build_pass(bindir_path, out_dir, &cxxflags, &ldflags, src_dir, pass); } cc::Build::new() .file(src_dir.join("no-link-rt.c")) .compile("no-link-rt"); - - println!("cargo:rerun-if-changed=build.rs"); } diff --git a/libafl_cc/src/afl-coverage-pass.cc b/libafl_cc/src/afl-coverage-pass.cc index ad531b2280..a8d0328ab8 100644 --- a/libafl_cc/src/afl-coverage-pass.cc +++ b/libafl_cc/src/afl-coverage-pass.cc @@ -31,8 +31,12 @@ #include #include #include -#include -#include +#ifndef _WIN32 + #include + #include +#else + #include +#endif #include #include #include @@ -781,8 +785,13 @@ bool AFLCoverage::runOnModule(Module &M) { } if (DumpCFG) { int fd; +#ifndef _WIN32 if ((fd = open(DumpCFGPath.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0644)) < 0) +#else + if ((fd = _open(DumpCFGPath.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0644)) < + 0) +#endif FATAL("Could not open/create CFG dump file."); std::string cfg = ""; for (auto record = entry_bb.begin(); record != entry_bb.end(); record++) { diff --git a/libafl_cc/src/autotokens-pass.cc b/libafl_cc/src/autotokens-pass.cc index 170cd2750d..760f9bb6d6 100644 --- a/libafl_cc/src/autotokens-pass.cc +++ b/libafl_cc/src/autotokens-pass.cc @@ -18,9 +18,13 @@ #include #include -#include +#ifndef _WIN32 + #include + #include +#else + #include +#endif #include -#include #include #include #include @@ -213,7 +217,9 @@ void dict2file(int fd, uint8_t *mem, uint32_t len) { if (write(fd, line, strlen(line)) <= 0) { FATAL("Could not write to the dictionary file"); } +#ifndef _WIN32 fsync(fd); +#endif } #if USE_NEW_PM @@ -242,7 +248,11 @@ bool AutoTokensPass::runOnModule(Module &M) { } if (use_file) { +#ifndef _WIN32 if ((fd = open(ptr, O_WRONLY | O_APPEND | O_CREAT | O_DSYNC, 0644)) < 0) +#else + if ((fd = open(ptr, O_WRONLY | O_APPEND | O_CREAT, 0644)) < 0) +#endif FATAL("Could not open/create %s.", ptr); } @@ -684,4 +694,4 @@ static RegisterStandardPasses RegisterAutoTokensPass( static RegisterStandardPasses RegisterAutoTokensPass0( PassManagerBuilder::EP_EnabledOnOptLevel0, registerAutoTokensPass); -#endif \ No newline at end of file +#endif diff --git a/libafl_cc/src/clang.rs b/libafl_cc/src/clang.rs index c5cfd82230..28acc338ee 100644 --- a/libafl_cc/src/clang.rs +++ b/libafl_cc/src/clang.rs @@ -112,7 +112,11 @@ impl CompilerWrapper for ClangWrapper { self.name = args[0].as_ref().to_string(); // Detect C++ compiler looking at the wrapper name - self.is_cpp = self.is_cpp || self.name.ends_with("++"); + self.is_cpp = if cfg!(windows) { + self.is_cpp || self.name.ends_with("++.exe") + } else { + self.is_cpp || self.name.ends_with("++") + }; // Sancov flag // new_args.push("-fsanitize-coverage=trace-pc-guard".into()); @@ -252,21 +256,22 @@ impl CompilerWrapper for ClangWrapper { where S: AsRef, { - if cfg!(target_vendor = "apple") { - //self.add_link_arg("-force_load".into())?; + let lib_file = dir + .join(format!("{}{}.{}", LIB_PREFIX, name.as_ref(), LIB_EXT)) + .into_os_string() + .into_string() + .unwrap(); + + if cfg!(unix) { + if cfg!(target_vendor = "apple") { + self.add_link_arg(lib_file) + } else { + self.add_link_arg("-Wl,--whole-archive") + .add_link_arg(lib_file) + .add_link_arg("-Wl,--no-whole-archive") + } } else { - self.add_link_arg("-Wl,--whole-archive"); - } - self.add_link_arg( - dir.join(format!("{}{}.{}", LIB_PREFIX, name.as_ref(), LIB_EXT)) - .into_os_string() - .into_string() - .unwrap(), - ); - if cfg!(target_vendor = "apple") { - self - } else { - self.add_link_arg("-Wl,-no-whole-archive") + self.add_link_arg(format!("-Wl,-wholearchive:{}", lib_file)) } } diff --git a/libafl_cc/src/cmplog-routines-pass.cc b/libafl_cc/src/cmplog-routines-pass.cc index 4ddd4f3a37..04e92f3b61 100644 --- a/libafl_cc/src/cmplog-routines-pass.cc +++ b/libafl_cc/src/cmplog-routines-pass.cc @@ -17,12 +17,14 @@ #include #include -#include +#ifndef _WIN32 + #include + #include +#endif #include #include #include -#include #include "llvm/Config/llvm-config.h" #if USE_NEW_PM