388 lines
13 KiB
Rust
388 lines
13 KiB
Rust
|
|
use std::fs;
|
||
|
|
use std::io::Cursor;
|
||
|
|
use std::path::{Path, PathBuf};
|
||
|
|
use std::process::Command;
|
||
|
|
|
||
|
|
use ar_archive_writer::{ArchiveKind, COFFShortExport, MachineTypes};
|
||
|
|
use common::{create_archive_with_ar_archive_writer, create_archive_with_llvm_ar};
|
||
|
|
use object::coff::CoffFile;
|
||
|
|
use object::pe::ImageFileHeader;
|
||
|
|
use object::read::archive::ArchiveFile;
|
||
|
|
use object::{Architecture, Object, ObjectSection, ObjectSymbol, SubArchitecture, bytes_of};
|
||
|
|
use pretty_assertions::assert_eq;
|
||
|
|
|
||
|
|
mod common;
|
||
|
|
|
||
|
|
const DEFAULT_EXPORT: COFFShortExport = COFFShortExport {
|
||
|
|
name: String::new(),
|
||
|
|
ext_name: None,
|
||
|
|
symbol_name: None,
|
||
|
|
export_as: None,
|
||
|
|
import_name: None,
|
||
|
|
ordinal: 0,
|
||
|
|
noname: false,
|
||
|
|
data: false,
|
||
|
|
private: false,
|
||
|
|
constant: false,
|
||
|
|
};
|
||
|
|
|
||
|
|
fn get_members(machine_type: MachineTypes) -> Vec<COFFShortExport> {
|
||
|
|
let prefix = match machine_type {
|
||
|
|
MachineTypes::I386 => "_",
|
||
|
|
_ => "",
|
||
|
|
};
|
||
|
|
// This must match import_library.def.
|
||
|
|
vec![
|
||
|
|
COFFShortExport {
|
||
|
|
name: format!("{prefix}NormalFunc"),
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: format!("{prefix}NormalData"),
|
||
|
|
data: true,
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: format!("{prefix}NormalConstant"),
|
||
|
|
constant: true,
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: format!("{prefix}PrivateFunc"),
|
||
|
|
private: true,
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: format!("{prefix}FuncWithOrdinal"),
|
||
|
|
ordinal: 1,
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: format!("{prefix}FuncWithNoName"),
|
||
|
|
ordinal: 2,
|
||
|
|
noname: true,
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: format!("{prefix}InternalName"),
|
||
|
|
ext_name: Some(format!("{prefix}RenamedFunc")),
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: format!("{prefix}OtherModule.OtherName"),
|
||
|
|
ext_name: Some(format!("{prefix}ReexportedFunc")),
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: format!("{prefix}OtherModule.#42"),
|
||
|
|
ext_name: Some(format!("{prefix}ReexportedViaOrd")),
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: format!("{prefix}FuncWithImportName"),
|
||
|
|
import_name: Some("ImportName".to_string()),
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: "?foo@@$$hYAHXZ".to_string(),
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: "?GetValue@?$Wrapper@UA@@@@$$hQEBAHXZ".to_string(),
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: "??2@$$hYAPEAX_K@Z".to_string(),
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: "?f@Base@D@C@B@A@@$$hUEAAHXZ".to_string(),
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: "??0Base@D@C@B@A@@$$hQEAA@XZ".to_string(),
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: "?f@Derived@@$$hUEAAHXZ".to_string(),
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: "??0Derived@@$$hQEAA@XZ".to_string(),
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: "?MakeObj@@$$hYAPEAUBase@D@C@B@A@@XZ".to_string(),
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
COFFShortExport {
|
||
|
|
name: "?GetValue@?$Wrapper@UA@@@@$$hQEBAHUZ@?$WW@UA@@@@@Z".to_string(),
|
||
|
|
..DEFAULT_EXPORT
|
||
|
|
},
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|
||
|
|
fn create_import_library_with_ar_archive_writer(
|
||
|
|
temp_dir: &Path,
|
||
|
|
machine_type: MachineTypes,
|
||
|
|
mingw: bool,
|
||
|
|
comdat: bool,
|
||
|
|
) -> Vec<u8> {
|
||
|
|
let mut output_bytes = Cursor::new(Vec::new());
|
||
|
|
ar_archive_writer::write_import_library(
|
||
|
|
&mut output_bytes,
|
||
|
|
"MyLibrary.dll",
|
||
|
|
&get_members(machine_type),
|
||
|
|
machine_type,
|
||
|
|
mingw,
|
||
|
|
comdat,
|
||
|
|
&[],
|
||
|
|
)
|
||
|
|
.unwrap();
|
||
|
|
|
||
|
|
let output_archive_bytes = output_bytes.into_inner();
|
||
|
|
let ar_archive_writer_file_path = temp_dir.join("output_ar_archive_writer.a");
|
||
|
|
fs::write(ar_archive_writer_file_path, &output_archive_bytes).unwrap();
|
||
|
|
|
||
|
|
output_archive_bytes
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn compare_to_lib() {
|
||
|
|
for machine_type in [
|
||
|
|
MachineTypes::I386,
|
||
|
|
MachineTypes::AMD64,
|
||
|
|
MachineTypes::ARMNT,
|
||
|
|
MachineTypes::ARM64,
|
||
|
|
MachineTypes::ARM64EC,
|
||
|
|
] {
|
||
|
|
let temp_dir = common::create_tmp_dir("import_library_compare_to_lib");
|
||
|
|
|
||
|
|
let archive_writer_bytes =
|
||
|
|
create_import_library_with_ar_archive_writer(&temp_dir, machine_type, false, false);
|
||
|
|
|
||
|
|
let llvm_lib_bytes = {
|
||
|
|
let machine_arg = match machine_type {
|
||
|
|
MachineTypes::I386 => "X86",
|
||
|
|
MachineTypes::AMD64 => "X64",
|
||
|
|
MachineTypes::ARMNT => "ARM",
|
||
|
|
MachineTypes::ARM64 => "ARM64",
|
||
|
|
MachineTypes::ARM64EC => "ARM64EC",
|
||
|
|
_ => panic!("Unsupported machine type"),
|
||
|
|
};
|
||
|
|
|
||
|
|
let llvm_lib_tool_path = common::create_llvm_lib_tool(&temp_dir);
|
||
|
|
let output_library_path = temp_dir.join("output_llvm_lib.a");
|
||
|
|
let def_path =
|
||
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/import_library.def");
|
||
|
|
let output = Command::new(llvm_lib_tool_path)
|
||
|
|
.arg(format!("/MACHINE:{machine_arg}"))
|
||
|
|
.arg(format!("/DEF:{}", def_path.to_string_lossy()))
|
||
|
|
.arg(format!("/OUT:{}", output_library_path.to_string_lossy()))
|
||
|
|
.output()
|
||
|
|
.unwrap();
|
||
|
|
|
||
|
|
assert_eq!(
|
||
|
|
String::from_utf8_lossy(&output.stderr),
|
||
|
|
"",
|
||
|
|
"llvm-lib failed. archive: {output_library_path:?}"
|
||
|
|
);
|
||
|
|
fs::read(output_library_path).unwrap()
|
||
|
|
};
|
||
|
|
|
||
|
|
assert_eq!(
|
||
|
|
llvm_lib_bytes, archive_writer_bytes,
|
||
|
|
"Import library differs. Machine type: {machine_type:?}",
|
||
|
|
);
|
||
|
|
|
||
|
|
compare_comdat(
|
||
|
|
&create_import_library_with_ar_archive_writer(&temp_dir, machine_type, false, true),
|
||
|
|
&llvm_lib_bytes,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn compare_to_dlltool() {
|
||
|
|
for machine_type in [
|
||
|
|
MachineTypes::I386,
|
||
|
|
MachineTypes::AMD64,
|
||
|
|
MachineTypes::ARMNT,
|
||
|
|
MachineTypes::ARM64,
|
||
|
|
] {
|
||
|
|
let temp_dir = common::create_tmp_dir("import_library_compare_to_dlltool");
|
||
|
|
|
||
|
|
let archive_writer_bytes =
|
||
|
|
create_import_library_with_ar_archive_writer(&temp_dir, machine_type, true, false);
|
||
|
|
|
||
|
|
let llvm_lib_bytes = {
|
||
|
|
let machine_arg = match machine_type {
|
||
|
|
MachineTypes::I386 => "i386",
|
||
|
|
MachineTypes::AMD64 => "i386:x86-64",
|
||
|
|
MachineTypes::ARMNT => "arm",
|
||
|
|
MachineTypes::ARM64 => "arm64",
|
||
|
|
_ => panic!("Unsupported machine type"),
|
||
|
|
};
|
||
|
|
|
||
|
|
let llvm_lib_tool_path = common::create_llvm_dlltool_tool(&temp_dir);
|
||
|
|
let output_library_path = temp_dir.join("output_llvm_lib.a");
|
||
|
|
let def_path =
|
||
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/import_library.def");
|
||
|
|
let output = Command::new(llvm_lib_tool_path)
|
||
|
|
.arg("--machine")
|
||
|
|
.arg(machine_arg)
|
||
|
|
.arg("--input-def")
|
||
|
|
.arg(def_path)
|
||
|
|
.arg("--output-lib")
|
||
|
|
.arg(&output_library_path)
|
||
|
|
.output()
|
||
|
|
.unwrap();
|
||
|
|
|
||
|
|
assert_eq!(
|
||
|
|
String::from_utf8_lossy(&output.stderr),
|
||
|
|
"",
|
||
|
|
"llvm-lib failed. archive: {output_library_path:?}"
|
||
|
|
);
|
||
|
|
fs::read(output_library_path).unwrap()
|
||
|
|
};
|
||
|
|
|
||
|
|
assert_eq!(
|
||
|
|
llvm_lib_bytes, archive_writer_bytes,
|
||
|
|
"Import library differs. Machine type: {machine_type:?}",
|
||
|
|
);
|
||
|
|
|
||
|
|
compare_comdat(
|
||
|
|
&create_import_library_with_ar_archive_writer(&temp_dir, machine_type, true, true),
|
||
|
|
&llvm_lib_bytes,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Creates an import library and then wraps that in an archive.
|
||
|
|
#[test]
|
||
|
|
fn wrap_in_archive() {
|
||
|
|
for (architecture, subarch, machine_type) in [
|
||
|
|
(Architecture::I386, None, MachineTypes::I386),
|
||
|
|
(Architecture::X86_64, None, MachineTypes::AMD64),
|
||
|
|
(Architecture::Arm, None, MachineTypes::ARMNT),
|
||
|
|
(Architecture::Aarch64, None, MachineTypes::ARM64),
|
||
|
|
(
|
||
|
|
Architecture::Aarch64,
|
||
|
|
Some(SubArchitecture::Arm64EC),
|
||
|
|
MachineTypes::ARM64EC,
|
||
|
|
),
|
||
|
|
] {
|
||
|
|
let temp_dir = common::create_tmp_dir("import_library_wrap_in_archive");
|
||
|
|
|
||
|
|
let mut import_lib_bytes = Cursor::new(Vec::new());
|
||
|
|
ar_archive_writer::write_import_library(
|
||
|
|
&mut import_lib_bytes,
|
||
|
|
&temp_dir.join("MyLibrary.dll").to_string_lossy(),
|
||
|
|
&get_members(machine_type),
|
||
|
|
machine_type,
|
||
|
|
false,
|
||
|
|
false,
|
||
|
|
&[],
|
||
|
|
)
|
||
|
|
.unwrap();
|
||
|
|
let import_lib_bytes = import_lib_bytes.into_inner();
|
||
|
|
|
||
|
|
let is_ec = subarch == Some(SubArchitecture::Arm64EC);
|
||
|
|
let llvm_ar_archive = create_archive_with_llvm_ar(
|
||
|
|
&temp_dir,
|
||
|
|
ArchiveKind::Coff,
|
||
|
|
[("MyLibrary.dll.lib", import_lib_bytes.as_slice())],
|
||
|
|
false,
|
||
|
|
is_ec,
|
||
|
|
);
|
||
|
|
// When a archive is passed into lib.exe, it is opened and the individual members are included
|
||
|
|
// in the new output library. Also, for whatever reason, the members are reversed.
|
||
|
|
let archive = ArchiveFile::parse(import_lib_bytes.as_slice()).unwrap();
|
||
|
|
let mut members = archive
|
||
|
|
.members()
|
||
|
|
.map(|m| {
|
||
|
|
let member = m.unwrap();
|
||
|
|
(
|
||
|
|
String::from_utf8(member.name().to_vec()).unwrap(),
|
||
|
|
member.data(import_lib_bytes.as_slice()).unwrap(),
|
||
|
|
)
|
||
|
|
})
|
||
|
|
.collect::<Vec<_>>();
|
||
|
|
members.reverse();
|
||
|
|
let ar_archive_writer_archive = create_archive_with_ar_archive_writer(
|
||
|
|
&temp_dir,
|
||
|
|
ArchiveKind::Coff,
|
||
|
|
members.iter().map(|(name, data)| (name.as_str(), *data)),
|
||
|
|
false,
|
||
|
|
is_ec,
|
||
|
|
);
|
||
|
|
|
||
|
|
assert_eq!(
|
||
|
|
llvm_ar_archive, ar_archive_writer_archive,
|
||
|
|
"Archives differ for architecture: {architecture:?}, subarch: {subarch:?}, machine type: {machine_type:?}",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn compare_comdat(archive_writer_bytes: &[u8], llvm_bytes: &[u8]) {
|
||
|
|
let archive_writer = ArchiveFile::parse(archive_writer_bytes).unwrap();
|
||
|
|
let llvm = ArchiveFile::parse(llvm_bytes).unwrap();
|
||
|
|
|
||
|
|
for (archive_member, llvm_member) in archive_writer.members().zip(llvm.members()) {
|
||
|
|
let archive_member = archive_member.unwrap();
|
||
|
|
let llvm_member = llvm_member.unwrap();
|
||
|
|
|
||
|
|
// skip the EC symbols table.
|
||
|
|
if archive_member.name() == b"/<ECSYMBOLS>/" {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if archive_member.size() != llvm_member.size() {
|
||
|
|
// Ensure that the member header is the same except for the file size.
|
||
|
|
let mut llvm_file_header = *llvm_member.header().unwrap();
|
||
|
|
llvm_file_header.size = archive_member.header().unwrap().size;
|
||
|
|
assert_eq!(
|
||
|
|
bytes_of(archive_member.header().unwrap()),
|
||
|
|
bytes_of(&llvm_file_header)
|
||
|
|
);
|
||
|
|
|
||
|
|
// Make sure they are both COFF files with the same sections and symbols,
|
||
|
|
// except for the different naming for the null import descriptor.
|
||
|
|
let archive_data = archive_member.data(archive_writer_bytes).unwrap();
|
||
|
|
let llvm_data = llvm_member.data(llvm_bytes).unwrap();
|
||
|
|
let archive_file = CoffFile::<_, ImageFileHeader>::parse(archive_data).unwrap();
|
||
|
|
let llvm_file = CoffFile::<_, ImageFileHeader>::parse(llvm_data).unwrap();
|
||
|
|
|
||
|
|
for (archive_section, llvm_section) in archive_file.sections().zip(llvm_file.sections())
|
||
|
|
{
|
||
|
|
assert_eq!(archive_section.data(), llvm_section.data());
|
||
|
|
}
|
||
|
|
for (archive_symbol, llvm_symbol) in archive_file.symbols().zip(llvm_file.symbols()) {
|
||
|
|
if llvm_symbol.name().unwrap() == "__NULL_IMPORT_DESCRIPTOR" {
|
||
|
|
assert!(
|
||
|
|
archive_symbol
|
||
|
|
.name()
|
||
|
|
.unwrap()
|
||
|
|
.starts_with("__NULL_IMPORT_DESCRIPTOR_")
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
assert_eq!(archive_symbol.name(), llvm_symbol.name());
|
||
|
|
}
|
||
|
|
let archive_coff_symbol = archive_symbol.coff_symbol();
|
||
|
|
let mut llvm_coff_symbol = *llvm_symbol.coff_symbol();
|
||
|
|
llvm_coff_symbol.name = archive_coff_symbol.name;
|
||
|
|
assert_eq!(bytes_of(archive_coff_symbol), bytes_of(&llvm_coff_symbol));
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
assert_eq!(
|
||
|
|
bytes_of(archive_member.header().unwrap()),
|
||
|
|
bytes_of(llvm_member.header().unwrap())
|
||
|
|
);
|
||
|
|
assert_eq!(
|
||
|
|
archive_member.data(archive_writer_bytes).unwrap(),
|
||
|
|
llvm_member.data(llvm_bytes).unwrap()
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|