diff --git a/ctest/src/runner.rs b/ctest/src/runner.rs index 26c9d82cb2d38..4ccfddb5e7d79 100644 --- a/ctest/src/runner.rs +++ b/ctest/src/runner.rs @@ -141,9 +141,16 @@ pub fn __compile_test( // Pass in a different target, linker or flags if set, useful for cross compilation. - let target = env::var("TARGET_PLATFORM").unwrap_or_default(); + // Fall back to TARGET if TARGET_PLATFORM is not set (e.g. during local cargo test). + let target = env::var("TARGET_PLATFORM") + .or_else(|_| env::var("TARGET")) + .unwrap_or_default(); if !target.is_empty() { - cmd.arg("--target").arg(target); + cmd.arg("--target").arg(&target); + } + + if target.contains("windows") && target.contains("gnu") { + cmd.arg("-lws2_32"); } let linker = env::var("LINKER").unwrap_or_default(); diff --git a/ctest/templates/test.c b/ctest/templates/test.c index bcc4fc613319a..f81a3b89e098c 100644 --- a/ctest/templates/test.c +++ b/ctest/templates/test.c @@ -8,6 +8,14 @@ #include #include +#ifdef _MSC_VER + /* Disable MSVC warning C4197: 'top-level volatile in cast is ignored'. + * This occurs when casting to a volatile-qualified type (e.g. `(volatile char) -1`) + * to check its signedness, since volatile is ignored on rvalue expressions. + */ + #pragma warning(disable:4197) +#endif + {%- for (header, defines) in self.headers +%} {%- for define in defines +%} @@ -89,7 +97,10 @@ CTEST_EXTERN uint64_t ctest_offset_of__{{ item.id }}__{{ item.field.ident() }}(v } CTEST_EXTERN uint64_t ctest_size_of__{{ item.id }}__{{ item.field.ident() }}(void) { - return sizeof((({{ item.c_ty }}){}).{{ item.c_field }}); + /* MSVC C compiler does not support empty compound literals like `(Type){}`. + * Using the standard pointer-to-null dereference is portable across all compilers. + */ + return sizeof((({{ item.c_ty }} *)0)->{{ item.c_field }}); } {%- endfor +%} diff --git a/ctest/templates/test.rs b/ctest/templates/test.rs index 3a71c73ba3242..84780cd04068d 100644 --- a/ctest/templates/test.rs +++ b/ctest/templates/test.rs @@ -39,6 +39,53 @@ mod generated_tests { } } + fn check_same_fn_ptr(rust: u64, c: u64, attr: &str) { + if rust == c { + NTESTS.fetch_add(1, Ordering::Relaxed); + return; + } + + #[cfg(target_os = "windows")] + let (rust, c) = { + let resolve = |addr: u64| -> u64 { + if addr == 0 { + return 0; + } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + unsafe { + let bytes = addr as *const u8; + // Check for a jump instruction (0xff 0x25), which is used for import thunks. + if *bytes == 0xff && *bytes.add(1) == 0x25 { + if cfg!(target_arch = "x86_64") { + // On x86_64, the jump is RIP-relative: jmp [RIP + displacement] + // bytes[2..6] holds the 32-bit signed displacement. + // bytes.add(6) is the instruction pointer (RIP) after the instruction. + let displacement = (bytes.add(2) as *const i32).read_unaligned(); + let slot_addr = bytes.add(6).offset(displacement as isize) as *const *const (); + return *slot_addr as u64; + } else if cfg!(target_arch = "x86") { + // On x86, the jump contains the absolute address of the import table slot: jmp [absolute_address] + // bytes[2..6] holds the 32-bit absolute address. + let slot_addr_val = (bytes.add(2) as *const usize).read_unaligned(); + let slot_addr = slot_addr_val as *const *const (); + return *slot_addr as u64; + } + } + } + addr + }; + + (resolve(rust), resolve(c)) + }; + + if rust != c { + eprintln!("bad {attr}: rust: {rust:?} != c {c:?}"); + FAILED.store(true, Ordering::Relaxed); + } else { + NTESTS.fetch_add(1, Ordering::Relaxed); + } + } + fn check_same_bytes(rust: &[u8], c: &[u8], attr: &str) { if rust == c { NTESTS.fetch_add(1, Ordering::Relaxed); @@ -360,7 +407,7 @@ mod generated_tests { } let actual = unsafe { ctest_foreign_fn__{{ item.id }}() } as u64; let expected = {{ item.id }} as *const () as u64; - check_same(actual, expected, "`{{ item.id }}` function pointer"); + check_same_fn_ptr(actual, expected, "`{{ item.id }}` function pointer"); } {%- endfor +%} diff --git a/ctest/tests/input/hierarchy.out.c b/ctest/tests/input/hierarchy.out.c index d34e157ee8f72..eab5113b45197 100644 --- a/ctest/tests/input/hierarchy.out.c +++ b/ctest/tests/input/hierarchy.out.c @@ -4,6 +4,14 @@ #include #include #include + +#ifdef _MSC_VER + /* Disable MSVC warning C4197: 'top-level volatile in cast is ignored'. + * This occurs when casting to a volatile-qualified type (e.g. `(volatile char) -1`) + * to check its signedness, since volatile is ignored on rvalue expressions. + */ + #pragma warning(disable:4197) +#endif #include #if defined(__cplusplus) diff --git a/ctest/tests/input/hierarchy.out.rs b/ctest/tests/input/hierarchy.out.rs index 9a9ab6151488a..87b787cd533d7 100644 --- a/ctest/tests/input/hierarchy.out.rs +++ b/ctest/tests/input/hierarchy.out.rs @@ -35,6 +35,53 @@ mod generated_tests { } } + fn check_same_fn_ptr(rust: u64, c: u64, attr: &str) { + if rust == c { + NTESTS.fetch_add(1, Ordering::Relaxed); + return; + } + + #[cfg(target_os = "windows")] + let (rust, c) = { + let resolve = |addr: u64| -> u64 { + if addr == 0 { + return 0; + } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + unsafe { + let bytes = addr as *const u8; + // Check for a jump instruction (0xff 0x25), which is used for import thunks. + if *bytes == 0xff && *bytes.add(1) == 0x25 { + if cfg!(target_arch = "x86_64") { + // On x86_64, the jump is RIP-relative: jmp [RIP + displacement] + // bytes[2..6] holds the 32-bit signed displacement. + // bytes.add(6) is the instruction pointer (RIP) after the instruction. + let displacement = (bytes.add(2) as *const i32).read_unaligned(); + let slot_addr = bytes.add(6).offset(displacement as isize) as *const *const (); + return *slot_addr as u64; + } else if cfg!(target_arch = "x86") { + // On x86, the jump contains the absolute address of the import table slot: jmp [absolute_address] + // bytes[2..6] holds the 32-bit absolute address. + let slot_addr_val = (bytes.add(2) as *const usize).read_unaligned(); + let slot_addr = slot_addr_val as *const *const (); + return *slot_addr as u64; + } + } + } + addr + }; + + (resolve(rust), resolve(c)) + }; + + if rust != c { + eprintln!("bad {attr}: rust: {rust:?} != c {c:?}"); + FAILED.store(true, Ordering::Relaxed); + } else { + NTESTS.fetch_add(1, Ordering::Relaxed); + } + } + fn check_same_bytes(rust: &[u8], c: &[u8], attr: &str) { if rust == c { NTESTS.fetch_add(1, Ordering::Relaxed); @@ -255,7 +302,7 @@ mod generated_tests { } let actual = unsafe { ctest_foreign_fn__malloc() } as u64; let expected = malloc as *const () as u64; - check_same(actual, expected, "`malloc` function pointer"); + check_same_fn_ptr(actual, expected, "`malloc` function pointer"); } /* Tests if the pointer to the static variable matches in both Rust and C. */ diff --git a/ctest/tests/input/macro.out.c b/ctest/tests/input/macro.out.c index fd66dbe297e8e..ec872b530db7c 100644 --- a/ctest/tests/input/macro.out.c +++ b/ctest/tests/input/macro.out.c @@ -5,6 +5,14 @@ #include #include +#ifdef _MSC_VER + /* Disable MSVC warning C4197: 'top-level volatile in cast is ignored'. + * This occurs when casting to a volatile-qualified type (e.g. `(volatile char) -1`) + * to check its signedness, since volatile is ignored on rvalue expressions. + */ + #pragma warning(disable:4197) +#endif + #define SUPPRESS_ERROR #include #undef SUPPRESS_ERROR @@ -56,7 +64,10 @@ CTEST_EXTERN uint64_t ctest_offset_of__VecU8__x(void) { } CTEST_EXTERN uint64_t ctest_size_of__VecU8__x(void) { - return sizeof(((struct VecU8){}).x); + /* MSVC C compiler does not support empty compound literals like `(Type){}`. + * Using the standard pointer-to-null dereference is portable across all compilers. + */ + return sizeof(((struct VecU8 *)0)->x); } CTEST_EXTERN uint64_t ctest_offset_of__VecU8__y(void) { @@ -64,7 +75,10 @@ CTEST_EXTERN uint64_t ctest_offset_of__VecU8__y(void) { } CTEST_EXTERN uint64_t ctest_size_of__VecU8__y(void) { - return sizeof(((struct VecU8){}).y); + /* MSVC C compiler does not support empty compound literals like `(Type){}`. + * Using the standard pointer-to-null dereference is portable across all compilers. + */ + return sizeof(((struct VecU8 *)0)->y); } CTEST_EXTERN uint64_t ctest_offset_of__VecU16__x(void) { @@ -72,7 +86,10 @@ CTEST_EXTERN uint64_t ctest_offset_of__VecU16__x(void) { } CTEST_EXTERN uint64_t ctest_size_of__VecU16__x(void) { - return sizeof(((struct VecU16){}).x); + /* MSVC C compiler does not support empty compound literals like `(Type){}`. + * Using the standard pointer-to-null dereference is portable across all compilers. + */ + return sizeof(((struct VecU16 *)0)->x); } CTEST_EXTERN uint64_t ctest_offset_of__VecU16__y(void) { @@ -80,7 +97,10 @@ CTEST_EXTERN uint64_t ctest_offset_of__VecU16__y(void) { } CTEST_EXTERN uint64_t ctest_size_of__VecU16__y(void) { - return sizeof(((struct VecU16){}).y); + /* MSVC C compiler does not support empty compound literals like `(Type){}`. + * Using the standard pointer-to-null dereference is portable across all compilers. + */ + return sizeof(((struct VecU16 *)0)->y); } diff --git a/ctest/tests/input/macro.out.edition-2024.c b/ctest/tests/input/macro.out.edition-2024.c index fd66dbe297e8e..ec872b530db7c 100644 --- a/ctest/tests/input/macro.out.edition-2024.c +++ b/ctest/tests/input/macro.out.edition-2024.c @@ -5,6 +5,14 @@ #include #include +#ifdef _MSC_VER + /* Disable MSVC warning C4197: 'top-level volatile in cast is ignored'. + * This occurs when casting to a volatile-qualified type (e.g. `(volatile char) -1`) + * to check its signedness, since volatile is ignored on rvalue expressions. + */ + #pragma warning(disable:4197) +#endif + #define SUPPRESS_ERROR #include #undef SUPPRESS_ERROR @@ -56,7 +64,10 @@ CTEST_EXTERN uint64_t ctest_offset_of__VecU8__x(void) { } CTEST_EXTERN uint64_t ctest_size_of__VecU8__x(void) { - return sizeof(((struct VecU8){}).x); + /* MSVC C compiler does not support empty compound literals like `(Type){}`. + * Using the standard pointer-to-null dereference is portable across all compilers. + */ + return sizeof(((struct VecU8 *)0)->x); } CTEST_EXTERN uint64_t ctest_offset_of__VecU8__y(void) { @@ -64,7 +75,10 @@ CTEST_EXTERN uint64_t ctest_offset_of__VecU8__y(void) { } CTEST_EXTERN uint64_t ctest_size_of__VecU8__y(void) { - return sizeof(((struct VecU8){}).y); + /* MSVC C compiler does not support empty compound literals like `(Type){}`. + * Using the standard pointer-to-null dereference is portable across all compilers. + */ + return sizeof(((struct VecU8 *)0)->y); } CTEST_EXTERN uint64_t ctest_offset_of__VecU16__x(void) { @@ -72,7 +86,10 @@ CTEST_EXTERN uint64_t ctest_offset_of__VecU16__x(void) { } CTEST_EXTERN uint64_t ctest_size_of__VecU16__x(void) { - return sizeof(((struct VecU16){}).x); + /* MSVC C compiler does not support empty compound literals like `(Type){}`. + * Using the standard pointer-to-null dereference is portable across all compilers. + */ + return sizeof(((struct VecU16 *)0)->x); } CTEST_EXTERN uint64_t ctest_offset_of__VecU16__y(void) { @@ -80,7 +97,10 @@ CTEST_EXTERN uint64_t ctest_offset_of__VecU16__y(void) { } CTEST_EXTERN uint64_t ctest_size_of__VecU16__y(void) { - return sizeof(((struct VecU16){}).y); + /* MSVC C compiler does not support empty compound literals like `(Type){}`. + * Using the standard pointer-to-null dereference is portable across all compilers. + */ + return sizeof(((struct VecU16 *)0)->y); } diff --git a/ctest/tests/input/macro.out.edition-2024.rs b/ctest/tests/input/macro.out.edition-2024.rs index 6970695f02ff0..bc3e8b06ac49d 100644 --- a/ctest/tests/input/macro.out.edition-2024.rs +++ b/ctest/tests/input/macro.out.edition-2024.rs @@ -35,6 +35,53 @@ mod generated_tests { } } + fn check_same_fn_ptr(rust: u64, c: u64, attr: &str) { + if rust == c { + NTESTS.fetch_add(1, Ordering::Relaxed); + return; + } + + #[cfg(target_os = "windows")] + let (rust, c) = { + let resolve = |addr: u64| -> u64 { + if addr == 0 { + return 0; + } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + unsafe { + let bytes = addr as *const u8; + // Check for a jump instruction (0xff 0x25), which is used for import thunks. + if *bytes == 0xff && *bytes.add(1) == 0x25 { + if cfg!(target_arch = "x86_64") { + // On x86_64, the jump is RIP-relative: jmp [RIP + displacement] + // bytes[2..6] holds the 32-bit signed displacement. + // bytes.add(6) is the instruction pointer (RIP) after the instruction. + let displacement = (bytes.add(2) as *const i32).read_unaligned(); + let slot_addr = bytes.add(6).offset(displacement as isize) as *const *const (); + return *slot_addr as u64; + } else if cfg!(target_arch = "x86") { + // On x86, the jump contains the absolute address of the import table slot: jmp [absolute_address] + // bytes[2..6] holds the 32-bit absolute address. + let slot_addr_val = (bytes.add(2) as *const usize).read_unaligned(); + let slot_addr = slot_addr_val as *const *const (); + return *slot_addr as u64; + } + } + } + addr + }; + + (resolve(rust), resolve(c)) + }; + + if rust != c { + eprintln!("bad {attr}: rust: {rust:?} != c {c:?}"); + FAILED.store(true, Ordering::Relaxed); + } else { + NTESTS.fetch_add(1, Ordering::Relaxed); + } + } + fn check_same_bytes(rust: &[u8], c: &[u8], attr: &str) { if rust == c { NTESTS.fetch_add(1, Ordering::Relaxed); diff --git a/ctest/tests/input/macro.out.rs b/ctest/tests/input/macro.out.rs index 25555e0c25650..45db653ef5e07 100644 --- a/ctest/tests/input/macro.out.rs +++ b/ctest/tests/input/macro.out.rs @@ -35,6 +35,53 @@ mod generated_tests { } } + fn check_same_fn_ptr(rust: u64, c: u64, attr: &str) { + if rust == c { + NTESTS.fetch_add(1, Ordering::Relaxed); + return; + } + + #[cfg(target_os = "windows")] + let (rust, c) = { + let resolve = |addr: u64| -> u64 { + if addr == 0 { + return 0; + } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + unsafe { + let bytes = addr as *const u8; + // Check for a jump instruction (0xff 0x25), which is used for import thunks. + if *bytes == 0xff && *bytes.add(1) == 0x25 { + if cfg!(target_arch = "x86_64") { + // On x86_64, the jump is RIP-relative: jmp [RIP + displacement] + // bytes[2..6] holds the 32-bit signed displacement. + // bytes.add(6) is the instruction pointer (RIP) after the instruction. + let displacement = (bytes.add(2) as *const i32).read_unaligned(); + let slot_addr = bytes.add(6).offset(displacement as isize) as *const *const (); + return *slot_addr as u64; + } else if cfg!(target_arch = "x86") { + // On x86, the jump contains the absolute address of the import table slot: jmp [absolute_address] + // bytes[2..6] holds the 32-bit absolute address. + let slot_addr_val = (bytes.add(2) as *const usize).read_unaligned(); + let slot_addr = slot_addr_val as *const *const (); + return *slot_addr as u64; + } + } + } + addr + }; + + (resolve(rust), resolve(c)) + }; + + if rust != c { + eprintln!("bad {attr}: rust: {rust:?} != c {c:?}"); + FAILED.store(true, Ordering::Relaxed); + } else { + NTESTS.fetch_add(1, Ordering::Relaxed); + } + } + fn check_same_bytes(rust: &[u8], c: &[u8], attr: &str) { if rust == c { NTESTS.fetch_add(1, Ordering::Relaxed); diff --git a/ctest/tests/input/simple.out.with-renames.c b/ctest/tests/input/simple.out.with-renames.c index 0d4dddccfde43..8851bc4d3a0ca 100644 --- a/ctest/tests/input/simple.out.with-renames.c +++ b/ctest/tests/input/simple.out.with-renames.c @@ -4,6 +4,14 @@ #include #include #include + +#ifdef _MSC_VER + /* Disable MSVC warning C4197: 'top-level volatile in cast is ignored'. + * This occurs when casting to a volatile-qualified type (e.g. `(volatile char) -1`) + * to check its signedness, since volatile is ignored on rvalue expressions. + */ + #pragma warning(disable:4197) +#endif #include #if defined(__cplusplus) @@ -105,7 +113,10 @@ CTEST_EXTERN uint64_t ctest_offset_of__Person__name(void) { } CTEST_EXTERN uint64_t ctest_size_of__Person__name(void) { - return sizeof(((struct Person){}).name); + /* MSVC C compiler does not support empty compound literals like `(Type){}`. + * Using the standard pointer-to-null dereference is portable across all compilers. + */ + return sizeof(((struct Person *)0)->name); } CTEST_EXTERN uint64_t ctest_offset_of__Person__age(void) { @@ -113,7 +124,10 @@ CTEST_EXTERN uint64_t ctest_offset_of__Person__age(void) { } CTEST_EXTERN uint64_t ctest_size_of__Person__age(void) { - return sizeof(((struct Person){}).age); + /* MSVC C compiler does not support empty compound literals like `(Type){}`. + * Using the standard pointer-to-null dereference is portable across all compilers. + */ + return sizeof(((struct Person *)0)->age); } CTEST_EXTERN uint64_t ctest_offset_of__Person__job(void) { @@ -121,7 +135,10 @@ CTEST_EXTERN uint64_t ctest_offset_of__Person__job(void) { } CTEST_EXTERN uint64_t ctest_size_of__Person__job(void) { - return sizeof(((struct Person){}).job); + /* MSVC C compiler does not support empty compound literals like `(Type){}`. + * Using the standard pointer-to-null dereference is portable across all compilers. + */ + return sizeof(((struct Person *)0)->job); } CTEST_EXTERN uint64_t ctest_offset_of__Person__favorite_color(void) { @@ -129,7 +146,10 @@ CTEST_EXTERN uint64_t ctest_offset_of__Person__favorite_color(void) { } CTEST_EXTERN uint64_t ctest_size_of__Person__favorite_color(void) { - return sizeof(((struct Person){}).favorite_color); + /* MSVC C compiler does not support empty compound literals like `(Type){}`. + * Using the standard pointer-to-null dereference is portable across all compilers. + */ + return sizeof(((struct Person *)0)->favorite_color); } CTEST_EXTERN uint64_t ctest_offset_of__Word__word(void) { @@ -137,7 +157,10 @@ CTEST_EXTERN uint64_t ctest_offset_of__Word__word(void) { } CTEST_EXTERN uint64_t ctest_size_of__Word__word(void) { - return sizeof(((union Word){}).word); + /* MSVC C compiler does not support empty compound literals like `(Type){}`. + * Using the standard pointer-to-null dereference is portable across all compilers. + */ + return sizeof(((union Word *)0)->word); } CTEST_EXTERN uint64_t ctest_offset_of__Word__byte(void) { @@ -145,7 +168,10 @@ CTEST_EXTERN uint64_t ctest_offset_of__Word__byte(void) { } CTEST_EXTERN uint64_t ctest_size_of__Word__byte(void) { - return sizeof(((union Word){}).byte); + /* MSVC C compiler does not support empty compound literals like `(Type){}`. + * Using the standard pointer-to-null dereference is portable across all compilers. + */ + return sizeof(((union Word *)0)->byte); } diff --git a/ctest/tests/input/simple.out.with-renames.rs b/ctest/tests/input/simple.out.with-renames.rs index b025039103584..89a46a0f9d135 100644 --- a/ctest/tests/input/simple.out.with-renames.rs +++ b/ctest/tests/input/simple.out.with-renames.rs @@ -35,6 +35,53 @@ mod generated_tests { } } + fn check_same_fn_ptr(rust: u64, c: u64, attr: &str) { + if rust == c { + NTESTS.fetch_add(1, Ordering::Relaxed); + return; + } + + #[cfg(target_os = "windows")] + let (rust, c) = { + let resolve = |addr: u64| -> u64 { + if addr == 0 { + return 0; + } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + unsafe { + let bytes = addr as *const u8; + // Check for a jump instruction (0xff 0x25), which is used for import thunks. + if *bytes == 0xff && *bytes.add(1) == 0x25 { + if cfg!(target_arch = "x86_64") { + // On x86_64, the jump is RIP-relative: jmp [RIP + displacement] + // bytes[2..6] holds the 32-bit signed displacement. + // bytes.add(6) is the instruction pointer (RIP) after the instruction. + let displacement = (bytes.add(2) as *const i32).read_unaligned(); + let slot_addr = bytes.add(6).offset(displacement as isize) as *const *const (); + return *slot_addr as u64; + } else if cfg!(target_arch = "x86") { + // On x86, the jump contains the absolute address of the import table slot: jmp [absolute_address] + // bytes[2..6] holds the 32-bit absolute address. + let slot_addr_val = (bytes.add(2) as *const usize).read_unaligned(); + let slot_addr = slot_addr_val as *const *const (); + return *slot_addr as u64; + } + } + } + addr + }; + + (resolve(rust), resolve(c)) + }; + + if rust != c { + eprintln!("bad {attr}: rust: {rust:?} != c {c:?}"); + FAILED.store(true, Ordering::Relaxed); + } else { + NTESTS.fetch_add(1, Ordering::Relaxed); + } + } + fn check_same_bytes(rust: &[u8], c: &[u8], attr: &str) { if rust == c { NTESTS.fetch_add(1, Ordering::Relaxed); @@ -1086,7 +1133,7 @@ mod generated_tests { } let actual = unsafe { ctest_foreign_fn__calloc() } as u64; let expected = calloc as *const () as u64; - check_same(actual, expected, "`calloc` function pointer"); + check_same_fn_ptr(actual, expected, "`calloc` function pointer"); } /* Tests if the pointer to the static variable matches in both Rust and C. */ diff --git a/ctest/tests/input/simple.out.with-skips.c b/ctest/tests/input/simple.out.with-skips.c index f35a37b3957c1..48321a1b7b360 100644 --- a/ctest/tests/input/simple.out.with-skips.c +++ b/ctest/tests/input/simple.out.with-skips.c @@ -4,6 +4,14 @@ #include #include #include + +#ifdef _MSC_VER + /* Disable MSVC warning C4197: 'top-level volatile in cast is ignored'. + * This occurs when casting to a volatile-qualified type (e.g. `(volatile char) -1`) + * to check its signedness, since volatile is ignored on rvalue expressions. + */ + #pragma warning(disable:4197) +#endif #include #if defined(__cplusplus) diff --git a/ctest/tests/input/simple.out.with-skips.rs b/ctest/tests/input/simple.out.with-skips.rs index 3e5a6b051ce33..2b6637347c757 100644 --- a/ctest/tests/input/simple.out.with-skips.rs +++ b/ctest/tests/input/simple.out.with-skips.rs @@ -35,6 +35,53 @@ mod generated_tests { } } + fn check_same_fn_ptr(rust: u64, c: u64, attr: &str) { + if rust == c { + NTESTS.fetch_add(1, Ordering::Relaxed); + return; + } + + #[cfg(target_os = "windows")] + let (rust, c) = { + let resolve = |addr: u64| -> u64 { + if addr == 0 { + return 0; + } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + unsafe { + let bytes = addr as *const u8; + // Check for a jump instruction (0xff 0x25), which is used for import thunks. + if *bytes == 0xff && *bytes.add(1) == 0x25 { + if cfg!(target_arch = "x86_64") { + // On x86_64, the jump is RIP-relative: jmp [RIP + displacement] + // bytes[2..6] holds the 32-bit signed displacement. + // bytes.add(6) is the instruction pointer (RIP) after the instruction. + let displacement = (bytes.add(2) as *const i32).read_unaligned(); + let slot_addr = bytes.add(6).offset(displacement as isize) as *const *const (); + return *slot_addr as u64; + } else if cfg!(target_arch = "x86") { + // On x86, the jump contains the absolute address of the import table slot: jmp [absolute_address] + // bytes[2..6] holds the 32-bit absolute address. + let slot_addr_val = (bytes.add(2) as *const usize).read_unaligned(); + let slot_addr = slot_addr_val as *const *const (); + return *slot_addr as u64; + } + } + } + addr + }; + + (resolve(rust), resolve(c)) + }; + + if rust != c { + eprintln!("bad {attr}: rust: {rust:?} != c {c:?}"); + FAILED.store(true, Ordering::Relaxed); + } else { + NTESTS.fetch_add(1, Ordering::Relaxed); + } + } + fn check_same_bytes(rust: &[u8], c: &[u8], attr: &str) { if rust == c { NTESTS.fetch_add(1, Ordering::Relaxed); diff --git a/libc-test/build.rs b/libc-test/build.rs index 50c813e665763..523c4f5a467b8 100644 --- a/libc-test/build.rs +++ b/libc-test/build.rs @@ -847,8 +847,12 @@ fn test_windows(target: &str) { }); cfg.skip_struct_field(move |s, field| s.ident() == "CONTEXT" && field.ident() == "Fp"); - // FIXME(windows): All functions point to the wrong addresses? - cfg.skip_fn_ptrcheck(|_| true); + // FIXME(windows): ctime, difftime, gmtime_s, localtime_s, and getpid either resolve to different + // addresses or are inline/wrapper functions on the C side. + cfg.skip_fn_ptrcheck(|name| match name { + "ctime" | "difftime" | "gmtime_s" | "localtime_s" | "getpid" => true, + _ => false, + }); cfg.skip_signededness(move |c| { match c {