1 //! A helper class for dealing with static archives
4 use std
::ffi
::{c_char, c_void, CStr, CString, OsString}
;
7 use std
::path
::{Path, PathBuf}
;
13 DlltoolFailImportLibrary
, ErrorCallingDllTool
, ErrorCreatingImportLibrary
, ErrorWritingDEFFile
,
15 use crate::llvm
::archive_ro
::{ArchiveRO, Child}
;
16 use crate::llvm
::{self, ArchiveKind, LLVMMachineType, LLVMRustCOFFShortExport}
;
17 use rustc_codegen_ssa
::back
::archive
::{
18 get_native_object_symbols
, try_extract_macho_fat_archive
, ArArchiveBuilder
,
19 ArchiveBuildFailure
, ArchiveBuilder
, ArchiveBuilderBuilder
, UnknownArchiveKind
,
22 use rustc_session
::cstore
::DllImport
;
23 use rustc_session
::Session
;
25 /// Helper for adding many files to an archive.
26 #[must_use = "must call build() to finish building the archive"]
27 pub(crate) struct LlvmArchiveBuilder
<'a
> {
29 additions
: Vec
<Addition
>,
33 File { path: PathBuf, name_in_archive: String }
,
34 Archive { path: PathBuf, archive: ArchiveRO, skip: Box<dyn FnMut(&str) -> bool> }
,
38 fn path(&self) -> &Path
{
40 Addition
::File { path, .. }
| Addition
::Archive { path, .. }
=> path
,
45 fn is_relevant_child(c
: &Child
<'_
>) -> bool
{
47 Some(name
) => !name
.contains("SYMDEF"),
52 /// Map machine type strings to values of LLVM's MachineTypes enum.
53 fn llvm_machine_type(cpu
: &str) -> LLVMMachineType
{
55 "x86_64" => LLVMMachineType
::AMD64
,
56 "x86" => LLVMMachineType
::I386
,
57 "aarch64" => LLVMMachineType
::ARM64
,
58 "arm" => LLVMMachineType
::ARM
,
59 _
=> panic
!("unsupported cpu type {}", cpu
),
63 impl<'a
> ArchiveBuilder
<'a
> for LlvmArchiveBuilder
<'a
> {
67 skip
: Box
<dyn FnMut(&str) -> bool
+ '
static>,
69 let mut archive
= archive
.to_path_buf();
70 if self.sess
.target
.llvm_target
.contains("-apple-macosx") {
71 if let Some(new_archive
) = try_extract_macho_fat_archive(&self.sess
, &archive
)?
{
75 let archive_ro
= match ArchiveRO
::open(&archive
) {
77 Err(e
) => return Err(io
::Error
::new(io
::ErrorKind
::Other
, e
)),
79 if self.additions
.iter().any(|ar
| ar
.path() == archive
) {
82 self.additions
.push(Addition
::Archive
{
90 /// Adds an arbitrary file to this archive
91 fn add_file(&mut self, file
: &Path
) {
92 let name
= file
.file_name().unwrap().to_str().unwrap();
94 .push(Addition
::File { path: file.to_path_buf(), name_in_archive: name.to_owned() }
);
97 /// Combine the provided files, rlibs, and native libraries into a single
99 fn build(mut self: Box
<Self>, output
: &Path
) -> bool
{
100 match self.build_with_llvm(output
) {
101 Ok(any_members
) => any_members
,
102 Err(e
) => self.sess
.emit_fatal(ArchiveBuildFailure { error: e }
),
107 pub struct LlvmArchiveBuilderBuilder
;
109 impl ArchiveBuilderBuilder
for LlvmArchiveBuilderBuilder
{
110 fn new_archive_builder
<'a
>(&self, sess
: &'a Session
) -> Box
<dyn ArchiveBuilder
<'a
> + 'a
> {
111 // FIXME use ArArchiveBuilder on most targets again once reading thin archives is
114 Box
::new(LlvmArchiveBuilder { sess, additions: Vec::new() }
)
116 Box
::new(ArArchiveBuilder
::new(sess
, get_llvm_object_symbols
))
120 fn create_dll_import_lib(
124 dll_imports
: &[DllImport
],
126 is_direct_dependency
: bool
,
128 let name_suffix
= if is_direct_dependency { "_imports" }
else { "_imports_indirect" }
;
130 let mut output_path
: PathBuf
= tmpdir
.to_path_buf();
131 output_path
.push(format
!("{}{}", lib_name
, name_suffix
));
132 output_path
.with_extension("lib")
135 let target
= &sess
.target
;
136 let mingw_gnu_toolchain
= common
::is_mingw_gnu_toolchain(target
);
138 let import_name_and_ordinal_vector
: Vec
<(String
, Option
<u16>)> = dll_imports
140 .map(|import
: &DllImport
| {
141 if sess
.target
.arch
== "x86" {
143 common
::i686_decorated_name(import
, mingw_gnu_toolchain
, false),
147 (import
.name
.to_string(), import
.ordinal())
152 if mingw_gnu_toolchain
{
153 // The binutils linker used on -windows-gnu targets cannot read the import
154 // libraries generated by LLVM: in our attempts, the linker produced an .EXE
155 // that loaded but crashed with an AV upon calling one of the imported
156 // functions. Therefore, use binutils to create the import library instead,
157 // by writing a .DEF file to the temp dir and calling binutils's dlltool.
159 tmpdir
.join(format
!("{}{}", lib_name
, name_suffix
)).with_extension("def");
161 let def_file_content
= format
!(
163 import_name_and_ordinal_vector
165 .map(|(name
, ordinal
)| {
167 Some(n
) => format
!("{} @{} NONAME", name
, n
),
171 .collect
::<Vec
<String
>>()
175 match std
::fs
::write(&def_file_path
, def_file_content
) {
178 sess
.emit_fatal(ErrorWritingDEFFile { error: e }
);
182 // --no-leading-underscore: For the `import_name_type` feature to work, we need to be
183 // able to control the *exact* spelling of each of the symbols that are being imported:
184 // hence we don't want `dlltool` adding leading underscores automatically.
185 let dlltool
= find_binutils_dlltool(sess
);
187 let mut path
= PathBuf
::from(&output_path
);
192 // dlltool target architecture args from:
193 // https://github.com/llvm/llvm-project-release-prs/blob/llvmorg-15.0.6/llvm/lib/ToolDrivers/llvm-dlltool/DlltoolDriver.cpp#L69
194 let (dlltool_target_arch
, dlltool_target_bitness
) = match sess
.target
.arch
.as_ref() {
195 "x86_64" => ("i386:x86-64", "--64"),
196 "x86" => ("i386", "--32"),
197 "aarch64" => ("arm64", "--64"),
198 "arm" => ("arm", "--32"),
199 _
=> panic
!("unsupported arch {}", sess
.target
.arch
),
201 let result
= std
::process
::Command
::new(&dlltool
)
204 def_file_path
.to_str().unwrap(),
208 output_path
.to_str().unwrap(),
212 dlltool_target_bitness
,
213 "--no-leading-underscore",
215 temp_prefix
.to_str().unwrap(),
221 sess
.emit_fatal(ErrorCallingDllTool
{
222 dlltool_path
: dlltool
.to_string_lossy(),
226 // dlltool returns '0' on failure, so check for error output instead.
227 Ok(output
) if !output
.stderr
.is_empty() => {
228 sess
.emit_fatal(DlltoolFailImportLibrary
{
229 stdout
: String
::from_utf8_lossy(&output
.stdout
),
230 stderr
: String
::from_utf8_lossy(&output
.stderr
),
236 // we've checked for \0 characters in the library name already
237 let dll_name_z
= CString
::new(lib_name
).unwrap();
239 let output_path_z
= rustc_fs_util
::path_to_c_string(&output_path
);
241 trace
!("invoking LLVMRustWriteImportLibrary");
242 trace
!(" dll_name {:#?}", dll_name_z
);
243 trace
!(" output_path {}", output_path
.display());
248 .map(|import
| import
.name
.to_string())
253 // All import names are Rust identifiers and therefore cannot contain \0 characters.
254 // FIXME: when support for #[link_name] is implemented, ensure that the import names
255 // still don't contain any \0 characters. Also need to check that the names don't
256 // contain substrings like " @" or "NONAME" that are keywords or otherwise reserved
257 // in definition files.
258 let cstring_import_name_and_ordinal_vector
: Vec
<(CString
, Option
<u16>)> =
259 import_name_and_ordinal_vector
261 .map(|(name
, ordinal
)| (CString
::new(name
).unwrap(), ordinal
))
264 let ffi_exports
: Vec
<LLVMRustCOFFShortExport
> = cstring_import_name_and_ordinal_vector
266 .map(|(name_z
, ordinal
)| LLVMRustCOFFShortExport
::new(name_z
.as_ptr(), *ordinal
))
268 let result
= unsafe {
269 crate::llvm
::LLVMRustWriteImportLibrary(
271 output_path_z
.as_ptr(),
272 ffi_exports
.as_ptr(),
274 llvm_machine_type(&sess
.target
.arch
) as u16,
275 !sess
.target
.is_like_msvc
,
279 if result
== crate::llvm
::LLVMRustResult
::Failure
{
280 sess
.emit_fatal(ErrorCreatingImportLibrary
{
282 error
: llvm
::last_error().unwrap_or("unknown LLVM error".to_string()),
291 // The object crate doesn't know how to get symbols for LLVM bitcode and COFF bigobj files.
292 // As such we need to use LLVM for them.
293 #[deny(unsafe_op_in_unsafe_fn)]
294 fn get_llvm_object_symbols(
296 f
: &mut dyn FnMut(&[u8]) -> io
::Result
<()>,
297 ) -> io
::Result
<bool
> {
298 let is_bitcode
= unsafe { llvm::LLVMRustIsBitcode(buf.as_ptr(), buf.len()) }
;
300 // COFF bigobj file, msvc LTO file or import library. See
301 // https://github.com/llvm/llvm-project/blob/453f27bc9/llvm/lib/BinaryFormat/Magic.cpp#L38-L51
302 let is_unsupported_windows_obj_file
= buf
.get(0..4) == Some(b
"\0\0\xFF\xFF");
304 if is_bitcode
|| is_unsupported_windows_obj_file
{
305 let mut state
= Box
::new(f
);
308 llvm
::LLVMRustGetSymbols(
311 &mut *state
as *mut &mut _
as *mut c_void
,
320 return Err(unsafe { *Box::from_raw(err as *mut io::Error) }
);
323 unsafe extern "C" fn callback(
325 symbol_name
: *const c_char
,
327 let f
= unsafe { &mut *(state as *mut &mut dyn FnMut(&[u8]) -> io::Result<()>) }
;
328 match f(unsafe { CStr::from_ptr(symbol_name) }
.to_bytes()) {
329 Ok(()) => std
::ptr
::null_mut(),
330 Err(err
) => Box
::into_raw(Box
::new(err
)) as *mut c_void
,
334 unsafe extern "C" fn error_callback(error
: *const c_char
) -> *mut c_void
{
335 let error
= unsafe { CStr::from_ptr(error) }
;
336 Box
::into_raw(Box
::new(io
::Error
::new(
337 io
::ErrorKind
::Other
,
338 format
!("LLVM error: {}", error
.to_string_lossy()),
342 get_native_object_symbols(buf
, f
)
346 impl<'a
> LlvmArchiveBuilder
<'a
> {
347 fn build_with_llvm(&mut self, output
: &Path
) -> io
::Result
<bool
> {
348 let kind
= &*self.sess
.target
.archive_format
;
350 .parse
::<ArchiveKind
>()
352 .unwrap_or_else(|kind
| self.sess
.emit_fatal(UnknownArchiveKind { kind }
));
354 let mut additions
= mem
::take(&mut self.additions
);
355 let mut strings
= Vec
::new();
356 let mut members
= Vec
::new();
358 let dst
= CString
::new(output
.to_str().unwrap())?
;
361 for addition
in &mut additions
{
363 Addition
::File { path, name_in_archive }
=> {
364 let path
= CString
::new(path
.to_str().unwrap())?
;
365 let name
= CString
::new(name_in_archive
.clone())?
;
366 members
.push(llvm
::LLVMRustArchiveMemberNew(
374 Addition
::Archive { archive, skip, .. }
=> {
375 for child
in archive
.iter() {
376 let child
= child
.map_err(string_to_io_error
)?
;
377 if !is_relevant_child(&child
) {
380 let child_name
= child
.name().unwrap();
381 if skip(child_name
) {
385 // It appears that LLVM's archive writer is a little
386 // buggy if the name we pass down isn't just the
387 // filename component, so chop that off here and
390 // See LLVM bug 25877 for more info.
392 Path
::new(child_name
).file_name().unwrap().to_str().unwrap();
393 let name
= CString
::new(child_name
)?
;
394 let m
= llvm
::LLVMRustArchiveMemberNew(
406 let r
= llvm
::LLVMRustWriteArchive(
408 members
.len() as libc
::size_t
,
409 members
.as_ptr() as *const &_
,
413 let ret
= if r
.into_result().is_err() {
414 let err
= llvm
::LLVMRustGetLastError();
415 let msg
= if err
.is_null() {
416 "failed to write archive".into()
418 String
::from_utf8_lossy(CStr
::from_ptr(err
).to_bytes())
420 Err(io
::Error
::new(io
::ErrorKind
::Other
, msg
))
422 Ok(!members
.is_empty())
424 for member
in members
{
425 llvm
::LLVMRustArchiveMemberFree(member
);
432 fn string_to_io_error(s
: String
) -> io
::Error
{
433 io
::Error
::new(io
::ErrorKind
::Other
, format
!("bad archive: {}", s
))
436 fn find_binutils_dlltool(sess
: &Session
) -> OsString
{
437 assert
!(sess
.target
.options
.is_like_windows
&& !sess
.target
.options
.is_like_msvc
);
438 if let Some(dlltool_path
) = &sess
.opts
.cg
.dlltool
{
439 return dlltool_path
.clone().into_os_string();
442 let tool_name
: OsString
= if sess
.host
.options
.is_like_windows
{
443 // If we're compiling on Windows, always use "dlltool.exe".
446 // On other platforms, use the architecture-specific name.
447 match sess
.target
.arch
.as_ref() {
448 "x86_64" => "x86_64-w64-mingw32-dlltool",
449 "x86" => "i686-w64-mingw32-dlltool",
450 "aarch64" => "aarch64-w64-mingw32-dlltool",
452 // For non-standard architectures (e.g., aarch32) fallback to "dlltool".
458 // NOTE: it's not clear how useful it is to explicitly search PATH.
459 for dir
in env
::split_paths(&env
::var_os("PATH").unwrap_or_default()) {
460 let full_path
= dir
.join(&tool_name
);
461 if full_path
.is_file() {
462 return full_path
.into_os_string();
466 // The user didn't specify the location of the dlltool binary, and we weren't able
467 // to find the appropriate one on the PATH. Just return the name of the tool
468 // and let the invocation fail with a hopefully useful error message.