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