Call out to binutils' dlltool for raw-dylib on windows-gnu platforms.
This commit is contained in:
parent
4961b107f2
commit
0cf7fd1208
24 changed files with 252 additions and 113 deletions
|
@ -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,55 +159,128 @@ 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");
|
||||
|
||||
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(", "),
|
||||
);
|
||||
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")
|
||||
);
|
||||
|
||||
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,
|
||||
)
|
||||
match std::fs::write(&def_file_path, def_file_content) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
self.config.sess.fatal(&format!("Error writing .DEF file: {}", e));
|
||||
}
|
||||
};
|
||||
|
||||
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(", "),
|
||||
);
|
||||
|
||||
// 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())
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
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!(
|
||||
"failed to add native library {}: {}",
|
||||
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue