Skip to main content

rustc_codegen_ssa/back/link/
raw_dylib.rs

1use std::fs;
2use std::io::{BufWriter, Write};
3use std::path::{Path, PathBuf};
4
5use rustc_abi::Endian;
6use rustc_crate_store::{DllImport, DllImportSymbolType};
7use rustc_data_structures::base_n::{CASE_INSENSITIVE, ToBaseN};
8use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
9use rustc_data_structures::stable_hash::StableHasher;
10use rustc_hashes::Hash128;
11use rustc_session::Session;
12use rustc_span::Symbol;
13use rustc_structures::NativeLibKind;
14use rustc_target::spec::Arch;
15
16use crate::back::archive::ImportLibraryItem;
17use crate::back::link::ArchiveBuilderBuilder;
18use crate::diagnostics::ErrorCreatingImportLibrary;
19use crate::{NativeLib, common, diagnostics};
20
21/// Extract all symbols defined in raw-dylib libraries, collated by library name.
22///
23/// If we have multiple extern blocks that specify symbols defined in the same raw-dylib library,
24/// then the CodegenResults value contains one NativeLib instance for each block. However, the
25/// linker appears to expect only a single import library for each library used, so we need to
26/// collate the symbols together by library name before generating the import libraries.
27fn collate_raw_dylibs_windows<'a>(
28    sess: &Session,
29    used_libraries: impl IntoIterator<Item = &'a NativeLib>,
30) -> Vec<(String, Vec<DllImport>)> {
31    // Use index maps to preserve original order of imports and libraries.
32    let mut dylib_table = FxIndexMap::<String, FxIndexMap<Symbol, &DllImport>>::default();
33
34    for lib in used_libraries {
35        if let NativeLibKind::RawDylib { .. } = lib.kind {
36            let ext = if lib.verbatim { "" } else { ".dll" };
37            let name = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}{1}", lib.name, ext))
    })format!("{}{}", lib.name, ext);
38            let imports = dylib_table.entry(name.clone()).or_default();
39            for import in &lib.dll_imports {
40                if let Some(old_import) = imports.insert(import.name, import) {
41                    // FIXME: when we add support for ordinals, figure out if we need to do anything
42                    // if we have two DllImport values with the same name but different ordinals.
43                    if import.calling_convention != old_import.calling_convention {
44                        sess.dcx().emit_err(diagnostics::MultipleExternalFuncDecl {
45                            span: import.span,
46                            function: import.name,
47                            library_name: &name,
48                        });
49                    }
50                }
51            }
52        }
53    }
54    sess.dcx().abort_if_errors();
55    dylib_table
56        .into_iter()
57        .map(|(name, imports)| {
58            (name, imports.into_iter().map(|(_, import)| import.clone()).collect())
59        })
60        .collect()
61}
62
63pub(super) fn create_raw_dylib_dll_import_libs<'a>(
64    sess: &Session,
65    archive_builder_builder: &dyn ArchiveBuilderBuilder,
66    used_libraries: impl IntoIterator<Item = &'a NativeLib>,
67    tmpdir: &Path,
68    is_direct_dependency: bool,
69) -> Vec<PathBuf> {
70    collate_raw_dylibs_windows(sess, used_libraries)
71        .into_iter()
72        .map(|(raw_dylib_name, raw_dylib_imports)| {
73            let name_suffix = if is_direct_dependency { "_imports" } else { "_imports_indirect" };
74            let output_path = tmpdir.join(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}{1}.lib", raw_dylib_name,
                name_suffix))
    })format!("{raw_dylib_name}{name_suffix}.lib"));
75
76            let using_dlltool = common::is_using_dlltool(&sess.target);
77
78            let items: Vec<ImportLibraryItem> = raw_dylib_imports
79                .iter()
80                .map(|import: &DllImport| {
81                    if sess.target.arch == Arch::X86 {
82                        ImportLibraryItem {
83                            name: common::i686_decorated_name(import, using_dlltool, false, false),
84                            ordinal: import.ordinal(),
85                            symbol_name: import.is_missing_decorations().then(|| {
86                                common::i686_decorated_name(import, using_dlltool, false, true)
87                            }),
88                            is_data: import.symbol_type != DllImportSymbolType::Function,
89                        }
90                    } else {
91                        ImportLibraryItem {
92                            name: import.name.to_string(),
93                            ordinal: import.ordinal(),
94                            symbol_name: None,
95                            is_data: import.symbol_type != DllImportSymbolType::Function,
96                        }
97                    }
98                })
99                .collect();
100
101            archive_builder_builder.create_dll_import_lib(
102                sess,
103                &raw_dylib_name,
104                items,
105                &output_path,
106            );
107
108            output_path
109        })
110        .collect()
111}
112
113/// Extract all symbols defined in raw-dylib libraries, collated by library name.
114///
115/// If we have multiple extern blocks that specify symbols defined in the same raw-dylib library,
116/// then the CodegenResults value contains one NativeLib instance for each block. However, the
117/// linker appears to expect only a single import library for each library used, so we need to
118/// collate the symbols together by library name before generating the import libraries.
119fn collate_raw_dylibs_elf<'a>(
120    sess: &Session,
121    used_libraries: impl IntoIterator<Item = &'a NativeLib>,
122) -> Vec<(String, Vec<DllImport>, bool)> {
123    // Use index maps to preserve original order of imports and libraries.
124    let mut dylib_table = FxIndexMap::<String, (FxIndexMap<Symbol, &DllImport>, bool)>::default();
125
126    for lib in used_libraries {
127        if let NativeLibKind::RawDylib { as_needed } = lib.kind {
128            let filename = if lib.verbatim {
129                lib.name.as_str().to_owned()
130            } else {
131                let ext = sess.target.dll_suffix.as_ref();
132                let prefix = sess.target.dll_prefix.as_ref();
133                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{1}{0}{2}", lib.name, prefix, ext))
    })format!("{prefix}{}{ext}", lib.name)
134            };
135
136            let (stub_imports, stub_as_needed) =
137                dylib_table.entry(filename.clone()).or_insert((Default::default(), true));
138            for import in &lib.dll_imports {
139                stub_imports.insert(import.name, import);
140            }
141            *stub_as_needed = *stub_as_needed && as_needed.unwrap_or(true);
142        }
143    }
144    sess.dcx().abort_if_errors();
145    dylib_table
146        .into_iter()
147        .map(|(name, (imports, as_needed))| {
148            (name, imports.into_iter().map(|(_, import)| import.clone()).collect(), as_needed)
149        })
150        .collect()
151}
152
153pub(super) fn create_raw_dylib_elf_stub_shared_objects<'a>(
154    sess: &Session,
155    used_libraries: impl IntoIterator<Item = &'a NativeLib>,
156    raw_dylib_so_dir: &Path,
157) -> Vec<(String, bool)> {
158    collate_raw_dylibs_elf(sess, used_libraries)
159        .into_iter()
160        .map(|(load_filename, raw_dylib_imports, as_needed)| {
161            use std::hash::Hash;
162
163            // `load_filename` is the *target/loader* filename that will end up in NEEDED.
164            // Usually this will be something like `libc.so` or `libc.so.6` but with
165            // verbatim it might also be an absolute path.
166            // To be able to support this properly, we always put this load filename
167            // into the SONAME of the library and link it via a temporary file with a random name.
168            // This also avoids naming conflicts with non-raw-dylib linkage of the same library.
169
170            let shared_object = create_elf_raw_dylib_stub(sess, &load_filename, &raw_dylib_imports);
171
172            let mut file_name_hasher = StableHasher::new();
173            load_filename.hash(&mut file_name_hasher);
174            for raw_dylib in raw_dylib_imports {
175                raw_dylib.name.as_str().hash(&mut file_name_hasher);
176            }
177
178            let library_filename: Hash128 = file_name_hasher.finish();
179            let temporary_lib_name = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}{1}{2}", sess.target.dll_prefix,
                library_filename.as_u128().to_base_fixed_len(CASE_INSENSITIVE),
                sess.target.dll_suffix))
    })format!(
180                "{}{}{}",
181                sess.target.dll_prefix,
182                library_filename.as_u128().to_base_fixed_len(CASE_INSENSITIVE),
183                sess.target.dll_suffix
184            );
185            let link_path = raw_dylib_so_dir.join(&temporary_lib_name);
186
187            let file = match fs::File::create_new(&link_path) {
188                Ok(file) => file,
189                Err(error) => sess.dcx().emit_fatal(ErrorCreatingImportLibrary {
190                    lib_name: &load_filename,
191                    error: error.to_string(),
192                }),
193            };
194            if let Err(error) = BufWriter::new(file).write_all(&shared_object) {
195                sess.dcx().emit_fatal(ErrorCreatingImportLibrary {
196                    lib_name: &load_filename,
197                    error: error.to_string(),
198                });
199            };
200
201            (temporary_lib_name, as_needed)
202        })
203        .collect()
204}
205
206/// Create an ELF .so stub file for raw-dylib.
207/// It exports all the provided symbols, but is otherwise empty.
208fn create_elf_raw_dylib_stub(sess: &Session, soname: &str, symbols: &[DllImport]) -> Vec<u8> {
209    use object::write::elf as write;
210    use object::{AddressSize, Architecture, elf};
211
212    let mut stub_buf = Vec::new();
213
214    // Build the stub ELF using the object crate.
215    // The high-level portable API does not allow for the fine-grained control we need,
216    // so this uses the low-level object::write::elf API.
217    // The low-level API consists of two stages: reservation and writing.
218    // We first reserve space for all the things in the binary and then write them.
219    // It is important that the order of reservation matches the order of writing.
220    // The object crate contains many debug asserts that fire if you get this wrong.
221
222    let Some((arch, sub_arch)) = sess.target.object_architecture(&sess.internal_target_features)
223    else {
224        sess.dcx().fatal(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("raw-dylib is not supported for the architecture `{0}`",
                sess.target.arch))
    })format!(
225            "raw-dylib is not supported for the architecture `{}`",
226            sess.target.arch
227        ));
228    };
229
230    let endianness = match sess.target.options.endian {
231        Endian::Little => object::Endianness::Little,
232        Endian::Big => object::Endianness::Big,
233    };
234
235    let is_64 = match arch.address_size() {
236        Some(AddressSize::U8 | AddressSize::U16 | AddressSize::U32) => false,
237        Some(AddressSize::U64) => true,
238        _ => sess.dcx().fatal(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("raw-dylib is not supported for the architecture `{0}`",
                sess.target.arch))
    })format!(
239            "raw-dylib is not supported for the architecture `{}`",
240            sess.target.arch
241        )),
242    };
243
244    let mut stub = write::Writer::new(endianness, is_64, &mut stub_buf);
245
246    let mut vers = Vec::new();
247    let mut vers_map = FxHashMap::default();
248    let mut syms = Vec::new();
249
250    for symbol in symbols {
251        let symbol_name = symbol.name.as_str();
252        if let Some((name, version_name)) = symbol_name.split_once('@') {
253            if !!version_name.contains('@') {
    ::core::panicking::panic("assertion failed: !version_name.contains(\'@\')")
};assert!(!version_name.contains('@'));
254            let dynstr = stub.add_dynamic_string(name.as_bytes());
255            let ver = if let Some(&ver_id) = vers_map.get(version_name) {
256                ver_id
257            } else {
258                let id = vers.len();
259                vers_map.insert(version_name, id);
260                let dynstr = stub.add_dynamic_string(version_name.as_bytes());
261                vers.push((version_name, dynstr));
262                id
263            };
264            syms.push((name, dynstr, Some(ver), symbol.symbol_type, symbol.size));
265        } else {
266            let dynstr = stub.add_dynamic_string(symbol_name.as_bytes());
267            syms.push((symbol_name, dynstr, None, symbol.symbol_type, symbol.size));
268        }
269    }
270
271    let soname = stub.add_dynamic_string(soname.as_bytes());
272
273    // These initial reservations don't reserve any bytes in the binary yet,
274    // they just allocate in the internal data structures.
275
276    // First, we create the dynamic symbol table. It starts with a null symbol
277    // and then all the symbols and their dynamic strings.
278    stub.reserve_null_dynamic_symbol_index();
279
280    for _ in syms.iter() {
281        stub.reserve_dynamic_symbol_index();
282    }
283
284    // Reserve the sections.
285    // We have the minimal sections for a dynamic SO and .text where we point our dummy symbols to.
286    stub.reserve_shstrtab_section_index();
287    let text_section_name = stub.add_section_name(".text".as_bytes());
288    let text_section = stub.reserve_section_index();
289    let data_section_name = stub.add_section_name(".data".as_bytes());
290    let data_section = stub.reserve_section_index();
291    stub.reserve_dynsym_section_index();
292    stub.reserve_dynstr_section_index();
293    if !vers.is_empty() {
294        stub.reserve_gnu_versym_section_index();
295        stub.reserve_gnu_verdef_section_index();
296    }
297    stub.reserve_dynamic_section_index();
298
299    // These reservations now determine the actual layout order of the object file.
300    stub.reserve_file_header();
301    stub.reserve_shstrtab();
302    stub.reserve_section_headers();
303    stub.reserve_dynsym();
304    stub.reserve_dynstr();
305    let verdef_count = 1 + vers.len();
306    let mut dynamic_entries = 2; // DT_SONAME, DT_NULL
307    if !vers.is_empty() {
308        stub.reserve_gnu_versym();
309        stub.reserve_gnu_verdef(verdef_count, verdef_count);
310        dynamic_entries += 1; // DT_VERDEFNUM
311    }
312    stub.reserve_dynamic(dynamic_entries);
313
314    // First write the ELF header with the arch information.
315    let e_machine = match (arch, sub_arch) {
316        (Architecture::Aarch64, None) => elf::EM_AARCH64,
317        (Architecture::Aarch64_Ilp32, None) => elf::EM_AARCH64,
318        (Architecture::Arm, None) => elf::EM_ARM,
319        (Architecture::Avr, None) => elf::EM_AVR,
320        (Architecture::Bpf, None) => elf::EM_BPF,
321        (Architecture::Csky, None) => elf::EM_CSKY,
322        (Architecture::E2K32, None) => elf::EM_MCST_ELBRUS,
323        (Architecture::E2K64, None) => elf::EM_MCST_ELBRUS,
324        (Architecture::I386, None) => elf::EM_386,
325        (Architecture::X86_64, None) => elf::EM_X86_64,
326        (Architecture::X86_64_X32, None) => elf::EM_X86_64,
327        (Architecture::Hexagon, None) => elf::EM_HEXAGON,
328        (Architecture::LoongArch32, None) => elf::EM_LOONGARCH,
329        (Architecture::LoongArch64, None) => elf::EM_LOONGARCH,
330        (Architecture::M68k, None) => elf::EM_68K,
331        (Architecture::Mips, None) => elf::EM_MIPS,
332        (Architecture::Mips64, None) => elf::EM_MIPS,
333        (Architecture::Mips64_N32, None) => elf::EM_MIPS,
334        (Architecture::Msp430, None) => elf::EM_MSP430,
335        (Architecture::PowerPc, None) => elf::EM_PPC,
336        (Architecture::PowerPc64, None) => elf::EM_PPC64,
337        (Architecture::Riscv32, None) => elf::EM_RISCV,
338        (Architecture::Riscv64, None) => elf::EM_RISCV,
339        (Architecture::S390x, None) => elf::EM_S390,
340        (Architecture::Sbf, None) => elf::EM_SBF,
341        (Architecture::Sharc, None) => elf::EM_SHARC,
342        (Architecture::Sparc, None) => elf::EM_SPARC,
343        (Architecture::Sparc32Plus, None) => elf::EM_SPARC32PLUS,
344        (Architecture::Sparc64, None) => elf::EM_SPARCV9,
345        (Architecture::Xtensa, None) => elf::EM_XTENSA,
346        _ => {
347            sess.dcx().fatal(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("raw-dylib is not supported for the architecture `{0}`",
                sess.target.arch))
    })format!(
348                "raw-dylib is not supported for the architecture `{}`",
349                sess.target.arch
350            ));
351        }
352    };
353
354    stub.write_file_header(&write::FileHeader {
355        os_abi: crate::back::metadata::elf_os_abi(sess),
356        abi_version: 0,
357        e_type: object::elf::ET_DYN,
358        e_machine,
359        e_entry: 0,
360        e_flags: crate::back::metadata::elf_e_flags(arch, sess),
361    })
362    .unwrap();
363
364    // .shstrtab
365    stub.write_shstrtab();
366
367    // Section headers
368    stub.write_null_section_header();
369    stub.write_shstrtab_section_header();
370    // Create a dummy .text section for our dummy non-data symbols.
371    stub.write_section_header(&write::SectionHeader {
372        name: Some(text_section_name),
373        sh_type: elf::SHT_PROGBITS,
374        sh_flags: 0,
375        sh_addr: 0,
376        sh_offset: 0,
377        sh_size: 0,
378        sh_link: 0,
379        sh_info: 0,
380        sh_addralign: 16,
381        sh_entsize: 0,
382    });
383    // And also a dummy .data section for our dummy data symbols.
384    stub.write_section_header(&write::SectionHeader {
385        name: Some(data_section_name),
386        sh_type: elf::SHT_PROGBITS,
387        sh_flags: (elf::SHF_WRITE | elf::SHF_ALLOC) as u64,
388        sh_addr: 0,
389        sh_offset: 0,
390        sh_size: 0,
391        sh_link: 0,
392        sh_info: 0,
393        sh_addralign: 16,
394        sh_entsize: 0,
395    });
396    stub.write_dynsym_section_header(0, 1);
397    stub.write_dynstr_section_header(0);
398    if !vers.is_empty() {
399        stub.write_gnu_versym_section_header(0);
400        stub.write_gnu_verdef_section_header(0);
401    }
402    stub.write_dynamic_section_header(0);
403
404    // .dynsym
405    stub.write_null_dynamic_symbol();
406    // Linkers like LLD require at least somewhat reasonable symbol values rather than zero,
407    // otherwise all the symbols might get put at the same address. Thus we increment the value
408    // every time we write a symbol.
409    let mut st_value = 0;
410    for (_name, dynstr, _ver, symbol_type, size) in syms.iter().copied() {
411        let sym_type = match symbol_type {
412            DllImportSymbolType::Function => elf::STT_FUNC,
413            DllImportSymbolType::Static => elf::STT_OBJECT,
414            DllImportSymbolType::ThreadLocal => elf::STT_TLS,
415        };
416        let section =
417            if symbol_type == DllImportSymbolType::Static { data_section } else { text_section };
418        stub.write_dynamic_symbol(&write::Sym {
419            name: Some(dynstr),
420            st_info: (elf::STB_GLOBAL << 4) | sym_type,
421            st_other: elf::STV_DEFAULT,
422            section: Some(section),
423            st_shndx: 0, // ignored by object in favor of the `section` field
424            st_value,
425            st_size: size.bytes(),
426        });
427        st_value += 8;
428    }
429
430    // .dynstr
431    stub.write_dynstr();
432
433    // ld.bfd is unhappy if these sections exist without any symbols, so we only generate them when necessary.
434    if !vers.is_empty() {
435        // .gnu_version
436        stub.write_null_gnu_versym();
437        for (_name, _dynstr, ver, _symbol_type, _size) in syms.iter().copied() {
438            stub.write_gnu_versym(if let Some(ver) = ver {
439                if !((2 + ver as u16) < elf::VERSYM_HIDDEN) {
    ::core::panicking::panic("assertion failed: (2 + ver as u16) < elf::VERSYM_HIDDEN")
};assert!((2 + ver as u16) < elf::VERSYM_HIDDEN);
440                elf::VERSYM_HIDDEN | (2 + ver as u16)
441            } else {
442                1
443            });
444        }
445
446        // .gnu_version_d
447        stub.write_align_gnu_verdef();
448        stub.write_gnu_verdef(&write::Verdef {
449            version: elf::VER_DEF_CURRENT,
450            flags: elf::VER_FLG_BASE,
451            index: 1,
452            aux_count: 1,
453            name: soname,
454        });
455        for (ver, (_name, dynstr)) in vers.into_iter().enumerate() {
456            stub.write_gnu_verdef(&write::Verdef {
457                version: elf::VER_DEF_CURRENT,
458                flags: 0,
459                index: 2 + ver as u16,
460                aux_count: 1,
461                name: dynstr,
462            });
463        }
464    }
465
466    // .dynamic
467    // the DT_SONAME will be used by the linker to populate DT_NEEDED
468    // which the loader uses to find the library.
469    stub.write_align_dynamic();
470    stub.write_dynamic_string(elf::DT_SONAME, soname).unwrap();
471    // LSB section "2.7. Symbol Versioning" requires `DT_VERDEFNUM` to be reliable.
472    if verdef_count > 1 {
473        stub.write_dynamic(elf::DT_VERDEFNUM, verdef_count as u64).unwrap();
474    }
475    // DT_NULL terminates the .dynamic table.
476    stub.write_dynamic(elf::DT_NULL, 0).unwrap();
477
478    stub_buf
479}