Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand tests: test multiple object files in same archive #11

Merged
merged 1 commit into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 224 additions & 0 deletions tests/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
#![allow(dead_code)]

use std::fs;
use std::io::Cursor;
use std::path::{Path, PathBuf};
use std::process::Command;

use ar_archive_writer::{ArchiveKind, NewArchiveMember};
use object::write::{self, Object};
use object::{Architecture, BinaryFormat, Endianness, SymbolFlags, SymbolKind, SymbolScope};
use pretty_assertions::assert_eq;

/// Creates the temporary directory for a test.
pub fn create_tmp_dir(test_name: &str) -> PathBuf {
let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join(test_name);
match fs::remove_dir_all(&tmpdir) {
Ok(_) => {}
Err(err) => {
if err.kind() != std::io::ErrorKind::NotFound {
panic!("Failed to delete directory: {:?}", tmpdir);
}
}
}
fs::create_dir_all(&tmpdir).unwrap();
tmpdir
}

fn run_llvm_ar(object_paths: &[PathBuf], archive_path: &Path, archive_kind: ArchiveKind) {
let ar_path = cargo_binutils::Tool::Ar.path().unwrap();

let format_arg = match archive_kind {
ArchiveKind::Gnu => "gnu",
ArchiveKind::Darwin => "darwin",
ArchiveKind::AixBig => "bigarchive",
_ => panic!("unsupported archive kind: {:?}", archive_kind),
};

let output = Command::new(ar_path)
.arg(format!("--format={}", format_arg))
.arg("rcs")
.arg(archive_path)
.args(object_paths)
.output()
.unwrap();
assert_eq!(
String::from_utf8_lossy(&output.stderr),
"",
"llvm-ar failed. archive: {archive_path:?}"
);
}

/// Creates an archive with the given objects using `llvm-ar`.
/// The generated archive is written to disk as `output_llvm_ar.a`.
pub fn create_archive_with_llvm_ar<'a>(
tmpdir: &Path,
archive_kind: ArchiveKind,
input_objects: impl IntoIterator<Item = (&'static str, &'a [u8])>,
) -> Vec<u8> {
let archive_file_path = tmpdir.join("output_llvm_ar.a");

let input_file_paths = input_objects
.into_iter()
.map(|(name, bytes)| {
let input_file_path = tmpdir.join(name);
if name.contains('/') {
fs::create_dir_all(input_file_path.parent().unwrap()).unwrap();
}
fs::write(&input_file_path, bytes).unwrap();
input_file_path
})
.collect::<Vec<_>>();

run_llvm_ar(&input_file_paths, &archive_file_path, archive_kind);
fs::read(archive_file_path).unwrap()
}

/// Creates an archive with the given objects using `ar_archive_writer`.
/// The generated archive is written to disk as `output_ar_archive_writer.a`.
pub fn create_archive_with_ar_archive_writer<'a>(
tmpdir: &Path,
archive_kind: ArchiveKind,
input_objects: impl IntoIterator<Item = (&'static str, &'a [u8])>,
) -> Vec<u8> {
let members = input_objects
.into_iter()
.map(|(name, bytes)| NewArchiveMember {
buf: Box::new(bytes) as Box<dyn AsRef<[u8]>>,
get_symbols: ar_archive_writer::get_native_object_symbols,
member_name: name
.rsplit_once('/')
.map_or(name, |(_, filename)| filename)
.to_string(),
mtime: 0,
uid: 0,
gid: 0,
perms: 0o644,
})
.collect::<Vec<_>>();
let mut output_bytes = Cursor::new(Vec::new());
ar_archive_writer::write_archive_to_stream(
&mut output_bytes,
&members,
true,
archive_kind,
true,
false,
)
.unwrap();

let output_archive_bytes = output_bytes.into_inner();
let ar_archive_writer_file_path = tmpdir.join("output_ar_archive_writer.a");
fs::write(&ar_archive_writer_file_path, &output_archive_bytes).unwrap();
output_archive_bytes
}

/// Helper for comparing archives generated by `llvm-ar` and `ar_archive_writer`
/// across a variety of archive kinds and their relevant object formats.
pub fn generate_archive_and_compare<F>(test_name: &str, generate_objects: F)
where
F: Fn(Architecture, Endianness, BinaryFormat) -> Vec<(&'static str, Vec<u8>)>,
{
for (architecture, endianness, binary_format, archive_kind) in [
// Elf + GNU
(
Architecture::X86_64,
Endianness::Little,
BinaryFormat::Elf,
ArchiveKind::Gnu,
),
(
Architecture::I386,
Endianness::Little,
BinaryFormat::Elf,
ArchiveKind::Gnu,
),
(
Architecture::Aarch64,
Endianness::Little,
BinaryFormat::Elf,
ArchiveKind::Gnu,
),
// AIX Big
(
Architecture::PowerPc64,
Endianness::Big,
BinaryFormat::Elf,
ArchiveKind::AixBig,
),
// PE + GNU
(
Architecture::X86_64,
Endianness::Little,
BinaryFormat::Coff,
ArchiveKind::Gnu,
),
(
Architecture::I386,
Endianness::Little,
BinaryFormat::Coff,
ArchiveKind::Gnu,
),
// MachO
(
Architecture::X86_64,
Endianness::Little,
BinaryFormat::MachO,
ArchiveKind::Darwin,
),
(
Architecture::Aarch64,
Endianness::Little,
BinaryFormat::MachO,
ArchiveKind::Darwin,
),
] {
let tmpdir = create_tmp_dir(test_name);
let input_objects = generate_objects(architecture, endianness, binary_format);
let llvm_ar_archive = create_archive_with_llvm_ar(
&tmpdir,
archive_kind,
input_objects
.iter()
.map(|(name, bytes)| (*name, bytes.as_slice())),
);
let ar_archive_writer_archive = create_archive_with_ar_archive_writer(
&tmpdir,
archive_kind,
input_objects
.iter()
.map(|(name, bytes)| (*name, bytes.as_slice())),
);

assert_eq!(
llvm_ar_archive, ar_archive_writer_archive,
"Archives differ for architecture: {architecture:?}, binary_format: {binary_format:?}, archive_kind: {archive_kind:?}",
);
}
}

pub fn add_file_with_functions_to_object(
object: &mut Object,
file_name: &[u8],
func_names: &[&[u8]],
) {
object.add_file_symbol(file_name.to_vec());

let text = object.section_id(write::StandardSection::Text);
object.append_section_data(text, &[1; 30], 4);

for func_name in func_names {
let offset = object.append_section_data(text, &[1; 30], 4);

object.add_symbol(write::Symbol {
name: func_name.to_vec(),
value: offset,
size: 32,
kind: SymbolKind::Text,
scope: SymbolScope::Linkage,
weak: false,
section: write::SymbolSection::Section(text),
flags: SymbolFlags::None,
});
}
}
65 changes: 65 additions & 0 deletions tests/multiple_objects.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use object::{write, Architecture};

mod common;

/// Tests creating an archive with multiple objects.
/// Note that `func_overlapping` exists in both objects - this is to test
/// deduplication of symbols in the symbol table (where supported).
#[test]
fn basic_multiple_objects() {
common::generate_archive_and_compare(
"basic_multiple_objects",
|architecture, endianness, binary_format| {
let mut object1 = write::Object::new(binary_format, architecture, endianness);
// FIXME: Need 64-bit Big AIX symbol table support to match llvm-ar.
if architecture != Architecture::PowerPc64 {
common::add_file_with_functions_to_object(
&mut object1,
b"file1.c",
&[b"func1", b"func2", b"func_overlapping"],
);
}

let mut object2 = write::Object::new(binary_format, architecture, endianness);
if architecture != Architecture::PowerPc64 {
common::add_file_with_functions_to_object(
&mut object2,
b"file2.c",
&[b"func3", b"func4", b"func_overlapping"],
);
}

vec![
("file1.o", object1.write().unwrap()),
("file2.o", object2.write().unwrap()),
]
},
);
}

/// Tests creating an archive with multiple objects with the same name.
/// This is important for Mach), which uses the timestamp when in deterministic
/// mode to differentiate the two objects.
#[test]
fn multiple_objects_same_name() {
common::generate_archive_and_compare(
"multiple_objects_same_name",
|architecture, endianness, binary_format| {
let mut object1 = write::Object::new(binary_format, architecture, endianness);
// FIXME: Need 64-bit Big AIX symbol table support to match llvm-ar.
if architecture != Architecture::PowerPc64 {
common::add_file_with_functions_to_object(&mut object1, b"file1.c", &[b"func1"]);
}

let mut object2 = write::Object::new(binary_format, architecture, endianness);
if architecture != Architecture::PowerPc64 {
common::add_file_with_functions_to_object(&mut object2, b"file2.c", &[b"func2"]);
}

vec![
("1/file.o", object1.write().unwrap()),
("2/file.o", object2.write().unwrap()),
]
},
);
}
Loading
Loading