blob: eb01c3fdc905c98c79c0b9bebe250f519021ea7f [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2013 Google, Inc. All rights reserved.
* **********************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Google, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
/* kernel32.dll and kernelbase.dll memory-related redirection routines */
#include "kernel32_redir.h" /* must be included first */
#include "../../globals.h"
#include "ntdll_redir.h"
#include "../ntdll.h"
#include "drwinapi_private.h"
#include "../os_private.h"
/* Value used to encrypt pointers. Xor with a per-process magic value
* seems plenty secure enough for private libs (this isn't affecting
* the app).
*/
static ptr_uint_t magic_val;
/* Lock for the Local* routines. We can't use redirect_RtlLockHeap
* to mirror the real kernel32 b/c that's a nop.
*/
DECLARE_CXTSWPROT_VAR(static mutex_t localheap_lock,
INIT_LOCK_FREE(drwinapi_localheap_lock));
void
kernel32_redir_init_mem(void)
{
magic_val = (ptr_uint_t) get_random_offset(POINTER_MAX);
}
void
kernel32_redir_exit_mem(void)
{
DELETE_LOCK(localheap_lock);
}
PVOID
WINAPI
redirect_DecodePointer(
__in_opt PVOID Ptr
)
{
return (PVOID) ((ptr_uint_t)Ptr ^ magic_val);
}
PVOID
WINAPI
redirect_EncodePointer (
__in_opt PVOID Ptr
)
{
return (PVOID) ((ptr_uint_t)Ptr ^ magic_val);
}
HANDLE
WINAPI
redirect_GetProcessHeap(VOID)
{
#ifdef CLIENT_INTERFACE
/* XXX: perhaps all of these redirection routines should be ifdef CLIENT_INTERFACE.
* The loader itself is not, for use w/ hotpatching, etc.
*/
return get_private_peb()->ProcessHeap;
#else
return get_peb(NT_CURRENT_PROCESS)->ProcessHeap;
#endif
}
__bcount(dwBytes)
LPVOID
WINAPI
redirect_HeapAlloc(
__in HANDLE hHeap,
__in DWORD dwFlags,
__in SIZE_T dwBytes
)
{
return redirect_RtlAllocateHeap(hHeap, dwFlags, dwBytes);
}
SIZE_T
WINAPI
redirect_HeapCompact(
__in HANDLE hHeap,
__in DWORD dwFlags
)
{
/* We do not support compacting/coalescing here so we just return
* a reasonably large size for the "largest committed free block".
* We don't bother checking hHeap and forwarding: won't affect
* correctness as the app can't rely on this value.
*/
return 8*1024;
}
HANDLE
WINAPI
redirect_HeapCreate(
__in DWORD flOptions,
__in SIZE_T dwInitialSize,
__in SIZE_T dwMaximumSize
)
{
return redirect_RtlCreateHeap(flOptions | HEAP_CLASS_PRIVATE |
(dwMaximumSize == 0 ? HEAP_GROWABLE : 0),
NULL, dwMaximumSize, dwInitialSize, NULL, NULL);
}
BOOL
WINAPI
redirect_HeapDestroy(
__in HANDLE hHeap
)
{
return redirect_RtlDestroyHeap(hHeap);
}
BOOL
WINAPI
redirect_HeapFree(
__inout HANDLE hHeap,
__in DWORD dwFlags,
__deref LPVOID lpMem
)
{
return redirect_RtlFreeHeap(hHeap, dwFlags, lpMem);
}
__bcount(dwBytes)
LPVOID
WINAPI
redirect_HeapReAlloc(
__inout HANDLE hHeap,
__in DWORD dwFlags,
__deref LPVOID lpMem,
__in SIZE_T dwBytes
)
{
return redirect_RtlReAllocateHeap(hHeap, dwFlags, lpMem, dwBytes);
}
BOOL
WINAPI
redirect_HeapSetInformation (
__in_opt HANDLE HeapHandle,
__in HEAP_INFORMATION_CLASS HeapInformationClass,
__in_bcount_opt(HeapInformationLength) PVOID HeapInformation,
__in SIZE_T HeapInformationLength
)
{
if (HeapInformationClass == HeapCompatibilityInformation) {
if (HeapInformationLength != sizeof(ULONG) || HeapInformation == NULL)
return FALSE;
/* We just ignore LFH requests */
return TRUE;
} else if (HeapInformationClass == HeapEnableTerminationOnCorruption) {
if (HeapInformationLength != 0 || HeapInformation != NULL)
return FALSE;
/* We just ignore */
return TRUE;
}
return FALSE;
}
SIZE_T
WINAPI
redirect_HeapSize(
__in HANDLE hHeap,
__in DWORD dwFlags,
__in LPCVOID lpMem
)
{
return redirect_RtlSizeHeap(hHeap, dwFlags, (PVOID) lpMem);
}
BOOL
WINAPI
redirect_HeapValidate(
__in HANDLE hHeap,
__in DWORD dwFlags,
__in_opt LPCVOID lpMem
)
{
return redirect_RtlValidateHeap(hHeap, dwFlags, (PVOID) lpMem);
}
BOOL
WINAPI
redirect_HeapWalk(
__in HANDLE hHeap,
__inout LPPROCESS_HEAP_ENTRY lpEntry
)
{
/* XXX: what msvcrt routine really depends on this? Should be
* used primarily for debugging, right?
*/
set_last_error(ERROR_NO_MORE_ITEMS);
return FALSE;
}
/***************************************************************************
* Local heap
*
* Although our target of {msvcp*,msvcr*,dbghelp} only uses Local{Alloc,Free}
* we must implement the full set in case a privlib calls another routine.
*
* We use a custom header and we synchronize with localheap_lock. We
* return a pointer beyond the header to support LMEM_FIXED where
* handle==pointer and local_header_t.alloc==NULL. For LMEM_MOVEABLE,
* we start out with an inlined alloc, but if it's resized we store
* the separate alloc in local_header_t.alloc. We use a header on the
* separate alloc (with flags==LMEM_INVALID_HANDLE to distinguish, and
* with alloc==original header) so we can map back to the handle for
* LocalHandle. On LocalDiscard() we keep a 0-sized alloc pointed at
* by local_header_t.alloc.
*/
typedef struct _local_header_t {
ushort lock_count;
ushort flags;
struct _local_header_t *alloc;
} local_header_t;
static inline local_header_t *
local_header_from_handle(HLOCAL handle)
{
return ((local_header_t *)handle) - 1;
}
HLOCAL
WINAPI
redirect_LocalAlloc(
__in UINT uFlags,
__in SIZE_T uBytes
)
{
HLOCAL res = NULL;
local_header_t *hdr;
HANDLE heap = redirect_GetProcessHeap();
/* for back-compat, LocalAlloc asks for +x */
uint rtl_flags = HEAP_NO_SERIALIZE | HEAP_CREATE_ENABLE_EXECUTE;
if (TEST(LMEM_ZEROINIT, uFlags))
rtl_flags |= HEAP_ZERO_MEMORY;
/* flags should be in ushort range */
if ((uFlags & 0xffff0000) != 0) {
set_last_error(ERROR_INVALID_PARAMETER);
return NULL;
}
/* No lock is needed as the lock is to synchronize w/ other Local* routines
* accessing the same object, and this object has not been returned yet.
*/
hdr = (local_header_t *)
redirect_RtlAllocateHeap(heap, rtl_flags, uBytes + sizeof(local_header_t));
if (hdr == NULL) {
set_last_error(ERROR_NOT_ENOUGH_MEMORY);
return NULL;
}
res = (HLOCAL) (hdr + 1); /* even for LMEM_MOVEABLE we return the usable mem */
hdr->lock_count = 0;
hdr->flags = (ushort) uFlags; /* we checked for truncation above */
hdr->alloc = NULL;
return res;
}
HLOCAL
WINAPI
redirect_LocalFree(
__deref HLOCAL hMem
)
{
HANDLE heap = redirect_GetProcessHeap();
local_header_t *hdr = local_header_from_handle(hMem);
mutex_lock(&localheap_lock);
/* XXX: supposed to raise debug msg + bp if freeing locked object */
if (hdr->alloc != NULL) {
ASSERT(TEST(LMEM_MOVEABLE, hdr->flags));
redirect_RtlFreeHeap(heap, HEAP_NO_SERIALIZE, hdr->alloc);
}
redirect_RtlFreeHeap(heap, HEAP_NO_SERIALIZE, hdr);
mutex_unlock(&localheap_lock);
return NULL;
}
HLOCAL
WINAPI
redirect_LocalReAlloc(
__in HLOCAL hMem,
__in SIZE_T uBytes,
__in UINT uFlags
)
{
HLOCAL res = NULL;
HANDLE heap = redirect_GetProcessHeap();
local_header_t *hdr = local_header_from_handle(hMem);
uint rtl_flags = HEAP_NO_SERIALIZE | HEAP_CREATE_ENABLE_EXECUTE;
if (TEST(LMEM_ZEROINIT, uFlags))
rtl_flags |= HEAP_ZERO_MEMORY;
mutex_lock(&localheap_lock);
if (TEST(LMEM_MODIFY, uFlags)) {
/* no realloc, just update flags */
if ((uFlags & 0xffff0000) != 0 || /* flags should be in ushort range */
/* we don't allow turning moveable w/ sep alloc into fixed */
(!TEST(LMEM_MOVEABLE, uFlags) && hdr->alloc != NULL)) {
set_last_error(ERROR_INVALID_PARAMETER);
return NULL;
}
hdr->flags = (ushort) uFlags;
res = hMem;
} else {
/* if fixed or locked and LMEM_MOVEABLE is not specified, must realloc in-place */
if (!TEST(LMEM_MOVEABLE, uFlags) &&
(!TEST(LMEM_MOVEABLE, hdr->flags) || hdr->lock_count > 0))
rtl_flags |= HEAP_REALLOC_IN_PLACE_ONLY;
else if (TEST(LMEM_MOVEABLE, hdr->flags) && hdr->alloc == NULL) {
size_t copy_sz = redirect_RtlSizeHeap(heap, 0, (byte *) hdr) -
sizeof(local_header_t);
copy_sz = MIN(copy_sz, uBytes);
hdr->alloc = redirect_RtlAllocateHeap(heap, rtl_flags,
uBytes + sizeof(local_header_t));
if (hdr->alloc == NULL) {
set_last_error(ERROR_NOT_ENOUGH_MEMORY);
return NULL;
}
hdr->alloc->flags = LMEM_INVALID_HANDLE; /* mark as sep alloc */
hdr->alloc->alloc = hdr; /* backpointer */
memcpy(hdr->alloc + 1, hMem, copy_sz);
res = hMem;
}
if (res == NULL) {
local_header_t *newmem;
if (hdr->alloc != NULL) {
newmem = (local_header_t *)
redirect_RtlReAllocateHeap(heap, rtl_flags, hdr->alloc,
uBytes + sizeof(local_header_t));
if (newmem == NULL) {
set_last_error(ERROR_NOT_ENOUGH_MEMORY);
return NULL;
}
hdr->alloc = newmem;
ASSERT(hdr->alloc->flags == LMEM_INVALID_HANDLE);
ASSERT(hdr->alloc->alloc == hdr);
res = hMem;
} else {
newmem = (local_header_t *)
redirect_RtlReAllocateHeap(heap, rtl_flags, hdr,
uBytes + sizeof(local_header_t));
if (newmem == NULL) {
set_last_error(ERROR_NOT_ENOUGH_MEMORY);
return NULL;
}
res = (HLOCAL) (newmem + 1);
}
}
}
mutex_unlock(&localheap_lock);
return res;
}
LPVOID
WINAPI
redirect_LocalLock(
__in HLOCAL hMem
)
{
LPVOID res = NULL;
local_header_t *hdr = local_header_from_handle(hMem);
mutex_lock(&localheap_lock);
if (TEST(LMEM_MOVEABLE, hdr->flags))
hdr->lock_count++;
if (hdr->alloc != NULL)
res = (LPVOID) (hdr->alloc + 1);
else
res = (LPVOID) hMem;
mutex_unlock(&localheap_lock);
return res;
}
HLOCAL
WINAPI
redirect_LocalHandle(
__in LPCVOID pMem
)
{
local_header_t *hdr = local_header_from_handle((PVOID)pMem);
if (TEST(LMEM_INVALID_HANDLE, hdr->flags)) {
/* separate alloc stores the original header */
hdr = hdr->alloc;
}
return (HLOCAL) (hdr + 1);
}
BOOL
WINAPI
redirect_LocalUnlock(
__in HLOCAL hMem
)
{
BOOL res = FALSE;
local_header_t *hdr = local_header_from_handle(hMem);
mutex_lock(&localheap_lock);
if (hdr->lock_count == 0) {
res = FALSE;
set_last_error(ERROR_NOT_LOCKED);
} else {
hdr->lock_count--;
if (hdr->lock_count == 0) {
res = FALSE;
set_last_error(NO_ERROR);
} else
res = TRUE;
}
mutex_unlock(&localheap_lock);
return res;
}
SIZE_T
WINAPI
redirect_LocalSize(
__in HLOCAL hMem
)
{
SIZE_T res = 0;
local_header_t *hdr = local_header_from_handle(hMem);
HANDLE heap = redirect_GetProcessHeap();
mutex_lock(&localheap_lock);
if (hdr->alloc != NULL) {
ASSERT(TEST(LMEM_MOVEABLE, hdr->flags));
res = redirect_RtlSizeHeap(heap, 0, (byte *) hdr->alloc);
} else
res = redirect_RtlSizeHeap(heap, 0, (byte *) hdr);
if (res != 0) {
ASSERT(res >= sizeof(local_header_t));
res -= sizeof(local_header_t);
}
mutex_unlock(&localheap_lock);
return res;
}
UINT
WINAPI
redirect_LocalFlags(
__in HLOCAL hMem
)
{
UINT res = 0;
local_header_t *hdr = local_header_from_handle(hMem);
HANDLE heap = redirect_GetProcessHeap();
mutex_lock(&localheap_lock);
res |= (hdr->lock_count & LMEM_LOCKCOUNT);
if ((hdr->alloc != NULL &&
redirect_RtlSizeHeap(heap, 0, (byte *) hdr->alloc) == sizeof(local_header_t)) ||
(hdr->alloc == NULL &&
redirect_RtlSizeHeap(heap, 0, (byte *) hdr) == sizeof(local_header_t)))
res |= LMEM_DISCARDABLE;
mutex_unlock(&localheap_lock);
return res;
}
/***************************************************************************
* System calls
*/
BOOL
WINAPI
redirect_IsBadReadPtr(
__in_opt CONST VOID *lp,
__in UINT_PTR ucb
)
{
if (ucb == 0)
return FALSE;
return !is_readable_without_exception((const byte *)lp, ucb);
}
BOOL
WINAPI
redirect_ReadProcessMemory(
__in HANDLE hProcess,
__in LPCVOID lpBaseAddress,
__out_bcount_part(nSize, *lpNumberOfBytesRead) LPVOID lpBuffer,
__in SIZE_T nSize,
__out_opt SIZE_T * lpNumberOfBytesRead
)
{
size_t bytes_read;
NTSTATUS res = nt_raw_read_virtual_memory(hProcess, (void *) lpBaseAddress, lpBuffer,
nSize, &bytes_read);
if (!NT_SUCCESS(res)) {
set_last_error(ntstatus_to_last_error(res));
return FALSE;
}
if (lpNumberOfBytesRead != NULL)
*lpNumberOfBytesRead = bytes_read;
return TRUE;
}
__bcount(dwSize)
LPVOID
WINAPI
redirect_VirtualAlloc(
__in_opt LPVOID lpAddress,
__in SIZE_T dwSize,
__in DWORD flAllocationType,
__in DWORD flProtect
)
{
/* XXX: are MEM_* values beyond MEM_RESERVE and MEM_COMMIT passed to the kernel? */
PVOID base = lpAddress;
NTSTATUS res;
if (TEST(MEM_COMMIT, flAllocationType) &&
/* Any overlap when asking for MEM_RESERVE (even when combined w/ MEM_COMMIT)
* will fail anyway, so we only have to worry about overlap on plain MEM_COMMIT
*/
!TEST(MEM_RESERVE, flAllocationType) &&
lpAddress != NULL) {
/* i#1175: NtAllocateVirtualMemory can modify prot on existing pages */
if (!app_memory_pre_alloc(get_thread_private_dcontext(), lpAddress, dwSize,
osprot_to_memprot(flProtect), false)) {
set_last_error(ERROR_INVALID_ADDRESS);
return NULL;
}
}
res = nt_allocate_virtual_memory(&base, dwSize, flProtect, flAllocationType);
if (!NT_SUCCESS(res)) {
set_last_error(ntstatus_to_last_error(res));
return NULL;
}
if (NT_SUCCESS(res)) {
LOG(GLOBAL, LOG_LOADER, 2, "%s => "PFX"-"PFX"\n", __FUNCTION__,
lpAddress, (app_pc)lpAddress + dwSize);
}
return base;
}
BOOL
WINAPI
redirect_VirtualFree(
__in LPVOID lpAddress,
__in SIZE_T dwSize,
__in DWORD dwFreeType
)
{
NTSTATUS res;
if (TEST(MEM_DECOMMIT, dwFreeType))
res = nt_decommit_virtual_memory(lpAddress, dwSize);
else {
if (dwSize != 0) {
set_last_error(ERROR_INVALID_PARAMETER);
return FALSE;
}
res = nt_free_virtual_memory(lpAddress);
}
if (!NT_SUCCESS(res)) {
set_last_error(ntstatus_to_last_error(res));
return FALSE;
}
return TRUE;
}
BOOL
WINAPI
redirect_VirtualProtect(
__in LPVOID lpAddress,
__in SIZE_T dwSize,
__in DWORD flNewProtect,
__out PDWORD lpflOldProtect
)
{
#ifndef STANDALONE_UNIT_TEST
if (!dynamo_vm_area_overlap((byte *)lpAddress, ((byte *)lpAddress) + dwSize)) {
uint new_prot = osprot_to_memprot(flNewProtect);
uint mod_prot = osprot_to_memprot(new_prot);
uint old_prot;
uint res = app_memory_protection_change(get_thread_private_dcontext(),
lpAddress, dwSize, new_prot,
&mod_prot, &old_prot);
if (res == PRETEND_APP_MEM_PROT_CHANGE) {
if (lpflOldProtect != NULL)
*lpflOldProtect = memprot_to_osprot(old_prot);
return TRUE;
} else if (res == FAIL_APP_MEM_PROT_CHANGE)
return FALSE;
else if (res == SUBSET_APP_MEM_PROT_CHANGE)
flNewProtect = memprot_to_osprot(mod_prot);
else
ASSERT(res == DO_APP_MEM_PROT_CHANGE);
}
#endif
return (BOOL)
protect_virtual_memory(lpAddress, dwSize, flNewProtect, (uint *)lpflOldProtect);
}
SIZE_T
WINAPI
redirect_VirtualQuery(
__in_opt LPCVOID lpAddress,
__out_bcount_part(dwLength, return) PMEMORY_BASIC_INFORMATION lpBuffer,
__in SIZE_T dwLength
)
{
return redirect_VirtualQueryEx(NT_CURRENT_PROCESS, lpAddress, lpBuffer, dwLength);
}
SIZE_T
WINAPI
redirect_VirtualQueryEx(
__in HANDLE hProcess,
__in_opt LPCVOID lpAddress,
__out_bcount_part(dwLength, return) PMEMORY_BASIC_INFORMATION lpBuffer,
__in SIZE_T dwLength
)
{
size_t got;
app_pc page = (app_pc) PAGE_START(lpAddress);
NTSTATUS res = nt_remote_query_virtual_memory
(hProcess, page, lpBuffer, dwLength, &got);
if (!NT_SUCCESS(res)) {
set_last_error(ntstatus_to_last_error(res));
return 0;
}
return got;
}
#ifdef STANDALONE_UNIT_TEST
static void
test_heap(void)
{
HANDLE heap;
PVOID temp;
BOOL ok;
/* For now we're just ensuring we exercise these.
* XXX: add more corner cases.
*/
heap = redirect_HeapCreate(0, 0, 0);
EXPECT(heap != NULL, true);
temp = redirect_HeapAlloc(heap, 0, 32);
EXPECT(temp != NULL, true);
EXPECT(redirect_HeapSize(heap, 0, temp) >= 32, true);
EXPECT(redirect_HeapValidate(heap, 0, temp), true);
EXPECT(redirect_HeapCompact(heap, 0) > 0, true);
temp = redirect_HeapReAlloc(heap, 0, temp, 64);
EXPECT(temp != NULL, true);
EXPECT(redirect_IsBadReadPtr(temp, 64), false);
ok = redirect_HeapFree(heap, 0, temp);
EXPECT(ok, true);
ok = redirect_HeapDestroy(heap);
EXPECT(ok, true);
}
static void
test_local(void)
{
HLOCAL loc;
PVOID p;
BOOL ok;
/**************************************************/
/* test fixed */
loc = redirect_LocalAlloc(LMEM_ZEROINIT, 6);
EXPECT(*(int *)loc == 0, true);
EXPECT(redirect_LocalSize(loc), 6); /* *Size() returns requested, not padded */
loc = redirect_LocalReAlloc(loc, 26, LMEM_MOVEABLE|LMEM_ZEROINIT);
EXPECT(*(int *)loc == 0, true);
EXPECT(redirect_LocalSize(loc), 26);
EXPECT(TEST(LMEM_DISCARDABLE, redirect_LocalFlags(loc)), false);
/* locking should do nothing since fixed */
EXPECT(redirect_LocalLock(loc) == (LPVOID) loc, true);
EXPECT(redirect_LocalLock(loc) == (LPVOID) loc, true);
EXPECT(redirect_LocalUnlock(loc), FALSE);
EXPECT(get_last_error(), ERROR_NOT_LOCKED);
loc = redirect_LocalReAlloc(loc, 0, LMEM_MOVEABLE|LMEM_ZEROINIT);
EXPECT(redirect_LocalSize(loc), 0);
EXPECT(TEST(LMEM_DISCARDABLE, redirect_LocalFlags(loc)), true);
/* test LMEM_MODIFY */
loc = redirect_LocalReAlloc(loc, 0/*ignored*/, LMEM_MODIFY|LMEM_MOVEABLE);
EXPECT(loc != NULL, true);
/* locking should now do something */
EXPECT(redirect_LocalLock(loc) != NULL, true);
EXPECT(redirect_LocalLock(loc) != NULL, true);
EXPECT(redirect_LocalUnlock(loc), TRUE);
EXPECT(redirect_LocalUnlock(loc), FALSE);
EXPECT(get_last_error(), NO_ERROR);
loc = redirect_LocalFree(loc);
EXPECT(loc == NULL, true);
/**************************************************/
/* test moveable */
loc = redirect_LocalAlloc(LMEM_ZEROINIT|LMEM_MOVEABLE, 6);
EXPECT(loc != NULL, true);
p = redirect_LocalLock(loc);
EXPECT(p != NULL, true);
EXPECT(*(int *)p == 0, true);
EXPECT(redirect_LocalSize(loc), 6);
EXPECT(TEST(LMEM_DISCARDABLE, redirect_LocalFlags(loc)), false);
ok = redirect_LocalUnlock(loc);
EXPECT(ok, FALSE);
EXPECT(get_last_error(), NO_ERROR);
*(int *)p = 42;
loc = redirect_LocalReAlloc(loc, 126, LMEM_MOVEABLE|LMEM_ZEROINIT);
EXPECT(loc != NULL, true);
EXPECT(redirect_LocalSize(loc), 126);
p = redirect_LocalLock(p);
EXPECT(p != NULL, true);
EXPECT(*(int *)p == 42, true);
ok = redirect_LocalUnlock(loc);
EXPECT(ok, FALSE);
EXPECT(get_last_error(), NO_ERROR);
loc = redirect_LocalReAlloc(loc, 0, LMEM_MOVEABLE|LMEM_ZEROINIT);
EXPECT(redirect_LocalSize(loc), 0);
EXPECT(TEST(LMEM_DISCARDABLE, redirect_LocalFlags(loc)), true);
loc = redirect_LocalFree(loc);
EXPECT(loc == NULL, true);
}
static void
test_syscalls(void)
{
MEMORY_BASIC_INFORMATION mbi;
DWORD dw;
SIZE_T sz;
BOOL ok;
PVOID temp =
redirect_VirtualAlloc(0, PAGE_SIZE, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
EXPECT(temp != NULL, true);
sz = redirect_VirtualQuery((char *)temp + PAGE_SIZE/2, &mbi, sizeof(mbi));
EXPECT(sz == sizeof(mbi), true);
EXPECT(mbi.BaseAddress == temp, true);
EXPECT(mbi.AllocationBase == temp, true);
EXPECT(mbi.AllocationProtect == PAGE_READWRITE, true);
EXPECT(redirect_VirtualProtect(temp, PAGE_SIZE/2, PAGE_READONLY, &dw), true);
sz = redirect_VirtualQuery((char *)temp + PAGE_SIZE/4, &mbi, sizeof(mbi));
EXPECT(sz == sizeof(mbi), true);
EXPECT(mbi.BaseAddress == temp, true);
EXPECT(mbi.AllocationBase == temp, true);
EXPECT(mbi.AllocationProtect == PAGE_READWRITE, true);
ok = redirect_VirtualFree(temp, 0, MEM_RELEASE);
EXPECT(ok, true);
}
void
unit_test_drwinapi_kernel32_mem(void)
{
PVOID temp, ran;
print_file(STDERR, "testing drwinapi kernel32 memory-related routines\n");
ran = (PVOID) get_random_offset(POINTER_MAX);
temp = redirect_EncodePointer(ran);
EXPECT(temp != ran, true);
EXPECT(redirect_DecodePointer(temp) == ran, true);
test_heap();
test_local();
test_syscalls();
}
#endif