// Derived from object's round_trip.rs: // https://github.com/gimli-rs/object/blob/0.35.0/tests/round_trip/mod.rs use ar_archive_writer::ArchiveKind; use object::{read, write, RelocationFlags, SubArchitecture}; use object::{ Architecture, BinaryFormat, Endianness, RelocationEncoding, RelocationKind, SymbolFlags, SymbolKind, SymbolScope, }; use pretty_assertions::assert_eq; mod common; fn round_trip_and_diff( test_name: &str, object: write::Object<'_>, archive_kind: ArchiveKind, trim_output_bytes: usize, ) { let tmpdir = common::create_tmp_dir(test_name); let input_bytes = object.write().unwrap(); let is_ec = object.sub_architecture() == Some(SubArchitecture::Arm64EC); // Create a new archive using ar_archive_writer. let output_archive_bytes = common::create_archive_with_ar_archive_writer( &tmpdir, archive_kind, [("input.o", input_bytes.as_slice())], false, is_ec, ); // Use llvm-ar to create the archive and diff with ar_archive_writer. let output_llvm_ar_bytes = common::create_archive_with_llvm_ar( &tmpdir, archive_kind, [("input.o", input_bytes.as_slice())], false, is_ec, ); assert_eq!( output_archive_bytes, output_llvm_ar_bytes, "Comparing ar_archive_writer to llvm-ar. Test case: build {:?} for {:?}", archive_kind, object ); // Read the archive and member using object and diff with original data. { let output_archive = read::archive::ArchiveFile::parse(output_archive_bytes.as_slice()).unwrap(); let mut members = output_archive.members(); let output_member = members.next().unwrap().unwrap(); if archive_kind != ArchiveKind::Coff { assert_eq!(output_member.name(), b"input.o"); } let output_bytes = output_member.data(output_archive_bytes.as_slice()).unwrap(); // Apply fixup if required. let output_bytes = &output_bytes[..output_bytes.len() - trim_output_bytes]; assert_eq!( &input_bytes, output_bytes, "Comparing object after round-trip. Test case: build {:?} for {:?}", archive_kind, object ); } } #[test] fn coff_any() { for (arch, sub_arch) in [ (Architecture::Aarch64, None), (Architecture::Aarch64, Some(SubArchitecture::Arm64EC)), (Architecture::Arm, None), (Architecture::I386, None), (Architecture::X86_64, None), ] .iter() .copied() { for archive_kind in [ArchiveKind::Gnu, ArchiveKind::Coff] { let mut object = write::Object::new(BinaryFormat::Coff, arch, Endianness::Little); object.set_sub_architecture(sub_arch); object.add_file_symbol(b"file.c".to_vec()); let text = object.section_id(write::StandardSection::Text); object.append_section_data(text, &[1; 30], 4); let func1_offset = object.append_section_data(text, &[1; 30], 4); assert_eq!(func1_offset, 32); let func1_symbol = object.add_symbol(write::Symbol { name: b"func1".to_vec(), value: func1_offset, size: 32, kind: SymbolKind::Text, scope: SymbolScope::Linkage, weak: false, section: write::SymbolSection::Section(text), flags: SymbolFlags::None, }); let func2_offset = object.append_section_data(text, &[1; 30], 4); assert_eq!(func2_offset, 64); object.add_symbol(write::Symbol { name: b"func2_long".to_vec(), value: func2_offset, size: 32, kind: SymbolKind::Text, scope: SymbolScope::Linkage, weak: false, section: write::SymbolSection::Section(text), flags: SymbolFlags::None, }); object .add_relocation( text, write::Relocation { offset: 8, symbol: func1_symbol, addend: 0, flags: RelocationFlags::Generic { kind: RelocationKind::Absolute, encoding: RelocationEncoding::Generic, size: arch.address_size().unwrap().bytes() * 8, }, }, ) .unwrap(); round_trip_and_diff("coff_any", object, archive_kind, 0); } } } #[test] fn elf_x86_64() { let mut object = write::Object::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little); object.add_file_symbol(b"file.c".to_vec()); let text = object.section_id(write::StandardSection::Text); object.append_section_data(text, &[1; 30], 4); let func1_offset = object.append_section_data(text, &[1; 30], 4); assert_eq!(func1_offset, 32); let func1_symbol = object.add_symbol(write::Symbol { name: b"func1".to_vec(), value: func1_offset, size: 32, kind: SymbolKind::Text, scope: SymbolScope::Linkage, weak: false, section: write::SymbolSection::Section(text), flags: SymbolFlags::None, }); object .add_relocation( text, write::Relocation { offset: 8, symbol: func1_symbol, addend: 0, flags: RelocationFlags::Generic { kind: RelocationKind::Absolute, encoding: RelocationEncoding::Generic, size: 64, }, }, ) .unwrap(); round_trip_and_diff("elf_x86_64", object, ArchiveKind::Gnu, 0); } #[test] fn elf_any() { for (arch, endian, archive_kinds) in [ ( Architecture::Aarch64, Endianness::Little, &[ArchiveKind::Gnu][..], ), ( Architecture::Aarch64_Ilp32, Endianness::Little, &[ArchiveKind::Gnu], ), (Architecture::Arm, Endianness::Little, &[ArchiveKind::Gnu]), (Architecture::Avr, Endianness::Little, &[ArchiveKind::Gnu]), (Architecture::Bpf, Endianness::Little, &[ArchiveKind::Gnu]), (Architecture::Csky, Endianness::Little, &[ArchiveKind::Gnu]), (Architecture::I386, Endianness::Little, &[ArchiveKind::Gnu]), ( Architecture::X86_64, Endianness::Little, &[ArchiveKind::Gnu], ), ( Architecture::X86_64_X32, Endianness::Little, &[ArchiveKind::Gnu], ), ( Architecture::Hexagon, Endianness::Little, &[ArchiveKind::Gnu], ), ( Architecture::LoongArch64, Endianness::Little, &[ArchiveKind::Gnu], ), (Architecture::Mips, Endianness::Little, &[ArchiveKind::Gnu]), ( Architecture::Mips64, Endianness::Little, &[ArchiveKind::Gnu], ), ( Architecture::Msp430, Endianness::Little, &[ArchiveKind::Gnu], ), (Architecture::PowerPc, Endianness::Big, &[ArchiveKind::Gnu]), ( Architecture::PowerPc64, Endianness::Big, &[ArchiveKind::Gnu, ArchiveKind::AixBig], ), ( Architecture::Riscv32, Endianness::Little, &[ArchiveKind::Gnu], ), ( Architecture::Riscv64, Endianness::Little, &[ArchiveKind::Gnu], ), (Architecture::S390x, Endianness::Big, &[ArchiveKind::Gnu]), (Architecture::Sbf, Endianness::Little, &[ArchiveKind::Gnu]), (Architecture::Sparc64, Endianness::Big, &[ArchiveKind::Gnu]), ( Architecture::Xtensa, Endianness::Little, &[ArchiveKind::Gnu], ), ] .iter() .copied() { for archive_kind in archive_kinds { let mut object = write::Object::new(BinaryFormat::Elf, arch, endian); let section = object.section_id(write::StandardSection::Data); object.append_section_data(section, &[1; 30], 4); let symbol = object.section_symbol(section); object .add_relocation( section, write::Relocation { offset: 8, symbol, addend: 0, flags: RelocationFlags::Generic { kind: RelocationKind::Absolute, encoding: RelocationEncoding::Generic, size: 32, }, }, ) .unwrap(); if arch.address_size().unwrap().bytes() >= 8 { object .add_relocation( section, write::Relocation { offset: 16, symbol, addend: 0, flags: RelocationFlags::Generic { kind: RelocationKind::Absolute, encoding: RelocationEncoding::Generic, size: 64, }, }, ) .unwrap(); } round_trip_and_diff("elf_any", object, *archive_kind, 0); } } } #[test] fn macho_x86_64() { let mut object = write::Object::new( BinaryFormat::MachO, Architecture::X86_64, Endianness::Little, ); object.add_file_symbol(b"file.c".to_vec()); let text = object.section_id(write::StandardSection::Text); object.append_section_data(text, &[1; 30], 4); let func1_offset = object.append_section_data(text, &[1; 30], 4); assert_eq!(func1_offset, 32); let func1_symbol = object.add_symbol(write::Symbol { name: b"func1".to_vec(), value: func1_offset, size: 32, kind: SymbolKind::Text, scope: SymbolScope::Linkage, weak: false, section: write::SymbolSection::Section(text), flags: SymbolFlags::None, }); object .add_relocation( text, write::Relocation { offset: 8, symbol: func1_symbol, addend: 0, flags: RelocationFlags::Generic { kind: RelocationKind::Absolute, encoding: RelocationEncoding::Generic, size: 64, }, }, ) .unwrap(); object .add_relocation( text, write::Relocation { offset: 16, symbol: func1_symbol, addend: -4, flags: RelocationFlags::Generic { kind: RelocationKind::Relative, encoding: RelocationEncoding::Generic, size: 32, }, }, ) .unwrap(); round_trip_and_diff("macho_x86_64", object, ArchiveKind::Darwin, 0); } #[test] fn macho_any() { // 32-bit object files get additional padding after the round-trip: // https://github.com/llvm/llvm-project/blob/3d3ef9d073e1e27ea57480b371b7f5a9f5642ed2/llvm/lib/Object/ArchiveWriter.cpp#L560-L565 for (arch, subarch, endian, trim_output_bytes) in [ (Architecture::Aarch64, None, Endianness::Little, 0), ( Architecture::Aarch64, Some(SubArchitecture::Arm64E), Endianness::Little, 0, ), (Architecture::Aarch64_Ilp32, None, Endianness::Little, 4), /* TODO: (Architecture::Arm, None, Endianness::Little), */ (Architecture::I386, None, Endianness::Little, 4), (Architecture::X86_64, None, Endianness::Little, 0), /* TODO: (Architecture::PowerPc, None, Endianness::Big), (Architecture::PowerPc64, None, Endianness::Big), */ ] .iter() .copied() { let mut object = write::Object::new(BinaryFormat::MachO, arch, endian); object.set_sub_architecture(subarch); let section = object.section_id(write::StandardSection::Data); object.append_section_data(section, &[1; 30], 4); let symbol = object.section_symbol(section); object .add_relocation( section, write::Relocation { offset: 8, symbol, addend: 0, flags: RelocationFlags::Generic { kind: RelocationKind::Absolute, encoding: RelocationEncoding::Generic, size: 32, }, }, ) .unwrap(); if arch.address_size().unwrap().bytes() >= 8 { object .add_relocation( section, write::Relocation { offset: 16, symbol, addend: 0, flags: RelocationFlags::Generic { kind: RelocationKind::Absolute, encoding: RelocationEncoding::Generic, size: 64, }, }, ) .unwrap(); } round_trip_and_diff("macho_any", object, ArchiveKind::Darwin, trim_output_bytes); } } #[test] fn xcoff_powerpc() { for arch in [Architecture::PowerPc, Architecture::PowerPc64] { let mut object = write::Object::new(BinaryFormat::Xcoff, arch, Endianness::Big); object.add_file_symbol(b"file.c".to_vec()); let text = object.section_id(write::StandardSection::Text); object.append_section_data(text, &[1; 30], 4); let func1_offset = object.append_section_data(text, &[1; 30], 4); assert_eq!(func1_offset, 32); let func1_symbol = object.add_symbol(write::Symbol { name: b"func1".to_vec(), value: func1_offset, size: 32, kind: SymbolKind::Text, scope: SymbolScope::Linkage, weak: false, section: write::SymbolSection::Section(text), flags: SymbolFlags::None, }); object .add_relocation( text, write::Relocation { offset: 8, symbol: func1_symbol, addend: 0, flags: RelocationFlags::Generic { kind: RelocationKind::Absolute, encoding: RelocationEncoding::Generic, size: 64, }, }, ) .unwrap(); round_trip_and_diff("xcoff_powerpc", object, ArchiveKind::Gnu, 0); } }