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_hir::attrs::NativeLibKind;
11use rustc_session::Session;
12use rustc_session::cstore::DllImport;
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 let NativeLibKind::RawDylib { .. } = lib.kind {
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>, bool)> {
132    // Use index maps to preserve original order of imports and libraries.
133    let mut dylib_table = FxIndexMap::<String, (FxIndexMap<Symbol, &DllImport>, bool)>::default();
134
135    for lib in used_libraries {
136        if let NativeLibKind::RawDylib { as_needed } = lib.kind {
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 (stub_imports, stub_as_needed) =
146                dylib_table.entry(filename.clone()).or_insert((Default::default(), true));
147            for import in &lib.dll_imports {
148                stub_imports.insert(import.name, import);
149            }
150            *stub_as_needed = *stub_as_needed && as_needed.unwrap_or(true);
151        }
152    }
153    sess.dcx().abort_if_errors();
154    dylib_table
155        .into_iter()
156        .map(|(name, (imports, as_needed))| {
157            (name, imports.into_iter().map(|(_, import)| import.clone()).collect(), as_needed)
158        })
159        .collect()
160}
161
162pub(super) fn create_raw_dylib_elf_stub_shared_objects<'a>(
163    sess: &Session,
164    used_libraries: impl IntoIterator<Item = &'a NativeLib>,
165    raw_dylib_so_dir: &Path,
166) -> Vec<(String, bool)> {
167    collate_raw_dylibs_elf(sess, used_libraries)
168        .into_iter()
169        .map(|(load_filename, raw_dylib_imports, as_needed)| {
170            use std::hash::Hash;
171
172            // `load_filename` is the *target/loader* filename that will end up in NEEDED.
173            // Usually this will be something like `libc.so` or `libc.so.6` but with
174            // verbatim it might also be an absolute path.
175            // To be able to support this properly, we always put this load filename
176            // into the SONAME of the library and link it via a temporary file with a random name.
177            // This also avoids naming conflicts with non-raw-dylib linkage of the same library.
178
179            let shared_object = create_elf_raw_dylib_stub(sess, &load_filename, &raw_dylib_imports);
180
181            let mut file_name_hasher = StableHasher::new();
182            load_filename.hash(&mut file_name_hasher);
183            for raw_dylib in raw_dylib_imports {
184                raw_dylib.name.as_str().hash(&mut file_name_hasher);
185            }
186
187            let library_filename: Hash128 = file_name_hasher.finish();
188            let temporary_lib_name = format!(
189                "{}{}{}",
190                sess.target.dll_prefix,
191                library_filename.as_u128().to_base_fixed_len(CASE_INSENSITIVE),
192                sess.target.dll_suffix
193            );
194            let link_path = raw_dylib_so_dir.join(&temporary_lib_name);
195
196            let file = match fs::File::create_new(&link_path) {
197                Ok(file) => file,
198                Err(error) => sess.dcx().emit_fatal(ErrorCreatingImportLibrary {
199                    lib_name: &load_filename,
200                    error: error.to_string(),
201                }),
202            };
203            if let Err(error) = BufWriter::new(file).write_all(&shared_object) {
204                sess.dcx().emit_fatal(ErrorCreatingImportLibrary {
205                    lib_name: &load_filename,
206                    error: error.to_string(),
207                });
208            };
209
210            (temporary_lib_name, as_needed)
211        })
212        .collect()
213}
214
215/// Create an ELF .so stub file for raw-dylib.
216/// It exports all the provided symbols, but is otherwise empty.
217fn create_elf_raw_dylib_stub(sess: &Session, soname: &str, symbols: &[DllImport]) -> Vec<u8> {
218    use object::write::elf as write;
219    use object::{AddressSize, Architecture, elf};
220
221    let mut stub_buf = Vec::new();
222
223    // Build the stub ELF using the object crate.
224    // The high-level portable API does not allow for the fine-grained control we need,
225    // so this uses the low-level object::write::elf API.
226    // The low-level API consists of two stages: reservation and writing.
227    // We first reserve space for all the things in the binary and then write them.
228    // It is important that the order of reservation matches the order of writing.
229    // The object crate contains many debug asserts that fire if you get this wrong.
230
231    let Some((arch, sub_arch)) = sess.target.object_architecture(&sess.unstable_target_features)
232    else {
233        sess.dcx().fatal(format!(
234            "raw-dylib is not supported for the architecture `{}`",
235            sess.target.arch
236        ));
237    };
238
239    let endianness = match sess.target.options.endian {
240        Endian::Little => object::Endianness::Little,
241        Endian::Big => object::Endianness::Big,
242    };
243
244    let is_64 = match arch.address_size() {
245        Some(AddressSize::U8 | AddressSize::U16 | AddressSize::U32) => false,
246        Some(AddressSize::U64) => true,
247        _ => sess.dcx().fatal(format!(
248            "raw-dylib is not supported for the architecture `{}`",
249            sess.target.arch
250        )),
251    };
252
253    let mut stub = write::Writer::new(endianness, is_64, &mut stub_buf);
254
255    let mut vers = Vec::new();
256    let mut vers_map = FxHashMap::default();
257    let mut syms = Vec::new();
258
259    for symbol in symbols {
260        let symbol_name = symbol.name.as_str();
261        if let Some((name, version_name)) = symbol_name.split_once('@') {
262            assert!(!version_name.contains('@'));
263            let dynstr = stub.add_dynamic_string(name.as_bytes());
264            let ver = if let Some(&ver_id) = vers_map.get(version_name) {
265                ver_id
266            } else {
267                let id = vers.len();
268                vers_map.insert(version_name, id);
269                let dynstr = stub.add_dynamic_string(version_name.as_bytes());
270                vers.push((version_name, dynstr));
271                id
272            };
273            syms.push((name, dynstr, Some(ver)));
274        } else {
275            let dynstr = stub.add_dynamic_string(symbol_name.as_bytes());
276            syms.push((symbol_name, dynstr, None));
277        }
278    }
279
280    let soname = stub.add_dynamic_string(soname.as_bytes());
281
282    // These initial reservations don't reserve any bytes in the binary yet,
283    // they just allocate in the internal data structures.
284
285    // First, we create the dynamic symbol table. It starts with a null symbol
286    // and then all the symbols and their dynamic strings.
287    stub.reserve_null_dynamic_symbol_index();
288
289    for _ in syms.iter() {
290        stub.reserve_dynamic_symbol_index();
291    }
292
293    // Reserve the sections.
294    // We have the minimal sections for a dynamic SO and .text where we point our dummy symbols to.
295    stub.reserve_shstrtab_section_index();
296    let text_section_name = stub.add_section_name(".text".as_bytes());
297    let text_section = stub.reserve_section_index();
298    stub.reserve_dynsym_section_index();
299    stub.reserve_dynstr_section_index();
300    if !vers.is_empty() {
301        stub.reserve_gnu_versym_section_index();
302        stub.reserve_gnu_verdef_section_index();
303    }
304    stub.reserve_dynamic_section_index();
305
306    // These reservations now determine the actual layout order of the object file.
307    stub.reserve_file_header();
308    stub.reserve_shstrtab();
309    stub.reserve_section_headers();
310    stub.reserve_dynsym();
311    stub.reserve_dynstr();
312    let verdef_count = 1 + vers.len();
313    let mut dynamic_entries = 2; // DT_SONAME, DT_NULL
314    if !vers.is_empty() {
315        stub.reserve_gnu_versym();
316        stub.reserve_gnu_verdef(verdef_count, verdef_count);
317        dynamic_entries += 1; // DT_VERDEFNUM
318    }
319    stub.reserve_dynamic(dynamic_entries);
320
321    // First write the ELF header with the arch information.
322    let e_machine = match (arch, sub_arch) {
323        (Architecture::Aarch64, None) => elf::EM_AARCH64,
324        (Architecture::Aarch64_Ilp32, None) => elf::EM_AARCH64,
325        (Architecture::Arm, None) => elf::EM_ARM,
326        (Architecture::Avr, None) => elf::EM_AVR,
327        (Architecture::Bpf, None) => elf::EM_BPF,
328        (Architecture::Csky, None) => elf::EM_CSKY,
329        (Architecture::E2K32, None) => elf::EM_MCST_ELBRUS,
330        (Architecture::E2K64, None) => elf::EM_MCST_ELBRUS,
331        (Architecture::I386, None) => elf::EM_386,
332        (Architecture::X86_64, None) => elf::EM_X86_64,
333        (Architecture::X86_64_X32, None) => elf::EM_X86_64,
334        (Architecture::Hexagon, None) => elf::EM_HEXAGON,
335        (Architecture::LoongArch32, None) => elf::EM_LOONGARCH,
336        (Architecture::LoongArch64, None) => elf::EM_LOONGARCH,
337        (Architecture::M68k, None) => elf::EM_68K,
338        (Architecture::Mips, None) => elf::EM_MIPS,
339        (Architecture::Mips64, None) => elf::EM_MIPS,
340        (Architecture::Mips64_N32, None) => elf::EM_MIPS,
341        (Architecture::Msp430, None) => elf::EM_MSP430,
342        (Architecture::PowerPc, None) => elf::EM_PPC,
343        (Architecture::PowerPc64, None) => elf::EM_PPC64,
344        (Architecture::Riscv32, None) => elf::EM_RISCV,
345        (Architecture::Riscv64, None) => elf::EM_RISCV,
346        (Architecture::S390x, None) => elf::EM_S390,
347        (Architecture::Sbf, None) => elf::EM_SBF,
348        (Architecture::Sharc, None) => elf::EM_SHARC,
349        (Architecture::Sparc, None) => elf::EM_SPARC,
350        (Architecture::Sparc32Plus, None) => elf::EM_SPARC32PLUS,
351        (Architecture::Sparc64, None) => elf::EM_SPARCV9,
352        (Architecture::Xtensa, None) => elf::EM_XTENSA,
353        _ => {
354            sess.dcx().fatal(format!(
355                "raw-dylib is not supported for the architecture `{}`",
356                sess.target.arch
357            ));
358        }
359    };
360
361    stub.write_file_header(&write::FileHeader {
362        os_abi: crate::back::metadata::elf_os_abi(sess),
363        abi_version: 0,
364        e_type: object::elf::ET_DYN,
365        e_machine,
366        e_entry: 0,
367        e_flags: crate::back::metadata::elf_e_flags(arch, sess),
368    })
369    .unwrap();
370
371    // .shstrtab
372    stub.write_shstrtab();
373
374    // Section headers
375    stub.write_null_section_header();
376    stub.write_shstrtab_section_header();
377    // Create a dummy .text section for our dummy symbols.
378    stub.write_section_header(&write::SectionHeader {
379        name: Some(text_section_name),
380        sh_type: elf::SHT_PROGBITS,
381        sh_flags: 0,
382        sh_addr: 0,
383        sh_offset: 0,
384        sh_size: 0,
385        sh_link: 0,
386        sh_info: 0,
387        sh_addralign: 1,
388        sh_entsize: 0,
389    });
390    stub.write_dynsym_section_header(0, 1);
391    stub.write_dynstr_section_header(0);
392    if !vers.is_empty() {
393        stub.write_gnu_versym_section_header(0);
394        stub.write_gnu_verdef_section_header(0);
395    }
396    stub.write_dynamic_section_header(0);
397
398    // .dynsym
399    stub.write_null_dynamic_symbol();
400    for (_name, dynstr, _ver) in syms.iter().copied() {
401        stub.write_dynamic_symbol(&write::Sym {
402            name: Some(dynstr),
403            st_info: (elf::STB_GLOBAL << 4) | elf::STT_NOTYPE,
404            st_other: elf::STV_DEFAULT,
405            section: Some(text_section),
406            st_shndx: 0, // ignored by object in favor of the `section` field
407            st_value: 0,
408            st_size: 0,
409        });
410    }
411
412    // .dynstr
413    stub.write_dynstr();
414
415    // ld.bfd is unhappy if these sections exist without any symbols, so we only generate them when necessary.
416    if !vers.is_empty() {
417        // .gnu_version
418        stub.write_null_gnu_versym();
419        for (_name, _dynstr, ver) in syms.iter().copied() {
420            stub.write_gnu_versym(if let Some(ver) = ver {
421                assert!((2 + ver as u16) < elf::VERSYM_HIDDEN);
422                elf::VERSYM_HIDDEN | (2 + ver as u16)
423            } else {
424                1
425            });
426        }
427
428        // .gnu_version_d
429        stub.write_align_gnu_verdef();
430        stub.write_gnu_verdef(&write::Verdef {
431            version: elf::VER_DEF_CURRENT,
432            flags: elf::VER_FLG_BASE,
433            index: 1,
434            aux_count: 1,
435            name: soname,
436        });
437        for (ver, (_name, dynstr)) in vers.into_iter().enumerate() {
438            stub.write_gnu_verdef(&write::Verdef {
439                version: elf::VER_DEF_CURRENT,
440                flags: 0,
441                index: 2 + ver as u16,
442                aux_count: 1,
443                name: dynstr,
444            });
445        }
446    }
447
448    // .dynamic
449    // the DT_SONAME will be used by the linker to populate DT_NEEDED
450    // which the loader uses to find the library.
451    stub.write_align_dynamic();
452    stub.write_dynamic_string(elf::DT_SONAME, soname);
453    // LSB section "2.7. Symbol Versioning" requires `DT_VERDEFNUM` to be reliable.
454    if verdef_count > 1 {
455        stub.write_dynamic(elf::DT_VERDEFNUM, verdef_count as u64);
456    }
457    // DT_NULL terminates the .dynamic table.
458    stub.write_dynamic(elf::DT_NULL, 0);
459
460    stub_buf
461}