Skip to content

Commit 0cf7fd1

Browse files
committedJan 12, 2022
Call out to binutils' dlltool for raw-dylib on windows-gnu platforms.
1 parent 4961b10 commit 0cf7fd1

24 files changed

+252
-113
lines changed
 

‎compiler/rustc_codegen_llvm/src/back/archive.rs

+159-46
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! A helper class for dealing with static archives
22
3-
use std::ffi::{CStr, CString};
3+
use std::env;
4+
use std::ffi::{CStr, CString, OsString};
45
use std::io;
56
use std::mem;
67
use std::path::{Path, PathBuf};
@@ -158,54 +159,127 @@ impl<'a> ArchiveBuilder<'a> for LlvmArchiveBuilder<'a> {
158159
output_path.with_extension("lib")
159160
};
160161

161-
// we've checked for \0 characters in the library name already
162-
let dll_name_z = CString::new(lib_name).unwrap();
163-
// All import names are Rust identifiers and therefore cannot contain \0 characters.
164-
// FIXME: when support for #[link_name] implemented, ensure that import.name values don't
165-
// have any \0 characters
166-
let import_name_and_ordinal_vector: Vec<(CString, Option<u16>)> = dll_imports
162+
let mingw_gnu_toolchain = self.config.sess.target.llvm_target.ends_with("pc-windows-gnu");
163+
164+
let import_name_and_ordinal_vector: Vec<(String, Option<u16>)> = dll_imports
167165
.iter()
168166
.map(|import: &DllImport| {
169167
if self.config.sess.target.arch == "x86" {
170-
(LlvmArchiveBuilder::i686_decorated_name(import), import.ordinal)
168+
(
169+
LlvmArchiveBuilder::i686_decorated_name(import, mingw_gnu_toolchain),
170+
import.ordinal,
171+
)
171172
} else {
172-
(CString::new(import.name.to_string()).unwrap(), import.ordinal)
173+
(import.name.to_string(), import.ordinal)
173174
}
174175
})
175176
.collect();
176177

177-
let output_path_z = rustc_fs_util::path_to_c_string(&output_path);
178+
if mingw_gnu_toolchain {
179+
// The binutils linker used on -windows-gnu targets cannot read the import
180+
// libraries generated by LLVM: in our attempts, the linker produced an .EXE
181+
// that loaded but crashed with an AV upon calling one of the imported
182+
// functions. Therefore, use binutils to create the import library instead,
183+
// by writing a .DEF file to the temp dir and calling binutils's dlltool.
184+
let def_file_path =
185+
tmpdir.as_ref().join(format!("{}_imports", lib_name)).with_extension("def");
186+
187+
let def_file_content = format!(
188+
"EXPORTS\n{}",
189+
import_name_and_ordinal_vector
190+
.into_iter()
191+
.map(|(name, ordinal)| {
192+
match ordinal {
193+
Some(n) => format!("{} @{} NONAME", name, n),
194+
None => name,
195+
}
196+
})
197+
.collect::<Vec<String>>()
198+
.join("\n")
199+
);
178200

179-
tracing::trace!("invoking LLVMRustWriteImportLibrary");
180-
tracing::trace!(" dll_name {:#?}", dll_name_z);
181-
tracing::trace!(" output_path {}", output_path.display());
182-
tracing::trace!(
183-
" import names: {}",
184-
dll_imports.iter().map(|import| import.name.to_string()).collect::<Vec<_>>().join(", "),
185-
);
201+
match std::fs::write(&def_file_path, def_file_content) {
202+
Ok(_) => {}
203+
Err(e) => {
204+
self.config.sess.fatal(&format!("Error writing .DEF file: {}", e));
205+
}
206+
};
186207

187-
let ffi_exports: Vec<LLVMRustCOFFShortExport> = import_name_and_ordinal_vector
188-
.iter()
189-
.map(|(name_z, ordinal)| LLVMRustCOFFShortExport::new(name_z.as_ptr(), *ordinal))
190-
.collect();
191-
let result = unsafe {
192-
crate::llvm::LLVMRustWriteImportLibrary(
193-
dll_name_z.as_ptr(),
194-
output_path_z.as_ptr(),
195-
ffi_exports.as_ptr(),
196-
ffi_exports.len(),
197-
llvm_machine_type(&self.config.sess.target.arch) as u16,
198-
!self.config.sess.target.is_like_msvc,
199-
)
200-
};
208+
let dlltool = find_binutils_dlltool(self.config.sess);
209+
let result = std::process::Command::new(dlltool)
210+
.args([
211+
"-d",
212+
def_file_path.to_str().unwrap(),
213+
"-D",
214+
lib_name,
215+
"-l",
216+
output_path.to_str().unwrap(),
217+
])
218+
.output();
219+
220+
match result {
221+
Err(e) => {
222+
self.config.sess.fatal(&format!("Error calling dlltool: {}", e.to_string()));
223+
}
224+
Ok(output) if !output.status.success() => self.config.sess.fatal(&format!(
225+
"Dlltool could not create import library: {}\n{}",
226+
String::from_utf8_lossy(&output.stdout),
227+
String::from_utf8_lossy(&output.stderr)
228+
)),
229+
_ => {}
230+
}
231+
} else {
232+
// we've checked for \0 characters in the library name already
233+
let dll_name_z = CString::new(lib_name).unwrap();
234+
235+
let output_path_z = rustc_fs_util::path_to_c_string(&output_path);
236+
237+
tracing::trace!("invoking LLVMRustWriteImportLibrary");
238+
tracing::trace!(" dll_name {:#?}", dll_name_z);
239+
tracing::trace!(" output_path {}", output_path.display());
240+
tracing::trace!(
241+
" import names: {}",
242+
dll_imports
243+
.iter()
244+
.map(|import| import.name.to_string())
245+
.collect::<Vec<_>>()
246+
.join(", "),
247+
);
201248

202-
if result == crate::llvm::LLVMRustResult::Failure {
203-
self.config.sess.fatal(&format!(
204-
"Error creating import library for {}: {}",
205-
lib_name,
206-
llvm::last_error().unwrap_or("unknown LLVM error".to_string())
207-
));
208-
}
249+
// All import names are Rust identifiers and therefore cannot contain \0 characters.
250+
// FIXME: when support for #[link_name] is implemented, ensure that the import names
251+
// still don't contain any \0 characters. Also need to check that the names don't
252+
// contain substrings like " @" or "NONAME" that are keywords or otherwise reserved
253+
// in definition files.
254+
let cstring_import_name_and_ordinal_vector: Vec<(CString, Option<u16>)> =
255+
import_name_and_ordinal_vector
256+
.into_iter()
257+
.map(|(name, ordinal)| (CString::new(name).unwrap(), ordinal))
258+
.collect();
259+
260+
let ffi_exports: Vec<LLVMRustCOFFShortExport> = cstring_import_name_and_ordinal_vector
261+
.iter()
262+
.map(|(name_z, ordinal)| LLVMRustCOFFShortExport::new(name_z.as_ptr(), *ordinal))
263+
.collect();
264+
let result = unsafe {
265+
crate::llvm::LLVMRustWriteImportLibrary(
266+
dll_name_z.as_ptr(),
267+
output_path_z.as_ptr(),
268+
ffi_exports.as_ptr(),
269+
ffi_exports.len(),
270+
llvm_machine_type(&self.config.sess.target.arch) as u16,
271+
!self.config.sess.target.is_like_msvc,
272+
)
273+
};
274+
275+
if result == crate::llvm::LLVMRustResult::Failure {
276+
self.config.sess.fatal(&format!(
277+
"Error creating import library for {}: {}",
278+
lib_name,
279+
llvm::last_error().unwrap_or("unknown LLVM error".to_string())
280+
));
281+
}
282+
};
209283

210284
self.add_archive(&output_path, |_| false).unwrap_or_else(|e| {
211285
self.config.sess.fatal(&format!(
@@ -332,22 +406,61 @@ impl<'a> LlvmArchiveBuilder<'a> {
332406
}
333407
}
334408

335-
fn i686_decorated_name(import: &DllImport) -> CString {
409+
fn i686_decorated_name(import: &DllImport, mingw: bool) -> String {
336410
let name = import.name;
337-
// We verified during construction that `name` does not contain any NULL characters, so the
338-
// conversion to CString is guaranteed to succeed.
339-
CString::new(match import.calling_convention {
340-
DllCallingConvention::C => format!("_{}", name),
341-
DllCallingConvention::Stdcall(arg_list_size) => format!("_{}@{}", name, arg_list_size),
411+
let prefix = if mingw { "" } else { "_" };
412+
413+
match import.calling_convention {
414+
DllCallingConvention::C => format!("{}{}", prefix, name),
415+
DllCallingConvention::Stdcall(arg_list_size) => {
416+
format!("{}{}@{}", prefix, name, arg_list_size)
417+
}
342418
DllCallingConvention::Fastcall(arg_list_size) => format!("@{}@{}", name, arg_list_size),
343419
DllCallingConvention::Vectorcall(arg_list_size) => {
344420
format!("{}@@{}", name, arg_list_size)
345421
}
346-
})
347-
.unwrap()
422+
}
348423
}
349424
}
350425

351426
fn string_to_io_error(s: String) -> io::Error {
352427
io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s))
353428
}
429+
430+
fn find_binutils_dlltool(sess: &Session) -> OsString {
431+
assert!(sess.target.options.is_like_windows && !sess.target.options.is_like_msvc);
432+
if let Some(dlltool_path) = &sess.opts.debugging_opts.dlltool {
433+
return dlltool_path.clone().into_os_string();
434+
}
435+
436+
let mut tool_name: OsString = if sess.host.arch != sess.target.arch {
437+
// We are cross-compiling, so we need the tool with the prefix matching our target
438+
if sess.target.arch == "x86" {
439+
"i686-w64-mingw32-dlltool"
440+
} else {
441+
"x86_64-w64-mingw32-dlltool"
442+
}
443+
} else {
444+
// We are not cross-compiling, so we just want `dlltool`
445+
"dlltool"
446+
}
447+
.into();
448+
449+
if sess.host.options.is_like_windows {
450+
// If we're compiling on Windows, add the .exe suffix
451+
tool_name.push(".exe");
452+
}
453+
454+
// NOTE: it's not clear how useful it is to explicitly search PATH.
455+
for dir in env::split_paths(&env::var_os("PATH").unwrap_or_default()) {
456+
let full_path = dir.join(&tool_name);
457+
if full_path.is_file() {
458+
return full_path.into_os_string();
459+
}
460+
}
461+
462+
// The user didn't specify the location of the dlltool binary, and we weren't able
463+
// to find the appropriate one on the PATH. Just return the name of the tool
464+
// and let the invocation fail with a hopefully useful error message.
465+
tool_name
466+
}

‎compiler/rustc_interface/src/tests.rs

+1
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,7 @@ fn test_debugging_options_tracking_hash() {
640640
untracked!(borrowck, String::from("other"));
641641
untracked!(deduplicate_diagnostics, false);
642642
untracked!(dep_tasks, true);
643+
untracked!(dlltool, Some(PathBuf::from("custom_dlltool.exe")));
643644
untracked!(dont_buffer_diagnostics, true);
644645
untracked!(dump_dep_graph, true);
645646
untracked!(dump_mir, Some(String::from("abc")));

‎compiler/rustc_metadata/src/native_libs.rs

-5
Original file line numberDiff line numberDiff line change
@@ -274,11 +274,6 @@ impl Collector<'tcx> {
274274
span,
275275
"`#[link(...)]` with `kind = \"raw-dylib\"` only supported on Windows",
276276
);
277-
} else if !self.tcx.sess.target.options.is_like_msvc {
278-
self.tcx.sess.span_warn(
279-
span,
280-
"`#[link(...)]` with `kind = \"raw-dylib\"` not supported on windows-gnu",
281-
);
282277
}
283278

284279
if lib_name.as_str().contains('\0') {

‎compiler/rustc_session/src/options.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,8 @@ options! {
10731073
dep_tasks: bool = (false, parse_bool, [UNTRACKED],
10741074
"print tasks that execute and the color their dep node gets (requires debug build) \
10751075
(default: no)"),
1076+
dlltool: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED],
1077+
"import library generation tool (windows-gnu only)"),
10761078
dont_buffer_diagnostics: bool = (false, parse_bool, [UNTRACKED],
10771079
"emit diagnostics rather than buffering (breaks NLL error downgrading, sorting) \
10781080
(default: no)"),

‎src/test/run-make/raw-dylib-c/Makefile

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
# Test the behavior of #[link(.., kind = "raw-dylib")] on windows-msvc
22

3-
# only-windows-msvc
3+
# only-windows
44

55
-include ../../run-make-fulldeps/tools.mk
66

77
all:
88
$(call COMPILE_OBJ,"$(TMPDIR)"/extern_1.obj,extern_1.c)
99
$(call COMPILE_OBJ,"$(TMPDIR)"/extern_2.obj,extern_2.c)
10+
ifdef IS_MSVC
1011
$(CC) "$(TMPDIR)"/extern_1.obj -link -dll -out:"$(TMPDIR)"/extern_1.dll
1112
$(CC) "$(TMPDIR)"/extern_2.obj -link -dll -out:"$(TMPDIR)"/extern_2.dll
13+
else
14+
$(CC) "$(TMPDIR)"/extern_1.obj -shared -o "$(TMPDIR)"/extern_1.dll
15+
$(CC) "$(TMPDIR)"/extern_2.obj -shared -o "$(TMPDIR)"/extern_2.dll
16+
endif
1217
$(RUSTC) --crate-type lib --crate-name raw_dylib_test lib.rs
1318
$(RUSTC) --crate-type bin driver.rs -L "$(TMPDIR)"
1419
"$(TMPDIR)"/driver > "$(TMPDIR)"/output.txt

‎src/test/run-make/raw-dylib-link-ordinal/Makefile

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
# Test the behavior of #[link(.., kind = "raw-dylib")] and #[link_ordinal] on windows-msvc
22

3-
# only-windows-msvc
3+
# only-windows
44

55
-include ../../run-make-fulldeps/tools.mk
66

77
all:
88
$(call COMPILE_OBJ,"$(TMPDIR)"/exporter.obj,exporter.c)
9+
ifdef IS_MSVC
910
$(CC) "$(TMPDIR)"/exporter.obj exporter.def -link -dll -out:"$(TMPDIR)"/exporter.dll
11+
else
12+
$(CC) "$(TMPDIR)"/exporter.obj exporter.def -shared -o "$(TMPDIR)"/exporter.dll
13+
endif
1014
$(RUSTC) --crate-type lib --crate-name raw_dylib_test lib.rs
1115
$(RUSTC) --crate-type bin driver.rs -L "$(TMPDIR)"
1216
"$(TMPDIR)"/driver > "$(TMPDIR)"/output.txt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Test the behavior of #[link(.., kind = "raw-dylib")], #[link_ordinal], and alternative calling conventions on i686 windows.
2+
3+
# only-x86
4+
# only-windows
5+
6+
-include ../../run-make-fulldeps/tools.mk
7+
8+
all:
9+
$(call COMPILE_OBJ,"$(TMPDIR)"/exporter.obj,exporter.c)
10+
ifdef IS_MSVC
11+
$(CC) "$(TMPDIR)"/exporter.obj exporter-msvc.def -link -dll -out:"$(TMPDIR)"/exporter.dll
12+
else
13+
$(CC) "$(TMPDIR)"/exporter.obj exporter-gnu.def -shared -o "$(TMPDIR)"/exporter.dll
14+
endif
15+
$(RUSTC) --crate-type lib --crate-name raw_dylib_test lib.rs
16+
$(RUSTC) --crate-type bin driver.rs -L "$(TMPDIR)"
17+
"$(TMPDIR)"/driver > "$(TMPDIR)"/actual_output.txt
18+
19+
ifdef RUSTC_BLESS_TEST
20+
cp "$(TMPDIR)"/actual_output.txt expected_output.txt
21+
else
22+
$(DIFF) expected_output.txt "$(TMPDIR)"/actual_output.txt
23+
endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
extern crate raw_dylib_test;
2+
3+
fn main() {
4+
raw_dylib_test::library_function();
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
exported_function_stdcall(6)
2+
exported_function_fastcall(125)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
LIBRARY exporter
2+
EXPORTS
3+
exported_function_stdcall@4 @15 NONAME
4+
@exported_function_fastcall@4 @18 NONAME
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
LIBRARY exporter
2+
EXPORTS
3+
_exported_function_stdcall@4 @15 NONAME
4+
@exported_function_fastcall@4 @18 NONAME
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#include <stdio.h>
2+
3+
void __stdcall exported_function_stdcall(int i) {
4+
printf("exported_function_stdcall(%d)\n", i);
5+
fflush(stdout);
6+
}
7+
8+
void __fastcall exported_function_fastcall(int i) {
9+
printf("exported_function_fastcall(%d)\n", i);
10+
fflush(stdout);
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#![feature(raw_dylib)]
2+
3+
#[link(name = "exporter", kind = "raw-dylib")]
4+
extern "stdcall" {
5+
#[link_ordinal(15)]
6+
fn imported_function_stdcall(i: i32);
7+
}
8+
9+
#[link(name = "exporter", kind = "raw-dylib")]
10+
extern "fastcall" {
11+
#[link_ordinal(18)]
12+
fn imported_function_fastcall(i: i32);
13+
}
14+
15+
pub fn library_function() {
16+
unsafe {
17+
imported_function_stdcall(6);
18+
imported_function_fastcall(125);
19+
}
20+
}

‎src/test/ui/feature-gates/feature-gate-raw-dylib-windows-gnu.rs

-8
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.