//! `cargo xtask` automation. //! //! Please refer to for an explanation of the concept. //! //! Also see the docs in `asm.rs`. use object::read::{Object as _, ObjectSection as _}; use object::write::{Object, Symbol, SymbolSection}; use object::{ObjectSymbol, SymbolFlags}; use std::collections::BTreeMap; use std::env::current_dir; use std::fs::{self, File}; use std::process::{Command, Stdio}; fn toolchain() -> String { fs::read_to_string("asm-toolchain") .unwrap() .trim() .to_string() } fn rustc() -> Command { let mut cmd = Command::new("rustc"); cmd.arg(format!("+{}", toolchain())); cmd } /// Patches an object file so that it doesn't contain a panic handler. /// /// The panic handler defined in `asm/lib.rs` should never get linked to the final program. /// Unfortunately, Rust uses the same symbol for all panic handlers, and doesn't really like it if /// that ends up with multiple ones. It also demands that we define a panic handler for the inline /// assembly shim, even though none of that code should ever be able to panic. The result of this is /// that the supposedly unreachable panic handler does end up getting linked into the final program, /// unless it is built with optimizations enabled. /// /// To fix that, we put the never-to-be-used panic handler into its own section via /// `#[link_section]`, and then use this function to delete that section. fn trim_panic_handler(obj_file: &str) { let objdata = fs::read(&obj_file).unwrap(); let obj = object::File::parse(&objdata).unwrap(); let mut writer = Object::new(obj.format(), obj.architecture(), obj.endianness()); for (sec_index, section) in obj.sections().enumerate() { assert_eq!(section.index().0, sec_index); let name = section.name().unwrap(); if name.starts_with(".ARM") || name.starts_with(".rel.ARM") || name.contains("asm_panic_handler") || name == ".strtab" || name == ".symtab" { // We drop the ARM exception handling tables since they refer back to the panic handler // symbol. They aren't used either way. We also drop `.strtab` and `.symtab` since they // otherwise end up having the wrong section type. The object crate should rebuild any // index tables when writing the file. continue; } let segment = section .segment_name() .unwrap() .map(|s| s.as_bytes()) .unwrap_or(&[]); let sec_id = writer.add_section(segment.to_vec(), name.as_bytes().to_vec(), section.kind()); let align = if section.align() == 0 { // Not sure why but `section.align()` can return 0. 1 } else { section.align() }; writer.append_section_data(sec_id, section.data().unwrap(), align); // Import all symbols from the section. for symbol in obj.symbols() { if symbol.section_index() == Some(section.index()) { writer.add_symbol(Symbol { name: symbol.name().unwrap_or("").as_bytes().to_vec(), value: symbol.address(), size: symbol.size(), kind: symbol.kind(), scope: symbol.scope(), weak: symbol.is_weak(), section: match symbol.section() { object::SymbolSection::Unknown => unimplemented!(), object::SymbolSection::None => SymbolSection::None, object::SymbolSection::Undefined => SymbolSection::Undefined, object::SymbolSection::Absolute => SymbolSection::Absolute, object::SymbolSection::Common => SymbolSection::Common, object::SymbolSection::Section(_) => SymbolSection::Section(sec_id), }, flags: match symbol.flags() { SymbolFlags::None => SymbolFlags::None, SymbolFlags::Elf { st_info, st_other } => { SymbolFlags::Elf { st_info, st_other } } _ => unimplemented!(), }, }); } } } let obj = writer.write().unwrap(); fs::write(&obj_file, obj).unwrap(); } fn assemble_really(target: &str, cfgs: &[&str], plugin_lto: bool) { let mut cmd = rustc(); // Set the codegen target. cmd.arg("--target").arg(target); // Set all the `--cfg` directives for the target. cmd.args(cfgs.iter().map(|cfg| format!("--cfg={}", cfg))); // We want some level of debuginfo to allow unwinding through the functions. cmd.arg("-g"); // We always optimize the assembly shims. There's not really any reason not to. cmd.arg("-O"); // rustc will usually add frame pointers by default to aid with debugging, but that is a high // overhead for the tiny assembly routines. cmd.arg("-Cforce-frame-pointers=no"); // We don't want any system-specific paths to show up since we ship the result to other users. // Add `--remap-path-prefix $(pwd)=.`. let mut dir = current_dir().unwrap().as_os_str().to_os_string(); dir.push("=."); cmd.arg("--remap-path-prefix").arg(dir); // We let rustc build a single object file, not a staticlib, since the latter pulls in loads of // code that will never be used (`compiler_builtins` and `core::fmt`, etc.). We build the static // archive by hand after compiling. cmd.arg("--emit=obj"); if plugin_lto { // Make artifacts compatible with Linker-Plugin LTO (and incompatible with everything else). cmd.arg("-Clinker-plugin-lto"); } let file_stub = if plugin_lto { format!("{}-lto", target) } else { target.to_string() }; let obj_file = format!("bin/{}.o", file_stub); // Pass output and input file. cmd.arg("-o").arg(&obj_file); cmd.arg("asm/lib.rs"); println!("{:?}", cmd); let status = cmd.status().unwrap(); assert!(status.success()); if !plugin_lto { // Post-process the object file. trim_panic_handler(&obj_file); } // Archive `target.o` -> `bin/target.a`. let mut builder = ar::Builder::new(File::create(format!("bin/{}.a", file_stub)).unwrap()); // Use `append`, not `append_path`, to avoid adding any filesystem metadata (modification times, // etc.). let file = fs::read(&obj_file).unwrap(); builder .append( &ar::Header::new(obj_file.as_bytes().to_vec(), file.len() as u64), &*file, ) .unwrap(); fs::remove_file(&obj_file).unwrap(); } fn assemble(target: &str, cfgs: &[&str]) { assemble_really(target, cfgs, false); assemble_really(target, cfgs, true); } // `--target` -> `--cfg` list (mirrors what `build.rs` does). static TARGETS: &[(&str, &[&str])] = &[ ("thumbv6m-none-eabi", &[]), ("thumbv7m-none-eabi", &["armv7m"]), ("thumbv7em-none-eabi", &["armv7m", "armv7em"]), ("thumbv7em-none-eabihf", &["armv7m", "armv7em", "has_fpu"]), ("thumbv8m.base-none-eabi", &["armv8m", "armv8m_base"]), ( "thumbv8m.main-none-eabi", &["armv7m", "armv8m", "armv8m_main"], ), ( "thumbv8m.main-none-eabihf", &["armv7m", "armv8m", "armv8m_main", "has_fpu"], ), ]; pub fn install_targets(targets: &mut dyn Iterator, toolchain: Option<&str>) { let mut rustup = Command::new("rustup"); rustup.arg("target").arg("add").args(targets); if let Some(toolchain) = toolchain { rustup.arg("--toolchain").arg(toolchain); } let status = rustup.status().unwrap(); assert!(status.success(), "rustup command failed: {:?}", rustup); } pub fn assemble_blobs() { let mut cmd = rustc(); cmd.arg("-V"); cmd.stdout(Stdio::null()); let status = cmd.status().unwrap(); let toolchain = toolchain(); if !status.success() { println!( "asm toolchain {} does not seem to be installed. installing it now.", toolchain ); let mut rustup = Command::new("rustup"); let status = rustup.arg("install").arg(&toolchain).status().unwrap(); assert!(status.success(), "rustup command failed: {:?}", rustup); } install_targets( &mut TARGETS.iter().map(|(target, _)| *target), Some(&*toolchain), ); for (target, cfgs) in TARGETS { println!("building artifacts for {}", target); assemble(target, cfgs); } } pub fn check_blobs() { // Load each `.a` file in `bin` into memory. let mut files_before = BTreeMap::new(); for entry in fs::read_dir("bin").unwrap() { let entry = entry.unwrap(); if entry.path().extension().unwrap() == "a" { files_before.insert( entry .path() .file_name() .unwrap() .to_str() .unwrap() .to_string(), fs::read(entry.path()).unwrap(), ); } } assemble_blobs(); let mut files_after = BTreeMap::new(); for entry in fs::read_dir("bin").unwrap() { let entry = entry.unwrap(); if entry.path().extension().unwrap() == "a" { files_after.insert( entry .path() .file_name() .unwrap() .to_str() .unwrap() .to_string(), fs::read(entry.path()).unwrap(), ); } } // Ensure they contain the same files. let before = files_before.keys().collect::>(); let after = files_after.keys().collect::>(); assert_eq!(before, after); for ((file, before), (_, after)) in files_before.iter().zip(files_after.iter()) { if before != after { panic!( "{} is not up-to-date, please run `cargo xtask assemble`", file ); } } println!("Blobs identical."); }