use object::write::{self, StandardSegment, Symbol, SymbolSection};
use object::{
elf, Architecture, BinaryFormat, Endianness, FileFlags, SectionFlags, SectionKind, SymbolFlags,
SymbolKind, SymbolScope,
};
use crate::platform_detection::{is_32bit, is_apple, is_windows};
use crate::target_info::RustcTargetInfo;
pub fn create_metadata_file(
target_info: &RustcTargetInfo,
target_triple: &str,
contents: &[u8],
symbol_name: &str,
) -> Option<Vec<u8>> {
let mut file = create_object_file(target_info, target_triple)?;
let section = file.add_section(
file.segment_name(StandardSegment::Data).to_vec(),
b".dep-v0".to_vec(),
SectionKind::ReadOnlyData,
);
if let BinaryFormat::Elf = file.format() {
file.section_mut(section).flags = SectionFlags::Elf { sh_flags: 0 };
};
let offset = file.append_section_data(section, contents, 1);
file.add_symbol(Symbol {
name: symbol_name.as_bytes().to_vec(),
value: offset,
size: contents.len() as u64,
kind: SymbolKind::Data,
scope: SymbolScope::Dynamic,
weak: false,
section: SymbolSection::Section(section),
flags: SymbolFlags::None,
});
Some(file.write().unwrap())
}
fn create_object_file(
info: &RustcTargetInfo,
target_triple: &str,
) -> Option<write::Object<'static>> {
let endianness = match info["target_endian"].as_str() {
"little" => Endianness::Little,
"big" => Endianness::Big,
_ => unreachable!(),
};
let architecture = match info["target_arch"].as_str() {
"arm" => Architecture::Arm,
"aarch64" => {
if is_32bit(info) {
Architecture::Aarch64_Ilp32
} else {
Architecture::Aarch64
}
}
"x86" => Architecture::I386,
"s390x" => Architecture::S390x,
"mips" => Architecture::Mips,
"mips64" => Architecture::Mips64,
"x86_64" => {
if is_32bit(info) {
Architecture::X86_64_X32
} else {
Architecture::X86_64
}
}
"powerpc" => Architecture::PowerPc,
"powerpc64" => Architecture::PowerPc64,
"riscv32" => Architecture::Riscv32,
"riscv64" => Architecture::Riscv64,
"sparc64" => Architecture::Sparc64,
"loongarch64" => Architecture::LoongArch64,
_ => return None,
};
let binary_format = if is_apple(info) {
BinaryFormat::MachO
} else if is_windows(info) {
BinaryFormat::Coff
} else {
BinaryFormat::Elf
};
let mut file = write::Object::new(binary_format, architecture, endianness);
let e_flags = match architecture {
Architecture::Mips => {
let arch = if target_triple.contains("r6") {
elf::EF_MIPS_ARCH_32R6
} else {
elf::EF_MIPS_ARCH_32R2
};
let mut e_flags = elf::EF_MIPS_CPIC | elf::EF_MIPS_ABI_O32 | arch;
if target_triple.contains("r6") {
e_flags |= elf::EF_MIPS_NAN2008;
}
e_flags
}
Architecture::Mips64 => {
#[allow(clippy::let_and_return)] let e_flags = elf::EF_MIPS_CPIC
| elf::EF_MIPS_PIC
| if target_triple.contains("r6") {
elf::EF_MIPS_ARCH_64R6 | elf::EF_MIPS_NAN2008
} else {
elf::EF_MIPS_ARCH_64R2
};
e_flags
}
Architecture::Riscv32 | Architecture::Riscv64 => {
let mut e_flags: u32 = 0x0;
let features = riscv_features(target_triple, info);
if features.contains('c') {
e_flags |= elf::EF_RISCV_RVC;
}
if features.contains('d') {
e_flags |= elf::EF_RISCV_FLOAT_ABI_DOUBLE;
} else if features.contains('f') {
e_flags |= elf::EF_RISCV_FLOAT_ABI_SINGLE;
} else {
e_flags |= elf::EF_RISCV_FLOAT_ABI_SOFT;
}
e_flags
}
Architecture::LoongArch64 => {
let mut e_flags: u32 = elf::EF_LARCH_OBJABI_V1;
let features = loongarch_features(target_triple);
if features.contains('d') {
e_flags |= elf::EF_LARCH_ABI_DOUBLE_FLOAT;
} else if features.contains('f') {
e_flags |= elf::EF_LARCH_ABI_SINGLE_FLOAT;
} else {
e_flags |= elf::EF_LARCH_ABI_SOFT_FLOAT;
}
e_flags
}
_ => 0,
};
let os_abi = match info["target_os"].as_str() {
"hermit" => elf::ELFOSABI_STANDALONE,
"freebsd" => elf::ELFOSABI_FREEBSD,
"solaris" => elf::ELFOSABI_SOLARIS,
_ => elf::ELFOSABI_NONE,
};
let abi_version = 0;
file.flags = FileFlags::Elf {
os_abi,
abi_version,
e_flags,
};
if binary_format == BinaryFormat::Coff {
let original_mangling = file.mangling();
file.set_mangling(write::Mangling::None);
let mut feature: u64 = 0;
if architecture == Architecture::I386 {
feature |= 1; }
file.add_symbol(Symbol {
name: b"@feat.00".to_vec(),
value: feature,
size: 0,
kind: SymbolKind::Data,
scope: SymbolScope::Compilation,
weak: false,
section: SymbolSection::Absolute,
flags: SymbolFlags::None,
});
file.set_mangling(original_mangling);
}
Some(file)
}
fn riscv_features(target_triple: &str, info: &RustcTargetInfo) -> String {
let arch = target_triple.split('-').next().unwrap();
assert_eq!(&arch[..5], "riscv");
let mut extensions = arch[7..].to_owned();
if extensions.contains('g') {
extensions.push_str("imadf");
}
match info["target_os"].as_str() {
"linux" | "android" => extensions.push_str("imadfc"),
_ => (),
}
extensions
}
fn loongarch_features(target_triple: &str) -> String {
match target_triple {
"loongarch64-unknown-none-softfloat" => "".to_string(),
_ => "f,d".to_string(),
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
use crate::target_info::parse_rustc_target_info;
#[test]
fn test_riscv_abi_detection() {
let info = HashMap::from([("target_os".to_owned(), "linux".to_owned())]);
let features = riscv_features("riscv64gc-unknown-linux-gnu", &info);
assert!(features.contains('c'));
assert!(features.contains('d'));
assert!(features.contains('f'));
let info = HashMap::from([("target_os".to_owned(), "none".to_owned())]);
let features = riscv_features("riscv32imac-unknown-none-elf", &info);
assert!(features.contains('c'));
assert!(!features.contains('d'));
assert!(!features.contains('f'));
let info = HashMap::from([("target_os".to_owned(), "none".to_owned())]);
let features = riscv_features("riscv32i-unknown-none-elf", &info);
assert!(!features.contains('c'));
assert!(!features.contains('d'));
assert!(!features.contains('f'));
let info = HashMap::from([("target_os".to_owned(), "none".to_owned())]);
let features = riscv_features("riscv32if-unknown-none-elf", &info);
assert!(!features.contains('c'));
assert!(!features.contains('d'));
assert!(features.contains('f'));
let info = HashMap::from([("target_os".to_owned(), "android".to_owned())]);
let features = riscv_features("riscv64-linux-android", &info);
assert!(features.contains('c'));
assert!(features.contains('d'));
assert!(features.contains('f'));
}
#[test]
fn test_loongarch_abi_detection() {
let features = loongarch_features("loongarch64-unknown-linux-gnu");
assert!(features.contains('d'));
assert!(features.contains('f'));
let features = loongarch_features("loongarch64-unknown-linux-musl");
assert!(features.contains('d'));
assert!(features.contains('f'));
let features = loongarch_features("loongarch64-unknown-none");
assert!(features.contains('d'));
assert!(features.contains('f'));
let features = loongarch_features("loongarch64-unknown-none-softfloat");
assert!(!features.contains('d'));
assert!(!features.contains('f'));
}
#[test]
fn test_create_object_file_linux() {
let rustc_output = br#"debug_assertions
target_arch="x86_64"
target_endian="little"
target_env="gnu"
target_family="unix"
target_feature="fxsr"
target_feature="sse"
target_feature="sse2"
target_os="linux"
target_pointer_width="64"
target_vendor="unknown"
unix
"#;
let target_triple = "x86_64-unknown-linux-gnu";
let target_info = parse_rustc_target_info(rustc_output);
let result = create_object_file(&target_info, target_triple).unwrap();
assert_eq!(result.format(), BinaryFormat::Elf);
assert_eq!(result.architecture(), Architecture::X86_64);
}
#[test]
fn test_create_object_file_windows_msvc() {
let rustc_output = br#"debug_assertions
target_arch="x86_64"
target_endian="little"
target_env="msvc"
target_family="windows"
target_feature="fxsr"
target_feature="sse"
target_feature="sse2"
target_os="windows"
target_pointer_width="64"
target_vendor="pc"
windows
"#;
let target_triple = "x86_64-pc-windows-msvc";
let target_info = parse_rustc_target_info(rustc_output);
let result = create_object_file(&target_info, target_triple).unwrap();
assert_eq!(result.format(), BinaryFormat::Coff);
assert_eq!(result.architecture(), Architecture::X86_64);
}
#[test]
fn test_create_object_file_windows_msvc_i686() {
let rustc_output = br#"debug_assertions
target_arch="x86"
target_endian="little"
target_env="msvc"
target_family="windows"
target_feature="fxsr"
target_feature="sse"
target_feature="sse2"
target_os="windows"
target_pointer_width="32"
target_vendor="pc"
windows
"#;
let target_triple = "i686-pc-windows-msvc";
let target_info = parse_rustc_target_info(rustc_output);
let result = create_object_file(&target_info, target_triple).unwrap();
assert_eq!(result.format(), BinaryFormat::Coff);
assert_eq!(result.architecture(), Architecture::I386);
}
#[test]
fn test_create_metadata_file_windows_msvc_i686_has_feat00() {
let rustc_output = br#"debug_assertions
target_arch="x86"
target_endian="little"
target_env="msvc"
target_family="windows"
target_feature="fxsr"
target_feature="sse"
target_feature="sse2"
target_os="windows"
target_pointer_width="32"
target_vendor="pc"
windows
"#;
let target_triple = "i686-pc-windows-msvc";
let target_info = parse_rustc_target_info(rustc_output);
let contents = b"test audit data";
let result = create_metadata_file(
&target_info,
target_triple,
contents,
"AUDITABLE_VERSION_INFO",
)
.expect("should produce an object file for i686-pc-windows-msvc");
let symtab_ptr = u32::from_le_bytes(result[8..12].try_into().unwrap()) as usize;
let sym_count = u32::from_le_bytes(result[12..16].try_into().unwrap()) as usize;
let symbol_size = 18;
let feat = (0..sym_count).find_map(|i| {
let start = symtab_ptr + i * symbol_size;
let end = start + symbol_size;
let entry = result.get(start..end)?;
if &entry[0..8] != b"@feat.00" {
return None;
}
let value = u32::from_le_bytes(entry[8..12].try_into().unwrap());
let section_number = i16::from_le_bytes(entry[12..14].try_into().unwrap());
Some((value, section_number))
});
let (value, section_number) = feat.expect("COFF object for i686 must contain @feat.00");
assert_eq!(
value & 1,
1,
"@feat.00 must set IMAGE_FILE_SAFE_EXCEPTION_HANDLER on i686"
);
assert_eq!(
section_number, -1,
"@feat.00 must be an absolute COFF symbol (section number -1)"
);
}
#[test]
fn test_create_object_file_windows_gnu() {
let rustc_output = br#"debug_assertions
target_arch="x86_64"
target_endian="little"
target_env="gnu"
target_family="windows"
target_feature="fxsr"
target_feature="sse"
target_feature="sse2"
target_os="windows"
target_pointer_width="64"
target_vendor="pc"
windows
"#;
let target_triple = "x86_64-pc-windows-gnu";
let target_info = crate::target_info::parse_rustc_target_info(rustc_output);
let result = create_object_file(&target_info, target_triple).unwrap();
assert_eq!(result.format(), BinaryFormat::Coff);
assert_eq!(result.architecture(), Architecture::X86_64);
}
#[test]
fn test_create_object_file_macos() {
let rustc_output = br#"debug_assertions
target_arch="x86_64"
target_endian="little"
target_env=""
target_family="unix"
target_feature="fxsr"
target_feature="sse"
target_feature="sse2"
target_feature="sse3"
target_feature="ssse3"
target_os="macos"
target_pointer_width="64"
target_vendor="apple"
unix
"#;
let target_triple = "x86_64-apple-darwin";
let target_info = crate::target_info::parse_rustc_target_info(rustc_output);
let result = create_object_file(&target_info, target_triple).unwrap();
assert_eq!(result.format(), BinaryFormat::MachO);
assert_eq!(result.architecture(), Architecture::X86_64);
}
#[test]
fn test_create_object_file_linux_arm() {
let rustc_output = br#"debug_assertions
target_arch="aarch64"
target_endian="little"
target_env="gnu"
target_family="unix"
target_os="linux"
target_pointer_width="64"
target_vendor="unknown"
unix
"#;
let target_triple = "aarch64-unknown-linux-gnu";
let target_info = parse_rustc_target_info(rustc_output);
let result = create_object_file(&target_info, target_triple).unwrap();
assert_eq!(result.format(), BinaryFormat::Elf);
assert_eq!(result.architecture(), Architecture::Aarch64);
}
}