]>
Commit | Line | Data |
---|---|---|
c1a9b12d SL |
1 | //! A helper class for dealing with static archives |
2 | ||
5099ac24 FG |
3 | use std::env; |
4 | use std::ffi::{CStr, CString, OsString}; | |
2b03887a FG |
5 | use std::fs; |
6 | use std::io::{self, Write}; | |
c1a9b12d SL |
7 | use std::mem; |
8 | use std::path::{Path, PathBuf}; | |
e9174d1e | 9 | use std::ptr; |
c1a9b12d SL |
10 | use std::str; |
11 | ||
2b03887a FG |
12 | use object::read::macho::FatArch; |
13 | ||
f2b60f7d | 14 | use crate::common; |
9fa01778 | 15 | use crate::llvm::archive_ro::{ArchiveRO, Child}; |
17df50a5 | 16 | use crate::llvm::{self, ArchiveKind, LLVMMachineType, LLVMRustCOFFShortExport}; |
064997fb | 17 | use rustc_codegen_ssa::back::archive::{ArchiveBuilder, ArchiveBuilderBuilder}; |
2b03887a | 18 | use rustc_data_structures::memmap::Mmap; |
f2b60f7d | 19 | use rustc_session::cstore::DllImport; |
ba9703b0 | 20 | use rustc_session::Session; |
c1a9b12d | 21 | |
abe05a73 | 22 | /// Helper for adding many files to an archive. |
c1a9b12d | 23 | #[must_use = "must call build() to finish building the archive"] |
48663c56 | 24 | pub struct LlvmArchiveBuilder<'a> { |
923072b8 | 25 | sess: &'a Session, |
c1a9b12d | 26 | additions: Vec<Addition>, |
c1a9b12d SL |
27 | } |
28 | ||
29 | enum Addition { | |
dfeec247 XL |
30 | File { path: PathBuf, name_in_archive: String }, |
31 | Archive { path: PathBuf, archive: ArchiveRO, skip: Box<dyn FnMut(&str) -> bool> }, | |
c1a9b12d SL |
32 | } |
33 | ||
416331ca XL |
34 | impl Addition { |
35 | fn path(&self) -> &Path { | |
36 | match self { | |
37 | Addition::File { path, .. } | Addition::Archive { path, .. } => path, | |
38 | } | |
39 | } | |
40 | } | |
41 | ||
9fa01778 | 42 | fn is_relevant_child(c: &Child<'_>) -> bool { |
c1a9b12d SL |
43 | match c.name() { |
44 | Some(name) => !name.contains("SYMDEF"), | |
45 | None => false, | |
46 | } | |
47 | } | |
48 | ||
17df50a5 XL |
49 | /// Map machine type strings to values of LLVM's MachineTypes enum. |
50 | fn llvm_machine_type(cpu: &str) -> LLVMMachineType { | |
51 | match cpu { | |
52 | "x86_64" => LLVMMachineType::AMD64, | |
53 | "x86" => LLVMMachineType::I386, | |
54 | "aarch64" => LLVMMachineType::ARM64, | |
55 | "arm" => LLVMMachineType::ARM, | |
56 | _ => panic!("unsupported cpu type {}", cpu), | |
57 | } | |
58 | } | |
59 | ||
2b03887a FG |
60 | fn try_filter_fat_archs( |
61 | archs: object::read::Result<&[impl FatArch]>, | |
62 | target_arch: object::Architecture, | |
63 | archive_path: &Path, | |
64 | archive_map_data: &[u8], | |
65 | ) -> io::Result<Option<PathBuf>> { | |
66 | let archs = archs.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; | |
67 | ||
68 | let desired = match archs.iter().filter(|a| a.architecture() == target_arch).next() { | |
69 | Some(a) => a, | |
70 | None => return Ok(None), | |
71 | }; | |
72 | ||
73 | let (mut new_f, extracted_path) = tempfile::Builder::new() | |
74 | .suffix(archive_path.file_name().unwrap()) | |
75 | .tempfile()? | |
76 | .keep() | |
77 | .unwrap(); | |
78 | ||
79 | new_f.write_all( | |
80 | desired.data(archive_map_data).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?, | |
81 | )?; | |
82 | ||
83 | Ok(Some(extracted_path)) | |
84 | } | |
85 | ||
86 | fn try_extract_macho_fat_archive( | |
87 | sess: &Session, | |
88 | archive_path: &Path, | |
89 | ) -> io::Result<Option<PathBuf>> { | |
90 | let archive_map = unsafe { Mmap::map(fs::File::open(&archive_path)?)? }; | |
91 | let target_arch = match sess.target.arch.as_ref() { | |
92 | "aarch64" => object::Architecture::Aarch64, | |
93 | "x86_64" => object::Architecture::X86_64, | |
94 | _ => return Ok(None), | |
95 | }; | |
96 | ||
97 | match object::macho::FatHeader::parse(&*archive_map) { | |
98 | Ok(h) if h.magic.get(object::endian::BigEndian) == object::macho::FAT_MAGIC => { | |
99 | let archs = object::macho::FatHeader::parse_arch32(&*archive_map); | |
100 | try_filter_fat_archs(archs, target_arch, archive_path, &*archive_map) | |
101 | } | |
102 | Ok(h) if h.magic.get(object::endian::BigEndian) == object::macho::FAT_MAGIC_64 => { | |
103 | let archs = object::macho::FatHeader::parse_arch64(&*archive_map); | |
104 | try_filter_fat_archs(archs, target_arch, archive_path, &*archive_map) | |
105 | } | |
106 | // Not a FatHeader at all, just return None. | |
107 | _ => Ok(None), | |
108 | } | |
109 | } | |
110 | ||
48663c56 | 111 | impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> { |
064997fb FG |
112 | fn add_archive( |
113 | &mut self, | |
114 | archive: &Path, | |
115 | skip: Box<dyn FnMut(&str) -> bool + 'static>, | |
116 | ) -> io::Result<()> { | |
2b03887a FG |
117 | let mut archive = archive.to_path_buf(); |
118 | if self.sess.target.llvm_target.contains("-apple-macosx") { | |
119 | if let Some(new_archive) = try_extract_macho_fat_archive(&self.sess, &archive)? { | |
120 | archive = new_archive | |
121 | } | |
122 | } | |
123 | let archive_ro = match ArchiveRO::open(&archive) { | |
c295e0f8 XL |
124 | Ok(ar) => ar, |
125 | Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)), | |
126 | }; | |
127 | if self.additions.iter().any(|ar| ar.path() == archive) { | |
128 | return Ok(()); | |
129 | } | |
130 | self.additions.push(Addition::Archive { | |
2b03887a | 131 | path: archive, |
c295e0f8 XL |
132 | archive: archive_ro, |
133 | skip: Box::new(skip), | |
b039eaaf | 134 | }); |
c295e0f8 | 135 | Ok(()) |
c1a9b12d SL |
136 | } |
137 | ||
c1a9b12d | 138 | /// Adds an arbitrary file to this archive |
48663c56 | 139 | fn add_file(&mut self, file: &Path) { |
c1a9b12d | 140 | let name = file.file_name().unwrap().to_str().unwrap(); |
dfeec247 XL |
141 | self.additions |
142 | .push(Addition::File { path: file.to_path_buf(), name_in_archive: name.to_owned() }); | |
c1a9b12d SL |
143 | } |
144 | ||
c1a9b12d SL |
145 | /// Combine the provided files, rlibs, and native libraries into a single |
146 | /// `Archive`. | |
064997fb FG |
147 | fn build(mut self: Box<Self>, output: &Path) -> bool { |
148 | match self.build_with_llvm(output) { | |
923072b8 FG |
149 | Ok(any_members) => any_members, |
150 | Err(e) => self.sess.fatal(&format!("failed to build archive: {}", e)), | |
c1a9b12d | 151 | } |
c1a9b12d | 152 | } |
064997fb | 153 | } |
17df50a5 | 154 | |
064997fb FG |
155 | pub struct LlvmArchiveBuilderBuilder; |
156 | ||
157 | impl ArchiveBuilderBuilder for LlvmArchiveBuilderBuilder { | |
158 | fn new_archive_builder<'a>(&self, sess: &'a Session) -> Box<dyn ArchiveBuilder<'a> + 'a> { | |
159 | Box::new(LlvmArchiveBuilder { sess, additions: Vec::new() }) | |
160 | } | |
161 | ||
162 | fn create_dll_import_lib( | |
163 | &self, | |
164 | sess: &Session, | |
17df50a5 XL |
165 | lib_name: &str, |
166 | dll_imports: &[DllImport], | |
064997fb | 167 | tmpdir: &Path, |
f2b60f7d | 168 | is_direct_dependency: bool, |
064997fb | 169 | ) -> PathBuf { |
f2b60f7d | 170 | let name_suffix = if is_direct_dependency { "_imports" } else { "_imports_indirect" }; |
17df50a5 | 171 | let output_path = { |
064997fb | 172 | let mut output_path: PathBuf = tmpdir.to_path_buf(); |
f2b60f7d | 173 | output_path.push(format!("{}{}", lib_name, name_suffix)); |
17df50a5 XL |
174 | output_path.with_extension("lib") |
175 | }; | |
176 | ||
064997fb | 177 | let target = &sess.target; |
f2b60f7d | 178 | let mingw_gnu_toolchain = common::is_mingw_gnu_toolchain(target); |
5099ac24 FG |
179 | |
180 | let import_name_and_ordinal_vector: Vec<(String, Option<u16>)> = dll_imports | |
17df50a5 | 181 | .iter() |
136023e0 | 182 | .map(|import: &DllImport| { |
064997fb | 183 | if sess.target.arch == "x86" { |
5099ac24 | 184 | ( |
f2b60f7d FG |
185 | common::i686_decorated_name(import, mingw_gnu_toolchain, false), |
186 | import.ordinal(), | |
5099ac24 | 187 | ) |
136023e0 | 188 | } else { |
f2b60f7d | 189 | (import.name.to_string(), import.ordinal()) |
136023e0 | 190 | } |
17df50a5 XL |
191 | }) |
192 | .collect(); | |
193 | ||
5099ac24 FG |
194 | if mingw_gnu_toolchain { |
195 | // The binutils linker used on -windows-gnu targets cannot read the import | |
196 | // libraries generated by LLVM: in our attempts, the linker produced an .EXE | |
197 | // that loaded but crashed with an AV upon calling one of the imported | |
198 | // functions. Therefore, use binutils to create the import library instead, | |
199 | // by writing a .DEF file to the temp dir and calling binutils's dlltool. | |
f2b60f7d FG |
200 | let def_file_path = |
201 | tmpdir.join(format!("{}{}", lib_name, name_suffix)).with_extension("def"); | |
5099ac24 FG |
202 | |
203 | let def_file_content = format!( | |
204 | "EXPORTS\n{}", | |
205 | import_name_and_ordinal_vector | |
206 | .into_iter() | |
207 | .map(|(name, ordinal)| { | |
208 | match ordinal { | |
209 | Some(n) => format!("{} @{} NONAME", name, n), | |
210 | None => name, | |
211 | } | |
212 | }) | |
213 | .collect::<Vec<String>>() | |
214 | .join("\n") | |
215 | ); | |
17df50a5 | 216 | |
5099ac24 FG |
217 | match std::fs::write(&def_file_path, def_file_content) { |
218 | Ok(_) => {} | |
219 | Err(e) => { | |
064997fb | 220 | sess.fatal(&format!("Error writing .DEF file: {}", e)); |
5099ac24 FG |
221 | } |
222 | }; | |
17df50a5 | 223 | |
f2b60f7d FG |
224 | // --no-leading-underscore: For the `import_name_type` feature to work, we need to be |
225 | // able to control the *exact* spelling of each of the symbols that are being imported: | |
226 | // hence we don't want `dlltool` adding leading underscores automatically. | |
064997fb | 227 | let dlltool = find_binutils_dlltool(sess); |
5099ac24 FG |
228 | let result = std::process::Command::new(dlltool) |
229 | .args([ | |
230 | "-d", | |
231 | def_file_path.to_str().unwrap(), | |
232 | "-D", | |
233 | lib_name, | |
234 | "-l", | |
235 | output_path.to_str().unwrap(), | |
f2b60f7d | 236 | "--no-leading-underscore", |
5099ac24 FG |
237 | ]) |
238 | .output(); | |
239 | ||
240 | match result { | |
241 | Err(e) => { | |
064997fb | 242 | sess.fatal(&format!("Error calling dlltool: {}", e)); |
5099ac24 | 243 | } |
064997fb | 244 | Ok(output) if !output.status.success() => sess.fatal(&format!( |
5099ac24 FG |
245 | "Dlltool could not create import library: {}\n{}", |
246 | String::from_utf8_lossy(&output.stdout), | |
247 | String::from_utf8_lossy(&output.stderr) | |
248 | )), | |
249 | _ => {} | |
250 | } | |
251 | } else { | |
252 | // we've checked for \0 characters in the library name already | |
253 | let dll_name_z = CString::new(lib_name).unwrap(); | |
254 | ||
255 | let output_path_z = rustc_fs_util::path_to_c_string(&output_path); | |
256 | ||
f2b60f7d FG |
257 | trace!("invoking LLVMRustWriteImportLibrary"); |
258 | trace!(" dll_name {:#?}", dll_name_z); | |
259 | trace!(" output_path {}", output_path.display()); | |
260 | trace!( | |
5099ac24 FG |
261 | " import names: {}", |
262 | dll_imports | |
263 | .iter() | |
264 | .map(|import| import.name.to_string()) | |
265 | .collect::<Vec<_>>() | |
266 | .join(", "), | |
267 | ); | |
17df50a5 | 268 | |
5099ac24 FG |
269 | // All import names are Rust identifiers and therefore cannot contain \0 characters. |
270 | // FIXME: when support for #[link_name] is implemented, ensure that the import names | |
271 | // still don't contain any \0 characters. Also need to check that the names don't | |
272 | // contain substrings like " @" or "NONAME" that are keywords or otherwise reserved | |
273 | // in definition files. | |
274 | let cstring_import_name_and_ordinal_vector: Vec<(CString, Option<u16>)> = | |
275 | import_name_and_ordinal_vector | |
276 | .into_iter() | |
277 | .map(|(name, ordinal)| (CString::new(name).unwrap(), ordinal)) | |
278 | .collect(); | |
279 | ||
280 | let ffi_exports: Vec<LLVMRustCOFFShortExport> = cstring_import_name_and_ordinal_vector | |
281 | .iter() | |
282 | .map(|(name_z, ordinal)| LLVMRustCOFFShortExport::new(name_z.as_ptr(), *ordinal)) | |
283 | .collect(); | |
284 | let result = unsafe { | |
285 | crate::llvm::LLVMRustWriteImportLibrary( | |
286 | dll_name_z.as_ptr(), | |
287 | output_path_z.as_ptr(), | |
288 | ffi_exports.as_ptr(), | |
289 | ffi_exports.len(), | |
064997fb FG |
290 | llvm_machine_type(&sess.target.arch) as u16, |
291 | !sess.target.is_like_msvc, | |
5099ac24 FG |
292 | ) |
293 | }; | |
294 | ||
295 | if result == crate::llvm::LLVMRustResult::Failure { | |
064997fb | 296 | sess.fatal(&format!( |
5099ac24 FG |
297 | "Error creating import library for {}: {}", |
298 | lib_name, | |
299 | llvm::last_error().unwrap_or("unknown LLVM error".to_string()) | |
300 | )); | |
301 | } | |
302 | }; | |
17df50a5 | 303 | |
064997fb | 304 | output_path |
17df50a5 | 305 | } |
48663c56 XL |
306 | } |
307 | ||
308 | impl<'a> LlvmArchiveBuilder<'a> { | |
064997fb | 309 | fn build_with_llvm(&mut self, output: &Path) -> io::Result<bool> { |
923072b8 FG |
310 | let kind = &*self.sess.target.archive_format; |
311 | let kind = kind.parse::<ArchiveKind>().map_err(|_| kind).unwrap_or_else(|kind| { | |
312 | self.sess.fatal(&format!("Don't know how to build archive of type: {}", kind)) | |
313 | }); | |
c1a9b12d | 314 | |
416331ca | 315 | let mut additions = mem::take(&mut self.additions); |
c1a9b12d SL |
316 | let mut strings = Vec::new(); |
317 | let mut members = Vec::new(); | |
b7449926 | 318 | |
064997fb | 319 | let dst = CString::new(output.to_str().unwrap())?; |
c1a9b12d SL |
320 | |
321 | unsafe { | |
b7449926 | 322 | for addition in &mut additions { |
c1a9b12d SL |
323 | match addition { |
324 | Addition::File { path, name_in_archive } => { | |
54a0048b | 325 | let path = CString::new(path.to_str().unwrap())?; |
b7449926 | 326 | let name = CString::new(name_in_archive.clone())?; |
dfeec247 XL |
327 | members.push(llvm::LLVMRustArchiveMemberNew( |
328 | path.as_ptr(), | |
329 | name.as_ptr(), | |
330 | None, | |
331 | )); | |
c1a9b12d SL |
332 | strings.push(path); |
333 | strings.push(name); | |
334 | } | |
416331ca | 335 | Addition::Archive { archive, skip, .. } => { |
7453a54e | 336 | for child in archive.iter() { |
54a0048b | 337 | let child = child.map_err(string_to_io_error)?; |
7453a54e | 338 | if !is_relevant_child(&child) { |
dfeec247 | 339 | continue; |
7453a54e | 340 | } |
c1a9b12d | 341 | let child_name = child.name().unwrap(); |
7453a54e | 342 | if skip(child_name) { |
dfeec247 | 343 | continue; |
7453a54e SL |
344 | } |
345 | ||
346 | // It appears that LLVM's archive writer is a little | |
347 | // buggy if the name we pass down isn't just the | |
348 | // filename component, so chop that off here and | |
349 | // pass it in. | |
350 | // | |
351 | // See LLVM bug 25877 for more info. | |
dfeec247 XL |
352 | let child_name = |
353 | Path::new(child_name).file_name().unwrap().to_str().unwrap(); | |
54a0048b | 354 | let name = CString::new(child_name)?; |
dfeec247 XL |
355 | let m = llvm::LLVMRustArchiveMemberNew( |
356 | ptr::null(), | |
357 | name.as_ptr(), | |
358 | Some(child.raw), | |
359 | ); | |
c1a9b12d SL |
360 | members.push(m); |
361 | strings.push(name); | |
362 | } | |
c1a9b12d SL |
363 | } |
364 | } | |
365 | } | |
366 | ||
dfeec247 XL |
367 | let r = llvm::LLVMRustWriteArchive( |
368 | dst.as_ptr(), | |
369 | members.len() as libc::size_t, | |
370 | members.as_ptr() as *const &_, | |
5099ac24 | 371 | true, |
dfeec247 XL |
372 | kind, |
373 | ); | |
5bcae85e | 374 | let ret = if r.into_result().is_err() { |
c1a9b12d SL |
375 | let err = llvm::LLVMRustGetLastError(); |
376 | let msg = if err.is_null() { | |
a1dfa0c6 | 377 | "failed to write archive".into() |
c1a9b12d SL |
378 | } else { |
379 | String::from_utf8_lossy(CStr::from_ptr(err).to_bytes()) | |
c1a9b12d SL |
380 | }; |
381 | Err(io::Error::new(io::ErrorKind::Other, msg)) | |
382 | } else { | |
923072b8 | 383 | Ok(!members.is_empty()) |
c1a9b12d SL |
384 | }; |
385 | for member in members { | |
386 | llvm::LLVMRustArchiveMemberFree(member); | |
387 | } | |
a1dfa0c6 | 388 | ret |
c1a9b12d SL |
389 | } |
390 | } | |
391 | } | |
7453a54e SL |
392 | |
393 | fn string_to_io_error(s: String) -> io::Error { | |
394 | io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s)) | |
395 | } | |
5099ac24 FG |
396 | |
397 | fn find_binutils_dlltool(sess: &Session) -> OsString { | |
398 | assert!(sess.target.options.is_like_windows && !sess.target.options.is_like_msvc); | |
064997fb | 399 | if let Some(dlltool_path) = &sess.opts.unstable_opts.dlltool { |
5099ac24 FG |
400 | return dlltool_path.clone().into_os_string(); |
401 | } | |
402 | ||
403 | let mut tool_name: OsString = if sess.host.arch != sess.target.arch { | |
404 | // We are cross-compiling, so we need the tool with the prefix matching our target | |
405 | if sess.target.arch == "x86" { | |
406 | "i686-w64-mingw32-dlltool" | |
407 | } else { | |
408 | "x86_64-w64-mingw32-dlltool" | |
409 | } | |
410 | } else { | |
411 | // We are not cross-compiling, so we just want `dlltool` | |
412 | "dlltool" | |
413 | } | |
414 | .into(); | |
415 | ||
416 | if sess.host.options.is_like_windows { | |
417 | // If we're compiling on Windows, add the .exe suffix | |
418 | tool_name.push(".exe"); | |
419 | } | |
420 | ||
421 | // NOTE: it's not clear how useful it is to explicitly search PATH. | |
422 | for dir in env::split_paths(&env::var_os("PATH").unwrap_or_default()) { | |
423 | let full_path = dir.join(&tool_name); | |
424 | if full_path.is_file() { | |
425 | return full_path.into_os_string(); | |
426 | } | |
427 | } | |
428 | ||
429 | // The user didn't specify the location of the dlltool binary, and we weren't able | |
430 | // to find the appropriate one on the PATH. Just return the name of the tool | |
431 | // and let the invocation fail with a hopefully useful error message. | |
432 | tool_name | |
433 | } |