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::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::{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 endianness = match sess.target.options.endian {
230        Endian::Little => object::Endianness::Little,
231        Endian::Big => object::Endianness::Big,
232    };
233    let mut stub = write::Writer::new(endianness, true, &mut stub_buf);
234
235    // These initial reservations don't reserve any bytes in the binary yet,
236    // they just allocate in the internal data structures.
237
238    // First, we crate the dynamic symbol table. It starts with a null symbol
239    // and then all the symbols and their dynamic strings.
240    stub.reserve_null_dynamic_symbol_index();
241
242    let dynstrs = symbols
243        .iter()
244        .map(|sym| {
245            stub.reserve_dynamic_symbol_index();
246            (sym, stub.add_dynamic_string(sym.name.as_str().as_bytes()))
247        })
248        .collect::<Vec<_>>();
249
250    let soname = stub.add_dynamic_string(soname.as_bytes());
251
252    // Reserve the sections.
253    // We have the minimal sections for a dynamic SO and .text where we point our dummy symbols to.
254    stub.reserve_shstrtab_section_index();
255    let text_section_name = stub.add_section_name(".text".as_bytes());
256    let text_section = stub.reserve_section_index();
257    stub.reserve_dynstr_section_index();
258    stub.reserve_dynsym_section_index();
259    stub.reserve_dynamic_section_index();
260
261    // These reservations now determine the actual layout order of the object file.
262    stub.reserve_file_header();
263    stub.reserve_shstrtab();
264    stub.reserve_section_headers();
265    stub.reserve_dynstr();
266    stub.reserve_dynsym();
267    stub.reserve_dynamic(2); // DT_SONAME, DT_NULL
268
269    // First write the ELF header with the arch information.
270    let Some((arch, sub_arch)) = sess.target.object_architecture(&sess.unstable_target_features)
271    else {
272        sess.dcx().fatal(format!(
273            "raw-dylib is not supported for the architecture `{}`",
274            sess.target.arch
275        ));
276    };
277    let e_machine = match (arch, sub_arch) {
278        (Architecture::Aarch64, None) => elf::EM_AARCH64,
279        (Architecture::Aarch64_Ilp32, None) => elf::EM_AARCH64,
280        (Architecture::Arm, None) => elf::EM_ARM,
281        (Architecture::Avr, None) => elf::EM_AVR,
282        (Architecture::Bpf, None) => elf::EM_BPF,
283        (Architecture::Csky, None) => elf::EM_CSKY,
284        (Architecture::E2K32, None) => elf::EM_MCST_ELBRUS,
285        (Architecture::E2K64, None) => elf::EM_MCST_ELBRUS,
286        (Architecture::I386, None) => elf::EM_386,
287        (Architecture::X86_64, None) => elf::EM_X86_64,
288        (Architecture::X86_64_X32, None) => elf::EM_X86_64,
289        (Architecture::Hexagon, None) => elf::EM_HEXAGON,
290        (Architecture::LoongArch64, None) => elf::EM_LOONGARCH,
291        (Architecture::M68k, None) => elf::EM_68K,
292        (Architecture::Mips, None) => elf::EM_MIPS,
293        (Architecture::Mips64, None) => elf::EM_MIPS,
294        (Architecture::Mips64_N32, None) => elf::EM_MIPS,
295        (Architecture::Msp430, None) => elf::EM_MSP430,
296        (Architecture::PowerPc, None) => elf::EM_PPC,
297        (Architecture::PowerPc64, None) => elf::EM_PPC64,
298        (Architecture::Riscv32, None) => elf::EM_RISCV,
299        (Architecture::Riscv64, None) => elf::EM_RISCV,
300        (Architecture::S390x, None) => elf::EM_S390,
301        (Architecture::Sbf, None) => elf::EM_SBF,
302        (Architecture::Sharc, None) => elf::EM_SHARC,
303        (Architecture::Sparc, None) => elf::EM_SPARC,
304        (Architecture::Sparc32Plus, None) => elf::EM_SPARC32PLUS,
305        (Architecture::Sparc64, None) => elf::EM_SPARCV9,
306        (Architecture::Xtensa, None) => elf::EM_XTENSA,
307        _ => {
308            sess.dcx().fatal(format!(
309                "raw-dylib is not supported for the architecture `{}`",
310                sess.target.arch
311            ));
312        }
313    };
314
315    stub.write_file_header(&write::FileHeader {
316        os_abi: crate::back::metadata::elf_os_abi(sess),
317        abi_version: 0,
318        e_type: object::elf::ET_DYN,
319        e_machine,
320        e_entry: 0,
321        e_flags: crate::back::metadata::elf_e_flags(arch, sess),
322    })
323    .unwrap();
324
325    // .shstrtab
326    stub.write_shstrtab();
327
328    // Section headers
329    stub.write_null_section_header();
330    stub.write_shstrtab_section_header();
331    // Create a dummy .text section for our dummy symbols.
332    stub.write_section_header(&write::SectionHeader {
333        name: Some(text_section_name),
334        sh_type: elf::SHT_PROGBITS,
335        sh_flags: 0,
336        sh_addr: 0,
337        sh_offset: 0,
338        sh_size: 0,
339        sh_link: 0,
340        sh_info: 0,
341        sh_addralign: 1,
342        sh_entsize: 0,
343    });
344    stub.write_dynstr_section_header(0);
345    stub.write_dynsym_section_header(0, 1);
346    stub.write_dynamic_section_header(0);
347
348    // .dynstr
349    stub.write_dynstr();
350
351    // .dynsym
352    stub.write_null_dynamic_symbol();
353    for (_, name) in dynstrs {
354        stub.write_dynamic_symbol(&write::Sym {
355            name: Some(name),
356            st_info: (elf::STB_GLOBAL << 4) | elf::STT_NOTYPE,
357            st_other: elf::STV_DEFAULT,
358            section: Some(text_section),
359            st_shndx: 0, // ignored by object in favor of the `section` field
360            st_value: 0,
361            st_size: 0,
362        });
363    }
364
365    // .dynamic
366    // the DT_SONAME will be used by the linker to populate DT_NEEDED
367    // which the loader uses to find the library.
368    // DT_NULL terminates the .dynamic table.
369    stub.write_dynamic_string(elf::DT_SONAME, soname);
370    stub.write_dynamic(elf::DT_NULL, 0);
371
372    stub_buf
373}