#[cfg(feature = "use_bindgen")]
extern crate bindgen;
extern crate cc;
#[cfg(feature = "use_bindgen")]
extern crate regex;
#[cfg(feature = "use_bindgen")]
use {
regex::Regex,
std::{fs::File, io::Write},
};
use std::env;
use std::fs::copy;
use std::path::PathBuf;
include!("common.rs");
const CAPSTONE_DIR: &str = "capstone";
#[allow(dead_code)]
enum LinkType {
Dynamic,
Static,
}
fn build_capstone_cc() {
use std::fs::DirEntry;
fn read_dir_and_filter<F: Fn(&DirEntry) -> bool>(dir: &str, filter: F) -> Vec<String> {
use std::fs::read_dir;
read_dir(dir)
.expect("Failed to read capstone source directory")
.map(|e| e.expect("Failed to read capstone source directory"))
.filter(|e| filter(e))
.map(|e| {
format!(
"{}/{}",
dir,
e.file_name().to_str().expect("Invalid filename")
)
})
.collect()
}
fn find_c_source_files(dir: &str) -> Vec<String> {
read_dir_and_filter(dir, |e| {
let file_type = e
.file_type()
.expect("Failed to read capstone source directory");
let file_name = e.file_name().into_string().expect("Invalid filename");
file_type.is_file() && (file_name.ends_with(".c") || file_name.ends_with(".C"))
})
}
fn find_arch_dirs() -> Vec<String> {
read_dir_and_filter(&format!("{}/{}", CAPSTONE_DIR, "arch"), |e| {
let file_type = e
.file_type()
.expect("Failed to read capstone source directory");
file_type.is_dir()
})
}
let mut files = find_c_source_files(CAPSTONE_DIR);
for arch_dir in find_arch_dirs().into_iter() {
files.append(&mut find_c_source_files(&arch_dir));
}
files.sort();
let use_static_crt = {
let target_features = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or_default();
target_features.split(',').any(|f| f == "crt-static")
};
let mut builder = cc::Build::new();
builder
.files(files)
.include(format!("{}/{}", CAPSTONE_DIR, "include"))
.define("CAPSTONE_USE_SYS_DYN_MEM", None)
.define("CAPSTONE_HAS_ARM", None)
.define("CAPSTONE_HAS_ARM64", None)
.define("CAPSTONE_HAS_EVM", None)
.define("CAPSTONE_HAS_M680X", None)
.define("CAPSTONE_HAS_M68K", None)
.define("CAPSTONE_HAS_MIPS", None)
.define("CAPSTONE_HAS_POWERPC", None)
.define("CAPSTONE_HAS_RISCV", None)
.define("CAPSTONE_HAS_SPARC", None)
.define("CAPSTONE_HAS_SYSZ", None)
.define("CAPSTONE_HAS_TMS320C64X", None)
.define("CAPSTONE_HAS_WASM", None)
.define("CAPSTONE_HAS_X86", None)
.define("CAPSTONE_HAS_XCORE", None)
.flag_if_supported("-w")
.static_crt(use_static_crt);
if !cfg!(feature = "full") {
builder.define("CAPSTONE_DIET", "yes");
}
builder.compile("capstone");
}
#[cfg(feature = "use_bindgen")]
fn find_capstone_header(header_search_paths: &[PathBuf], name: &str) -> Option<PathBuf> {
for search_path in header_search_paths.iter() {
let potential_file = search_path.join(name);
if potential_file.is_file() {
return Some(potential_file);
}
}
None
}
fn env_var(var: &str) -> String {
env::var(var).unwrap_or_else(|_| panic!("Environment variable {} is not set", var))
}
#[cfg(feature = "use_bindgen")]
fn impl_insid_to_insenum(bindings: &str) -> String {
use std::fmt::Write as _;
let mut impl_arch_enum = String::new();
impl_arch_enum.push_str("use core::convert::From;\n");
for cs_arch in ARCH_INCLUDES {
let arch = cs_arch.cs_name();
let re_enum_def = Regex::new(&format!("pub enum {}_insn (?s)\\{{.*?\\}}", arch))
.expect("Unable to compile regex");
let cap_enum_def = &re_enum_def
.captures(bindings)
.expect("Unable to capture group")[0];
let re_ins_ids = Regex::new(&format!(
"{}_INS_(?P<ins>[A-Z0-9_]+) = (?P<id>\\d+)",
&arch.to_uppercase()
))
.expect("Unable to compile regex");
write!(
impl_arch_enum,
"impl From<u32> for {}_insn {{\n
fn from(id: u32) -> Self {{\n
match id {{\n",
&arch
)
.unwrap();
for cap_ins_id in re_ins_ids.captures_iter(cap_enum_def) {
writeln!(
impl_arch_enum,
"{} => {}_insn::{}_INS_{},",
&cap_ins_id["id"],
&arch,
&arch.to_uppercase(),
&cap_ins_id["ins"]
)
.unwrap();
}
let invalid_str = match arch {
"m680x" => "INVLD",
_ => "INVALID",
};
write!(
impl_arch_enum,
"_ => {}_insn::{}_INS_{},",
&arch,
&arch.to_uppercase(),
invalid_str,
)
.unwrap();
impl_arch_enum.push_str("}\n}\n}\n");
}
impl_arch_enum
}
#[cfg(feature = "use_bindgen")]
fn write_bindgen_bindings(
header_search_paths: &[PathBuf],
update_pregenerated_bindings: bool,
pregenerated_bindgen_header: PathBuf,
pregenerated_bindgen_impl: PathBuf,
out_bindings_path: PathBuf,
out_impl_path: PathBuf,
) {
let mut builder = bindgen::Builder::default()
.rust_target(bindgen::RustTarget::Stable_1_19)
.size_t_is_usize(true)
.use_core()
.ctypes_prefix("libc")
.header(
find_capstone_header(header_search_paths, "capstone.h")
.expect("Could not find header")
.to_str()
.unwrap(),
)
.disable_name_namespacing()
.prepend_enum_name(false)
.generate_comments(true)
.layout_tests(false) .impl_debug(true)
.constified_enum_module("cs_err|cs_group_type|cs_opt_value")
.bitfield_enum("cs_mode|cs_ac_type")
.rustified_enum(".*")
.no_copy("cs_insn");
let pattern = String::from("cs_.*");
builder = builder
.allowlist_function(&pattern)
.allowlist_type(&pattern);
for arch in ARCH_INCLUDES {
let arch_type_pattern = format!(".*(^|_){}(_|$).*", arch.cs_name);
let const_mod_pattern = format!("^{}_(reg|insn_group)$", arch.cs_name);
builder = builder
.allowlist_type(&arch_type_pattern)
.constified_enum_module(&const_mod_pattern);
}
let bindings = builder.generate().expect("Unable to generate bindings");
bindings
.write_to_file(&out_bindings_path)
.expect("Unable to write bindings");
let bindings_impl_str = impl_insid_to_insenum(&bindings.to_string());
let mut bindings_impl = File::create(&out_impl_path).expect("Unable to open file");
bindings_impl
.write_all(bindings_impl_str.as_bytes())
.expect("Unable to write file");
if update_pregenerated_bindings {
copy(out_bindings_path, pregenerated_bindgen_header)
.expect("Unable to update capstone bindings");
copy(out_impl_path, pregenerated_bindgen_impl).expect("Unable to update capstone bindings");
}
}
fn main() {
#[allow(unused_assignments)]
let mut link_type: Option<LinkType> = None;
let mut header_search_paths: Vec<PathBuf> = Vec::new();
build_capstone_cc();
header_search_paths.push([CAPSTONE_DIR, "include", "capstone"].iter().collect());
link_type = Some(LinkType::Static);
match link_type.expect("Must specify link type") {
LinkType::Dynamic => {
println!("cargo:rustc-link-lib=dylib=capstone");
}
LinkType::Static => {
println!("cargo:rustc-link-lib=static=capstone");
}
}
let update_pregenerated_bindings = env::var("UPDATE_CAPSTONE_BINDINGS").is_ok();
if update_pregenerated_bindings && !cfg!(feature = "use_bindgen") {
panic!( "Setting UPDATE_CAPSTONE_BINDINGS only makes sense when enabling feature \"use_bindgen\"");
}
let pregenerated_bindgen_header: PathBuf = [
env_var("CARGO_MANIFEST_DIR"),
"pre_generated".into(),
BINDINGS_FILE.into(),
]
.iter()
.collect();
let pregenerated_bindgen_impl: PathBuf = [
env_var("CARGO_MANIFEST_DIR"),
"pre_generated".into(),
BINDINGS_IMPL_FILE.into(),
]
.iter()
.collect();
let out_bindings_path = PathBuf::from(env_var("OUT_DIR")).join(BINDINGS_FILE);
let out_impl_path = PathBuf::from(env_var("OUT_DIR")).join(BINDINGS_IMPL_FILE);
#[cfg(feature = "use_bindgen")]
write_bindgen_bindings(
&header_search_paths,
update_pregenerated_bindings,
pregenerated_bindgen_header,
pregenerated_bindgen_impl,
out_bindings_path,
out_impl_path,
);
#[cfg(not(feature = "use_bindgen"))]
{
copy(pregenerated_bindgen_header, out_bindings_path)
.expect("Unable to update capstone bindings");
copy(pregenerated_bindgen_impl, out_impl_path).expect("Unable to update capstone bindings");
}
}