| 1 | use std::fs; |
| 2 | use std::io::{BufWriter, Write}; |
| 3 | use std::path::{Path, PathBuf}; |
| 4 | |
| 5 | use rustc_abi::Endian; |
| 6 | use rustc_data_structures::base_n::{CASE_INSENSITIVE, ToBaseN}; |
| 7 | use rustc_data_structures::fx::FxIndexMap; |
| 8 | use rustc_data_structures::stable_hasher::StableHasher; |
| 9 | use rustc_hashes::Hash128; |
| 10 | use rustc_session::Session; |
| 11 | use rustc_session::cstore::DllImport; |
| 12 | use rustc_session::utils::NativeLibKind; |
| 13 | use rustc_span::Symbol; |
| 14 | |
| 15 | use crate::back::archive::ImportLibraryItem; |
| 16 | use crate::back::link::ArchiveBuilderBuilder; |
| 17 | use crate::errors::ErrorCreatingImportLibrary; |
| 18 | use 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. |
| 26 | fn 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 | |
| 62 | pub(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. |
| 128 | fn 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 | |
| 160 | pub(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. |
| 215 | fn 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 | } |
| 374 | |