在 Windows 上触发缺页异常

Triggering page fault exception on Windows

我正在尝试对 Windows 上的页面错误异常进行一些测试。需求是将一些数据放到page boundary,这样读取数据会触发page fault异常。

具体来说,测试由逻辑上连续分配的7个字节(例如)组成。我需要前 5 个字节位于物理分配的页面上,但接下来的 2 个字节位于尚未物理分配的页面上。

因此读取这7个字节会触发页面错误异常,而只读取4个字节则不会。

我最初认为我应该分配两个页面,在边界上写入7字节数据,然后将第二页调出。

我可以使用一些用户模式来做到这一点 Windows API 吗?

是的,这在用户空间是可能的。您通常会在提交后使用 VirtualAlloc and then commit part of it. You can change the protection back and forth with VirtualProtect 立即保留所有页面。

#include <cstdint>
#include <iostream>
#include <Windows.h>

int main()
{
    //Retrieve currently configured page-size
    SYSTEM_INFO info;
    GetSystemInfo(&info);
    DWORD pageSize = info.dwPageSize;
    //Reserve 2 pages
    void *mem = VirtualAlloc(NULL, pageSize*2, MEM_RESERVE, PAGE_NOACCESS);
    //Commit first page with read/write premissions
    VirtualAlloc(mem, pageSize, MEM_COMMIT, PAGE_READWRITE);
    //get pointer with 5 bytes in the first page
    uint8_t* ptrAcross = (uint8_t*)mem + pageSize - 5;
    //Fill first 5 bytes
    FillMemory(ptrAcross, 5, 0x55);
    try
    {
        //Try to fill 6th byte
        FillMemory(ptrAcross+5, 1, 0x55);
    }
    catch(...) // only catches the access violation when compiled with /EHa
    {
        std::cout << "Access violation" << std::endl;
    }
    std::cout << "Program finished" <<std::endl;
    VirtualFree(mem, 0, MEM_RELEASE);

    return 0;
}

基本上,这个答案没有添加任何比@PeterT 之一更重要的东西。对其进行了一些修改以在页面边界上写入任意字节(如我们所愿):

use std::{
    mem::{transmute, MaybeUninit},
    ptr,
};

use winapi::{
    shared::{minwindef::TRUE, ntdef::NULL},
    um::{
        memoryapi::{VirtualAlloc, VirtualFree, VirtualProtect},
        sysinfoapi::GetSystemInfo,
        winnt::{
            MEM_COMMIT, MEM_DECOMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE,
        },
    },
};

fn main() {
    let page_size = {
        let mut sys_info = MaybeUninit::uninit();
        let sys_info = unsafe {
            GetSystemInfo(sys_info.as_mut_ptr());
            sys_info.assume_init()
        };
        sys_info.dwPageSize
    } as usize;

    let region_base = unsafe {
        VirtualAlloc(
            NULL,
            page_size * 2,
            MEM_RESERVE | MEM_COMMIT,
            PAGE_EXECUTE_READWRITE,
        )
    } as usize;

    println!("Allocated region base: 0x{:x}", region_base);

    let ud1_addr = region_base + page_size - 0x2;
    print!("Writing 0f b9 27 (ud1 esp, [rdi]) to: 0x{:x}... ", ud1_addr);

    let ud1_ptr = ud1_addr as *mut u8;
    unsafe {
        ptr::write(ud1_ptr, 0x0f);
        ptr::write(ud1_ptr.add(1), 0xb9);
        ptr::write(ud1_ptr.add(2), 0x27);
    };

    println!("ok");

    let last_page_addr: usize = region_base + page_size;
    print!("Decommitting the last page at 0x{:x}... ", last_page_addr);
    let free_ok = unsafe { VirtualFree(last_page_addr as _, page_size, MEM_DECOMMIT) };

    if free_ok == TRUE {
        println!("ok. Executing: ud1 esp, [rdi]");
        let ud1: extern "C" fn() = unsafe { transmute(ud1_ptr as *const ()) };
        ud1();
    } else {
        println!("failed");
    }
}