use std::fs; use std::path::{Path, PathBuf}; use object::coff::CoffFile; use object::pe::{ImageResourceDataEntry, ImageResourceDirectory, ImageResourceDirectoryEntry, IMAGE_RESOURCE_DATA_IS_DIRECTORY}; use object::{ pod, Architecture, LittleEndian, Object, ObjectSection, ObjectSymbol, RelocationEncoding, RelocationKind, SectionKind, }; use tempfile::{tempdir, TempDir}; use crate::embed::coff::MachineType; use crate::embed::{create_object_file, link_manifest_gnu, link_manifest_msvc, TargetOs}; use crate::new_manifest; #[test] fn create_obj() { let res = do_embed_file(MachineType::X86_64, TargetOs::WindowsGnu); let data = fs::read(&res.object_file()).unwrap(); let obj = CoffFile::parse(&data[..]).unwrap(); assert_eq!(obj.architecture(), Architecture::X86_64); let expected_manifest = fs::read(&sample_manifest_path()).unwrap(); check_object_file(obj, &expected_manifest); } #[test] fn link_lib_gnu() { let res = do_embed_file(MachineType::X86_64, TargetOs::WindowsGnu); assert!(res.object_file().exists()); let object_option = format!("cargo:rustc-link-arg-bins={}", res.object_file().display()); assert_eq!(res.lines(), &[object_option.as_str()]); } #[test] fn link_file_msvc() { let res = do_embed_file(MachineType::X86_64, TargetOs::WindowsMsvc); assert!(!res.object_file().exists()); let mut input_option = String::from("cargo:rustc-link-arg-bins=/MANIFESTINPUT:"); input_option.push_str(res.manifest_path.canonicalize().unwrap().to_str().unwrap()); assert_eq!( res.lines(), &[ "cargo:rustc-link-arg-bins=/MANIFEST:EMBED", input_option.as_str(), "cargo:rustc-link-arg-bins=/MANIFESTUAC:NO" ] ); } struct EmbedResult { manifest_path: PathBuf, out_dir: TempDir, output: String, } impl EmbedResult { fn object_file(&self) -> PathBuf { self.out_dir.path().join("embed-manifest.o") } fn lines(&self) -> Vec<&str> { self.output.lines().collect() } } fn sample_manifest_path() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("testdata/sample.exe.manifest") } fn do_embed_file(arch: MachineType, os: TargetOs) -> EmbedResult { let manifest_path = sample_manifest_path(); let out_dir = tempdir().unwrap(); let mut buf: Vec = Vec::new(); if matches!(os, TargetOs::WindowsMsvc) { link_manifest_msvc(&manifest_path, &mut buf).unwrap(); } else { link_manifest_gnu(&fs::read(&manifest_path).unwrap(), out_dir.path(), arch, &mut buf).unwrap(); } EmbedResult { manifest_path, out_dir, output: String::from_utf8(buf).unwrap(), } } #[test] fn object_file_x86() { let manifest = new_manifest("Test.X86").to_string().into_bytes(); let file = create_object_file(&manifest, MachineType::I386).unwrap(); let obj = CoffFile::parse(&file[..]).unwrap(); assert_eq!(obj.architecture(), Architecture::I386); check_object_file(obj, &manifest); } #[test] fn object_file_x86_64() { let manifest = new_manifest("Test.X86_64").to_string().into_bytes(); let file = create_object_file(&manifest, MachineType::X86_64).unwrap(); let obj = CoffFile::parse(&file[..]).unwrap(); assert_eq!(obj.architecture(), Architecture::X86_64); check_object_file(obj, &manifest); } #[test] fn object_file_aarch64() { let manifest = new_manifest("Test.AARCH64").to_string().into_bytes(); let file = create_object_file(&manifest, MachineType::Aarch64).unwrap(); let obj = CoffFile::parse(&file[..]).unwrap(); assert_eq!(obj.architecture(), Architecture::Aarch64); check_object_file(obj, &manifest); } fn check_object_file(obj: CoffFile, expected_manifest: &[u8]) { // There should be one sections `.rsrc`. assert_eq!( obj.sections().map(|s| s.name().unwrap().to_string()).collect::>(), &[".rsrc"] ); // There should be one section symbol. assert_eq!( obj.symbols().map(|s| s.name().unwrap().to_string()).collect::>(), &[".rsrc"] ); // The resource section must be a data section. let rsrc = obj.section_by_name(".rsrc").unwrap(); assert_eq!(rsrc.address(), 0); assert_eq!(rsrc.kind(), SectionKind::Data); // The data RVA in the resource data entry must be relocatable. let (addr, reloc) = rsrc.relocations().next().unwrap(); assert_eq!(reloc.kind(), RelocationKind::ImageOffset); assert_eq!(reloc.encoding(), RelocationEncoding::Generic); assert_eq!(addr, 0x48); // size of the directory table, three directories, and no strings assert_eq!(reloc.addend(), 0); // The resource directory contains one manifest resource type subdirectory. let data = rsrc.data().unwrap(); let (dir, rest) = pod::from_bytes::(data).unwrap(); assert_eq!(0, dir.number_of_named_entries.get(LittleEndian)); assert_eq!(1, dir.number_of_id_entries.get(LittleEndian)); let (entries, _) = pod::slice_from_bytes::(rest, 1).unwrap(); assert_eq!(24, entries[0].name_or_id.get(LittleEndian)); let offset = entries[0].offset_to_data_or_directory.get(LittleEndian); assert_eq!(IMAGE_RESOURCE_DATA_IS_DIRECTORY, offset & IMAGE_RESOURCE_DATA_IS_DIRECTORY); let offset = (offset & !IMAGE_RESOURCE_DATA_IS_DIRECTORY) as usize; // The manifest subdirectory contains one image (not DLL) manifest subdirectory. let (dir, rest) = pod::from_bytes::(&data[offset..]).unwrap(); assert_eq!(0, dir.number_of_named_entries.get(LittleEndian)); assert_eq!(1, dir.number_of_id_entries.get(LittleEndian)); let (entries, _) = pod::slice_from_bytes::(rest, 1).unwrap(); assert_eq!(1, entries[0].name_or_id.get(LittleEndian)); let offset = entries[0].offset_to_data_or_directory.get(LittleEndian); assert_eq!(IMAGE_RESOURCE_DATA_IS_DIRECTORY, offset & IMAGE_RESOURCE_DATA_IS_DIRECTORY); let offset = (offset & !IMAGE_RESOURCE_DATA_IS_DIRECTORY) as usize; // The image manifest subdirectory contains one US English manifest data entry. let (dir, rest) = pod::from_bytes::(&data[offset..]).unwrap(); assert_eq!(0, dir.number_of_named_entries.get(LittleEndian)); assert_eq!(1, dir.number_of_id_entries.get(LittleEndian)); let (entries, _) = pod::slice_from_bytes::(rest, 1).unwrap(); assert_eq!(0x0409, entries[0].name_or_id.get(LittleEndian)); let offset = entries[0].offset_to_data_or_directory.get(LittleEndian); assert_eq!(0, offset & IMAGE_RESOURCE_DATA_IS_DIRECTORY); let offset = offset as usize; // The manifest data matches what was added. let (entry, resource_data) = pod::from_bytes::(&data[offset..]).unwrap(); let end = entry.size.get(LittleEndian) as usize; let manifest = &resource_data[..end]; assert_eq!(manifest, expected_manifest); }