| //! Reading of the rustc metadata for rlibs and dylibs | |
| use std::borrow::Cow; | |
| use std::fs::File; | |
| use std::io::Write; | |
| use std::path::Path; | |
| use itertools::Itertools; | |
| use object::write::{self, StandardSegment, Symbol, SymbolSection}; | |
| use object::{ | |
| Architecture, BinaryFormat, Endianness, FileFlags, Object, ObjectSection, ObjectSymbol, | |
| SectionFlags, SectionKind, SymbolFlags, SymbolKind, SymbolScope, elf, pe, xcoff, | |
| }; | |
| use rustc_abi::Endian; | |
| use rustc_data_structures::memmap::Mmap; | |
| use rustc_data_structures::owned_slice::{OwnedSlice, try_slice_owned}; | |
| use rustc_metadata::EncodedMetadata; | |
| use rustc_metadata::creader::MetadataLoader; | |
| use rustc_metadata::fs::METADATA_FILENAME; | |
| use rustc_middle::bug; | |
| use rustc_session::Session; | |
| use rustc_span::sym; | |
| use rustc_target::spec::{CfgAbi, LlvmAbi, Os, RelocModel, Target, ef_avr_arch}; | |
| use tracing::debug; | |
| use super::apple; | |
| use crate::diagnostics; | |
| /// The default metadata loader. This is used by cg_llvm and cg_clif. | |
| /// | |
| /// # Metadata location | |
| /// | |
| /// <dl> | |
| /// <dt>rlib</dt> | |
| /// <dd>The metadata can be found in the `lib.rmeta` file inside of the ar archive.</dd> | |
| /// <dt>dylib</dt> | |
| /// <dd>The metadata can be found in the `.rustc` section of the shared library.</dd> | |
| /// </dl> | |
| #[derive(Debug)] | |
| pub struct DefaultMetadataLoader; | |
| static AIX_METADATA_SYMBOL_NAME: &'static str = "__aix_rust_metadata"; | |
| fn load_metadata_with( | |
| path: &Path, | |
| f: impl for<'a> FnOnce(&'a [u8]) -> Result<&'a [u8], String>, | |
| ) -> Result<OwnedSlice, String> { | |
| let file = | |
| File::open(path).map_err(|e| format!("failed to open file '{}': {}", path.display(), e))?; | |
| unsafe { Mmap::map(file) } | |
| .map_err(|e| format!("failed to mmap file '{}': {}", path.display(), e)) | |
| .and_then(|mmap| try_slice_owned(mmap, |mmap| f(mmap))) | |
| } | |
| impl MetadataLoader for DefaultMetadataLoader { | |
| fn get_rlib_metadata(&self, target: &Target, path: &Path) -> Result<OwnedSlice, String> { | |
| debug!("getting rlib metadata for {}", path.display()); | |
| load_metadata_with(path, |data| { | |
| let archive = object::read::archive::ArchiveFile::parse(&*data) | |
| .map_err(|e| format!("failed to parse rlib '{}': {}", path.display(), e))?; | |
| for entry_result in archive.members() { | |
| let entry = entry_result | |
| .map_err(|e| format!("failed to parse rlib '{}': {}", path.display(), e))?; | |
| if entry.name() == METADATA_FILENAME.as_bytes() { | |
| let data = entry | |
| .data(data) | |
| .map_err(|e| format!("failed to parse rlib '{}': {}", path.display(), e))?; | |
| if target.is_like_aix { | |
| return get_metadata_xcoff(path, data); | |
| } else { | |
| return search_for_section(path, data, ".rmeta"); | |
| } | |
| } | |
| } | |
| Err(format!("metadata not found in rlib '{}'", path.display())) | |
| }) | |
| } | |
| fn get_dylib_metadata(&self, target: &Target, path: &Path) -> Result<OwnedSlice, String> { | |
| debug!("getting dylib metadata for {}", path.display()); | |
| if target.is_like_aix { | |
| load_metadata_with(path, |data| { | |
| let archive = object::read::archive::ArchiveFile::parse(&*data).map_err(|e| { | |
| format!("failed to parse aix dylib '{}': {}", path.display(), e) | |
| })?; | |
| match archive.members().exactly_one() { | |
| Ok(lib) => { | |
| let lib = lib.map_err(|e| { | |
| format!("failed to parse aix dylib '{}': {}", path.display(), e) | |
| })?; | |
| let data = lib.data(data).map_err(|e| { | |
| format!("failed to parse aix dylib '{}': {}", path.display(), e) | |
| })?; | |
| get_metadata_xcoff(path, data) | |
| } | |
| Err(e) => Err(format!("failed to parse aix dylib '{}': {}", path.display(), e)), | |
| } | |
| }) | |
| } else { | |
| load_metadata_with(path, |data| search_for_section(path, data, ".rustc")) | |
| } | |
| } | |
| } | |
| pub(super) fn search_for_section<'a>( | |
| path: &Path, | |
| bytes: &'a [u8], | |
| section: &str, | |
| ) -> Result<&'a [u8], String> { | |
| let Ok(file) = object::File::parse(bytes) else { | |
| // The parse above could fail for odd reasons like corruption, but for | |
| // now we just interpret it as this target doesn't support metadata | |
| // emission in object files so the entire byte slice itself is probably | |
| // a metadata file. Ideally though if necessary we could at least check | |
| // the prefix of bytes to see if it's an actual metadata object and if | |
| // not forward the error along here. | |
| return Ok(bytes); | |
| }; | |
| file.section_by_name(section) | |
| .ok_or_else(|| format!("no `{}` section in '{}'", section, path.display()))? | |
| .data() | |
| .map_err(|e| format!("failed to read {} section in '{}': {}", section, path.display(), e)) | |
| } | |
| fn add_gnu_property_note( | |
| file: &mut write::Object<'static>, | |
| architecture: Architecture, | |
| binary_format: BinaryFormat, | |
| endianness: Endianness, | |
| ) { | |
| // check bti protection | |
| if binary_format != BinaryFormat::Elf | |
| || !matches!(architecture, Architecture::X86_64 | Architecture::Aarch64) | |
| { | |
| return; | |
| } | |
| let section = file.add_section( | |
| file.segment_name(StandardSegment::Data).to_vec(), | |
| b".note.gnu.property".to_vec(), | |
| SectionKind::Note, | |
| ); | |
| let mut data: Vec<u8> = Vec::new(); | |
| let n_namsz: u32 = 4; // Size of the n_name field | |
| let n_descsz: u32 = 16; // Size of the n_desc field | |
| let n_type: u32 = object::elf::NT_GNU_PROPERTY_TYPE_0; // Type of note descriptor | |
| let header_values = [n_namsz, n_descsz, n_type]; | |
| header_values.iter().for_each(|v| { | |
| data.extend_from_slice(&match endianness { | |
| Endianness::Little => v.to_le_bytes(), | |
| Endianness::Big => v.to_be_bytes(), | |
| }) | |
| }); | |
| data.extend_from_slice(b"GNU\0"); // Owner of the program property note | |
| let pr_type: u32 = match architecture { | |
| Architecture::X86_64 => object::elf::GNU_PROPERTY_X86_FEATURE_1_AND, | |
| Architecture::Aarch64 => object::elf::GNU_PROPERTY_AARCH64_FEATURE_1_AND, | |
| _ => unreachable!(), | |
| }; | |
| let pr_datasz: u32 = 4; //size of the pr_data field | |
| let pr_data: u32 = 3; //program property descriptor | |
| let pr_padding: u32 = 0; | |
| let property_values = [pr_type, pr_datasz, pr_data, pr_padding]; | |
| property_values.iter().for_each(|v| { | |
| data.extend_from_slice(&match endianness { | |
| Endianness::Little => v.to_le_bytes(), | |
| Endianness::Big => v.to_be_bytes(), | |
| }) | |
| }); | |
| file.append_section_data(section, &data, 8); | |
| } | |
| pub(super) fn get_metadata_xcoff<'a>(path: &Path, data: &'a [u8]) -> Result<&'a [u8], String> { | |
| let Ok(file) = object::File::parse(data) else { | |
| return Ok(data); | |
| }; | |
| let info_data = search_for_section(path, data, ".info")?; | |
| if let Some(metadata_symbol) = | |
| file.symbols().find(|sym| sym.name() == Ok(AIX_METADATA_SYMBOL_NAME)) | |
| { | |
| let offset = metadata_symbol.address() as usize; | |
| // The offset specifies the location of rustc metadata in the .info section of XCOFF. | |
| // Each string stored in .info section of XCOFF is preceded by a 4-byte length field. | |
| if offset < 4 { | |
| return Err(format!("Invalid metadata symbol offset: {offset}")); | |
| } | |
| // XCOFF format uses big-endian byte order. | |
| let len = u32::from_be_bytes(info_data[(offset - 4)..offset].try_into().unwrap()) as usize; | |
| if offset + len > (info_data.len() as usize) { | |
| return Err(format!( | |
| "Metadata at offset {offset} with size {len} is beyond .info section" | |
| )); | |
| } | |
| Ok(&info_data[offset..(offset + len)]) | |
| } else { | |
| Err(format!("Unable to find symbol {AIX_METADATA_SYMBOL_NAME}")) | |
| } | |
| } | |
| pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static>> { | |
| let endianness = match sess.target.options.endian { | |
| Endian::Little => Endianness::Little, | |
| Endian::Big => Endianness::Big, | |
| }; | |
| let Some((architecture, sub_architecture)) = | |
| sess.target.object_architecture(&sess.internal_target_features) | |
| else { | |
| return None; | |
| }; | |
| let binary_format = sess.target.binary_format.to_object(); | |
| let mut file = write::Object::new(binary_format, architecture, endianness); | |
| file.set_sub_architecture(sub_architecture); | |
| if sess.target.is_like_darwin { | |
| if macho_is_arm64e(&sess.target) { | |
| file.set_macho_cpu_subtype( | |
| object::macho::CPU_SUBTYPE_ARM64E | object::macho::CPU_SUBTYPE_PTRAUTH_ABI, | |
| ); | |
| } | |
| file.set_macho_build_version(macho_object_build_version_for_target(sess)) | |
| } | |
| if binary_format == BinaryFormat::Coff { | |
| // Disable the default mangler to avoid mangling the special "@feat.00" symbol name. | |
| let original_mangling = file.mangling(); | |
| file.set_mangling(object::write::Mangling::None); | |
| let mut feature = 0; | |
| if file.architecture() == object::Architecture::I386 { | |
| // When linking with /SAFESEH on x86, lld requires that all linker inputs be marked as | |
| // safe exception handling compatible. Metadata files masquerade as regular COFF | |
| // objects and are treated as linker inputs, despite containing no actual code. Thus, | |
| // they still need to be marked as safe exception handling compatible. See #96498. | |
| // Reference: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format | |
| feature |= 1; | |
| } | |
| file.add_symbol(object::write::Symbol { | |
| name: "@feat.00".into(), | |
| value: feature, | |
| size: 0, | |
| kind: object::SymbolKind::Data, | |
| scope: object::SymbolScope::Compilation, | |
| weak: false, | |
| section: object::write::SymbolSection::Absolute, | |
| flags: object::SymbolFlags::None, | |
| }); | |
| file.set_mangling(original_mangling); | |
| } | |
| let e_flags = elf_e_flags(architecture, sess); | |
| // adapted from LLVM's `MCELFObjectTargetWriter::getOSABI` | |
| let os_abi = elf_os_abi(sess); | |
| let abi_version = 0; | |
| add_gnu_property_note(&mut file, architecture, binary_format, endianness); | |
| file.flags = FileFlags::Elf { os_abi, abi_version, e_flags }; | |
| Some(file) | |
| } | |
| pub(super) fn elf_os_abi(sess: &Session) -> u8 { | |
| match sess.target.options.os { | |
| Os::Hermit => elf::ELFOSABI_STANDALONE, | |
| Os::FreeBsd => elf::ELFOSABI_FREEBSD, | |
| Os::Solaris => elf::ELFOSABI_SOLARIS, | |
| _ => elf::ELFOSABI_NONE, | |
| } | |
| } | |
| pub(super) fn elf_e_flags(architecture: Architecture, sess: &Session) -> u32 { | |
| match architecture { | |
| Architecture::Mips | Architecture::Mips64 | Architecture::Mips64_N32 => { | |
| // "N32" indicates an "ILP32" data model on a 64-bit MIPS CPU | |
| // like SPARC's "v8+", x86_64's "x32", or the watchOS "arm64_32". | |
| let is_32bit = architecture == Architecture::Mips; | |
| let mut e_flags = match sess.target.options.cpu.as_ref() { | |
| "mips1" if is_32bit => elf::EF_MIPS_ARCH_1, | |
| "mips2" if is_32bit => elf::EF_MIPS_ARCH_2, | |
| "mips3" => elf::EF_MIPS_ARCH_3, | |
| "mips4" => elf::EF_MIPS_ARCH_4, | |
| "mips5" => elf::EF_MIPS_ARCH_5, | |
| "mips32r2" if is_32bit => elf::EF_MIPS_ARCH_32R2, | |
| "mips32r6" if is_32bit => elf::EF_MIPS_ARCH_32R6, | |
| "mips64r2" if !is_32bit => elf::EF_MIPS_ARCH_64R2, | |
| "mips64r6" if !is_32bit => elf::EF_MIPS_ARCH_64R6, | |
| s if s.starts_with("mips32") && !is_32bit => { | |
| sess.dcx().fatal(format!("invalid CPU `{}` for 64-bit MIPS target", s)) | |
| } | |
| s if s.starts_with("mips64") && is_32bit => { | |
| sess.dcx().fatal(format!("invalid CPU `{}` for 32-bit MIPS target", s)) | |
| } | |
| _ if is_32bit => elf::EF_MIPS_ARCH_32R2, | |
| _ => elf::EF_MIPS_ARCH_64R2, | |
| }; | |
| // Use the explicitly given ABI. | |
| match &sess.target.options.llvm_abiname { | |
| LlvmAbi::O32 if is_32bit => e_flags |= elf::EF_MIPS_ABI_O32, | |
| LlvmAbi::N32 if !is_32bit => e_flags |= elf::EF_MIPS_ABI2, | |
| LlvmAbi::N64 if !is_32bit => {} | |
| // The rest is invalid (which is already ensured by the target spec check). | |
| s => bug!("invalid LLVM ABI `{}` for MIPS target", s), | |
| }; | |
| if sess.target.options.relocation_model != RelocModel::Static { | |
| // PIC means position-independent code. CPIC means "calls PIC". | |
| // CPIC was mutually exclusive with PIC according to | |
| // the SVR4 MIPS ABI https://refspecs.linuxfoundation.org/elf/mipsabi.pdf | |
| // and should have only appeared on static objects with dynamically calls. | |
| // At some point someone (GCC?) decided to set CPIC even for PIC. | |
| // Nowadays various things expect both set on the same object file | |
| // and may even error if you mix CPIC and non-CPIC object files, | |
| // despite that being the entire point of the CPIC ABI extension! | |
| // As we are in Rome, we do as the Romans do. | |
| e_flags |= elf::EF_MIPS_PIC | elf::EF_MIPS_CPIC; | |
| } | |
| if sess.target.options.cpu.contains("r6") { | |
| e_flags |= elf::EF_MIPS_NAN2008; | |
| } | |
| e_flags | |
| } | |
| Architecture::Riscv32 | Architecture::Riscv64 => { | |
| // Source: https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/079772828bd10933d34121117a222b4cc0ee2200/riscv-elf.adoc | |
| let mut e_flags: u32 = 0x0; | |
| // Check if compression is enabled | |
| if sess.internal_target_features.contains(&sym::zca) { | |
| e_flags |= elf::EF_RISCV_RVC; | |
| } | |
| // Check if RVTSO is enabled | |
| if sess.internal_target_features.contains(&sym::ztso) { | |
| e_flags |= elf::EF_RISCV_TSO; | |
| } | |
| // Set the appropriate flag based on ABI | |
| // This needs to match LLVM `RISCVELFStreamer.cpp` | |
| match &sess.target.llvm_abiname { | |
| LlvmAbi::Ilp32 | LlvmAbi::Lp64 => (), | |
| LlvmAbi::Ilp32f | LlvmAbi::Lp64f => e_flags |= elf::EF_RISCV_FLOAT_ABI_SINGLE, | |
| LlvmAbi::Ilp32d | LlvmAbi::Lp64d => e_flags |= elf::EF_RISCV_FLOAT_ABI_DOUBLE, | |
| // Note that the `lp64e` is still unstable as it's not (yet) part of the ELF psABI. | |
| LlvmAbi::Ilp32e | LlvmAbi::Lp64e => e_flags |= elf::EF_RISCV_RVE, | |
| _ => bug!("unknown RISC-V ABI name"), | |
| } | |
| e_flags | |
| } | |
| Architecture::LoongArch32 | Architecture::LoongArch64 => { | |
| // Source: https://github.com/loongson/la-abi-specs/blob/release/laelf.adoc#e_flags-identifies-abi-type-and-version | |
| let mut e_flags: u32 = elf::EF_LARCH_OBJABI_V1; | |
| // Set the appropriate flag based on ABI | |
| // This needs to match LLVM `LoongArchELFStreamer.cpp` | |
| match &sess.target.llvm_abiname { | |
| LlvmAbi::Ilp32s | LlvmAbi::Lp64s => e_flags |= elf::EF_LARCH_ABI_SOFT_FLOAT, | |
| LlvmAbi::Ilp32f | LlvmAbi::Lp64f => e_flags |= elf::EF_LARCH_ABI_SINGLE_FLOAT, | |
| LlvmAbi::Ilp32d | LlvmAbi::Lp64d => e_flags |= elf::EF_LARCH_ABI_DOUBLE_FLOAT, | |
| _ => bug!("unknown LoongArch ABI name"), | |
| } | |
| e_flags | |
| } | |
| Architecture::Avr => { | |
| // Resolve the ISA revision and set | |
| // the appropriate EF_AVR_ARCH flag. | |
| if let Some(ref cpu) = sess.opts.cg.target_cpu { | |
| ef_avr_arch(cpu) | |
| } else { | |
| sess.dcx().emit_fatal(diagnostics::CpuRequired) | |
| } | |
| } | |
| Architecture::Csky => { | |
| if matches!(sess.target.options.cfg_abi, CfgAbi::AbiV2) { | |
| elf::EF_CSKY_ABIV2 | |
| } else { | |
| elf::EF_CSKY_ABIV1 | |
| } | |
| } | |
| Architecture::PowerPc64 => { | |
| const EF_PPC64_ABI_UNKNOWN: u32 = 0; | |
| const EF_PPC64_ABI_ELF_V1: u32 = 1; | |
| const EF_PPC64_ABI_ELF_V2: u32 = 2; | |
| match sess.target.options.llvm_abiname { | |
| // If the flags do not correctly indicate the ABI, | |
| // linkers such as ld.lld assume that the ppc64 object files are always ELFv2 | |
| // which leads to broken binaries if ELFv1 is used for the object files. | |
| LlvmAbi::ElfV1 => EF_PPC64_ABI_ELF_V1, | |
| LlvmAbi::ElfV2 => EF_PPC64_ABI_ELF_V2, | |
| _ if sess.target.options.binary_format.to_object() == BinaryFormat::Elf => { | |
| bug!("invalid ABI specified for this PPC64 ELF target"); | |
| } | |
| // Fall back | |
| _ => EF_PPC64_ABI_UNKNOWN, | |
| } | |
| } | |
| Architecture::Sparc32Plus => elf::EF_SPARC_32PLUS, | |
| _ => 0, | |
| } | |
| } | |
| /// Mach-O files contain information about: | |
| /// - The platform/OS they were built for (macOS/watchOS/Mac Catalyst/iOS simulator etc). | |
| /// - The minimum OS version / deployment target. | |
| /// - The version of the SDK they were targetting. | |
| /// | |
| /// In the past, this was accomplished using the LC_VERSION_MIN_MACOSX, LC_VERSION_MIN_IPHONEOS, | |
| /// LC_VERSION_MIN_TVOS or LC_VERSION_MIN_WATCHOS load commands, which each contain information | |
| /// about the deployment target and SDK version, and implicitly, by their presence, which OS they | |
| /// target. Simulator targets were determined if the architecture was x86_64, but there was e.g. a | |
| /// LC_VERSION_MIN_IPHONEOS present. | |
| /// | |
| /// This is of course brittle and limited, so modern tooling emit the LC_BUILD_VERSION load | |
| /// command (which contains all three pieces of information in one) when the deployment target is | |
| /// high enough, or the target is something that wouldn't be encodable with the old load commands | |
| /// (such as Mac Catalyst, or Aarch64 iOS simulator). | |
| /// | |
| /// Since Xcode 15, Apple's LD apparently requires object files to use this load command, so this | |
| /// returns the `MachOBuildVersion` for the target to do so. | |
| fn macho_object_build_version_for_target(sess: &Session) -> object::write::MachOBuildVersion { | |
| /// The `object` crate demands "X.Y.Z encoded in nibbles as xxxx.yy.zz" | |
| /// e.g. minOS 14.0 = 0x000E0000, or SDK 16.2 = 0x00100200 | |
| fn pack_version(apple::OSVersion { major, minor, patch }: apple::OSVersion) -> u32 { | |
| let (major, minor, patch) = (major as u32, minor as u32, patch as u32); | |
| (major << 16) | (minor << 8) | patch | |
| } | |
| let platform = apple::macho_platform(&sess.target); | |
| let min_os = sess.apple_deployment_target(); | |
| let mut build_version = object::write::MachOBuildVersion::default(); | |
| build_version.platform = platform; | |
| build_version.minos = pack_version(min_os); | |
| // The version here does not _really_ matter, since it is only used at runtime, and we specify | |
| // it when linking the final binary, so we will omit the version. This is also what LLVM does, | |
| // and the tooling also allows this (and shows the SDK version as `n/a`). Finally, it is the | |
| // semantically correct choice, as the SDK has not influenced the binary generated by rustc at | |
| // this point in time. | |
| build_version.sdk = 0; | |
| build_version | |
| } | |
| /// Is Apple's CPU subtype `arm64e`s | |
| fn macho_is_arm64e(target: &Target) -> bool { | |
| target.llvm_target.starts_with("arm64e") | |
| } | |
| pub(crate) enum MetadataPosition { | |
| First, | |
| Last, | |
| } | |
| /// For rlibs we "pack" rustc metadata into a dummy object file. | |
| /// | |
| /// Historically it was needed because rustc linked rlibs as whole-archive in some cases. | |
| /// In that case linkers try to include all files located in an archive, so if metadata is stored | |
| /// in an archive then it needs to be of a form that the linker is able to process. | |
| /// Now it's not clear whether metadata still needs to be wrapped into an object file or not. | |
| /// | |
| /// Note, though, that we don't actually want this metadata to show up in any | |
| /// final output of the compiler. Instead this is purely for rustc's own | |
| /// metadata tracking purposes. | |
| /// | |
| /// With the above in mind, each "flavor" of object format gets special | |
| /// handling here depending on the target: | |
| /// | |
| /// * MachO - macos-like targets will insert the metadata into a section that | |
| /// is sort of fake dwarf debug info. Inspecting the source of the macos | |
| /// linker this causes these sections to be skipped automatically because | |
| /// it's not in an allowlist of otherwise well known dwarf section names to | |
| /// go into the final artifact. | |
| /// | |
| /// * WebAssembly - this uses wasm files themselves as the object file format | |
| /// so an empty file with no linking metadata but a single custom section is | |
| /// created holding our metadata. | |
| /// | |
| /// * COFF - Windows-like targets create an object with a section that has | |
| /// the `IMAGE_SCN_LNK_REMOVE` flag set which ensures that if the linker | |
| /// ever sees the section it doesn't process it and it's removed. | |
| /// | |
| /// * ELF - All other targets are similar to Windows in that there's a | |
| /// `SHF_EXCLUDE` flag we can set on sections in an object file to get | |
| /// automatically removed from the final output. | |
| pub(crate) fn create_wrapper_file( | |
| sess: &Session, | |
| section_name: String, | |
| data: &[u8], | |
| ) -> (Vec<u8>, MetadataPosition) { | |
| let Some(mut file) = create_object_file(sess) else { | |
| if sess.target.is_like_wasm { | |
| return ( | |
| create_metadata_file_for_wasm(sess, data, §ion_name), | |
| MetadataPosition::First, | |
| ); | |
| } | |
| // Targets using this branch don't have support implemented here yet or | |
| // they're not yet implemented in the `object` crate and will likely | |
| // fill out this module over time. | |
| return (data.to_vec(), MetadataPosition::Last); | |
| }; | |
| let section = if file.format() == BinaryFormat::Xcoff { | |
| file.add_section(Vec::new(), b".info".to_vec(), SectionKind::Debug) | |
| } else { | |
| file.add_section( | |
| file.segment_name(StandardSegment::Debug).to_vec(), | |
| section_name.into_bytes(), | |
| SectionKind::Debug, | |
| ) | |
| }; | |
| match file.format() { | |
| BinaryFormat::Coff => { | |
| file.section_mut(section).flags = | |
| SectionFlags::Coff { characteristics: pe::IMAGE_SCN_LNK_REMOVE }; | |
| } | |
| BinaryFormat::Elf => { | |
| file.section_mut(section).flags = | |
| SectionFlags::Elf { sh_flags: elf::SHF_EXCLUDE as u64 }; | |
| } | |
| BinaryFormat::Xcoff => { | |
| // AIX system linker may aborts if it meets a valid XCOFF file in archive with no .text, no .data and no .bss. | |
| file.add_section(Vec::new(), b".text".to_vec(), SectionKind::Text); | |
| file.section_mut(section).flags = | |
| SectionFlags::Xcoff { s_flags: xcoff::STYP_INFO as u32 }; | |
| // Encode string stored in .info section of XCOFF. | |
| // FIXME: The length of data here is not guaranteed to fit in a u32. | |
| // We may have to split the data into multiple pieces in order to | |
| // store in .info section. | |
| let len: u32 = data.len().try_into().unwrap(); | |
| let offset = file.append_section_data(section, &len.to_be_bytes(), 1); | |
| // Add a symbol referring to the data in .info section. | |
| file.add_symbol(Symbol { | |
| name: AIX_METADATA_SYMBOL_NAME.into(), | |
| value: offset + 4, | |
| size: 0, | |
| kind: SymbolKind::Unknown, | |
| scope: SymbolScope::Compilation, | |
| weak: false, | |
| section: SymbolSection::Section(section), | |
| flags: SymbolFlags::Xcoff { | |
| n_sclass: xcoff::C_INFO, | |
| x_smtyp: xcoff::C_HIDEXT, | |
| x_smclas: xcoff::C_HIDEXT, | |
| containing_csect: None, | |
| }, | |
| }); | |
| } | |
| _ => {} | |
| }; | |
| file.append_section_data(section, data, 1); | |
| (file.write().unwrap(), MetadataPosition::First) | |
| } | |
| // Historical note: | |
| // | |
| // When using link.exe it was seen that the section name `.note.rustc` | |
| // was getting shortened to `.note.ru`, and according to the PE and COFF | |
| // specification: | |
| // | |
| // > Executable images do not use a string table and do not support | |
| // > section names longer than 8Â characters | |
| // | |
| // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format | |
| // | |
| // As a result, we choose a slightly shorter name! As to why | |
| // `.note.rustc` works on MinGW, see | |
| // https://github.com/llvm/llvm-project/blob/llvmorg-12.0.0/lld/COFF/Writer.cpp#L1190-L1197 | |
| pub fn create_compressed_metadata_file( | |
| sess: &Session, | |
| metadata: &EncodedMetadata, | |
| symbol_name: &str, | |
| ) -> Vec<u8> { | |
| let mut packed_metadata = rustc_metadata::METADATA_HEADER.to_vec(); | |
| packed_metadata.write_all(&(metadata.stub_or_full().len() as u64).to_le_bytes()).unwrap(); | |
| packed_metadata.extend(metadata.stub_or_full()); | |
| let Some(mut file) = create_object_file(sess) else { | |
| if sess.target.is_like_wasm { | |
| return create_metadata_file_for_wasm(sess, &packed_metadata, ".rustc"); | |
| } | |
| return packed_metadata.to_vec(); | |
| }; | |
| if file.format() == BinaryFormat::Xcoff { | |
| return create_compressed_metadata_file_for_xcoff(file, &packed_metadata, symbol_name); | |
| } | |
| let section = file.add_section( | |
| file.segment_name(StandardSegment::Data).to_vec(), | |
| b".rustc".to_vec(), | |
| SectionKind::ReadOnlyData, | |
| ); | |
| match file.format() { | |
| BinaryFormat::Elf => { | |
| // Explicitly set no flags to avoid SHF_ALLOC default for data section. | |
| file.section_mut(section).flags = SectionFlags::Elf { sh_flags: 0 }; | |
| } | |
| _ => {} | |
| }; | |
| let offset = file.append_section_data(section, &packed_metadata, 1); | |
| // For MachO and probably PE this is necessary to prevent the linker from throwing away the | |
| // .rustc section. For ELF this isn't necessary, but it also doesn't harm. | |
| file.add_symbol(Symbol { | |
| name: symbol_name.as_bytes().to_vec(), | |
| value: offset, | |
| size: packed_metadata.len() as u64, | |
| kind: SymbolKind::Data, | |
| scope: SymbolScope::Dynamic, | |
| weak: false, | |
| section: SymbolSection::Section(section), | |
| flags: SymbolFlags::None, | |
| }); | |
| file.write().unwrap() | |
| } | |
| /// * Xcoff - On AIX, custom sections are merged into predefined sections, | |
| /// so custom .rustc section is not preserved during linking. | |
| /// For this reason, we store metadata in predefined .info section, and | |
| /// define a symbol to reference the metadata. To preserve metadata during | |
| /// linking on AIX, we have to | |
| /// 1. Create an empty .text section, a empty .data section. | |
| /// 2. Define an empty symbol named `symbol_name` inside .data section. | |
| /// 3. Define an symbol named `AIX_METADATA_SYMBOL_NAME` referencing | |
| /// data inside .info section. | |
| /// From XCOFF's view, (2) creates a csect entry in the symbol table, the | |
| /// symbol created by (3) is a info symbol for the preceding csect. Thus | |
| /// two symbols are preserved during linking and we can use the second symbol | |
| /// to reference the metadata. | |
| pub fn create_compressed_metadata_file_for_xcoff( | |
| mut file: write::Object<'_>, | |
| data: &[u8], | |
| symbol_name: &str, | |
| ) -> Vec<u8> { | |
| assert!(file.format() == BinaryFormat::Xcoff); | |
| // AIX system linker may aborts if it meets a valid XCOFF file in archive with no .text, no .data and no .bss. | |
| file.add_section(Vec::new(), b".text".to_vec(), SectionKind::Text); | |
| let data_section = file.add_section(Vec::new(), b".data".to_vec(), SectionKind::Data); | |
| let section = file.add_section(Vec::new(), b".info".to_vec(), SectionKind::Debug); | |
| file.add_file_symbol("lib.rmeta".into()); | |
| file.section_mut(section).flags = SectionFlags::Xcoff { s_flags: xcoff::STYP_INFO as u32 }; | |
| // Add a global symbol to data_section. | |
| file.add_symbol(Symbol { | |
| name: symbol_name.as_bytes().into(), | |
| value: 0, | |
| size: 0, | |
| kind: SymbolKind::Data, | |
| scope: SymbolScope::Dynamic, | |
| weak: true, | |
| section: SymbolSection::Section(data_section), | |
| flags: SymbolFlags::None, | |
| }); | |
| let len: u32 = data.len().try_into().unwrap(); | |
| let offset = file.append_section_data(section, &len.to_be_bytes(), 1); | |
| // Add a symbol referring to the rustc metadata. | |
| file.add_symbol(Symbol { | |
| name: AIX_METADATA_SYMBOL_NAME.into(), | |
| value: offset + 4, // The metadata is preceded by a 4-byte length field. | |
| size: 0, | |
| kind: SymbolKind::Unknown, | |
| scope: SymbolScope::Dynamic, | |
| weak: false, | |
| section: SymbolSection::Section(section), | |
| flags: SymbolFlags::Xcoff { | |
| n_sclass: xcoff::C_INFO, | |
| x_smtyp: xcoff::C_HIDEXT, | |
| x_smclas: xcoff::C_HIDEXT, | |
| containing_csect: None, | |
| }, | |
| }); | |
| file.append_section_data(section, data, 1); | |
| file.write().unwrap() | |
| } | |
| /// Creates a simple WebAssembly object file, which is itself a wasm module, | |
| /// that contains a custom section of the name `section_name` with contents | |
| /// `data`. | |
| /// | |
| /// NB: the `object` crate does not yet have support for writing the wasm | |
| /// object file format. In lieu of that the `wasm-encoder` crate is used to | |
| /// build a wasm file by hand. | |
| /// | |
| /// The wasm object file format is defined at | |
| /// <https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md> | |
| /// and mainly consists of a `linking` custom section. In this case the custom | |
| /// section there is empty except for a version marker indicating what format | |
| /// it's in. | |
| /// | |
| /// The main purpose of this is to contain a custom section with `section_name`, | |
| /// which is then appended after `linking`. | |
| /// | |
| /// As a further detail the object needs to have a 64-bit memory if `wasm64` is | |
| /// the target or otherwise it's interpreted as a 32-bit object which is | |
| /// incompatible with 64-bit ones. | |
| pub fn create_metadata_file_for_wasm(sess: &Session, data: &[u8], section_name: &str) -> Vec<u8> { | |
| assert!(sess.target.is_like_wasm); | |
| let mut module = wasm_encoder::Module::new(); | |
| let mut imports = wasm_encoder::ImportSection::new(); | |
| if sess.target.pointer_width == 64 { | |
| imports.import( | |
| "env", | |
| "__linear_memory", | |
| wasm_encoder::MemoryType { | |
| minimum: 0, | |
| maximum: None, | |
| memory64: true, | |
| shared: false, | |
| page_size_log2: None, | |
| }, | |
| ); | |
| } | |
| if imports.len() > 0 { | |
| module.section(&imports); | |
| } | |
| module.section(&wasm_encoder::CustomSection { | |
| name: "linking".into(), | |
| data: Cow::Borrowed(&[2]), | |
| }); | |
| module.section(&wasm_encoder::CustomSection { name: section_name.into(), data: data.into() }); | |
| module.finish() | |
| } |