Skip to content

Commit afee545

Browse files
committed
Fix linking on 32-bit Windows
1 parent d390e92 commit afee545

File tree

3 files changed

+126
-2
lines changed

3 files changed

+126
-2
lines changed

cargo-auditable/src/object_file.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,38 @@ fn create_object_file(
181181
abi_version,
182182
e_flags,
183183
};
184+
185+
// Add the COFF `@feat.00` symbol used to communicate linker feature flags.
186+
//
187+
// For i386, bit 0 (`IMAGE_FILE_SAFE_EXCEPTION_HANDLER`) marks the object as SAFESEH-compatible.
188+
// Without this, linkers can reject the object with `/safeseh`.
189+
//
190+
// - <https://github.com/rust-lang/rust/issues/96498>
191+
// - <https://learn.microsoft.com/en-us/windows/win32/debug/pe-format>
192+
if binary_format == BinaryFormat::Coff {
193+
// Disable mangling so the "@feat.00" symbol name is written verbatim.
194+
// CoffI386 mangling adds a `_` prefix which would break this special symbol.
195+
let original_mangling = file.mangling();
196+
file.set_mangling(write::Mangling::None);
197+
198+
let mut feature: u64 = 0;
199+
if architecture == Architecture::I386 {
200+
feature |= 1; // IMAGE_FILE_SAFE_EXCEPTION_HANDLER
201+
}
202+
file.add_symbol(Symbol {
203+
name: b"@feat.00".to_vec(),
204+
value: feature,
205+
size: 0,
206+
kind: SymbolKind::Data,
207+
scope: SymbolScope::Compilation,
208+
weak: false,
209+
section: SymbolSection::Absolute,
210+
flags: SymbolFlags::None,
211+
});
212+
213+
file.set_mangling(original_mangling);
214+
}
215+
184216
Some(file)
185217
}
186218

@@ -322,6 +354,87 @@ windows
322354
assert_eq!(result.architecture(), Architecture::X86_64);
323355
}
324356

357+
#[test]
358+
fn test_create_object_file_windows_msvc_i686() {
359+
let rustc_output = br#"debug_assertions
360+
target_arch="x86"
361+
target_endian="little"
362+
target_env="msvc"
363+
target_family="windows"
364+
target_feature="fxsr"
365+
target_feature="sse"
366+
target_feature="sse2"
367+
target_os="windows"
368+
target_pointer_width="32"
369+
target_vendor="pc"
370+
windows
371+
"#;
372+
let target_triple = "i686-pc-windows-msvc";
373+
let target_info = parse_rustc_target_info(rustc_output);
374+
let result = create_object_file(&target_info, target_triple).unwrap();
375+
assert_eq!(result.format(), BinaryFormat::Coff);
376+
assert_eq!(result.architecture(), Architecture::I386);
377+
}
378+
379+
/// Verify that i686 COFF metadata objects contain an absolute `@feat.00` symbol with
380+
/// `IMAGE_FILE_SAFE_EXCEPTION_HANDLER` (bit 0) set.
381+
///
382+
/// See <https://github.com/rust-lang/rust/issues/96498>
383+
#[test]
384+
fn test_create_metadata_file_windows_msvc_i686_has_feat00() {
385+
let rustc_output = br#"debug_assertions
386+
target_arch="x86"
387+
target_endian="little"
388+
target_env="msvc"
389+
target_family="windows"
390+
target_feature="fxsr"
391+
target_feature="sse"
392+
target_feature="sse2"
393+
target_os="windows"
394+
target_pointer_width="32"
395+
target_vendor="pc"
396+
windows
397+
"#;
398+
let target_triple = "i686-pc-windows-msvc";
399+
let target_info = parse_rustc_target_info(rustc_output);
400+
let contents = b"test audit data";
401+
let result = create_metadata_file(
402+
&target_info,
403+
target_triple,
404+
contents,
405+
"AUDITABLE_VERSION_INFO",
406+
)
407+
.expect("should produce an object file for i686-pc-windows-msvc");
408+
409+
// Parse the COFF symbol table and verify `@feat.00` has value bit0=1 and absolute section.
410+
let symtab_ptr = u32::from_le_bytes(result[8..12].try_into().unwrap()) as usize;
411+
let sym_count = u32::from_le_bytes(result[12..16].try_into().unwrap()) as usize;
412+
let symbol_size = 18;
413+
414+
let feat = (0..sym_count).find_map(|i| {
415+
let start = symtab_ptr + i * symbol_size;
416+
let end = start + symbol_size;
417+
let entry = result.get(start..end)?;
418+
if &entry[0..8] != b"@feat.00" {
419+
return None;
420+
}
421+
let value = u32::from_le_bytes(entry[8..12].try_into().unwrap());
422+
let section_number = i16::from_le_bytes(entry[12..14].try_into().unwrap());
423+
Some((value, section_number))
424+
});
425+
426+
let (value, section_number) = feat.expect("COFF object for i686 must contain @feat.00");
427+
assert_eq!(
428+
value & 1,
429+
1,
430+
"@feat.00 must set IMAGE_FILE_SAFE_EXCEPTION_HANDLER on i686"
431+
);
432+
assert_eq!(
433+
section_number, -1,
434+
"@feat.00 must be an absolute COFF symbol (section number -1)"
435+
);
436+
}
437+
325438
#[test]
326439
fn test_create_object_file_windows_gnu() {
327440
let rustc_output = br#"debug_assertions

cargo-auditable/src/platform_detection.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ pub fn is_32bit(target_info: &RustcTargetInfo) -> bool {
2222
key_equals(target_info, "target_pointer_width", "32")
2323
}
2424

25+
pub fn is_x86(target_info: &RustcTargetInfo) -> bool {
26+
key_equals(target_info, "target_arch", "x86")
27+
}
28+
2529
fn key_equals(target_info: &RustcTargetInfo, key: &str, value: &str) -> bool {
2630
target_info.get(key).map(|s| s.as_str()) == Some(value)
2731
}

cargo-auditable/src/rustc_wrapper.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{
66

77
use crate::{
88
binary_file, collect_audit_data,
9-
platform_detection::{is_apple, is_msvc, is_wasm},
9+
platform_detection::{is_apple, is_msvc, is_wasm, is_x86},
1010
rustc_arguments::{self, should_embed_audit_data},
1111
target_info,
1212
};
@@ -134,7 +134,14 @@ fn rustc_command_with_audit_data(rustc_path: &OsStr) -> Option<Command> {
134134
command.arg("-Clink-arg=-Wl,-u,_AUDITABLE_VERSION_INFO");
135135
}
136136
} else if is_msvc(&target_info) {
137-
command.arg("-Clink-arg=/INCLUDE:AUDITABLE_VERSION_INFO");
137+
// On x86 MSVC, the `object` crate's CoffI386 mangling adds a `_`
138+
// prefix to global symbols, so the linker must reference the
139+
// decorated name.
140+
if is_x86(&target_info) {
141+
command.arg("-Clink-arg=/INCLUDE:_AUDITABLE_VERSION_INFO");
142+
} else {
143+
command.arg("-Clink-arg=/INCLUDE:AUDITABLE_VERSION_INFO");
144+
}
138145
} else if is_wasm(&target_info) {
139146
// We don't emit the symbol name in WASM, so nothing to do
140147
} else {

0 commit comments

Comments
 (0)