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