From d4ccd5c0520fb75e92af4ae554aa8420075db515 Mon Sep 17 00:00:00 2001 From: Peter Eastham Date: Thu, 4 Aug 2022 17:44:39 -0600 Subject: [PATCH 1/4] Added c.s.j.win32.Psapi.QueryWorkingSetEx --- CHANGES.md | 1 + .../src/com/sun/jna/platform/win32/Psapi.java | 107 ++++++++++++++++++ .../com/sun/jna/platform/win32/PsapiTest.java | 43 +++++++ 3 files changed, 151 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index d8a80ce078..ce984a541d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ Next Release (5.13.0) Features -------- +* [#1454](https://github.com/java-native-access/jna/pull/1454): Add `c.s.j.p.win32.Psapi.QueryWorkingSetEx` and associated Types = [@crain-32](https://github.com/Crain-32). Bug Fixes --------- diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Psapi.java b/contrib/platform/src/com/sun/jna/platform/win32/Psapi.java index dfd13d204d..4bfc4483a4 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Psapi.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Psapi.java @@ -26,8 +26,10 @@ import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.Structure; +import com.sun.jna.Structure.ByReference; import com.sun.jna.Structure.FieldOrder; import com.sun.jna.platform.win32.BaseTSD.SIZE_T; +import com.sun.jna.platform.win32.BaseTSD.ULONG_PTR; import com.sun.jna.platform.win32.WinDef.DWORD; import com.sun.jna.platform.win32.WinDef.HMODULE; import com.sun.jna.platform.win32.WinNT.HANDLE; @@ -292,6 +294,18 @@ public interface Psapi extends StdCallLibrary { */ boolean EnumProcesses(int[] lpidProcess, int cb, IntByReference lpcbNeeded); + /** + * Retrieves extended information about the pages at specific + * virtual addresses in the address space of the specified process. + * + * @param hProcess A Handle to the Process + * @param pv A pointer to an array of PSAPI_WORKING_SET_EX_INFORMATION structures + * @param cb The size of the pv buffer, in bytes. + * @return If the function succeeds, the return value is nonzero. + * @see MSDN + */ + boolean QueryWorkingSetEx(HANDLE hProcess, Pointer pv, int cb); + @FieldOrder({"lpBaseOfDll", "SizeOfImage", "EntryPoint"}) class MODULEINFO extends Structure { public Pointer EntryPoint; @@ -320,4 +334,97 @@ class PERFORMANCE_INFORMATION extends Structure { public DWORD ProcessCount; public DWORD ThreadCount; } + + @FieldOrder({"Flags", "Data"}) + class PSAPI_WORKING_SET_EX_BLOCK extends Structure implements ByReference { + public ULONG_PTR Flags; + public ULONG_PTR[] Data = new ULONG_PTR[Native.POINTER_SIZE == 8 ? 1 : 2]; + private long innerValue; + + @Override + public void read() { + super.read(); + innerValue = this.Data[0].longValue(); + } + + /** + * If this bit is 1, the subsequent members are valid; otherwise they should be ignored. + */ + public boolean Valid() { + return getBitFieldValue(1, 0) == 1; + } + + /** + * The number of processes that share this page. The maximum value of this member is 7. + */ + public int ShareCount() { + return getBitFieldValue(3, 1); + } + + /** + * The memory protection attributes of the page. For a list of values see below. + * @see Memory Protection Constants. + */ + public int Win32Protection() { + return getBitFieldValue(11, 3 + 1); + } + + /** + * If this bit is 1, the page can be shared. + */ + public boolean Shared() { + return getBitFieldValue(1, 11 + 3 + 1) == 1; + } + + /** + * The NUMA node. The maximum value of this member is 63. + */ + public int Node() { + return getBitFieldValue(6, 1 + 11 + 3 + 1); + } + + /** + * If this bit is 1, the virtual page is locked in physical memory. + */ + public boolean Locked() { + return getBitFieldValue(1, 6 + 1 + 11 + 3 + 1) == 1; + } + + /** + * If this bit is 1, the page is a large page. + */ + public boolean LargePage() { + return getBitFieldValue(1, 1 + 6 + 1 + 11 + 3 + 1) == 1; + } + + /** + * If this bit is 1, the page is has been reported as bad. + */ + public boolean Bad() { + return getBitFieldValue(1,1 + 1 + 1 + 6 + 1 + 11 + 3 + 1) == 1; + } + + /** + * Returns innerValue after shifting the value rightShiftAmount, and applying a Bit Mask of size maskLength. Example,
+ * innerValue = 0011
getBitFieldValue(2, 1) = 0011 >> 1 & 11 = 01 + * @param maskLength Size of the Bit Mask + * @param rightShiftAmount Amount to Shift innerValue to the right by + * @return innerValue with the mask and shift applied. + */ + private int getBitFieldValue(final int maskLength, final int rightShiftAmount) { + long bitMask = 0; + + for (int l = 0; l < maskLength; l++) { + bitMask |= 1 << l; + } + return (int) ((innerValue >>> rightShiftAmount) & bitMask); + } + } + + + @FieldOrder({"VirtualAddress", "VirtualAttributes"}) + class PSAPI_WORKING_SET_EX_INFORMATION extends Structure { + public Pointer VirtualAddress; + public PSAPI_WORKING_SET_EX_BLOCK VirtualAttributes; + } } diff --git a/contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java b/contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java index 7af0c9b275..a617d1db7f 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java @@ -24,6 +24,8 @@ package com.sun.jna.platform.win32; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertFalse; import java.util.LinkedList; import java.util.List; @@ -34,12 +36,15 @@ import com.sun.jna.Memory; import com.sun.jna.Native; +import com.sun.jna.Pointer; import com.sun.jna.platform.win32.Psapi.MODULEINFO; import com.sun.jna.platform.win32.Psapi.PERFORMANCE_INFORMATION; import com.sun.jna.platform.win32.WinDef.DWORD; import com.sun.jna.platform.win32.WinDef.HMODULE; import com.sun.jna.platform.win32.WinDef.HWND; import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.platform.win32.WinNT.MEMORY_BASIC_INFORMATION; +import com.sun.jna.platform.win32.BaseTSD.SIZE_T; import com.sun.jna.ptr.IntByReference; /** @@ -273,4 +278,42 @@ public void testEnumProcesses() { } assertTrue("List should contain my pid", foundMyPid); } + + @Test + public void testQueryWorkingSetEx() { + Win32Exception we = null; + HANDLE selfHandle = Kernel32.INSTANCE.GetCurrentProcess(); + MEMORY_BASIC_INFORMATION mbi = new MEMORY_BASIC_INFORMATION(); + try { + SIZE_T bytesRead = Kernel32.INSTANCE.VirtualQueryEx(selfHandle, Pointer.NULL, mbi, new SIZE_T(mbi.size())); + assertNotEquals("Kernel should be able to read this Process' Bytes", bytesRead.intValue(), 0); + Psapi.PSAPI_WORKING_SET_EX_INFORMATION pswsi = new Psapi.PSAPI_WORKING_SET_EX_INFORMATION(); + pswsi.VirtualAddress = mbi.baseAddress; + if (!Psapi.INSTANCE.QueryWorkingSetEx(selfHandle, pswsi.VirtualAddress, pswsi.size())) { + throw new Win32Exception(Native.getLastError()); + } + assertTrue("Virual Attributes should not be null", pswsi.VirtualAttributes != null); + if (Psapi.INSTANCE.QueryWorkingSetEx(new HANDLE(), pswsi.VirtualAddress, pswsi.size())) { + throw new Win32Exception(Native.getLastError()); + } + assertFalse("This line should never be called", true); + } catch (Win32Exception e) { + we = e; + throw we; // re-throw to invoke finally block + } finally { + try { + Kernel32Util.closeHandle(selfHandle); + } catch (Win32Exception e) { + if (we == null) { + we = e; + } else { + we.addSuppressedReflected(e); + } + } + if (we != null) { + throw we; + } + } + } } + From dbf8751cd71c63f71cfe94db9a7cd1728063abc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Thu, 11 Aug 2022 18:14:34 +0200 Subject: [PATCH 2/4] Drop definition of PSAPI_WORKING_SET_EX_BLOCK, move functions to PSAPI_WORKING_SET_EX_INFORMATION, adjust function names The definition of PSAPI_WORKING_SET_EX_BLOCK was not correct. The outer definition is a UNION with two members: - ULONG_PTR Flags and - another anonymous union holding two bitfield definitions It effectively defines a union with 3 defintions. The bitfield definition differs between 32bit and 64bit. The 32bit version defines data elements filling exactly the 32bits a single ULONG_PTR can hold. The 64bit version adds an additional 32bit reserved element, which agains fill the full size a single ULONG_PTR can hold on 64bit. The two bitfield definitions both map to ULONG_PTR, so PSAPI_WORKING_SET_EX_BLOCK is basicly just a ULONG_PTR with C syntactic sugar for decoding. The PSAPI_WORKING_SET_EX_BLOCK is only used in PSAPI_WORKING_SET_EX_INFORMATION and given that the union definition of PSAPI_WORKING_SET_EX_BLOCK is syntactic sugar, we can drop the definition and integrate the decoding logic directly into PSAPI_WORKING_SET_EX_INFORMATION. ----------------------------------------------------------------------- typedef struct _PSAPI_WORKING_SET_EX_INFORMATION { PVOID VirtualAddress; PSAPI_WORKING_SET_EX_BLOCK VirtualAttributes; } PSAPI_WORKING_SET_EX_INFORMATION, *PPSAPI_WORKING_SET_EX_INFORMATION; typedef union _PSAPI_WORKING_SET_EX_BLOCK { ULONG_PTR Flags; union { struct { ULONG_PTR Valid : 1; ULONG_PTR ShareCount : 3; ULONG_PTR Win32Protection : 11; ULONG_PTR Shared : 1; ULONG_PTR Node : 6; ULONG_PTR Locked : 1; ULONG_PTR LargePage : 1; ULONG_PTR Reserved : 7; ULONG_PTR Bad : 1; #if defined(_WIN64) ULONG_PTR ReservedUlong : 32; #endif }; struct { ULONG_PTR Valid : 1; // Valid = 0 in this format. ULONG_PTR Reserved0 : 14; ULONG_PTR Shared : 1; ULONG_PTR Reserved1 : 15; ULONG_PTR Bad : 1; #if defined(_WIN64) ULONG_PTR ReservedUlong : 32; #endif } Invalid; }; } PSAPI_WORKING_SET_EX_BLOCK, *PPSAPI_WORKING_SET_EX_BLOCK; --- .../src/com/sun/jna/platform/win32/Psapi.java | 59 +++++++++---------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Psapi.java b/contrib/platform/src/com/sun/jna/platform/win32/Psapi.java index 4bfc4483a4..e5b99d4bbc 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Psapi.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Psapi.java @@ -335,78 +335,80 @@ class PERFORMANCE_INFORMATION extends Structure { public DWORD ThreadCount; } - @FieldOrder({"Flags", "Data"}) - class PSAPI_WORKING_SET_EX_BLOCK extends Structure implements ByReference { - public ULONG_PTR Flags; - public ULONG_PTR[] Data = new ULONG_PTR[Native.POINTER_SIZE == 8 ? 1 : 2]; - private long innerValue; + @FieldOrder({"VirtualAddress", "VirtualAttributes"}) + class PSAPI_WORKING_SET_EX_INFORMATION extends Structure { - @Override - public void read() { - super.read(); - innerValue = this.Data[0].longValue(); - } + public Pointer VirtualAddress; + public ULONG_PTR VirtualAttributes; /** - * If this bit is 1, the subsequent members are valid; otherwise they should be ignored. + * If this bit is 1, the subsequent members are valid; otherwise they + * should be ignored. */ - public boolean Valid() { + public boolean isValid() { return getBitFieldValue(1, 0) == 1; } /** - * The number of processes that share this page. The maximum value of this member is 7. + * The number of processes that share this page. The maximum value of + * this member is 7. */ - public int ShareCount() { + public int getShareCount() { return getBitFieldValue(3, 1); } /** - * The memory protection attributes of the page. For a list of values see below. - * @see Memory Protection Constants. + * The memory protection attributes of the page. For a list of values + * see below. + * + * @see + * Memory + * Protection Constants. */ - public int Win32Protection() { + public int getWin32Protection() { return getBitFieldValue(11, 3 + 1); } /** * If this bit is 1, the page can be shared. */ - public boolean Shared() { + public boolean isShared() { return getBitFieldValue(1, 11 + 3 + 1) == 1; } /** * The NUMA node. The maximum value of this member is 63. */ - public int Node() { + public int getNode() { return getBitFieldValue(6, 1 + 11 + 3 + 1); } /** * If this bit is 1, the virtual page is locked in physical memory. */ - public boolean Locked() { + public boolean isLocked() { return getBitFieldValue(1, 6 + 1 + 11 + 3 + 1) == 1; } /** * If this bit is 1, the page is a large page. */ - public boolean LargePage() { + public boolean isLargePage() { return getBitFieldValue(1, 1 + 6 + 1 + 11 + 3 + 1) == 1; } /** * If this bit is 1, the page is has been reported as bad. */ - public boolean Bad() { - return getBitFieldValue(1,1 + 1 + 1 + 6 + 1 + 11 + 3 + 1) == 1; + public boolean isBad() { + return getBitFieldValue(1, 1 + 1 + 1 + 6 + 1 + 11 + 3 + 1) == 1; } /** - * Returns innerValue after shifting the value rightShiftAmount, and applying a Bit Mask of size maskLength. Example,
+ * Returns innerValue after shifting the value rightShiftAmount, and + * applying a Bit Mask of size maskLength. Example,
* innerValue = 0011
getBitFieldValue(2, 1) = 0011 >> 1 & 11 = 01 + * * @param maskLength Size of the Bit Mask * @param rightShiftAmount Amount to Shift innerValue to the right by * @return innerValue with the mask and shift applied. @@ -417,14 +419,7 @@ private int getBitFieldValue(final int maskLength, final int rightShiftAmount) { for (int l = 0; l < maskLength; l++) { bitMask |= 1 << l; } - return (int) ((innerValue >>> rightShiftAmount) & bitMask); + return (int) ((VirtualAttributes.longValue() >>> rightShiftAmount) & bitMask); } } - - - @FieldOrder({"VirtualAddress", "VirtualAttributes"}) - class PSAPI_WORKING_SET_EX_INFORMATION extends Structure { - public Pointer VirtualAddress; - public PSAPI_WORKING_SET_EX_BLOCK VirtualAttributes; - } } From 04ae48f997dbc7b76e2024aa79ff43d36083a051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Thu, 11 Aug 2022 20:49:28 +0200 Subject: [PATCH 3/4] Bind VirtualLock and VirtualUnlock in c.s.j.p.win32.Kernel32 --- CHANGES.md | 3 +- .../com/sun/jna/platform/win32/Kernel32.java | 80 +++++++++++++++++++ .../sun/jna/platform/win32/Kernel32Test.java | 11 +++ 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index ce984a541d..041326a905 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,7 +7,8 @@ Next Release (5.13.0) Features -------- -* [#1454](https://github.com/java-native-access/jna/pull/1454): Add `c.s.j.p.win32.Psapi.QueryWorkingSetEx` and associated Types = [@crain-32](https://github.com/Crain-32). +* [#1454](https://github.com/java-native-access/jna/pull/1454): Add `c.s.j.p.win32.Psapi.QueryWorkingSetEx` and associated Types - [@crain-32](https://github.com/Crain-32). +* [#1459](https://github.com/java-native-access/jna/pull/1459): Add `VirtualLock` and `VirtualUnlock` in `c.s.j.p.win32.Kernel32` - [@matthiasblaesing](https://github.com/matthiasblaesing). Bug Fixes --------- diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java index 947beb396e..14a1bd772b 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java @@ -4307,4 +4307,84 @@ Pointer VirtualAllocEx(HANDLE hProcess, Pointer lpAddress, SIZE_T dwSize, * @see MSDN Entry */ HRESULT GetApplicationRestartSettings(HANDLE hProcess, char[] pwzCommandline, IntByReference pcchSize, IntByReference pdwFlags); + + /** + * Locks the specified region of the process's virtual address space into + * physical memory, ensuring that subsequent access to the region will not + * incur a page fault. + * + * @param lpAddress A pointer to the base address of the region of pages to + * be locked. + * @param dwSize The size of the region to be locked, in bytes. The + * region of affected pages includes all pages that contain + * one or more bytes in the range from the lpAddress + * parameter to (lpAddress+dwSize). This means that a + * 2-byte range straddling a page boundary causes both + * pages to be locked. + * + * @return {@code true} if locking succeeded. + * + *

+ * All pages in the specified region must be committed. Memory protected + * with {@code PAGE_NOACCESS} cannot be locked. + *

+ *

+ * Locking pages into memory may degrade the performance of the system by + * reducing the available RAM and forcing the system to swap out other + * critical pages to the paging file. Each version of Windows has a limit on + * the maximum number of pages a process can lock. This limit is + * intentionally small to avoid severe performance degradation. Applications + * that need to lock larger numbers of pages must first call the + * SetProcessWorkingSetSize function to increase their minimum and maximum + * working set sizes. The maximum number of pages that a process can lock is + * equal to the number of pages in its minimum working set minus a small + * overhead. + *

+ *

+ * Pages that a process has locked remain in physical memory until the + * process unlocks them or terminates. These pages are guaranteed not to be + * written to the pagefile while they are locked. + *

+ *

+ * To unlock a region of locked pages, use the VirtualUnlock function. + * Locked pages are automatically unlocked when the process terminates. + *

+ *

+ * This function is not like the GlobalLock or LocalLock function in that it + * does not increment a lock count and translate a handle into a pointer. + * There is no lock count for virtual pages, so multiple calls to the + * VirtualUnlock function are never required to unlock a region of pages. + *

+ */ + boolean VirtualLock(Pointer lpAddress, SIZE_T dwSize); + + /** + * Unlocks a specified range of pages in the virtual address space of a + * process, enabling the system to swap the pages out to the paging file if + * necessary. + * + * @param lpAddress A pointer to the base address of the region of pages to + * be unlocked. + * @param dwSize The size of the region being unlocked, in bytes. The + * region of affected pages includes all pages containing + * one or more bytes in the range from the lpAddress + * parameter to (lpAddress+dwSize). This means that a + * 2-byte range straddling a page boundary causes both + * pages to be unlocked. + * + * @return {@code true} if unlocking succeeded. + * + *

+ * For the function to succeed, the range specified need not match a range + * passed to a previous call to the VirtualLock function, but all pages in + * the range must be locked. If any of the pages in the specified range are + * not locked, VirtualUnlock removes such pages from the working set, sets + * last error to ERROR_NOT_LOCKED, and returns FALSE. + *

+ *

+ * Calling VirtualUnlock on a range of memory that is not locked releases + * the pages from the process's working set. + *

+ */ + boolean VirtualUnlock(Pointer lpAddress, SIZE_T dwSize); } diff --git a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java index 6eedbcb8a3..d964c70dec 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java @@ -2088,4 +2088,15 @@ public void testOpenFileMapping() throws IOException { } + public void testVirtualLockUnlock() { + Memory mem = new Memory(4096); + // Test that locking works + assertTrue(Kernel32.INSTANCE.VirtualLock(mem, new SIZE_T(4096))); + // Test that unlocked region can be unlocked + assertTrue(Kernel32.INSTANCE.VirtualUnlock(mem, new SIZE_T(4096))); + // Locking a region we don't have access to should fail + assertFalse(Kernel32.INSTANCE.VirtualLock(null, new SIZE_T(4096))); + // Unlocking an unlocked region should fail + assertFalse(Kernel32.INSTANCE.VirtualUnlock(mem, new SIZE_T(4096))); + } } From d5258eac5b5d5a8f11203a2ebde488ca9bf5a6ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Thu, 11 Aug 2022 21:06:52 +0200 Subject: [PATCH 4/4] Improve/fix unittest for Psapi#QueryWorkingSetEx --- .../com/sun/jna/platform/win32/PsapiTest.java | 112 ++++++++++++++---- 1 file changed, 90 insertions(+), 22 deletions(-) diff --git a/contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java b/contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java index a617d1db7f..4b7613d3b6 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertEquals; import java.util.LinkedList; import java.util.List; @@ -45,6 +46,7 @@ import com.sun.jna.platform.win32.WinNT.HANDLE; import com.sun.jna.platform.win32.WinNT.MEMORY_BASIC_INFORMATION; import com.sun.jna.platform.win32.BaseTSD.SIZE_T; +import com.sun.jna.platform.win32.Psapi.PSAPI_WORKING_SET_EX_INFORMATION; import com.sun.jna.ptr.IntByReference; /** @@ -280,38 +282,104 @@ public void testEnumProcesses() { } @Test + @SuppressWarnings("ResultOfObjectAllocationIgnored") public void testQueryWorkingSetEx() { - Win32Exception we = null; HANDLE selfHandle = Kernel32.INSTANCE.GetCurrentProcess(); - MEMORY_BASIC_INFORMATION mbi = new MEMORY_BASIC_INFORMATION(); + + Memory[] mem = new Memory[4]; + mem[0] = new Memory(4096); + new Memory(8192); // Try to ensure the memory pages are not adjacent + mem[1] = new Memory(4096); + new Memory(8192); // Try to ensure the memory pages are not adjacent + mem[3] = new Memory(4096); + try { - SIZE_T bytesRead = Kernel32.INSTANCE.VirtualQueryEx(selfHandle, Pointer.NULL, mbi, new SIZE_T(mbi.size())); - assertNotEquals("Kernel should be able to read this Process' Bytes", bytesRead.intValue(), 0); - Psapi.PSAPI_WORKING_SET_EX_INFORMATION pswsi = new Psapi.PSAPI_WORKING_SET_EX_INFORMATION(); - pswsi.VirtualAddress = mbi.baseAddress; - if (!Psapi.INSTANCE.QueryWorkingSetEx(selfHandle, pswsi.VirtualAddress, pswsi.size())) { - throw new Win32Exception(Native.getLastError()); + PSAPI_WORKING_SET_EX_INFORMATION[] pswsi = (PSAPI_WORKING_SET_EX_INFORMATION[]) new PSAPI_WORKING_SET_EX_INFORMATION().toArray(4); + + pswsi[0].VirtualAddress = mem[0]; + pswsi[1].VirtualAddress = mem[1]; + pswsi[2].VirtualAddress = mem[2]; + pswsi[3].VirtualAddress = mem[3]; + + for(int i = 0; i < pswsi.length; i++) { + pswsi[i].write(); } - assertTrue("Virual Attributes should not be null", pswsi.VirtualAttributes != null); - if (Psapi.INSTANCE.QueryWorkingSetEx(new HANDLE(), pswsi.VirtualAddress, pswsi.size())) { - throw new Win32Exception(Native.getLastError()); + + assertTrue("Failed to invoke QueryWorkingSetEx (1)", Psapi.INSTANCE.QueryWorkingSetEx(selfHandle, pswsi[0].getPointer(), pswsi[0].size() * pswsi.length)); + + for (int i = 0; i < pswsi.length; i++) { + pswsi[i].read(); + assertTrue("Virtual Attributes should not be null (1)", pswsi[i].VirtualAttributes != null); + assertEquals("Virtual Address should not change before and after call (1)", pswsi[i].VirtualAddress, mem[i]); + if (i != 2) { + assertTrue("Data was invalid (1)", pswsi[i].isValid()); + assertFalse("Data was reported as bad (1)", pswsi[i].isBad()); + assertEquals("Data indicates sharing (1)", pswsi[i].getShareCount(), 0); + assertEquals("Data indicated that protection does not match PAGE_READWRITE (1)", + pswsi[i].getWin32Protection(), WinNT.PAGE_READWRITE); + assertFalse("Data was reported as shared (1)", pswsi[i].isShared()); + assertFalse("Data was reported as locked (1)", pswsi[i].isLocked()); + assertFalse("Data was reported as large pages (1)", pswsi[i].isLargePage()); + } else { + assertFalse("Data was reported valid, but expected to be invalid (1)", pswsi[i].isValid()); + } } - assertFalse("This line should never be called", true); - } catch (Win32Exception e) { - we = e; - throw we; // re-throw to invoke finally block + + // Lock the page we used into memory - this should be reflected in the reported flags in the next call + assertTrue(Kernel32.INSTANCE.VirtualLock(mem[1], new SIZE_T(4096))); + + for (int i = 0; i < pswsi.length; i++) { + pswsi[i].write(); + } + + assertTrue("Failed to invoke QueryWorkingSetEx (2)", Psapi.INSTANCE.QueryWorkingSetEx(selfHandle, pswsi[0].getPointer(), pswsi[0].size() * pswsi.length)); + + for (int i = 0; i < pswsi.length; i++) { + pswsi[i].read(); + assertTrue("Virtual Attributes should not be null (2)", pswsi[i].VirtualAttributes != null); + assertEquals("Virtual Address should not change before and after call (2)", pswsi[i].VirtualAddress, mem[i]); + if (i != 2) { + assertTrue("Virtual Attributes should not be null (2)", pswsi[i].VirtualAttributes != null); + assertEquals("Virtual Address should not change before and after call (2)", pswsi[i].VirtualAddress, mem[i]); + assertTrue("Data was invalid (2)", pswsi[i].isValid()); + assertFalse("Data was reported as bad (2)", pswsi[i].isBad()); + assertEquals("Data indicates sharing (2)", pswsi[i].getShareCount(), 0); + assertEquals("Data indicated that protection does not match PAGE_READWRITE (2)", + pswsi[i].getWin32Protection(), WinNT.PAGE_READWRITE); + assertFalse("Data was reported as shared (2)", pswsi[i].isShared()); + // Only the second page should be locked + if( i == 1 ) { + assertTrue("Data was reported as unlocked (2)", pswsi[i].isLocked()); + } else { + assertFalse("Data was reported as locked (2)", pswsi[i].isLocked()); + } + assertFalse("Data was reported as large pages (2)", pswsi[i].isLargePage()); + } else { + assertFalse("Data was reported valid, but expected to be invalid (2)", pswsi[i].isValid()); + } + } + + // Check that a query against an invalid target succeeds, but report + // invalid data + PSAPI_WORKING_SET_EX_INFORMATION pswsi2 = new PSAPI_WORKING_SET_EX_INFORMATION(); + pswsi2.VirtualAddress = null; + pswsi2.write(); + assertTrue("Failed to invoke QueryWorkingSetEx (3)", Psapi.INSTANCE.QueryWorkingSetEx(WinBase.INVALID_HANDLE_VALUE, pswsi2.getPointer(), pswsi2.size())); + pswsi2.read(); + + assertTrue("Virtual Attributes should not be null (3)", pswsi2.VirtualAttributes != null); + assertTrue("Virtual Address should not change before and after call (3)", pswsi2.VirtualAddress == null); + assertFalse("Data was reported valid, but expected to be invalid (3)", pswsi2.isValid()); } finally { try { Kernel32Util.closeHandle(selfHandle); } catch (Win32Exception e) { - if (we == null) { - we = e; - } else { - we.addSuppressedReflected(e); - } + // Ignore } - if (we != null) { - throw we; + try { + Kernel32.INSTANCE.VirtualUnlock(mem[1], new SIZE_T(4096)); + } catch (Win32Exception e) { + // Ignore } } }