From 69376f61ca7afac70efb92a4270946d50d2622b5 Mon Sep 17 00:00:00 2001 From: gbeauche <> Date: Wed, 16 Jan 2008 22:36:35 +0000 Subject: [PATCH] Wrap the Windows write-watch API. --- BasiliskII/src/Unix/vm_alloc.cpp | 112 ++++++++++++++++++++++++++++++- BasiliskII/src/Unix/vm_alloc.h | 21 +++++- 2 files changed, 130 insertions(+), 3 deletions(-) diff --git a/BasiliskII/src/Unix/vm_alloc.cpp b/BasiliskII/src/Unix/vm_alloc.cpp index c3468026..b24eeecd 100644 --- a/BasiliskII/src/Unix/vm_alloc.cpp +++ b/BasiliskII/src/Unix/vm_alloc.cpp @@ -188,6 +188,11 @@ void * vm_acquire(size_t size, int options) if (options & VM_MAP_FIXED) return VM_MAP_FAILED; +#ifndef HAVE_VM_WRITE_WATCH + if (options & VM_MAP_WRITE_WATCH) + return VM_MAP_FAILED; +#endif + #ifdef HAVE_MACH_VM // vm_allocate() returns a zero-filled memory region if (vm_allocate(mach_task_self(), (vm_address_t *)&addr, size, TRUE) != KERN_SUCCESS) @@ -207,7 +212,11 @@ void * vm_acquire(size_t size, int options) next_address = (char *)addr + size; #else #ifdef HAVE_WIN32_VM - if ((addr = VirtualAlloc(NULL, size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE)) == NULL) + int alloc_type = MEM_RESERVE | MEM_COMMIT; + if (options & VM_MAP_WRITE_WATCH) + alloc_type |= MEM_WRITE_WATCH; + + if ((addr = VirtualAlloc(NULL, size, alloc_type, PAGE_EXECUTE_READWRITE)) == NULL) return VM_MAP_FAILED; #else if ((addr = calloc(size, 1)) == 0) @@ -236,6 +245,11 @@ int vm_acquire_fixed(void * addr, size_t size, int options) if (options & VM_MAP_SHARED) return -1; +#ifndef HAVE_VM_WRITE_WATCH + if (options & VM_MAP_WRITE_WATCH) + return -1; +#endif + #ifdef HAVE_MACH_VM // vm_allocate() returns a zero-filled memory region if (vm_allocate(mach_task_self(), (vm_address_t *)&addr, size, 0) != KERN_SUCCESS) @@ -253,10 +267,14 @@ int vm_acquire_fixed(void * addr, size_t size, int options) if (addr == NULL) return -1; + int alloc_type = MEM_RESERVE | MEM_COMMIT; + if (options & VM_MAP_WRITE_WATCH) + alloc_type |= MEM_WRITE_WATCH; + // Allocate a possibly offset region to align on 64K boundaries LPVOID req_addr = align_addr_segment(addr); DWORD req_size = align_size_segment(addr, size); - LPVOID ret_addr = VirtualAlloc(req_addr, req_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + LPVOID ret_addr = VirtualAlloc(req_addr, req_size, alloc_type, PAGE_EXECUTE_READWRITE); if (ret_addr != req_addr) return -1; #else @@ -328,6 +346,49 @@ int vm_protect(void * addr, size_t size, int prot) #endif } +/* Return the addresses of the pages that got modified in the + specified range [ ADDR, ADDR + SIZE [ since the last reset of the watch + bits. Returns 0 if successful, -1 for errors. */ + +int vm_get_write_watch(void * addr, size_t size, + void ** pages, unsigned int * n_pages, + int options) +{ +#ifdef HAVE_VM_WRITE_WATCH +#ifdef HAVE_WIN32_VM + DWORD flags = 0; + if (options & VM_WRITE_WATCH_RESET) + flags |= WRITE_WATCH_FLAG_RESET; + + ULONG page_size; + ULONG count = *n_pages; + int ret_code = GetWriteWatch(flags, addr, size, pages, &count, &page_size); + if (ret_code != 0) + return -1; + + *n_pages = count; + return 0; +#endif +#endif + // Unsupported + return -1; +} + +/* Reset the write-tracking state for the specified range [ ADDR, ADDR + + SIZE [. Returns 0 if successful, -1 for errors. */ + +int vm_reset_write_watch(void * addr, size_t size) +{ +#ifdef HAVE_VM_WRITE_WATCH +#ifdef HAVE_WIN32_VM + int ret_code = ResetWriteWatch(addr, size); + return ret_code == 0 ? 0 : -1; +#endif +#endif + // Unsupported + return -1; +} + /* Returns the size of a page. */ int vm_get_page_size(void) @@ -345,6 +406,53 @@ int vm_get_page_size(void) #endif } +#ifdef CONFIGURE_TEST_VM_WRITE_WATCH +int main(void) +{ + int i, j; + + vm_init(); + + vm_uintptr_t page_size = vm_get_page_size(); + + char *area; + const int n_pages = 7; + const int area_size = n_pages * page_size; + const int map_options = VM_MAP_DEFAULT | VM_MAP_WRITE_WATCH; + if ((area = (char *)vm_acquire(area_size, map_options)) == VM_MAP_FAILED) + return 1; + + unsigned int n_modified_pages_expected = 0; + static const int touch_page[n_pages] = { 0, 1, 1, 0, 1, 0, 1 }; + for (i = 0; i < n_pages; i++) { + if (touch_page[i]) { + area[i * page_size] = 1; + ++n_modified_pages_expected; + } + } + + char *modified_pages[n_pages]; + unsigned int n_modified_pages = n_pages; + if (vm_get_write_watch(area, area_size, (void **)modified_pages, &n_modified_pages) < 0) + return 2; + if (n_modified_pages != n_modified_pages_expected) + return 3; + for (i = 0, j = 0; i < n_pages; i++) { + char v = area[i * page_size]; + if ((touch_page[i] && !v) || (!touch_page[i] && v)) + return 4; + if (!touch_page[i]) + continue; + if (modified_pages[j] != (area + i * page_size)) + return 5; + ++j; + } + + vm_release(area, area_size); + return 0; +} +#endif + #ifdef CONFIGURE_TEST_VM_MAP #include #include diff --git a/BasiliskII/src/Unix/vm_alloc.h b/BasiliskII/src/Unix/vm_alloc.h index 47d44208..3285bd1a 100644 --- a/BasiliskII/src/Unix/vm_alloc.h +++ b/BasiliskII/src/Unix/vm_alloc.h @@ -46,11 +46,17 @@ extern "C" { #endif #endif +/* Option to vm_get_write_watch() to reset the write-tracking state + once it was retrieved. Otherwise, you have to manually call + vm_reset_write_watch() and potentially lose some info. */ +#define VM_WRITE_WATCH_RESET 0x01 + /* Mapping options. */ #define VM_MAP_SHARED 0x01 #define VM_MAP_PRIVATE 0x02 #define VM_MAP_FIXED 0x04 #define VM_MAP_32BIT 0x08 +#define VM_MAP_WRITE_WATCH 0x10 /* Default mapping options. */ #define VM_MAP_DEFAULT (VM_MAP_PRIVATE) @@ -103,10 +109,23 @@ extern int vm_acquire_fixed(void * addr, size_t size, int options = VM_MAP_DEFAU extern int vm_release(void * addr, size_t size); /* Change the memory protection of the region starting at ADDR and - extending LEN bytes to PROT. Returns 0 if successful, -1 for errors. */ + extending SIZE bytes to PROT. Returns 0 if successful, -1 for errors. */ extern int vm_protect(void * addr, size_t size, int prot); +/* Return the addresses of the pages that got modified since the last + reset of the write-tracking state for the specified range [ ADDR, + ADDR + SIZE [. Returns 0 if successful, -1 for errors. */ + +extern int vm_get_write_watch(void * addr, size_t size, + void ** pages, unsigned int * n_pages, + int options = 0); + +/* Reset the write-tracking state for the specified range [ ADDR, ADDR + + SIZE [. Returns 0 if successful, -1 for errors. */ + +extern int vm_reset_write_watch(void * addr, size_t size); + /* Returns the size of a page. */ extern int vm_get_page_size(void);