rustc_codegen_ssa/back/
metadata.rs

1//! Reading of the rustc metadata for rlibs and dylibs
2
3use std::borrow::Cow;
4use std::fs::File;
5use std::io::Write;
6use std::path::Path;
7
8use itertools::Itertools;
9use object::write::{self, StandardSegment, Symbol, SymbolSection};
10use object::{
11    Architecture, BinaryFormat, Endianness, FileFlags, Object, ObjectSection, ObjectSymbol,
12    SectionFlags, SectionKind, SubArchitecture, SymbolFlags, SymbolKind, SymbolScope, elf, pe,
13    xcoff,
14};
15use rustc_abi::Endian;
16use rustc_data_structures::memmap::Mmap;
17use rustc_data_structures::owned_slice::{OwnedSlice, try_slice_owned};
18use rustc_metadata::EncodedMetadata;
19use rustc_metadata::creader::MetadataLoader;
20use rustc_metadata::fs::METADATA_FILENAME;
21use rustc_middle::bug;
22use rustc_session::Session;
23use rustc_span::sym;
24use rustc_target::spec::{RelocModel, Target, ef_avr_arch};
25use tracing::debug;
26
27use super::apple;
28
29/// The default metadata loader. This is used by cg_llvm and cg_clif.
30///
31/// # Metadata location
32///
33/// <dl>
34/// <dt>rlib</dt>
35/// <dd>The metadata can be found in the `lib.rmeta` file inside of the ar archive.</dd>
36/// <dt>dylib</dt>
37/// <dd>The metadata can be found in the `.rustc` section of the shared library.</dd>
38/// </dl>
39#[derive(Debug)]
40pub(crate) struct DefaultMetadataLoader;
41
42static AIX_METADATA_SYMBOL_NAME: &'static str = "__aix_rust_metadata";
43
44fn load_metadata_with(
45    path: &Path,
46    f: impl for<'a> FnOnce(&'a [u8]) -> Result<&'a [u8], String>,
47) -> Result<OwnedSlice, String> {
48    let file =
49        File::open(path).map_err(|e| format!("failed to open file '{}': {}", path.display(), e))?;
50
51    unsafe { Mmap::map(file) }
52        .map_err(|e| format!("failed to mmap file '{}': {}", path.display(), e))
53        .and_then(|mmap| try_slice_owned(mmap, |mmap| f(mmap)))
54}
55
56impl MetadataLoader for DefaultMetadataLoader {
57    fn get_rlib_metadata(&self, target: &Target, path: &Path) -> Result<OwnedSlice, String> {
58        debug!("getting rlib metadata for {}", path.display());
59        load_metadata_with(path, |data| {
60            let archive = object::read::archive::ArchiveFile::parse(&*data)
61                .map_err(|e| format!("failed to parse rlib '{}': {}", path.display(), e))?;
62
63            for entry_result in archive.members() {
64                let entry = entry_result
65                    .map_err(|e| format!("failed to parse rlib '{}': {}", path.display(), e))?;
66                if entry.name() == METADATA_FILENAME.as_bytes() {
67                    let data = entry
68                        .data(data)
69                        .map_err(|e| format!("failed to parse rlib '{}': {}", path.display(), e))?;
70                    if target.is_like_aix {
71                        return get_metadata_xcoff(path, data);
72                    } else {
73                        return search_for_section(path, data, ".rmeta");
74                    }
75                }
76            }
77
78            Err(format!("metadata not found in rlib '{}'", path.display()))
79        })
80    }
81
82    fn get_dylib_metadata(&self, target: &Target, path: &Path) -> Result<OwnedSlice, String> {
83        debug!("getting dylib metadata for {}", path.display());
84        if target.is_like_aix {
85            load_metadata_with(path, |data| {
86                let archive = object::read::archive::ArchiveFile::parse(&*data).map_err(|e| {
87                    format!("failed to parse aix dylib '{}': {}", path.display(), e)
88                })?;
89
90                match archive.members().exactly_one() {
91                    Ok(lib) => {
92                        let lib = lib.map_err(|e| {
93                            format!("failed to parse aix dylib '{}': {}", path.display(), e)
94                        })?;
95                        let data = lib.data(data).map_err(|e| {
96                            format!("failed to parse aix dylib '{}': {}", path.display(), e)
97                        })?;
98                        get_metadata_xcoff(path, data)
99                    }
100                    Err(e) => Err(format!("failed to parse aix dylib '{}': {}", path.display(), e)),
101                }
102            })
103        } else {
104            load_metadata_with(path, |data| search_for_section(path, data, ".rustc"))
105        }
106    }
107}
108
109pub(super) fn search_for_section<'a>(
110    path: &Path,
111    bytes: &'a [u8],
112    section: &str,
113) -> Result<&'a [u8], String> {
114    let Ok(file) = object::File::parse(bytes) else {
115        // The parse above could fail for odd reasons like corruption, but for
116        // now we just interpret it as this target doesn't support metadata
117        // emission in object files so the entire byte slice itself is probably
118        // a metadata file. Ideally though if necessary we could at least check
119        // the prefix of bytes to see if it's an actual metadata object and if
120        // not forward the error along here.
121        return Ok(bytes);
122    };
123    file.section_by_name(section)
124        .ok_or_else(|| format!("no `{}` section in '{}'", section, path.display()))?
125        .data()
126        .map_err(|e| format!("failed to read {} section in '{}': {}", section, path.display(), e))
127}
128
129fn add_gnu_property_note(
130    file: &mut write::Object<'static>,
131    architecture: Architecture,
132    binary_format: BinaryFormat,
133    endianness: Endianness,
134) {
135    // check bti protection
136    if binary_format != BinaryFormat::Elf
137        || !matches!(architecture, Architecture::X86_64 | Architecture::Aarch64)
138    {
139        return;
140    }
141
142    let section = file.add_section(
143        file.segment_name(StandardSegment::Data).to_vec(),
144        b".note.gnu.property".to_vec(),
145        SectionKind::Note,
146    );
147    let mut data: Vec<u8> = Vec::new();
148    let n_namsz: u32 = 4; // Size of the n_name field
149    let n_descsz: u32 = 16; // Size of the n_desc field
150    let n_type: u32 = object::elf::NT_GNU_PROPERTY_TYPE_0; // Type of note descriptor
151    let header_values = [n_namsz, n_descsz, n_type];
152    header_values.iter().for_each(|v| {
153        data.extend_from_slice(&match endianness {
154            Endianness::Little => v.to_le_bytes(),
155            Endianness::Big => v.to_be_bytes(),
156        })
157    });
158    data.extend_from_slice(b"GNU\0"); // Owner of the program property note
159    let pr_type: u32 = match architecture {
160        Architecture::X86_64 => object::elf::GNU_PROPERTY_X86_FEATURE_1_AND,
161        Architecture::Aarch64 => object::elf::GNU_PROPERTY_AARCH64_FEATURE_1_AND,
162        _ => unreachable!(),
163    };
164    let pr_datasz: u32 = 4; //size of the pr_data field
165    let pr_data: u32 = 3; //program property descriptor
166    let pr_padding: u32 = 0;
167    let property_values = [pr_type, pr_datasz, pr_data, pr_padding];
168    property_values.iter().for_each(|v| {
169        data.extend_from_slice(&match endianness {
170            Endianness::Little => v.to_le_bytes(),
171            Endianness::Big => v.to_be_bytes(),
172        })
173    });
174    file.append_section_data(section, &data, 8);
175}
176
177pub(super) fn get_metadata_xcoff<'a>(path: &Path, data: &'a [u8]) -> Result<&'a [u8], String> {
178    let Ok(file) = object::File::parse(data) else {
179        return Ok(data);
180    };
181    let info_data = search_for_section(path, data, ".info")?;
182    if let Some(metadata_symbol) =
183        file.symbols().find(|sym| sym.name() == Ok(AIX_METADATA_SYMBOL_NAME))
184    {
185        let offset = metadata_symbol.address() as usize;
186        // The offset specifies the location of rustc metadata in the .info section of XCOFF.
187        // Each string stored in .info section of XCOFF is preceded by a 4-byte length field.
188        if offset < 4 {
189            return Err(format!("Invalid metadata symbol offset: {offset}"));
190        }
191        // XCOFF format uses big-endian byte order.
192        let len = u32::from_be_bytes(info_data[(offset - 4)..offset].try_into().unwrap()) as usize;
193        if offset + len > (info_data.len() as usize) {
194            return Err(format!(
195                "Metadata at offset {offset} with size {len} is beyond .info section"
196            ));
197        }
198        Ok(&info_data[offset..(offset + len)])
199    } else {
200        Err(format!("Unable to find symbol {AIX_METADATA_SYMBOL_NAME}"))
201    }
202}
203
204pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static>> {
205    let endianness = match sess.target.options.endian {
206        Endian::Little => Endianness::Little,
207        Endian::Big => Endianness::Big,
208    };
209    let (architecture, sub_architecture) = match &sess.target.arch[..] {
210        "arm" => (Architecture::Arm, None),
211        "aarch64" => (
212            if sess.target.pointer_width == 32 {
213                Architecture::Aarch64_Ilp32
214            } else {
215                Architecture::Aarch64
216            },
217            None,
218        ),
219        "x86" => (Architecture::I386, None),
220        "s390x" => (Architecture::S390x, None),
221        "mips" | "mips32r6" => (Architecture::Mips, None),
222        "mips64" | "mips64r6" => (Architecture::Mips64, None),
223        "x86_64" => (
224            if sess.target.pointer_width == 32 {
225                Architecture::X86_64_X32
226            } else {
227                Architecture::X86_64
228            },
229            None,
230        ),
231        "powerpc" => (Architecture::PowerPc, None),
232        "powerpc64" => (Architecture::PowerPc64, None),
233        "riscv32" => (Architecture::Riscv32, None),
234        "riscv64" => (Architecture::Riscv64, None),
235        "sparc" => {
236            if sess.unstable_target_features.contains(&sym::v8plus) {
237                // Target uses V8+, aka EM_SPARC32PLUS, aka 64-bit V9 but in 32-bit mode
238                (Architecture::Sparc32Plus, None)
239            } else {
240                // Target uses V7 or V8, aka EM_SPARC
241                (Architecture::Sparc, None)
242            }
243        }
244        "sparc64" => (Architecture::Sparc64, None),
245        "avr" => (Architecture::Avr, None),
246        "msp430" => (Architecture::Msp430, None),
247        "hexagon" => (Architecture::Hexagon, None),
248        "bpf" => (Architecture::Bpf, None),
249        "loongarch64" => (Architecture::LoongArch64, None),
250        "csky" => (Architecture::Csky, None),
251        "arm64ec" => (Architecture::Aarch64, Some(SubArchitecture::Arm64EC)),
252        // Unsupported architecture.
253        _ => return None,
254    };
255    let binary_format = if sess.target.is_like_osx {
256        BinaryFormat::MachO
257    } else if sess.target.is_like_windows {
258        BinaryFormat::Coff
259    } else if sess.target.is_like_aix {
260        BinaryFormat::Xcoff
261    } else {
262        BinaryFormat::Elf
263    };
264
265    let mut file = write::Object::new(binary_format, architecture, endianness);
266    file.set_sub_architecture(sub_architecture);
267    if sess.target.is_like_osx {
268        if macho_is_arm64e(&sess.target) {
269            file.set_macho_cpu_subtype(object::macho::CPU_SUBTYPE_ARM64E);
270        }
271
272        file.set_macho_build_version(macho_object_build_version_for_target(sess))
273    }
274    if binary_format == BinaryFormat::Coff {
275        // Disable the default mangler to avoid mangling the special "@feat.00" symbol name.
276        let original_mangling = file.mangling();
277        file.set_mangling(object::write::Mangling::None);
278
279        let mut feature = 0;
280
281        if file.architecture() == object::Architecture::I386 {
282            // When linking with /SAFESEH on x86, lld requires that all linker inputs be marked as
283            // safe exception handling compatible. Metadata files masquerade as regular COFF
284            // objects and are treated as linker inputs, despite containing no actual code. Thus,
285            // they still need to be marked as safe exception handling compatible. See #96498.
286            // Reference: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
287            feature |= 1;
288        }
289
290        file.add_symbol(object::write::Symbol {
291            name: "@feat.00".into(),
292            value: feature,
293            size: 0,
294            kind: object::SymbolKind::Data,
295            scope: object::SymbolScope::Compilation,
296            weak: false,
297            section: object::write::SymbolSection::Absolute,
298            flags: object::SymbolFlags::None,
299        });
300
301        file.set_mangling(original_mangling);
302    }
303    let e_flags = match architecture {
304        Architecture::Mips => {
305            let arch = match sess.target.options.cpu.as_ref() {
306                "mips1" => elf::EF_MIPS_ARCH_1,
307                "mips2" => elf::EF_MIPS_ARCH_2,
308                "mips3" => elf::EF_MIPS_ARCH_3,
309                "mips4" => elf::EF_MIPS_ARCH_4,
310                "mips5" => elf::EF_MIPS_ARCH_5,
311                s if s.contains("r6") => elf::EF_MIPS_ARCH_32R6,
312                _ => elf::EF_MIPS_ARCH_32R2,
313            };
314
315            let mut e_flags = elf::EF_MIPS_CPIC | arch;
316
317            // If the ABI is explicitly given, use it or default to O32.
318            match sess.target.options.llvm_abiname.to_lowercase().as_str() {
319                "n32" => e_flags |= elf::EF_MIPS_ABI2,
320                "o32" => e_flags |= elf::EF_MIPS_ABI_O32,
321                _ => e_flags |= elf::EF_MIPS_ABI_O32,
322            };
323
324            if sess.target.options.relocation_model != RelocModel::Static {
325                e_flags |= elf::EF_MIPS_PIC;
326            }
327            if sess.target.options.cpu.contains("r6") {
328                e_flags |= elf::EF_MIPS_NAN2008;
329            }
330            e_flags
331        }
332        Architecture::Mips64 => {
333            // copied from `mips64el-linux-gnuabi64-gcc foo.c -c`
334            let e_flags = elf::EF_MIPS_CPIC
335                | elf::EF_MIPS_PIC
336                | if sess.target.options.cpu.contains("r6") {
337                    elf::EF_MIPS_ARCH_64R6 | elf::EF_MIPS_NAN2008
338                } else {
339                    elf::EF_MIPS_ARCH_64R2
340                };
341            e_flags
342        }
343        Architecture::Riscv32 | Architecture::Riscv64 => {
344            // Source: https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/079772828bd10933d34121117a222b4cc0ee2200/riscv-elf.adoc
345            let mut e_flags: u32 = 0x0;
346
347            // Check if compressed is enabled
348            // `unstable_target_features` is used here because "c" is gated behind riscv_target_feature.
349            if sess.unstable_target_features.contains(&sym::c) {
350                e_flags |= elf::EF_RISCV_RVC;
351            }
352
353            // Set the appropriate flag based on ABI
354            // This needs to match LLVM `RISCVELFStreamer.cpp`
355            match &*sess.target.llvm_abiname {
356                "ilp32" | "lp64" => (),
357                "ilp32f" | "lp64f" => e_flags |= elf::EF_RISCV_FLOAT_ABI_SINGLE,
358                "ilp32d" | "lp64d" => e_flags |= elf::EF_RISCV_FLOAT_ABI_DOUBLE,
359                // Note that the `lp64e` is still unstable as it's not (yet) part of the ELF psABI.
360                "ilp32e" | "lp64e" => e_flags |= elf::EF_RISCV_RVE,
361                _ => bug!("unknown RISC-V ABI name"),
362            }
363
364            e_flags
365        }
366        Architecture::LoongArch64 => {
367            // Source: https://github.com/loongson/la-abi-specs/blob/release/laelf.adoc#e_flags-identifies-abi-type-and-version
368            let mut e_flags: u32 = elf::EF_LARCH_OBJABI_V1;
369
370            // Set the appropriate flag based on ABI
371            // This needs to match LLVM `LoongArchELFStreamer.cpp`
372            match &*sess.target.llvm_abiname {
373                "ilp32s" | "lp64s" => e_flags |= elf::EF_LARCH_ABI_SOFT_FLOAT,
374                "ilp32f" | "lp64f" => e_flags |= elf::EF_LARCH_ABI_SINGLE_FLOAT,
375                "ilp32d" | "lp64d" => e_flags |= elf::EF_LARCH_ABI_DOUBLE_FLOAT,
376                _ => bug!("unknown LoongArch ABI name"),
377            }
378
379            e_flags
380        }
381        Architecture::Avr => {
382            // Resolve the ISA revision and set
383            // the appropriate EF_AVR_ARCH flag.
384            ef_avr_arch(&sess.target.options.cpu)
385        }
386        Architecture::Csky => {
387            let e_flags = match sess.target.options.abi.as_ref() {
388                "abiv2" => elf::EF_CSKY_ABIV2,
389                _ => elf::EF_CSKY_ABIV1,
390            };
391            e_flags
392        }
393        _ => 0,
394    };
395    // adapted from LLVM's `MCELFObjectTargetWriter::getOSABI`
396    let os_abi = match sess.target.options.os.as_ref() {
397        "hermit" => elf::ELFOSABI_STANDALONE,
398        "freebsd" => elf::ELFOSABI_FREEBSD,
399        "solaris" => elf::ELFOSABI_SOLARIS,
400        _ => elf::ELFOSABI_NONE,
401    };
402    let abi_version = 0;
403    add_gnu_property_note(&mut file, architecture, binary_format, endianness);
404    file.flags = FileFlags::Elf { os_abi, abi_version, e_flags };
405    Some(file)
406}
407
408/// Mach-O files contain information about:
409/// - The platform/OS they were built for (macOS/watchOS/Mac Catalyst/iOS simulator etc).
410/// - The minimum OS version / deployment target.
411/// - The version of the SDK they were targetting.
412///
413/// In the past, this was accomplished using the LC_VERSION_MIN_MACOSX, LC_VERSION_MIN_IPHONEOS,
414/// LC_VERSION_MIN_TVOS or LC_VERSION_MIN_WATCHOS load commands, which each contain information
415/// about the deployment target and SDK version, and implicitly, by their presence, which OS they
416/// target. Simulator targets were determined if the architecture was x86_64, but there was e.g. a
417/// LC_VERSION_MIN_IPHONEOS present.
418///
419/// This is of course brittle and limited, so modern tooling emit the LC_BUILD_VERSION load
420/// command (which contains all three pieces of information in one) when the deployment target is
421/// high enough, or the target is something that wouldn't be encodable with the old load commands
422/// (such as Mac Catalyst, or Aarch64 iOS simulator).
423///
424/// Since Xcode 15, Apple's LD apparently requires object files to use this load command, so this
425/// returns the `MachOBuildVersion` for the target to do so.
426fn macho_object_build_version_for_target(sess: &Session) -> object::write::MachOBuildVersion {
427    /// The `object` crate demands "X.Y.Z encoded in nibbles as xxxx.yy.zz"
428    /// e.g. minOS 14.0 = 0x000E0000, or SDK 16.2 = 0x00100200
429    fn pack_version((major, minor, patch): (u16, u8, u8)) -> u32 {
430        let (major, minor, patch) = (major as u32, minor as u32, patch as u32);
431        (major << 16) | (minor << 8) | patch
432    }
433
434    let platform = apple::macho_platform(&sess.target);
435    let min_os = apple::deployment_target(sess);
436
437    let mut build_version = object::write::MachOBuildVersion::default();
438    build_version.platform = platform;
439    build_version.minos = pack_version(min_os);
440    // The version here does not _really_ matter, since it is only used at runtime, and we specify
441    // it when linking the final binary, so we will omit the version. This is also what LLVM does,
442    // and the tooling also allows this (and shows the SDK version as `n/a`). Finally, it is the
443    // semantically correct choice, as the SDK has not influenced the binary generated by rustc at
444    // this point in time.
445    build_version.sdk = 0;
446
447    build_version
448}
449
450/// Is Apple's CPU subtype `arm64e`s
451fn macho_is_arm64e(target: &Target) -> bool {
452    target.llvm_target.starts_with("arm64e")
453}
454
455pub(crate) enum MetadataPosition {
456    First,
457    Last,
458}
459
460/// For rlibs we "pack" rustc metadata into a dummy object file.
461///
462/// Historically it was needed because rustc linked rlibs as whole-archive in some cases.
463/// In that case linkers try to include all files located in an archive, so if metadata is stored
464/// in an archive then it needs to be of a form that the linker is able to process.
465/// Now it's not clear whether metadata still needs to be wrapped into an object file or not.
466///
467/// Note, though, that we don't actually want this metadata to show up in any
468/// final output of the compiler. Instead this is purely for rustc's own
469/// metadata tracking purposes.
470///
471/// With the above in mind, each "flavor" of object format gets special
472/// handling here depending on the target:
473///
474/// * MachO - macos-like targets will insert the metadata into a section that
475///   is sort of fake dwarf debug info. Inspecting the source of the macos
476///   linker this causes these sections to be skipped automatically because
477///   it's not in an allowlist of otherwise well known dwarf section names to
478///   go into the final artifact.
479///
480/// * WebAssembly - this uses wasm files themselves as the object file format
481///   so an empty file with no linking metadata but a single custom section is
482///   created holding our metadata.
483///
484/// * COFF - Windows-like targets create an object with a section that has
485///   the `IMAGE_SCN_LNK_REMOVE` flag set which ensures that if the linker
486///   ever sees the section it doesn't process it and it's removed.
487///
488/// * ELF - All other targets are similar to Windows in that there's a
489///   `SHF_EXCLUDE` flag we can set on sections in an object file to get
490///   automatically removed from the final output.
491pub(crate) fn create_wrapper_file(
492    sess: &Session,
493    section_name: String,
494    data: &[u8],
495) -> (Vec<u8>, MetadataPosition) {
496    let Some(mut file) = create_object_file(sess) else {
497        if sess.target.is_like_wasm {
498            return (
499                create_metadata_file_for_wasm(sess, data, &section_name),
500                MetadataPosition::First,
501            );
502        }
503
504        // Targets using this branch don't have support implemented here yet or
505        // they're not yet implemented in the `object` crate and will likely
506        // fill out this module over time.
507        return (data.to_vec(), MetadataPosition::Last);
508    };
509    let section = if file.format() == BinaryFormat::Xcoff {
510        file.add_section(Vec::new(), b".info".to_vec(), SectionKind::Debug)
511    } else {
512        file.add_section(
513            file.segment_name(StandardSegment::Debug).to_vec(),
514            section_name.into_bytes(),
515            SectionKind::Debug,
516        )
517    };
518    match file.format() {
519        BinaryFormat::Coff => {
520            file.section_mut(section).flags =
521                SectionFlags::Coff { characteristics: pe::IMAGE_SCN_LNK_REMOVE };
522        }
523        BinaryFormat::Elf => {
524            file.section_mut(section).flags =
525                SectionFlags::Elf { sh_flags: elf::SHF_EXCLUDE as u64 };
526        }
527        BinaryFormat::Xcoff => {
528            // AIX system linker may aborts if it meets a valid XCOFF file in archive with no .text, no .data and no .bss.
529            file.add_section(Vec::new(), b".text".to_vec(), SectionKind::Text);
530            file.section_mut(section).flags =
531                SectionFlags::Xcoff { s_flags: xcoff::STYP_INFO as u32 };
532            // Encode string stored in .info section of XCOFF.
533            // FIXME: The length of data here is not guaranteed to fit in a u32.
534            // We may have to split the data into multiple pieces in order to
535            // store in .info section.
536            let len: u32 = data.len().try_into().unwrap();
537            let offset = file.append_section_data(section, &len.to_be_bytes(), 1);
538            // Add a symbol referring to the data in .info section.
539            file.add_symbol(Symbol {
540                name: AIX_METADATA_SYMBOL_NAME.into(),
541                value: offset + 4,
542                size: 0,
543                kind: SymbolKind::Unknown,
544                scope: SymbolScope::Compilation,
545                weak: false,
546                section: SymbolSection::Section(section),
547                flags: SymbolFlags::Xcoff {
548                    n_sclass: xcoff::C_INFO,
549                    x_smtyp: xcoff::C_HIDEXT,
550                    x_smclas: xcoff::C_HIDEXT,
551                    containing_csect: None,
552                },
553            });
554        }
555        _ => {}
556    };
557    file.append_section_data(section, data, 1);
558    (file.write().unwrap(), MetadataPosition::First)
559}
560
561// Historical note:
562//
563// When using link.exe it was seen that the section name `.note.rustc`
564// was getting shortened to `.note.ru`, and according to the PE and COFF
565// specification:
566//
567// > Executable images do not use a string table and do not support
568// > section names longer than 8 characters
569//
570// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
571//
572// As a result, we choose a slightly shorter name! As to why
573// `.note.rustc` works on MinGW, see
574// https://github.com/llvm/llvm-project/blob/llvmorg-12.0.0/lld/COFF/Writer.cpp#L1190-L1197
575pub fn create_compressed_metadata_file(
576    sess: &Session,
577    metadata: &EncodedMetadata,
578    symbol_name: &str,
579) -> Vec<u8> {
580    let mut packed_metadata = rustc_metadata::METADATA_HEADER.to_vec();
581    packed_metadata.write_all(&(metadata.raw_data().len() as u64).to_le_bytes()).unwrap();
582    packed_metadata.extend(metadata.raw_data());
583
584    let Some(mut file) = create_object_file(sess) else {
585        if sess.target.is_like_wasm {
586            return create_metadata_file_for_wasm(sess, &packed_metadata, ".rustc");
587        }
588        return packed_metadata.to_vec();
589    };
590    if file.format() == BinaryFormat::Xcoff {
591        return create_compressed_metadata_file_for_xcoff(file, &packed_metadata, symbol_name);
592    }
593    let section = file.add_section(
594        file.segment_name(StandardSegment::Data).to_vec(),
595        b".rustc".to_vec(),
596        SectionKind::ReadOnlyData,
597    );
598    match file.format() {
599        BinaryFormat::Elf => {
600            // Explicitly set no flags to avoid SHF_ALLOC default for data section.
601            file.section_mut(section).flags = SectionFlags::Elf { sh_flags: 0 };
602        }
603        _ => {}
604    };
605    let offset = file.append_section_data(section, &packed_metadata, 1);
606
607    // For MachO and probably PE this is necessary to prevent the linker from throwing away the
608    // .rustc section. For ELF this isn't necessary, but it also doesn't harm.
609    file.add_symbol(Symbol {
610        name: symbol_name.as_bytes().to_vec(),
611        value: offset,
612        size: packed_metadata.len() as u64,
613        kind: SymbolKind::Data,
614        scope: SymbolScope::Dynamic,
615        weak: false,
616        section: SymbolSection::Section(section),
617        flags: SymbolFlags::None,
618    });
619
620    file.write().unwrap()
621}
622
623/// * Xcoff - On AIX, custom sections are merged into predefined sections,
624///   so custom .rustc section is not preserved during linking.
625///   For this reason, we store metadata in predefined .info section, and
626///   define a symbol to reference the metadata. To preserve metadata during
627///   linking on AIX, we have to
628///   1. Create an empty .text section, a empty .data section.
629///   2. Define an empty symbol named `symbol_name` inside .data section.
630///   3. Define an symbol named `AIX_METADATA_SYMBOL_NAME` referencing
631///      data inside .info section.
632///   From XCOFF's view, (2) creates a csect entry in the symbol table, the
633///   symbol created by (3) is a info symbol for the preceding csect. Thus
634///   two symbols are preserved during linking and we can use the second symbol
635///   to reference the metadata.
636pub fn create_compressed_metadata_file_for_xcoff(
637    mut file: write::Object<'_>,
638    data: &[u8],
639    symbol_name: &str,
640) -> Vec<u8> {
641    assert!(file.format() == BinaryFormat::Xcoff);
642    // AIX system linker may aborts if it meets a valid XCOFF file in archive with no .text, no .data and no .bss.
643    file.add_section(Vec::new(), b".text".to_vec(), SectionKind::Text);
644    let data_section = file.add_section(Vec::new(), b".data".to_vec(), SectionKind::Data);
645    let section = file.add_section(Vec::new(), b".info".to_vec(), SectionKind::Debug);
646    file.add_file_symbol("lib.rmeta".into());
647    file.section_mut(section).flags = SectionFlags::Xcoff { s_flags: xcoff::STYP_INFO as u32 };
648    // Add a global symbol to data_section.
649    file.add_symbol(Symbol {
650        name: symbol_name.as_bytes().into(),
651        value: 0,
652        size: 0,
653        kind: SymbolKind::Data,
654        scope: SymbolScope::Dynamic,
655        weak: true,
656        section: SymbolSection::Section(data_section),
657        flags: SymbolFlags::None,
658    });
659    let len: u32 = data.len().try_into().unwrap();
660    let offset = file.append_section_data(section, &len.to_be_bytes(), 1);
661    // Add a symbol referring to the rustc metadata.
662    file.add_symbol(Symbol {
663        name: AIX_METADATA_SYMBOL_NAME.into(),
664        value: offset + 4, // The metadata is preceded by a 4-byte length field.
665        size: 0,
666        kind: SymbolKind::Unknown,
667        scope: SymbolScope::Dynamic,
668        weak: false,
669        section: SymbolSection::Section(section),
670        flags: SymbolFlags::Xcoff {
671            n_sclass: xcoff::C_INFO,
672            x_smtyp: xcoff::C_HIDEXT,
673            x_smclas: xcoff::C_HIDEXT,
674            containing_csect: None,
675        },
676    });
677    file.append_section_data(section, data, 1);
678    file.write().unwrap()
679}
680
681/// Creates a simple WebAssembly object file, which is itself a wasm module,
682/// that contains a custom section of the name `section_name` with contents
683/// `data`.
684///
685/// NB: the `object` crate does not yet have support for writing the wasm
686/// object file format. In lieu of that the `wasm-encoder` crate is used to
687/// build a wasm file by hand.
688///
689/// The wasm object file format is defined at
690/// <https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md>
691/// and mainly consists of a `linking` custom section. In this case the custom
692/// section there is empty except for a version marker indicating what format
693/// it's in.
694///
695/// The main purpose of this is to contain a custom section with `section_name`,
696/// which is then appended after `linking`.
697///
698/// As a further detail the object needs to have a 64-bit memory if `wasm64` is
699/// the target or otherwise it's interpreted as a 32-bit object which is
700/// incompatible with 64-bit ones.
701pub fn create_metadata_file_for_wasm(sess: &Session, data: &[u8], section_name: &str) -> Vec<u8> {
702    assert!(sess.target.is_like_wasm);
703    let mut module = wasm_encoder::Module::new();
704    let mut imports = wasm_encoder::ImportSection::new();
705
706    if sess.target.pointer_width == 64 {
707        imports.import(
708            "env",
709            "__linear_memory",
710            wasm_encoder::MemoryType {
711                minimum: 0,
712                maximum: None,
713                memory64: true,
714                shared: false,
715                page_size_log2: None,
716            },
717        );
718    }
719
720    if imports.len() > 0 {
721        module.section(&imports);
722    }
723    module.section(&wasm_encoder::CustomSection {
724        name: "linking".into(),
725        data: Cow::Borrowed(&[2]),
726    });
727    module.section(&wasm_encoder::CustomSection { name: section_name.into(), data: data.into() });
728    module.finish()
729}