1extern crate alloc;
3
4use core::hash::Hasher;
5use std::{
6 collections::hash_map,
7 env,
8 fs::{self, File},
9 io::{Read, Seek, SeekFrom, Write},
10 path::{Path, PathBuf},
11 process::Command,
12 sync::{LazyLock, Mutex},
13};
14
15use which::which;
20
21mod bindings;
22mod build;
23
24pub use build::build;
25
26#[rustversion::nightly]
27use crate::build::LIBAFL_QEMU_GIT_REV;
28
29const LLVM_VERSION_MAX: i32 = 33;
30
31static CARGO_RPATH: LazyLock<Mutex<Vec<String>>> = LazyLock::new(Mutex::default);
32static CARGO_RPATH_SEPARATOR: &str = "|";
33
34pub fn cargo_add_rpath(rpath: &str) {
37 CARGO_RPATH.lock().unwrap().push(rpath.to_string());
38}
39
40pub fn cargo_propagate_rpath() {
42 let cargo_cmds = CARGO_RPATH.lock().unwrap();
43 if !cargo_cmds.is_empty() {
44 let rpath = cargo_cmds.join(CARGO_RPATH_SEPARATOR);
45 println!("cargo:rpath={rpath}");
46 }
47}
48
49pub fn build_libafl_qemu() {
51 if let Some(rpaths) = env::var_os("DEP_QEMU_RPATH") {
53 let rpaths: Vec<&str> = rpaths
54 .to_str()
55 .expect("Cannot convert OsString to str")
56 .split(CARGO_RPATH_SEPARATOR)
57 .collect();
58 for rpath in rpaths {
59 println!("cargo:rustc-link-arg-bins=-Wl,-rpath,{rpath}");
60 }
61 }
62}
63
64pub fn build_with_bindings(
65 cpu_target: &str,
66 is_big_endian: bool,
67 is_usermode: bool,
68 jobs: Option<u32>,
69 bindings_file: &Path,
70) {
71 let build_result = build::build(cpu_target, is_big_endian, is_usermode, jobs);
72
73 let clang_args = qemu_bindgen_clang_args(
74 &build_result.qemu_path,
75 &build_result.build_dir,
76 cpu_target,
77 is_usermode,
78 );
79
80 let bind = bindings::generate(&build_result.build_dir, cpu_target, clang_args)
81 .expect("Failed to generate the bindings");
82
83 fs::write(bindings_file, bind.to_string()).expect("Unable to write file");
85
86 cargo_propagate_rpath();
87}
88
89fn find_llvm_config() -> Result<String, String> {
91 if let Ok(var) = env::var("LLVM_CONFIG") {
92 return Ok(var);
93 }
94
95 let rustc_llvm_ver = find_rustc_llvm_version().unwrap();
96 for version in (rustc_llvm_ver..=LLVM_VERSION_MAX).rev() {
97 let llvm_config_name: String = format!("llvm-config-{version}");
98 if which(&llvm_config_name).is_ok() {
99 return Ok(llvm_config_name);
100 }
101 }
102
103 if which("llvm-config").is_ok() {
104 if let Some(ver) = find_llvm_version("llvm-config".to_owned()) {
105 if ver < rustc_llvm_ver {
106 println!(
107 "cargo:warning=Version of llvm-config is {ver} but needs to be at least rustc's version ({rustc_llvm_ver})! We will (try to) continue to build. Continue at your own risk, or rebuild with a set LLVM_CONFIG_PATH env variable, pointing to a newer version."
108 );
109 }
110 return Ok("llvm-config".to_owned());
111 }
112 }
113
114 Err("Could not find llvm-config".to_owned())
115}
116
117fn exec_llvm_config(llvm_config: String, args: &[&str]) -> String {
118 match Command::new(llvm_config).args(args).output() {
119 Ok(output) => String::from_utf8(output.stdout)
120 .expect("Unexpected llvm-config output")
121 .trim()
122 .to_string(),
123 Err(e) => panic!("Could not execute llvm-config: {e}"),
124 }
125}
126
127fn find_llvm_version(llvm_config: String) -> Option<i32> {
128 let output = exec_llvm_config(llvm_config, &["--version"]);
129 if let Some(major) = output.split('.').collect::<Vec<&str>>().first() {
130 if let Ok(res) = major.parse::<i32>() {
131 return Some(res);
132 }
133 }
134 None
135}
136
137fn exec_rustc(args: &[&str]) -> String {
138 let rustc = env::var("RUSTC").unwrap();
139 match Command::new(rustc).args(args).output() {
140 Ok(output) => String::from_utf8(output.stdout)
141 .expect("Unexpected rustc output")
142 .trim()
143 .to_string(),
144 Err(e) => panic!("Could not execute rustc: {e}"),
145 }
146}
147
148fn find_rustc_llvm_version() -> Option<i32> {
149 let output = exec_rustc(&["--verbose", "--version"]);
150 let ver = output.split(':').next_back().unwrap().trim();
151 if let Some(major) = ver.split('.').collect::<Vec<&str>>().first() {
152 if let Ok(res) = major.parse::<i32>() {
153 return Some(res);
154 }
155 }
156 None
157}
158
159fn qemu_bindgen_clang_args(
162 qemu_dir: &Path,
163 build_dir: &Path,
164 cpu_target: &str,
165 is_usermode: bool,
166) -> Vec<String> {
167 if env::var("LLVM_CONFIG_PATH").is_err() {
168 let found = find_llvm_config().expect("Cannot find a suitable llvm-config, it must be a version equal or greater than the rustc LLVM version. Try specifying LLVM_CONFIG_PATH.");
169 unsafe {
172 env::set_var("LLVM_CONFIG_PATH", found);
173 }
174 }
175
176 let compile_commands_string = &fs::read_to_string(build_dir.join("compile_commands.json"))
178 .expect("failed to read compile commands");
179
180 let compile_commands =
181 json::parse(compile_commands_string).expect("Failed to parse compile commands");
182
183 let (main_file, main_obj) = if is_usermode {
184 (
185 "/linux-user/main.c",
186 format!("libqemu-{cpu_target}-linux-user.fa.p/linux-user_main.c.o"),
187 )
188 } else {
189 (
190 "/system/main.c",
191 format!("libqemu-system-{cpu_target}.so.p/system_main.c.o"),
192 )
193 };
194
195 let entry = compile_commands
197 .members()
198 .find(|entry| {
199 entry["output"] == main_obj
200 || entry["file"]
201 .as_str()
202 .is_some_and(|file| file.ends_with(main_file))
203 })
204 .expect("Didn't find compile command for qemu-system-arm");
205
206 let command = entry["command"].as_str().expect("Command is a string");
208
209 let mut clang_args = vec![];
211 let mut include_arg = false;
212 for arg in shell_words::split(command)
213 .expect("failed to parse command")
214 .into_iter()
215 .skip(1)
216 {
217 if arg.starts_with("-D") {
218 clang_args.push(arg);
219 } else if let Some(incpath) = arg.strip_prefix("-I") {
220 clang_args.push(format!("-I{}", include_path(build_dir, incpath)));
221 } else if arg == "-iquote" || arg == "-isystem" {
222 include_arg = true;
223 clang_args.push(arg);
224 } else if include_arg {
225 include_arg = false;
226 clang_args.push(include_path(build_dir, &arg));
227 }
228 }
229
230 let target_arch_dir = match cpu_target {
231 "x86_64" => format!("-I{}/target/i386", qemu_dir.display()),
232 "aarch64" => format!("-I{}/target/arm", qemu_dir.display()),
233 "riscv32" | "riscv64" => format!("-I{}/target/riscv", qemu_dir.display()),
234 _ => format!("-I{}/target/{cpu_target}", qemu_dir.display()),
235 };
236
237 clang_args.push(format!("-I{}", qemu_dir.display()));
239 clang_args.push(format!("-I{}/include", qemu_dir.display()));
240 clang_args.push(format!("-I{}/quote", qemu_dir.display()));
241 clang_args.push(target_arch_dir);
242
243 clang_args
244}
245
246fn include_path(build_dir: &Path, path: &str) -> String {
247 let include_path = PathBuf::from(path);
248
249 if include_path.is_absolute() {
250 path.to_string()
251 } else {
252 build_dir.join(include_path).display().to_string()
254 }
255}
256
257pub fn store_generated_content_if_different(
262 file_to_update: &Path,
263 fresh_content: &[u8],
264 content_file_to_update: Option<Vec<u8>>,
265 first_line_prefixes: Vec<&str>,
266 force_regeneration: bool,
267) {
268 let mut must_rewrite_file = true;
269
270 let mut file_to_check =
272 if let Ok(mut wrapper_file) = File::options().read(true).write(true).open(file_to_update) {
273 let existing_file_content = content_file_to_update.unwrap_or_else(|| {
274 let mut content = Vec::with_capacity(fresh_content.len());
275 wrapper_file.read_to_end(content.as_mut()).unwrap();
276 content
277 });
278
279 if !force_regeneration {
280 let mut existing_wrapper_hasher = hash_map::DefaultHasher::new();
281 existing_wrapper_hasher.write(existing_file_content.as_ref());
282
283 let mut wrapper_h_hasher = hash_map::DefaultHasher::new();
284 wrapper_h_hasher.write(fresh_content);
285
286 if existing_wrapper_hasher.finish() == wrapper_h_hasher.finish() {
288 must_rewrite_file = false;
289 }
290 }
291
292 if must_rewrite_file {
294 wrapper_file.set_len(0).expect("Could not set file len");
295 wrapper_file
296 .seek(SeekFrom::Start(0))
297 .expect("Could not seek file to beginning");
298 }
299
300 wrapper_file
301 } else {
302 File::create(file_to_update)
303 .unwrap_or_else(|_| panic!("Could not create {}", file_to_update.display()))
304 };
305
306 if must_rewrite_file {
307 println!(
308 "cargo::warning={} has been regenerated.",
309 file_to_update.file_name().unwrap().to_str().unwrap()
310 );
311
312 for prefix in first_line_prefixes {
313 writeln!(&file_to_check, "{prefix}").expect("Could not write prefix");
314 }
315
316 file_to_check
317 .write_all(fresh_content)
318 .unwrap_or_else(|_| panic!("Unable to write in {}", file_to_update.display()));
319 }
320}
321
322#[rustversion::nightly]
396pub fn maybe_generate_stub_bindings(
397 cpu_target: &str,
398 emulation_mode: &str,
399 stub_bindings_file: &Path,
400 bindings_file: &Path,
401) {
402 if env::var("LIBAFL_QEMU_GEN_STUBS").is_ok()
403 && cpu_target == "x86_64"
404 && emulation_mode == "usermode"
405 {
406 let current_rustc_version =
407 rustc_version::version().expect("Could not get current rustc version");
408
409 let header = format!("/* {current_rustc_version} */");
415
416 store_generated_content_if_different(
417 stub_bindings_file,
418 fs::read(bindings_file)
419 .expect("Could not read generated bindings file")
420 .as_slice(),
421 None,
422 vec![
423 header.as_str(),
424 format!("/* qemu git hash: {LIBAFL_QEMU_GIT_REV} */").as_str(),
425 ],
426 false,
427 );
428 } else if env::var("CARGO_CFG_DOC").is_ok() {
429 println!(
430 "cargo:warning=Bindings regeneration has been skipped. Please rerun with x86_64 with usermode to trigger the bindings regeneration."
431 );
432 }
433}
434
435#[rustversion::not(nightly)]
436pub fn maybe_generate_stub_bindings(
437 _cpu_target: &str,
438 _emulation_mode: &str,
439 _stub_bindings_file: &Path,
440 _bindings_file: &Path,
441) {
442 }