Skip to content

Commit 74e4c3a

Browse files
committed
Improve DLL filtering on Windows (#170)
1 parent d16b874 commit 74e4c3a

File tree

4 files changed

+129
-18
lines changed

4 files changed

+129
-18
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## [1.8.1] - UNRELEASED
2+
3+
### Fixed
4+
- Improve DLL search on Windows to take target architecture into account (e.g., ARM64 vs x86-64)
5+
16
## [1.8.0] - 2024-05-26
27

38
### Changed

build/dynamic.rs

+24-5
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ fn parse_elf_header(path: &Path) -> io::Result<u8> {
2323
}
2424
}
2525

26-
/// Extracts the magic number from the PE header in a shared library.
27-
fn parse_pe_header(path: &Path) -> io::Result<u16> {
26+
/// Extracts the magic number and machine type from the PE header in a shared library.
27+
fn parse_pe_header(path: &Path) -> io::Result<(u16, u16)> {
2828
let mut file = File::open(path)?;
2929

3030
// Extract the header offset.
@@ -45,7 +45,15 @@ fn parse_pe_header(path: &Path) -> io::Result<u16> {
4545
let mut buffer = [0; 2];
4646
file.seek(SeekFrom::Current(20))?;
4747
file.read_exact(&mut buffer)?;
48-
Ok(u16::from_le_bytes(buffer))
48+
let magic_number = u16::from_le_bytes(buffer);
49+
50+
// Extract the machine type.
51+
let mut buffer = [0; 2];
52+
file.seek(SeekFrom::Current(-22))?;
53+
file.read_exact(&mut buffer)?;
54+
let machine_type = u16::from_le_bytes(buffer);
55+
56+
return Ok((magic_number, machine_type));
4957
}
5058

5159
/// Checks that a `libclang` shared library matches the target platform.
@@ -63,7 +71,7 @@ fn validate_library(path: &Path) -> Result<(), String> {
6371

6472
Ok(())
6573
} else if target_os!("windows") {
66-
let magic = parse_pe_header(path).map_err(|e| e.to_string())?;
74+
let (magic, machine_type) = parse_pe_header(path).map_err(|e| e.to_string())?;
6775

6876
if target_pointer_width!("32") && magic != 267 {
6977
return Err("invalid DLL (64-bit)".into());
@@ -73,7 +81,18 @@ fn validate_library(path: &Path) -> Result<(), String> {
7381
return Err("invalid DLL (32-bit)".into());
7482
}
7583

76-
Ok(())
84+
let arch_mismatch = match machine_type {
85+
0x014C if !target_arch!("x86") => Some("x86"),
86+
0x8664 if !target_arch!("x86_64") => Some("x86-64"),
87+
0xAA64 if !target_arch!("aarch64") => Some("ARM64"),
88+
_ => None,
89+
};
90+
91+
if let Some(arch) = arch_mismatch {
92+
Err(format!("invalid DLL ({arch})"))
93+
} else {
94+
Ok(())
95+
}
7796
} else {
7897
Ok(())
7998
}

build/macros.rs

+11
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ macro_rules! target_os {
1515
};
1616
}
1717

18+
macro_rules! target_arch {
19+
($arch:expr) => {
20+
if cfg!(test) && ::std::env::var("_CLANG_SYS_TEST").is_ok() {
21+
let var = ::std::env::var("_CLANG_SYS_TEST_ARCH");
22+
var.map_or(false, |v| v == $arch)
23+
} else {
24+
cfg!(target_arch = $arch)
25+
}
26+
};
27+
}
28+
1829
macro_rules! target_pointer_width {
1930
($pointer_width:expr) => {
2031
if cfg!(test) && ::std::env::var("_CLANG_SYS_TEST").is_ok() {

tests/build.rs

+89-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![allow(dead_code)]
22

3+
use core::fmt;
34
use std::collections::HashMap;
45
use std::env;
56
use std::fs;
@@ -26,9 +27,38 @@ struct RunCommandMock {
2627
responses: HashMap<Vec<String>, String>,
2728
}
2829

30+
31+
#[derive(Copy, Clone, Debug)]
32+
enum Arch {
33+
ARM64,
34+
X86,
35+
X86_64,
36+
}
37+
38+
impl Arch {
39+
fn pe_machine_type(self) -> u16 {
40+
match self {
41+
Arch::ARM64 => 0xAA64,
42+
Arch::X86 => 0x014C,
43+
Arch::X86_64 => 0x8664,
44+
}
45+
}
46+
}
47+
48+
impl fmt::Display for Arch {
49+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50+
match self {
51+
Arch::ARM64 => write!(f, "aarch64"),
52+
Arch::X86 => write!(f, "x86"),
53+
Arch::X86_64 => write!(f, "x86_64"),
54+
}
55+
}
56+
}
57+
2958
#[derive(Debug)]
3059
struct Env {
3160
os: String,
61+
arch: Arch,
3262
pointer_width: String,
3363
env: Option<String>,
3464
vars: HashMap<String, (Option<String>, Option<String>)>,
@@ -39,9 +69,10 @@ struct Env {
3969
}
4070

4171
impl Env {
42-
fn new(os: &str, pointer_width: &str) -> Self {
72+
fn new(os: &str, arch: Arch, pointer_width: &str) -> Self {
4373
Env {
4474
os: os.into(),
75+
arch,
4576
pointer_width: pointer_width.into(),
4677
env: None,
4778
vars: HashMap::new(),
@@ -84,11 +115,12 @@ impl Env {
84115
self
85116
}
86117

87-
fn dll(self, path: &str, pointer_width: &str) -> Self {
118+
fn dll(self, path: &str, arch: Arch, pointer_width: &str) -> Self {
88119
// PE header.
89120
let mut contents = [0; 64];
90121
contents[0x3C..0x3C + 4].copy_from_slice(&i32::to_le_bytes(10));
91122
contents[10..14].copy_from_slice(&[b'P', b'E', 0, 0]);
123+
contents[14..16].copy_from_slice(&u16::to_le_bytes(arch.pe_machine_type()));
92124
let magic = if pointer_width == "64" { 523 } else { 267 };
93125
contents[34..36].copy_from_slice(&u16::to_le_bytes(magic));
94126

@@ -117,6 +149,7 @@ impl Env {
117149
fn enable(self) -> Self {
118150
env::set_var("_CLANG_SYS_TEST", "yep");
119151
env::set_var("_CLANG_SYS_TEST_OS", &self.os);
152+
env::set_var("_CLANG_SYS_TEST_ARCH", &format!("{}", self.arch));
120153
env::set_var("_CLANG_SYS_TEST_POINTER_WIDTH", &self.pointer_width);
121154
if let Some(env) = &self.env {
122155
env::set_var("_CLANG_SYS_TEST_ENV", env);
@@ -155,6 +188,7 @@ impl Drop for Env {
155188
fn drop(&mut self) {
156189
env::remove_var("_CLANG_SYS_TEST");
157190
env::remove_var("_CLANG_SYS_TEST_OS");
191+
env::remove_var("_CLANG_SYS_TEST_ARCH");
158192
env::remove_var("_CLANG_SYS_TEST_POINTER_WIDTH");
159193
env::remove_var("_CLANG_SYS_TEST_ENV");
160194

@@ -185,17 +219,31 @@ fn test_all() {
185219
test_windows_bin_sibling();
186220
test_windows_mingw_gnu();
187221
test_windows_mingw_msvc();
222+
test_windows_arm64_on_x86_64();
223+
test_windows_x86_64_on_arm64();
188224
}
189225
}
190226

227+
macro_rules! assert_error {
228+
($result:expr, $contents:expr $(,)?) => {
229+
if let Err(error) = $result {
230+
if !error.contains($contents) {
231+
panic!("expected error to contain {:?}, received: {error:?}", $contents);
232+
}
233+
} else {
234+
panic!("expected error, received: {:?}", $result);
235+
}
236+
};
237+
}
238+
191239
//================================================
192240
// Dynamic
193241
//================================================
194242

195243
// Linux -----------------------------------------
196244

197245
fn test_linux_directory_preference() {
198-
let _env = Env::new("linux", "64")
246+
let _env = Env::new("linux", Arch::X86_64, "64")
199247
.so("usr/lib/libclang.so.1", "64")
200248
.so("usr/local/lib/libclang.so.1", "64")
201249
.enable();
@@ -207,7 +255,7 @@ fn test_linux_directory_preference() {
207255
}
208256

209257
fn test_linux_version_preference() {
210-
let _env = Env::new("linux", "64")
258+
let _env = Env::new("linux", Arch::X86_64, "64")
211259
.so("usr/lib/libclang-3.so", "64")
212260
.so("usr/lib/libclang-3.5.so", "64")
213261
.so("usr/lib/libclang-3.5.0.so", "64")
@@ -220,7 +268,7 @@ fn test_linux_version_preference() {
220268
}
221269

222270
fn test_linux_directory_and_version_preference() {
223-
let _env = Env::new("linux", "64")
271+
let _env = Env::new("linux", Arch::X86_64, "64")
224272
.so("usr/local/llvm/lib/libclang-3.so", "64")
225273
.so("usr/local/lib/libclang-3.5.so", "64")
226274
.so("usr/lib/libclang-3.5.0.so", "64")
@@ -236,9 +284,9 @@ fn test_linux_directory_and_version_preference() {
236284

237285
#[cfg(target_os = "windows")]
238286
fn test_windows_bin_sibling() {
239-
let _env = Env::new("windows", "64")
287+
let _env = Env::new("windows", Arch::X86_64, "64")
240288
.dir("Program Files\\LLVM\\lib")
241-
.dll("Program Files\\LLVM\\bin\\libclang.dll", "64")
289+
.dll("Program Files\\LLVM\\bin\\libclang.dll", Arch::X86_64, "64")
242290
.enable();
243291

244292
assert_eq!(
@@ -249,12 +297,12 @@ fn test_windows_bin_sibling() {
249297

250298
#[cfg(target_os = "windows")]
251299
fn test_windows_mingw_gnu() {
252-
let _env = Env::new("windows", "64")
300+
let _env = Env::new("windows", Arch::X86_64, "64")
253301
.env("gnu")
254302
.dir("MSYS\\MinGW\\lib")
255-
.dll("MSYS\\MinGW\\bin\\clang.dll", "64")
303+
.dll("MSYS\\MinGW\\bin\\clang.dll", Arch::X86_64, "64")
256304
.dir("Program Files\\LLVM\\lib")
257-
.dll("Program Files\\LLVM\\bin\\libclang.dll", "64")
305+
.dll("Program Files\\LLVM\\bin\\libclang.dll", Arch::X86_64, "64")
258306
.enable();
259307

260308
assert_eq!(
@@ -265,16 +313,44 @@ fn test_windows_mingw_gnu() {
265313

266314
#[cfg(target_os = "windows")]
267315
fn test_windows_mingw_msvc() {
268-
let _env = Env::new("windows", "64")
316+
let _env = Env::new("windows", Arch::X86_64, "64")
269317
.env("msvc")
270318
.dir("MSYS\\MinGW\\lib")
271-
.dll("MSYS\\MinGW\\bin\\clang.dll", "64")
319+
.dll("MSYS\\MinGW\\bin\\clang.dll", Arch::X86_64, "64")
272320
.dir("Program Files\\LLVM\\lib")
273-
.dll("Program Files\\LLVM\\bin\\libclang.dll", "64")
321+
.dll("Program Files\\LLVM\\bin\\libclang.dll", Arch::X86_64, "64")
274322
.enable();
275323

276324
assert_eq!(
277325
dynamic::find(true),
278326
Ok(("Program Files\\LLVM\\bin".into(), "libclang.dll".into())),
279327
);
280328
}
329+
330+
#[cfg(target_os = "windows")]
331+
fn test_windows_arm64_on_x86_64() {
332+
let _env = Env::new("windows", Arch::X86_64, "64")
333+
.env("msvc")
334+
.dir("Program Files\\LLVM\\lib")
335+
.dll("Program Files\\LLVM\\bin\\libclang.dll", Arch::ARM64, "64")
336+
.enable();
337+
338+
assert_error!(
339+
dynamic::find(true),
340+
"invalid: [(Program Files\\LLVM\\bin\\libclang.dll: invalid DLL (ARM64)",
341+
);
342+
}
343+
344+
#[cfg(target_os = "windows")]
345+
fn test_windows_x86_64_on_arm64() {
346+
let _env = Env::new("windows", Arch::ARM64, "64")
347+
.env("msvc")
348+
.dir("Program Files\\LLVM\\lib")
349+
.dll("Program Files\\LLVM\\bin\\libclang.dll", Arch::X86_64, "64")
350+
.enable();
351+
352+
assert_error!(
353+
dynamic::find(true),
354+
"invalid: [(Program Files\\LLVM\\bin\\libclang.dll: invalid DLL (x86-64)",
355+
);
356+
}

0 commit comments

Comments
 (0)