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

Add c.s.j.win32.Psapi.QueryWorkingSetEx, c.s.j.win32.Kernel32.VirtualLock, c.s.j.win32.Kernel32.VirtualUnlock #1459

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +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).
* [#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
---------
Expand Down
80 changes: 80 additions & 0 deletions contrib/platform/src/com/sun/jna/platform/win32/Kernel32.java
Original file line number Diff line number Diff line change
Expand Up @@ -4307,4 +4307,84 @@ Pointer VirtualAllocEx(HANDLE hProcess, Pointer lpAddress, SIZE_T dwSize,
* @see <A HREF="https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getapplicationrestartsettings">MSDN Entry</A>
*/
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.
*
* <p>
* All pages in the specified region must be committed. Memory protected
* with {@code PAGE_NOACCESS} cannot be locked.
* </p>
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* To unlock a region of locked pages, use the VirtualUnlock function.
* Locked pages are automatically unlocked when the process terminates.
* </p>
* <p>
* 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.
* </p>
*/
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.
*
* <p>
* 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.
* </p>
* <p>
* Calling VirtualUnlock on a range of memory that is not locked releases
* the pages from the process's working set.
* </p>
*/
boolean VirtualUnlock(Pointer lpAddress, SIZE_T dwSize);
}
102 changes: 102 additions & 0 deletions contrib/platform/src/com/sun/jna/platform/win32/Psapi.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 <a href="https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-queryworkingsetex">MSDN</a>
*/
boolean QueryWorkingSetEx(HANDLE hProcess, Pointer pv, int cb);

@FieldOrder({"lpBaseOfDll", "SizeOfImage", "EntryPoint"})
class MODULEINFO extends Structure {
public Pointer EntryPoint;
Expand Down Expand Up @@ -320,4 +334,92 @@ class PERFORMANCE_INFORMATION extends Structure {
public DWORD ProcessCount;
public DWORD ThreadCount;
}

@FieldOrder({"VirtualAddress", "VirtualAttributes"})
class PSAPI_WORKING_SET_EX_INFORMATION extends Structure {

public Pointer VirtualAddress;
public ULONG_PTR VirtualAttributes;

/**
* If this bit is 1, the subsequent members are valid; otherwise they
* should be ignored.
*/
public boolean isValid() {
return getBitFieldValue(1, 0) == 1;
}

/**
* The number of processes that share this page. The maximum value of
* this member is 7.
*/
public int getShareCount() {
return getBitFieldValue(3, 1);
}

/**
* The memory protection attributes of the page. For a list of values
* see below.
*
* @see
* <a href="https://docs.microsoft.com/en-us/windows/desktop/Memory/memory-protection-constants">Memory
* Protection Constants</a>.
*/
public int getWin32Protection() {
return getBitFieldValue(11, 3 + 1);
}

/**
* If this bit is 1, the page can be shared.
*/
public boolean isShared() {
return getBitFieldValue(1, 11 + 3 + 1) == 1;
}

/**
* The NUMA node. The maximum value of this member is 63.
*/
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 isLocked() {
return getBitFieldValue(1, 6 + 1 + 11 + 3 + 1) == 1;
}

/**
* If this bit is 1, the page is a large page.
*/
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 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, <br/>
* innerValue = 0011<br/> 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) ((VirtualAttributes.longValue() >>> rightShiftAmount) & bitMask);
}
}
}
11 changes: 11 additions & 0 deletions contrib/platform/test/com/sun/jna/platform/win32/Kernel32Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}
}
111 changes: 111 additions & 0 deletions contrib/platform/test/com/sun/jna/platform/win32/PsapiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
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 static org.junit.Assert.assertEquals;

import java.util.LinkedList;
import java.util.List;
Expand All @@ -34,12 +37,16 @@

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.platform.win32.Psapi.PSAPI_WORKING_SET_EX_INFORMATION;
import com.sun.jna.ptr.IntByReference;

/**
Expand Down Expand Up @@ -273,4 +280,108 @@ public void testEnumProcesses() {
}
assertTrue("List should contain my pid", foundMyPid);
}

@Test
@SuppressWarnings("ResultOfObjectAllocationIgnored")
public void testQueryWorkingSetEx() {
HANDLE selfHandle = Kernel32.INSTANCE.GetCurrentProcess();

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 {
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("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());
}
}

// 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) {
// Ignore
}
try {
Kernel32.INSTANCE.VirtualUnlock(mem[1], new SIZE_T(4096));
} catch (Win32Exception e) {
// Ignore
}
}
}
}