Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WinApi SYSTEM_INFO dwPageSize always returning 0 #2136

Closed
V0ldek opened this issue May 20, 2022 · 18 comments · Fixed by #2140
Closed

WinApi SYSTEM_INFO dwPageSize always returning 0 #2136

V0ldek opened this issue May 20, 2022 · 18 comments · Fixed by #2140
Labels
A-shims Area: This affects the external function shims A-windows Area: affects only Windows targets E-good-first-issue A good way to start contributing, mentoring is available

Comments

@V0ldek
Copy link
Contributor

V0ldek commented May 20, 2022

This is a rather weird issue I reported first in the page_size crate. When running Miri with any of the Tier 1 Windows targets the crate reports page size 0, which is a fatal error for my use, where I want to allocate aligned to the page boundary.

This is witnessed most easily by the GitHub Action run. My code panics when the page size is not a power of two, so in this case it crashes with:

thread 'main' panicked at 'detected page size 0 that is not a power of two, this is unsupported', src/alignment.rs:111:21

The page size query works perfectly well for Unix targets.

Replication

This can be confirmed by referencing page_size and asking for it like this:

fn main() {
    println!("Page size: {}", page_size::get());
}

and running Miri:

cargo +nightly miri run --target x86_64-pc-windows-gnu

resulting in:

> Page size: 0

Whereas running it without Miri gives the correct result (on my PC it's 4KB):

cargo run
> Page size: 4096

If it matters, the host OS seems to not affect this, the issue is the same running the command both from Unix and Windows.

@RalfJung
Copy link
Member

RalfJung commented May 20, 2022

Thanks for the report!

Miri's Windows support is a big hack, barely enough to get our own test suite to pass. Windows is effectively at best a Tier 2 target in Miri. (Not that we have a formal target policy.)

Basically, I don't have a lot of time to spend on Windows support in Miri myself. But help with the Windows support would certainly be appreciated. :D

This is probably caused by some code in this file, though I do not know which function is responsible. Maybe GetSystemInfo?

@RalfJung RalfJung added the A-windows Area: affects only Windows targets label May 20, 2022
@V0ldek
Copy link
Contributor Author

V0ldek commented May 20, 2022

That would certainly be this code:

// Querying system information
"GetSystemInfo" => {
let [system_info] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
let system_info = this.deref_operand(system_info)?;
// Initialize with `0`.
this.write_bytes_ptr(
system_info.ptr,
iter::repeat(0u8).take(system_info.layout.size.bytes() as usize),
)?;
// Set number of processors.
let dword_size = Size::from_bytes(4);
let num_cpus = this.mplace_field(&system_info, 6)?;
this.write_scalar(Scalar::from_int(NUM_CPUS, dword_size), &num_cpus.into())?;
}

If I read it correctly, it fills the entire SYSTEM_INFO structure with zeroes and then sets the CPU count to the actual value.

@RalfJung
Copy link
Member

Yes, that's exactly what it does. We call num_cpus in our test suite so I got that to pass.^^

Looks like we should add the page_size crate as well, then. (But somewhere in this file, not mixed up with the subcrate test.)

@V0ldek
Copy link
Contributor Author

V0ldek commented May 20, 2022

Hah, but the page_size crate just calls the WinApi SYSTEM_INFO 😅

https://github.com/Elzair/page_size_rs/blob/290f3da944d0f1c86612172ec84958a9dbf910ac/src/lib.rs#L162-L170

use winapi::um::sysinfoapi::{SYSTEM_INFO, LPSYSTEM_INFO};
use winapi::um::sysinfoapi::GetSystemInfo;

#[inline]
pub fn get() -> usize {
    unsafe {
        let mut info: SYSTEM_INFO = mem::zeroed();
        GetSystemInfo(&mut info as LPSYSTEM_INFO);

        info.dwPageSize as usize
    }
}

@RalfJung RalfJung added A-shims Area: This affects the external function shims E-good-first-issue A good way to start contributing, mentoring is available labels May 20, 2022
@RalfJung
Copy link
Member

Okay, this should be easy to fix then. :)
If someone wants to work on this, let me know and I can provide more details if needed.

@V0ldek
Copy link
Contributor Author

V0ldek commented May 20, 2022

I don't see why that'd make it easy, doesn't that mean we couldn't use page_size for this?

  • page_size calls WinApi GetSystemInfo, which with Miri resolves to the code you linked before
  • Miri would call page_size to get the response
  • page_size calls WinApi GetSystemInfo, which with Miri resolves to the same code as before...

@bjorn3
Copy link
Member

bjorn3 commented May 20, 2022

Miri would call page_size as native dependency rather than as interpeted code which means that GetSystemInfo ends up as native system call rather than in the miri shim.

@V0ldek
Copy link
Contributor Author

V0ldek commented May 20, 2022

@bjorn3

Okay, I wasn't entirely sure how Miri works then 😅

I'd happily work on this then, it'd allow me to test my crate on more platforms with Miri. I've never seen this code though, so I'd need details on what would be required of the PR @RalfJung

@V0ldek
Copy link
Contributor Author

V0ldek commented May 21, 2022

@RalfJung

Actually, I can ask more focused questions.

  1. How would I test this change when implemented?
  2. Looking at the code for num_cpus I've stumbled upon this:

miri/src/machine.rs

Lines 34 to 38 in b96610b

// Some global facts about the emulated machine.
pub const PAGE_SIZE: u64 = 4 * 1024; // FIXME: adjust to target architecture
pub const STACK_ADDR: u64 = 32 * PAGE_SIZE; // not really about the "stack", but where we start assigning integer addresses to allocations
pub const STACK_SIZE: u64 = 16 * PAGE_SIZE; // whatever
pub const NUM_CPUS: u64 = 1;

I see that these constants are also used in posix shim to set some variables. Should we also do justice to this FIXME and switch this to the actual underlying page size?

@RalfJung
Copy link
Member

RalfJung commented May 21, 2022

I don't see why that'd make it easy, doesn't that mean we couldn't use page_size for this?

Sorry, what I meant is call page_size in a test inside Miri to ensure that we are implementing whatever APIs it needs on the platforms we support.

Miri would call page_size as native dependency rather than as interpeted code which means that GetSystemInfo ends up as native system call rather than in the miri shim.

Actually I was thinking Miri would just hard-code 4096 as the pagesize, as it does on Unix.

Should we also do justice to this FIXME and switch this to the actual underlying page size?

Right, we may want to do that, but then we should do it on all platforms.
However, I am also not 100% convinced it is the right thing to do -- this would be leaking some "host" information into the target, which is not right. Miri is supposed to emulate an arbitrary target, not something host-specific -- at least without -Zmiri-disable-isolation.

So for now I think it should just also use PAGE_SIZE on windows. Basically you would adjust this code to also fill out the page size field. Then if you want, open a separate issue for no longer hard-coding the page size, but making it user-configurable somehow.

@V0ldek
Copy link
Contributor Author

V0ldek commented May 21, 2022

Got it. I need guidance on how to test it, though, where would I put the page_size dep and test?

I guess the test would land in tests/run-pass, but I don't know how to add an external crate dependency to those tests.

@RalfJung
Copy link
Member

RalfJung commented May 21, 2022 via email

@V0ldek
Copy link
Contributor Author

V0ldek commented May 21, 2022

There's one thing I don't understand at all.

The layout of winapi::um::sysinfoapi::SYSTEM_INFO is this (comments added to mark the field names):

layout: TyAndLayout { 
    ty: winapi::um::sysinfoapi::SYSTEM_INFO,
    layout: Layout { 
        fields: Arbitrary { 
            offsets: [
                Size(0 bytes),  // 0, u                           : SYSTEM_INFO_u
                Size(4 bytes),  // 1, dwPageSize                  : DWORD
                Size(8 bytes),  // 2, lpMinimumApplicationAddress : LPVOID
                Size(16 bytes), // 3, lpMaximumApplicationAddress : LPVOID
                Size(24 bytes), // 4, dwActiveProcessorMask       : DWORD_PTR
                Size(32 bytes), // 5, dwNumberOfProcessors        : DWORD
                Size(36 bytes), // 6, dwProcessorType             : DWORD
                Size(40 bytes), // 7, dwAllocationGranularity     : DWORD
                Size(44 bytes), // 8, wProcessorLevel             : WORD
                Size(46 bytes)],// 9, wProcessorRevision          : WORD
                memory_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 
            }, 
            variants: Single { index: 0 },
            abi: Aggregate { sized: true },
            largest_niche: None,
            align: AbiAndPrefAlign { 
                abi: Align(8 bytes),
                pref: Align(8 bytes) 
            }, 
            size: Size(48 bytes) 
        } 
    } 
}

So number of processors is the 5th field. But the existing code writes to the 6th:

// Set number of processors.
let dword_size = Size::from_bytes(4);
let num_cpus = this.mplace_field(&system_info, 6)?;
this.write_scalar(Scalar::from_int(NUM_CPUS, dword_size), &num_cpus.into())?;
}

From this, the page size field must be either the 1st field (as in the layout) or the 2nd field (4 fields before dwProcessorType if that is the 6th). But none of these work:

2nd

If I write:

let page_size = this.mplace_field(&system_info, 2)?;
this.write_scalar(Scalar::from_int(PAGE_SIZE, dword_size), &page_size.into())?;

then page_size does not work and fails with:

error: Undefined Behavior: scalar size mismatch: expected 8 bytes but got 4 bytes instead
   --> /home/mat/.cargo/registry/src/github.aaakk.us.kg-1ecc6299db9ec823/page_size-0.4.2/src/lib.rs:166:13
    |
166 |             GetSystemInfo(&mut info as LPSYSTEM_INFO);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ scalar size mismatch: expected 8 bytes but got 4 bytes instead
    |
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

    = note: inside `page_size::windows::get` at /home/mat/.cargo/registry/src/github.aaakk.us.kg-1ecc6299db9ec823/page_size-0.4.2/src/lib.rs:166:13
    = note: inside closure at /home/mat/.cargo/registry/src/github.aaakk.us.kg-1ecc6299db9ec823/page_size-0.4.2/src/lib.rs:127:39
    = note: inside closure at /home/mat/.rustup/toolchains/miri/lib/rustlib/src/rust/library/std/src/sync/once.rs:276:41
    = note: inside `std::sync::Once::call_inner` at /home/mat/.rustup/toolchains/miri/lib/rustlib/src/rust/library/std/src/sync/once.rs:434:21
    = note: inside `std::sync::Once::call_once::<[closure@page_size::get_helper::{closure#0}]>` at /home/mat/.rustup/toolchains/miri/lib/rustlib/src/rust/library/std/src/sync/once.rs:276:9
    = note: inside `page_size::get_helper` at /home/mat/.cargo/registry/src/github.aaakk.us.kg-1ecc6299db9ec823/page_size-0.4.2/src/lib.rs:127:9
    = note: inside `page_size::get` at /home/mat/.cargo/registry/src/github.aaakk.us.kg-1ecc6299db9ec823/page_size-0.4.2/src/lib.rs:53:5
note: inside `main` at src/main.rs:2:31
   --> src/main.rs:2:31
    |
2   |     println!("Page size: {}", page_size::get());
    |                               ^^^^^^^^^^^^^^^^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to previous error

1st

If I write:

let page_size = this.mplace_field(&system_info, 1)?;
this.write_scalar(Scalar::from_int(PAGE_SIZE, dword_size), &page_size.into())?;

then page_size does work and correctly returns 4096. But miri's test suite fails!

$ MIRI_TEST_TARGET="x86_64-pc-windows-gnu" ./run-test.py

## Running `cargo miri` tests for target x86_64-pc-windows-gnu
A libstd for Miri is now available in `/home/mat/.cache/miri`.
Testing `cargo miri run` (no isolation)...
Testing `cargo miri run` (no rebuild)...
Testing `cargo miri run` (with arguments and target)...
Testing `cargo miri r` (subcrate, no isolation)...
Testing `cargo miri run` (custom target dir)...
Testing `cargo miri test`...
Test stdout or stderr did not match reference!
--- BEGIN test stdout ---
--- END test stdout ---
--- BEGIN test stderr ---
error: Undefined Behavior: scalar size mismatch: expected 2 bytes but got 4 bytes instead
   --> /home/mat/.rustup/toolchains/miri/lib/rustlib/src/rust/library/std/src/sys/windows/thread.rs:104:9
    |
104 |         c::GetSystemInfo(&mut sysinfo);
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ scalar size mismatch: expected 2 bytes but got 4 bytes instead
    |
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

    = note: inside `std::sys::windows::thread::available_parallelism` at /home/mat/.rustup/toolchains/miri/lib/rustlib/src/rust/library/std/src/sys/windows/thread.rs:104:9
    = note: inside `std::thread::available_parallelism` at /home/mat/.rustup/toolchains/miri/lib/rustlib/src/rust/library/std/src/thread/mod.rs:1609:5
    = note: inside `test::test::helpers::concurrency::get_concurrency` at /home/mat/.rustup/toolchains/miri/lib/rustlib/src/rust/library/test/src/helpers/concurrency.rs:12:9
    = note: inside `<fn() -> usize {test::test::helpers::concurrency::get_concurrency} as std::ops::FnOnce<()>>::call_once - shim(fn() -> usize {test::test::helpers::concurrency::get_concurrency})` at /home/mat/.rustup/toolchains/miri/lib/rustlib/src/rust/library/core/src/ops/function.rs:248:5
    = note: inside `std::option::Option::<usize>::unwrap_or_else::<fn() -> usize {test::test::helpers::concurrency::get_concurrency}>` at /home/mat/.rustup/toolchains/miri/lib/rustlib/src/rust/library/core/src/option.rs:805:21
    = note: inside `test::test::run_tests_console` at /home/mat/.rustup/toolchains/miri/lib/rustlib/src/rust/library/test/src/console.rs:269:28
    = note: inside `test::test::test_main` at /home/mat/.rustup/toolchains/miri/lib/rustlib/src/rust/library/test/src/lib.rs:114:15
    = note: inside `test::test::test_main_static` at /home/mat/.rustup/toolchains/miri/lib/rustlib/src/rust/library/test/src/lib.rs:133:5
    = note: inside `main`

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to previous error

error: test failed, to rerun pass '--bin cargo-miri-test'
--- END test stderr ---

TEST FAIL: exit code was 1

I tried digging into this but I found nothing apart from confusion.

@RalfJung
Copy link
Member

RalfJung commented May 21, 2022

FWIW, that existing code is unnecessarily roundabout -- this would be better written as

                let num_cpus = this.mplace_field(&system_info, 6)?;
                this.write_scalar(Scalar::from_u32(NUM_CPUS), &num_cpus.into())?;

and changing the type of NUM_CPUS to be u32.
I also noticed an as cast a few lines above... this is really old code.^^ We usually try to avoid as casts.


Regarding your problem, maybe there is something odd with the type signature used by num_cpus? You can find it here, and dwNumberOfProcessors is field number 6 (counting from 0).

If you want you can adjust the code to not use the caller signature at all, and instead hard-code offsets and types. This is the more correct thing to do. (Well, the deref_operand will still use caller signature, but 🤷 ) That will be a bit more work though. MPlaceTy has an offset method you can use to go to a field at a certain offset, but you will have to provide the layout of the field. this.machine.layouts contains a couple common ones, so this.machine.layouts.u32 (or is DWORD signed? then i32) should do it.

@V0ldek
Copy link
Contributor Author

V0ldek commented May 21, 2022

I think that is the crux of the issue. I don't know where the layout in the foreign_items code comes from, but

  • page_size uses the layout from winapi::um::sysinfoapi::SYSTEM_INFO, so when called from there page_size is indeed field number 1, so that works.
  • but num_cpus uses the linked layout, where the first field is inlined into the structure and actually takes 2 fields. So there writing to 6th field is actually dwNumberOfProcessors, but when I write to field number 1 that means the wReserved WORD-type field and it all goes to hell.

I think that can be detected though. Maybe the solution would be to check whether the first field is of size 4 or size 2 and offset according to that? In the first case use field 1 for page size and 5 for num cpus, in the latter use fields 2 and 6, respectively?

@RalfJung
Copy link
Member

RalfJung commented May 21, 2022 via email

@V0ldek
Copy link
Contributor Author

V0ldek commented May 21, 2022

The checking field counts approach led to simpler code, but I reimplemented it by explicitly using offset, and it still works 🥳 Okay, premature celebration :(

@V0ldek
Copy link
Contributor Author

V0ldek commented May 21, 2022

I implemented dynamically calculating the offsets of all fields in SYSTEM_INFO, since the concrete values depend on target's pointer size. With that adding values to other fields in that structure should be relatively straightforward. And now it indeed works (🥳).

@bors bors closed this as completed in b5fc544 May 22, 2022
bors added a commit that referenced this issue Jun 15, 2023
Dereference pointers in shims as correct types

Currently, shims will dereference pointers as the type written by the user. This can cause false positives, incorrect behavior such as #2136, and even ICEs if a field is not present.

This PR fixes this by having shims dereference pointers with types from `std` or `libc` that we can rely on the layout and field names of instead of with whatever the user passed in.

Fixes #1123
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-shims Area: This affects the external function shims A-windows Area: affects only Windows targets E-good-first-issue A good way to start contributing, mentoring is available
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants