diff options
Diffstat (limited to 'compiler/rustc_codegen_llvm/src/back/archive.rs')
| -rw-r--r-- | compiler/rustc_codegen_llvm/src/back/archive.rs | 205 |
1 files changed, 159 insertions, 46 deletions
diff --git a/compiler/rustc_codegen_llvm/src/back/archive.rs b/compiler/rustc_codegen_llvm/src/back/archive.rs index 2fb5a0f9faf..5703a72c686 100644 --- a/compiler/rustc_codegen_llvm/src/back/archive.rs +++ b/compiler/rustc_codegen_llvm/src/back/archive.rs @@ -1,6 +1,7 @@ //! A helper class for dealing with static archives -use std::ffi::{CStr, CString}; +use std::env; +use std::ffi::{CStr, CString, OsString}; use std::io; use std::mem; use std::path::{Path, PathBuf}; @@ -158,54 +159,127 @@ impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> { output_path.with_extension("lib") }; - // we've checked for \0 characters in the library name already - let dll_name_z = CString::new(lib_name).unwrap(); - // All import names are Rust identifiers and therefore cannot contain \0 characters. - // FIXME: when support for #[link_name] implemented, ensure that import.name values don't - // have any \0 characters - let import_name_and_ordinal_vector: Vec<(CString, Option<u16>)> = dll_imports + let mingw_gnu_toolchain = self.config.sess.target.llvm_target.ends_with("pc-windows-gnu"); + + let import_name_and_ordinal_vector: Vec<(String, Option<u16>)> = dll_imports .iter() .map(|import: &DllImport| { if self.config.sess.target.arch == "x86" { - (LlvmArchiveBuilder::i686_decorated_name(import), import.ordinal) + ( + LlvmArchiveBuilder::i686_decorated_name(import, mingw_gnu_toolchain), + import.ordinal, + ) } else { - (CString::new(import.name.to_string()).unwrap(), import.ordinal) + (import.name.to_string(), import.ordinal) } }) .collect(); - let output_path_z = rustc_fs_util::path_to_c_string(&output_path); + if mingw_gnu_toolchain { + // The binutils linker used on -windows-gnu targets cannot read the import + // libraries generated by LLVM: in our attempts, the linker produced an .EXE + // that loaded but crashed with an AV upon calling one of the imported + // functions. Therefore, use binutils to create the import library instead, + // by writing a .DEF file to the temp dir and calling binutils's dlltool. + let def_file_path = + tmpdir.as_ref().join(format!("{}_imports", lib_name)).with_extension("def"); + + let def_file_content = format!( + "EXPORTS\n{}", + import_name_and_ordinal_vector + .into_iter() + .map(|(name, ordinal)| { + match ordinal { + Some(n) => format!("{} @{} NONAME", name, n), + None => name, + } + }) + .collect::<Vec<String>>() + .join("\n") + ); - tracing::trace!("invoking LLVMRustWriteImportLibrary"); - tracing::trace!(" dll_name {:#?}", dll_name_z); - tracing::trace!(" output_path {}", output_path.display()); - tracing::trace!( - " import names: {}", - dll_imports.iter().map(|import| import.name.to_string()).collect::<Vec<_>>().join(", "), - ); + match std::fs::write(&def_file_path, def_file_content) { + Ok(_) => {} + Err(e) => { + self.config.sess.fatal(&format!("Error writing .DEF file: {}", e)); + } + }; - let ffi_exports: Vec<LLVMRustCOFFShortExport> = import_name_and_ordinal_vector - .iter() - .map(|(name_z, ordinal)| LLVMRustCOFFShortExport::new(name_z.as_ptr(), *ordinal)) - .collect(); - let result = unsafe { - crate::llvm::LLVMRustWriteImportLibrary( - dll_name_z.as_ptr(), - output_path_z.as_ptr(), - ffi_exports.as_ptr(), - ffi_exports.len(), - llvm_machine_type(&self.config.sess.target.arch) as u16, - !self.config.sess.target.is_like_msvc, - ) - }; + let dlltool = find_binutils_dlltool(self.config.sess); + let result = std::process::Command::new(dlltool) + .args([ + "-d", + def_file_path.to_str().unwrap(), + "-D", + lib_name, + "-l", + output_path.to_str().unwrap(), + ]) + .output(); + + match result { + Err(e) => { + self.config.sess.fatal(&format!("Error calling dlltool: {}", e.to_string())); + } + Ok(output) if !output.status.success() => self.config.sess.fatal(&format!( + "Dlltool could not create import library: {}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + )), + _ => {} + } + } else { + // we've checked for \0 characters in the library name already + let dll_name_z = CString::new(lib_name).unwrap(); + + let output_path_z = rustc_fs_util::path_to_c_string(&output_path); + + tracing::trace!("invoking LLVMRustWriteImportLibrary"); + tracing::trace!(" dll_name {:#?}", dll_name_z); + tracing::trace!(" output_path {}", output_path.display()); + tracing::trace!( + " import names: {}", + dll_imports + .iter() + .map(|import| import.name.to_string()) + .collect::<Vec<_>>() + .join(", "), + ); - if result == crate::llvm::LLVMRustResult::Failure { - self.config.sess.fatal(&format!( - "Error creating import library for {}: {}", - lib_name, - llvm::last_error().unwrap_or("unknown LLVM error".to_string()) - )); - } + // All import names are Rust identifiers and therefore cannot contain \0 characters. + // FIXME: when support for #[link_name] is implemented, ensure that the import names + // still don't contain any \0 characters. Also need to check that the names don't + // contain substrings like " @" or "NONAME" that are keywords or otherwise reserved + // in definition files. + let cstring_import_name_and_ordinal_vector: Vec<(CString, Option<u16>)> = + import_name_and_ordinal_vector + .into_iter() + .map(|(name, ordinal)| (CString::new(name).unwrap(), ordinal)) + .collect(); + + let ffi_exports: Vec<LLVMRustCOFFShortExport> = cstring_import_name_and_ordinal_vector + .iter() + .map(|(name_z, ordinal)| LLVMRustCOFFShortExport::new(name_z.as_ptr(), *ordinal)) + .collect(); + let result = unsafe { + crate::llvm::LLVMRustWriteImportLibrary( + dll_name_z.as_ptr(), + output_path_z.as_ptr(), + ffi_exports.as_ptr(), + ffi_exports.len(), + llvm_machine_type(&self.config.sess.target.arch) as u16, + !self.config.sess.target.is_like_msvc, + ) + }; + + if result == crate::llvm::LLVMRustResult::Failure { + self.config.sess.fatal(&format!( + "Error creating import library for {}: {}", + lib_name, + llvm::last_error().unwrap_or("unknown LLVM error".to_string()) + )); + } + }; self.add_archive(&output_path, |_| false).unwrap_or_else(|e| { self.config.sess.fatal(&format!( @@ -332,22 +406,61 @@ impl<'a> LlvmArchiveBuilder<'a> { } } - fn i686_decorated_name(import: &DllImport) -> CString { + fn i686_decorated_name(import: &DllImport, mingw: bool) -> String { let name = import.name; - // We verified during construction that `name` does not contain any NULL characters, so the - // conversion to CString is guaranteed to succeed. - CString::new(match import.calling_convention { - DllCallingConvention::C => format!("_{}", name), - DllCallingConvention::Stdcall(arg_list_size) => format!("_{}@{}", name, arg_list_size), + let prefix = if mingw { "" } else { "_" }; + + match import.calling_convention { + DllCallingConvention::C => format!("{}{}", prefix, name), + DllCallingConvention::Stdcall(arg_list_size) => { + format!("{}{}@{}", prefix, name, arg_list_size) + } DllCallingConvention::Fastcall(arg_list_size) => format!("@{}@{}", name, arg_list_size), DllCallingConvention::Vectorcall(arg_list_size) => { format!("{}@@{}", name, arg_list_size) } - }) - .unwrap() + } } } fn string_to_io_error(s: String) -> io::Error { io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s)) } + +fn find_binutils_dlltool(sess: &Session) -> OsString { + assert!(sess.target.options.is_like_windows && !sess.target.options.is_like_msvc); + if let Some(dlltool_path) = &sess.opts.debugging_opts.dlltool { + return dlltool_path.clone().into_os_string(); + } + + let mut tool_name: OsString = if sess.host.arch != sess.target.arch { + // We are cross-compiling, so we need the tool with the prefix matching our target + if sess.target.arch == "x86" { + "i686-w64-mingw32-dlltool" + } else { + "x86_64-w64-mingw32-dlltool" + } + } else { + // We are not cross-compiling, so we just want `dlltool` + "dlltool" + } + .into(); + + if sess.host.options.is_like_windows { + // If we're compiling on Windows, add the .exe suffix + tool_name.push(".exe"); + } + + // NOTE: it's not clear how useful it is to explicitly search PATH. + for dir in env::split_paths(&env::var_os("PATH").unwrap_or_default()) { + let full_path = dir.join(&tool_name); + if full_path.is_file() { + return full_path.into_os_string(); + } + } + + // The user didn't specify the location of the dlltool binary, and we weren't able + // to find the appropriate one on the PATH. Just return the name of the tool + // and let the invocation fail with a hopefully useful error message. + tool_name +} |
