2025-05-13 19:45:22 +03:00

1098 lines
30 KiB
C

/*
* Process Hacker -
* handle table
*
* Copyright (C) 2010-2011 wj32
*
* This file is part of Process Hacker.
*
* Process Hacker is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Process Hacker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
*/
#include <phbase.h>
#include <handle.h>
#include <handlep.h>
static PH_INITONCE PhHandleTableInitOnce = PH_INITONCE_INIT;
static PH_FREE_LIST PhHandleTableLevel0FreeList;
static PH_FREE_LIST PhHandleTableLevel1FreeList;
PPH_HANDLE_TABLE PhCreateHandleTable(
VOID
)
{
PPH_HANDLE_TABLE handleTable;
ULONG i;
if (PhBeginInitOnce(&PhHandleTableInitOnce))
{
PhInitializeFreeList(
&PhHandleTableLevel0FreeList,
sizeof(PH_HANDLE_TABLE_ENTRY) * PH_HANDLE_TABLE_LEVEL_ENTRIES,
64
);
PhInitializeFreeList(
&PhHandleTableLevel1FreeList,
sizeof(PPH_HANDLE_TABLE_ENTRY) * PH_HANDLE_TABLE_LEVEL_ENTRIES,
64
);
PhEndInitOnce(&PhHandleTableInitOnce);
}
#ifdef PH_HANDLE_TABLE_SAFE
handleTable = PhAllocateSafe(sizeof(PH_HANDLE_TABLE));
if (!handleTable)
return NULL;
#else
handleTable = PhAllocate(sizeof(PH_HANDLE_TABLE));
#endif
PhInitializeQueuedLock(&handleTable->Lock);
PhInitializeWakeEvent(&handleTable->HandleWakeEvent);
handleTable->NextValue = 0;
handleTable->Count = 0;
handleTable->TableValue = (ULONG_PTR)PhpCreateHandleTableLevel0(handleTable, TRUE);
#ifdef PH_HANDLE_TABLE_SAFE
if (!handleTable->TableValue)
{
PhFree(handleTable);
return NULL;
}
#endif
// We have now created the level 0 table. The free list can now be set up to point to handle 0,
// which points to the rest of the free list (1 -> 2 -> 3 -> ...). The next batch of handles
// that need to be created start at PH_HANDLE_TABLE_LEVEL_ENTRIES.
handleTable->FreeValue = 0;
handleTable->NextValue = PH_HANDLE_TABLE_LEVEL_ENTRIES;
handleTable->FreeValueAlt = PH_HANDLE_VALUE_INVALID; // no entries in alt. free list
handleTable->Flags = 0;
for (i = 0; i < PH_HANDLE_TABLE_LOCKS; i++)
PhInitializeQueuedLock(&handleTable->Locks[i]);
return handleTable;
}
VOID PhDestroyHandleTable(
_In_ _Post_invalid_ PPH_HANDLE_TABLE HandleTable
)
{
ULONG_PTR tableValue;
ULONG tableLevel;
PPH_HANDLE_TABLE_ENTRY table0;
PPH_HANDLE_TABLE_ENTRY *table1;
PPH_HANDLE_TABLE_ENTRY **table2;
ULONG i;
ULONG j;
tableValue = HandleTable->TableValue;
tableLevel = tableValue & PH_HANDLE_TABLE_LEVEL_MASK;
tableValue -= tableLevel;
switch (tableLevel)
{
case 0:
{
table0 = (PPH_HANDLE_TABLE_ENTRY)tableValue;
PhpFreeHandleTableLevel0(table0);
}
break;
case 1:
{
table1 = (PPH_HANDLE_TABLE_ENTRY *)tableValue;
for (i = 0; i < PH_HANDLE_TABLE_LEVEL_ENTRIES; i++)
{
if (!table1[i])
break;
PhpFreeHandleTableLevel0(table1[i]);
}
PhpFreeHandleTableLevel1(table1);
}
break;
case 2:
{
table2 = (PPH_HANDLE_TABLE_ENTRY **)tableValue;
for (i = 0; i < PH_HANDLE_TABLE_LEVEL_ENTRIES; i++)
{
if (!table2[i])
break;
for (j = 0; j < PH_HANDLE_TABLE_LEVEL_ENTRIES; j++)
{
if (!table2[i][j])
break;
PhpFreeHandleTableLevel0(table2[i][j]);
}
PhpFreeHandleTableLevel1(table2[i]);
}
PhpFreeHandleTableLevel2(table2);
}
break;
default:
ASSUME_NO_DEFAULT;
}
PhFree(HandleTable);
}
VOID PhpBlockOnLockedHandleTableEntry(
_Inout_ PPH_HANDLE_TABLE HandleTable,
_In_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry
)
{
PH_QUEUED_WAIT_BLOCK waitBlock;
ULONG_PTR value;
PhQueueWakeEvent(&HandleTable->HandleWakeEvent, &waitBlock);
value = HandleTableEntry->Value;
if (
(value & PH_HANDLE_TABLE_ENTRY_TYPE) != PH_HANDLE_TABLE_ENTRY_IN_USE ||
(value & PH_HANDLE_TABLE_ENTRY_LOCKED)
)
{
// Entry is not in use or has been unlocked; cancel the wait.
PhSetWakeEvent(&HandleTable->HandleWakeEvent, &waitBlock);
}
else
{
PhWaitForWakeEvent(&HandleTable->HandleWakeEvent, &waitBlock, TRUE, NULL);
}
}
BOOLEAN PhLockHandleTableEntry(
_Inout_ PPH_HANDLE_TABLE HandleTable,
_Inout_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry
)
{
ULONG_PTR value;
while (TRUE)
{
value = HandleTableEntry->Value;
if ((value & PH_HANDLE_TABLE_ENTRY_TYPE) != PH_HANDLE_TABLE_ENTRY_IN_USE)
return FALSE;
if (value & PH_HANDLE_TABLE_ENTRY_LOCKED)
{
if ((ULONG_PTR)_InterlockedCompareExchangePointer(
(PVOID *)&HandleTableEntry->Value,
(PVOID)(value - PH_HANDLE_TABLE_ENTRY_LOCKED),
(PVOID)value
) == value)
{
return TRUE;
}
}
PhpBlockOnLockedHandleTableEntry(HandleTable, HandleTableEntry);
}
}
VOID PhUnlockHandleTableEntry(
_Inout_ PPH_HANDLE_TABLE HandleTable,
_Inout_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry
)
{
_interlockedbittestandset(
(PLONG)&HandleTableEntry->Value,
PH_HANDLE_TABLE_ENTRY_LOCKED_SHIFT
);
PhSetWakeEvent(&HandleTable->HandleWakeEvent, NULL);
}
HANDLE PhCreateHandle(
_Inout_ PPH_HANDLE_TABLE HandleTable,
_In_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry
)
{
PPH_HANDLE_TABLE_ENTRY entry;
ULONG handleValue;
entry = PhpAllocateHandleTableEntry(HandleTable, &handleValue);
if (!entry)
return NULL;
// Copy the given handle table entry to the allocated entry.
// All free entries have the Free type and have the (not) locked bit clear. There is no problem
// with setting the Type now; the entry is still locked, so they will block.
entry->TypeAndValue.Type = PH_HANDLE_TABLE_ENTRY_IN_USE;
entry->TypeAndValue.Value = HandleTableEntry->TypeAndValue.Value;
entry->Value2 = HandleTableEntry->Value2;
// Now we unlock this entry, waking anyone who was caught back there before we had finished
// setting up the entry.
PhUnlockHandleTableEntry(HandleTable, entry);
return PhpEncodeHandle(handleValue);
}
BOOLEAN PhDestroyHandle(
_Inout_ PPH_HANDLE_TABLE HandleTable,
_In_ HANDLE Handle,
_In_opt_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry
)
{
ULONG handleValue;
handleValue = PhpDecodeHandle(Handle);
if (!HandleTableEntry)
{
HandleTableEntry = PhpLookupHandleTableEntry(HandleTable, handleValue);
if (!HandleTableEntry)
return FALSE;
if (!PhLockHandleTableEntry(HandleTable, HandleTableEntry))
return FALSE;
}
_InterlockedExchangePointer(
(PVOID *)&HandleTableEntry->Value,
(PVOID)PH_HANDLE_TABLE_ENTRY_FREE
);
// The handle table entry is now free; wake any waiters because they can't lock the entry now.
// Any future lock attempts will fail because the entry is marked as being free.
PhSetWakeEvent(&HandleTable->HandleWakeEvent, NULL);
PhpFreeHandleTableEntry(HandleTable, handleValue, HandleTableEntry);
return TRUE;
}
PPH_HANDLE_TABLE_ENTRY PhLookupHandleTableEntry(
_In_ PPH_HANDLE_TABLE HandleTable,
_In_ HANDLE Handle
)
{
PPH_HANDLE_TABLE_ENTRY entry;
entry = PhpLookupHandleTableEntry(HandleTable, PhpDecodeHandle(Handle));
if (!entry)
return NULL;
if (!PhLockHandleTableEntry(HandleTable, entry))
return NULL;
return entry;
}
VOID PhEnumHandleTable(
_In_ PPH_HANDLE_TABLE HandleTable,
_In_ PPH_ENUM_HANDLE_TABLE_CALLBACK Callback,
_In_opt_ PVOID Context
)
{
ULONG handleValue;
PPH_HANDLE_TABLE_ENTRY entry;
BOOLEAN cont;
handleValue = 0;
while (entry = PhpLookupHandleTableEntry(HandleTable, handleValue))
{
if (PhLockHandleTableEntry(HandleTable, entry))
{
cont = Callback(
HandleTable,
PhpEncodeHandle(handleValue),
entry,
Context
);
PhUnlockHandleTableEntry(HandleTable, entry);
if (!cont)
break;
}
handleValue++;
}
}
VOID PhSweepHandleTable(
_In_ PPH_HANDLE_TABLE HandleTable,
_In_ PPH_ENUM_HANDLE_TABLE_CALLBACK Callback,
_In_opt_ PVOID Context
)
{
ULONG handleValue;
PPH_HANDLE_TABLE_ENTRY entry;
BOOLEAN cont;
handleValue = 0;
while (entry = PhpLookupHandleTableEntry(HandleTable, handleValue))
{
if (entry->TypeAndValue.Type == PH_HANDLE_TABLE_ENTRY_IN_USE)
{
cont = Callback(
HandleTable,
PhpEncodeHandle(handleValue),
entry,
Context
);
if (!cont)
break;
}
handleValue++;
}
}
NTSTATUS PhQueryInformationHandleTable(
_In_ PPH_HANDLE_TABLE HandleTable,
_In_ PH_HANDLE_TABLE_INFORMATION_CLASS InformationClass,
_Out_writes_bytes_opt_(BufferLength) PVOID Buffer,
_In_ ULONG BufferLength,
_Out_opt_ PULONG ReturnLength
)
{
NTSTATUS status = STATUS_SUCCESS;
ULONG returnLength;
switch (InformationClass)
{
case HandleTableBasicInformation:
{
PPH_HANDLE_TABLE_BASIC_INFORMATION basicInfo = Buffer;
if (BufferLength == sizeof(PH_HANDLE_TABLE_BASIC_INFORMATION))
{
basicInfo->Count = HandleTable->Count;
basicInfo->Flags = HandleTable->Flags;
basicInfo->TableLevel = HandleTable->TableValue & PH_HANDLE_TABLE_LEVEL_MASK;
}
else
{
status = STATUS_INFO_LENGTH_MISMATCH;
}
returnLength = sizeof(PH_HANDLE_TABLE_BASIC_INFORMATION);
}
break;
case HandleTableFlagsInformation:
{
PPH_HANDLE_TABLE_FLAGS_INFORMATION flagsInfo = Buffer;
if (BufferLength == sizeof(PH_HANDLE_TABLE_FLAGS_INFORMATION))
{
flagsInfo->Flags = HandleTable->Flags;
}
else
{
status = STATUS_INFO_LENGTH_MISMATCH;
}
returnLength = sizeof(PH_HANDLE_TABLE_FLAGS_INFORMATION);
}
break;
default:
status = STATUS_INVALID_INFO_CLASS;
returnLength = 0;
break;
}
if (ReturnLength)
*ReturnLength = returnLength;
return status;
}
NTSTATUS PhSetInformationHandleTable(
_Inout_ PPH_HANDLE_TABLE HandleTable,
_In_ PH_HANDLE_TABLE_INFORMATION_CLASS InformationClass,
_In_reads_bytes_(BufferLength) PVOID Buffer,
_In_ ULONG BufferLength
)
{
NTSTATUS status = STATUS_SUCCESS;
switch (InformationClass)
{
case HandleTableFlagsInformation:
{
PPH_HANDLE_TABLE_FLAGS_INFORMATION flagsInfo = Buffer;
ULONG flags;
if (BufferLength == sizeof(PH_HANDLE_TABLE_FLAGS_INFORMATION))
{
flags = flagsInfo->Flags;
if ((flags & PH_HANDLE_TABLE_VALID_FLAGS) == flags)
HandleTable->Flags = flags;
else
status = STATUS_INVALID_PARAMETER;
}
else
{
status = STATUS_INFO_LENGTH_MISMATCH;
}
}
break;
default:
status = STATUS_INVALID_INFO_CLASS;
}
return status;
}
PPH_HANDLE_TABLE_ENTRY PhpAllocateHandleTableEntry(
_Inout_ PPH_HANDLE_TABLE HandleTable,
_Out_ PULONG HandleValue
)
{
PPH_HANDLE_TABLE_ENTRY entry;
ULONG freeValue;
ULONG lockIndex;
ULONG nextFreeValue;
ULONG oldFreeValue;
BOOLEAN result;
while (TRUE)
{
freeValue = HandleTable->FreeValue;
while (freeValue == PH_HANDLE_VALUE_INVALID)
{
PhAcquireQueuedLockExclusive(&HandleTable->Lock);
// Check again to see if we have free handles.
freeValue = HandleTable->FreeValue;
if (freeValue != PH_HANDLE_VALUE_INVALID)
{
PhReleaseQueuedLockExclusive(&HandleTable->Lock);
break;
}
// Move handles from the alt. free list to the main free list, and check again.
freeValue = PhpMoveFreeHandleTableEntries(HandleTable);
if (freeValue != PH_HANDLE_VALUE_INVALID)
{
PhReleaseQueuedLockExclusive(&HandleTable->Lock);
break;
}
result = PhpAllocateMoreHandleTableEntries(HandleTable, TRUE);
PhReleaseQueuedLockExclusive(&HandleTable->Lock);
freeValue = HandleTable->FreeValue;
// Note that PhpAllocateMoreHandleTableEntries only returns FALSE if it failed to
// allocate memory. Success does not guarantee a free handle to be allocated, as they
// may have been all used up (however unlikely) when we reach this point. Success simply
// means to retry the allocation using the fast path.
if (!result && freeValue == PH_HANDLE_VALUE_INVALID)
return NULL;
}
entry = PhpLookupHandleTableEntry(HandleTable, freeValue);
lockIndex = PH_HANDLE_TABLE_LOCK_INDEX(freeValue);
// To avoid the ABA problem, we would ideally have one queued lock per handle table entry.
// That would make the overhead too large, so instead there is a fixed number of locks,
// indexed by the handle value (mod no. locks).
// Possibilities at this point:
// 1. freeValue != A (our copy), but the other thread has freed A, so FreeValue = A. No ABA
// problem since freeValue != A.
// 2. freeValue != A, and FreeValue != A. No ABA problem.
// 3. freeValue = A, and the other thread has freed A, so FreeValue = A. No ABA problem
// since we haven't read NextFreeValue yet.
// 4. freeValue = A, and FreeValue != A. No problem if this stays the same later, as the CAS
// will take care of it.
PhpLockHandleTableShared(HandleTable, lockIndex);
if (HandleTable->FreeValue != freeValue)
{
PhpUnlockHandleTableShared(HandleTable, lockIndex);
continue;
}
MemoryBarrier();
nextFreeValue = entry->NextFreeValue;
// Possibilities/non-possibilities at this point:
// 1. freeValue != A (our copy), but the other thread has freed A, so FreeValue = A. This is
// actually impossible since we have acquired the lock on A and the free code checks that
// and uses the alt. free list instead.
// 2. freeValue != A, and FreeValue != A. No ABA problem.
// 3. freeValue = A, and the other thread has freed A, so FreeValue = A. Impossible like
// above. This is *the* ABA problem which we have now prevented.
// 4. freeValue = A, and FreeValue != A. CAS will take care of it.
oldFreeValue = _InterlockedCompareExchange(
&HandleTable->FreeValue,
nextFreeValue,
freeValue
);
PhpUnlockHandleTableShared(HandleTable, lockIndex);
if (oldFreeValue == freeValue)
break;
}
_InterlockedIncrement((PLONG)&HandleTable->Count);
*HandleValue = freeValue;
return entry;
}
VOID PhpFreeHandleTableEntry(
_Inout_ PPH_HANDLE_TABLE HandleTable,
_In_ ULONG HandleValue,
_Inout_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry
)
{
PULONG freeList;
ULONG flags;
ULONG oldValue;
_InterlockedDecrement((PLONG)&HandleTable->Count);
flags = HandleTable->Flags;
// Choose the free list to use depending on whether someone is popping from the main free list
// (see PhpAllocateHandleTableEntry for details). We always use the alt. free list if strict
// FIFO is enabled.
if (!(flags & PH_HANDLE_TABLE_STRICT_FIFO) &&
PhTryAcquireReleaseQueuedLockExclusive(
&HandleTable->Locks[PH_HANDLE_TABLE_LOCK_INDEX(HandleValue)]))
{
freeList = &HandleTable->FreeValue;
}
else
{
freeList = &HandleTable->FreeValueAlt;
}
while (TRUE)
{
oldValue = *freeList;
HandleTableEntry->NextFreeValue = oldValue;
if (_InterlockedCompareExchange(
freeList,
HandleValue,
oldValue
) == oldValue)
{
break;
}
}
}
BOOLEAN PhpAllocateMoreHandleTableEntries(
_In_ PPH_HANDLE_TABLE HandleTable,
_In_ BOOLEAN Initialize
)
{
ULONG_PTR tableValue;
ULONG tableLevel;
PPH_HANDLE_TABLE_ENTRY table0;
PPH_HANDLE_TABLE_ENTRY *table1;
PPH_HANDLE_TABLE_ENTRY **table2;
ULONG i;
ULONG j;
ULONG oldNextValue;
ULONG freeValue;
// Get a pointer to the table, and its level.
tableValue = HandleTable->TableValue;
tableLevel = tableValue & PH_HANDLE_TABLE_LEVEL_MASK;
tableValue -= tableLevel;
switch (tableLevel)
{
case 0:
{
// Create a level 1 table.
table1 = PhpCreateHandleTableLevel1(HandleTable);
#ifdef PH_HANDLE_TABLE_SAFE
if (!table1)
return FALSE;
#endif
// Create a new level 0 table and move the existing level into the new level 1 table.
table0 = PhpCreateHandleTableLevel0(HandleTable, Initialize);
#ifdef PH_HANDLE_TABLE_SAFE
if (!table0)
{
PhpFreeHandleTableLevel1(table1);
return FALSE;
}
#endif
table1[0] = (PPH_HANDLE_TABLE_ENTRY)tableValue;
table1[1] = table0;
tableValue = (ULONG_PTR)table1 | 1;
//_InterlockedExchangePointer((PVOID *)&HandleTable->TableValue, (PVOID)tableValue);
HandleTable->TableValue = tableValue;
}
break;
case 1:
{
table1 = (PPH_HANDLE_TABLE_ENTRY *)tableValue;
// Determine whether we need to create a new level 0 table or create a level 2 table.
i = HandleTable->NextValue / PH_HANDLE_TABLE_LEVEL_ENTRIES;
if (i < PH_HANDLE_TABLE_LEVEL_ENTRIES)
{
table0 = PhpCreateHandleTableLevel0(HandleTable, TRUE);
#ifdef PH_HANDLE_TABLE_SAFE
if (!table0)
return FALSE;
#endif
//_InterlockedExchangePointer((PVOID *)&table1[i], table0);
table1[i] = table0;
}
else
{
// Create a level 2 table.
table2 = PhpCreateHandleTableLevel2(HandleTable);
#ifdef PH_HANDLE_TABLE_SAFE
if (!table2)
return FALSE;
#endif
// Create a new level 1 table and move the existing level into the new level 2
// table.
table1 = PhpCreateHandleTableLevel1(HandleTable);
#ifdef PH_HANDLE_TABLE_SAFE
if (!table1)
{
PhpFreeHandleTableLevel2(table2);
return FALSE;
}
#endif
table0 = PhpCreateHandleTableLevel0(HandleTable, Initialize);
#ifdef PH_HANDLE_TABLE_SAFE
if (!table0)
{
PhpFreeHandleTableLevel1(table1);
PhpFreeHandleTableLevel2(table2);
return FALSE;
}
#endif
table1[0] = table0;
table2[0] = (PPH_HANDLE_TABLE_ENTRY *)tableValue;
table2[1] = table1;
tableValue = (ULONG_PTR)table2 | 2;
//_InterlockedExchangePointer((PVOID *)&HandleTable->TableValue, (PVOID)tableValue);
HandleTable->TableValue = tableValue;
}
}
break;
case 2:
{
table2 = (PPH_HANDLE_TABLE_ENTRY **)tableValue;
i = HandleTable->NextValue /
(PH_HANDLE_TABLE_LEVEL_ENTRIES * PH_HANDLE_TABLE_LEVEL_ENTRIES);
// i contains an index into the level 2 table, of the containing level 1 table.
// Check if we have exceeded the maximum number of handles.
if (i >= PH_HANDLE_TABLE_LEVEL_ENTRIES)
return FALSE;
// Check if we should create a new level 0 table or a new level 2 table.
if (table2[i])
{
table0 = PhpCreateHandleTableLevel0(HandleTable, Initialize);
#ifdef PH_HANDLE_TABLE_SAFE
if (!table0)
return FALSE;
#endif
// Same as j = HandleTable->NextValue % (no. entries * no. entries), but we already
// calculated i so just use it.
j = HandleTable->NextValue - i *
(PH_HANDLE_TABLE_LEVEL_ENTRIES * PH_HANDLE_TABLE_LEVEL_ENTRIES);
j /= PH_HANDLE_TABLE_LEVEL_ENTRIES;
// j now contains an index into the level 1 table, of the containing level 0 table
// (the one which was created).
//_InterlockedExchangePointer((PVOID *)&table2[i][j], table0);
table2[i][j] = table0;
}
else
{
table1 = PhpCreateHandleTableLevel1(HandleTable);
#ifdef PH_HANDLE_TABLE_SAFE
if (!table1)
return FALSE;
#endif
table0 = PhpCreateHandleTableLevel0(HandleTable, TRUE);
#ifdef PH_HANDLE_TABLE_SAFE
if (!table0)
{
PhpFreeHandleTableLevel1(table1);
return FALSE;
}
#endif
table1[0] = table0;
//_InterlockedExchangePointer((PVOID *)&table2[i], table1);
table2[i] = table1;
}
}
break;
default:
ASSUME_NO_DEFAULT;
}
// In each of the cases above, we allocated one additional level 0 table.
oldNextValue = _InterlockedExchangeAdd(
(PLONG)&HandleTable->NextValue,
PH_HANDLE_TABLE_LEVEL_ENTRIES
);
if (Initialize)
{
// No ABA problem since these are new handles being pushed.
while (TRUE)
{
freeValue = HandleTable->FreeValue;
table0[PH_HANDLE_TABLE_LEVEL_ENTRIES - 1].NextFreeValue = freeValue;
if (_InterlockedCompareExchange(
&HandleTable->FreeValue,
oldNextValue,
freeValue
) == freeValue)
{
break;
}
}
}
return TRUE;
}
PPH_HANDLE_TABLE_ENTRY PhpLookupHandleTableEntry(
_In_ PPH_HANDLE_TABLE HandleTable,
_In_ ULONG HandleValue
)
{
ULONG_PTR tableValue;
ULONG tableLevel;
PPH_HANDLE_TABLE_ENTRY table0;
PPH_HANDLE_TABLE_ENTRY *table1;
PPH_HANDLE_TABLE_ENTRY **table2;
PPH_HANDLE_TABLE_ENTRY entry;
if (HandleValue >= HandleTable->NextValue)
return NULL;
// Get a pointer to the table, and its level.
tableValue = HandleTable->TableValue;
tableLevel = tableValue & PH_HANDLE_TABLE_LEVEL_MASK;
tableValue -= tableLevel;
// No additional checking needed; aleady checked against NextValue.
switch (tableLevel)
{
case 0:
{
table0 = (PPH_HANDLE_TABLE_ENTRY)tableValue;
entry = &table0[HandleValue];
}
break;
case 1:
{
table1 = (PPH_HANDLE_TABLE_ENTRY *)tableValue;
table0 = table1[PH_HANDLE_VALUE_LEVEL1_U(HandleValue)];
entry = &table0[PH_HANDLE_VALUE_LEVEL0(HandleValue)];
}
break;
case 2:
{
table2 = (PPH_HANDLE_TABLE_ENTRY **)tableValue;
table1 = table2[PH_HANDLE_VALUE_LEVEL2_U(HandleValue)];
table0 = table1[PH_HANDLE_VALUE_LEVEL1(HandleValue)];
entry = &table0[PH_HANDLE_VALUE_LEVEL0(HandleValue)];
}
break;
default:
ASSUME_NO_DEFAULT;
}
return entry;
}
ULONG PhpMoveFreeHandleTableEntries(
_Inout_ PPH_HANDLE_TABLE HandleTable
)
{
ULONG freeValueAlt;
ULONG flags;
ULONG i;
ULONG index;
ULONG nextIndex;
ULONG lastIndex;
PPH_HANDLE_TABLE_ENTRY entry;
PPH_HANDLE_TABLE_ENTRY firstEntry;
ULONG count;
ULONG freeValue;
// Remove all entries from the alt. free list.
freeValueAlt = _InterlockedExchange(&HandleTable->FreeValueAlt, PH_HANDLE_VALUE_INVALID);
if (freeValueAlt == PH_HANDLE_VALUE_INVALID)
{
// No handles on the alt. free list.
return PH_HANDLE_VALUE_INVALID;
}
// Avoid the ABA problem by testing all locks (see PhpAllocateHandleTableEntry for details).
// Unlike in PhpFreeHandleTableEntry we have no "alternative" list, so we must allow blocking.
for (i = 0; i < PH_HANDLE_TABLE_LOCKS; i++)
PhAcquireReleaseQueuedLockExclusive(&HandleTable->Locks[i]);
flags = HandleTable->Flags;
if (!(flags & PH_HANDLE_TABLE_STRICT_FIFO))
{
// Shortcut: if there are no entries in the main free list and we don't need to reverse the
// chain, just return.
if (_InterlockedCompareExchange(
&HandleTable->FreeValue,
freeValueAlt,
PH_HANDLE_VALUE_INVALID
) == PH_HANDLE_VALUE_INVALID)
return freeValueAlt;
}
// Reverse the chain (even if strict FIFO is off; we have to traverse the list to find the last
// entry, so we might as well reverse it along the way).
index = freeValueAlt;
lastIndex = PH_HANDLE_VALUE_INVALID;
count = 0;
while (TRUE)
{
entry = PhpLookupHandleTableEntry(HandleTable, index);
count++;
if (lastIndex == PH_HANDLE_VALUE_INVALID)
firstEntry = entry;
nextIndex = entry->NextFreeValue;
entry->NextFreeValue = lastIndex;
lastIndex = index;
if (nextIndex == PH_HANDLE_VALUE_INVALID)
break;
index = nextIndex;
}
// Note that firstEntry actually contains the last free entry, since we reversed the list.
// Similarly index/lastIndex both contain the index of the first free entry.
// Push the entries onto the free list.
while (TRUE)
{
freeValue = HandleTable->FreeValue;
firstEntry->NextFreeValue = freeValue;
if (_InterlockedCompareExchange(
&HandleTable->FreeValue,
index,
freeValue
) == freeValue)
break;
}
// Force expansion if we don't have enough free handles.
if (
(flags & PH_HANDLE_TABLE_STRICT_FIFO) &&
count < PH_HANDLE_TABLE_FREE_COUNT
)
{
index = PH_HANDLE_VALUE_INVALID;
}
return index;
}
PPH_HANDLE_TABLE_ENTRY PhpCreateHandleTableLevel0(
_In_ PPH_HANDLE_TABLE HandleTable,
_In_ BOOLEAN Initialize
)
{
PPH_HANDLE_TABLE_ENTRY table;
PPH_HANDLE_TABLE_ENTRY entry;
ULONG baseValue;
ULONG i;
#ifdef PH_HANDLE_TABLE_SAFE
__try
{
table = PhAllocateFromFreeList(&PhHandleTableLevel0FreeList);
}
__except (SIMPLE_EXCEPTION_FILTER(GetExceptionCode() == STATUS_NO_MEMORY))
{
return NULL;
}
#else
table = PhAllocateFromFreeList(&PhHandleTableLevel0FreeList);
#endif
if (Initialize)
{
entry = &table[0];
baseValue = HandleTable->NextValue;
for (i = baseValue + 1; i < baseValue + PH_HANDLE_TABLE_LEVEL_ENTRIES; i++)
{
entry->Value = PH_HANDLE_TABLE_ENTRY_FREE;
entry->NextFreeValue = i;
entry++;
}
entry->Value = PH_HANDLE_TABLE_ENTRY_FREE;
entry->NextFreeValue = PH_HANDLE_VALUE_INVALID;
}
return table;
}
VOID PhpFreeHandleTableLevel0(
_In_ PPH_HANDLE_TABLE_ENTRY Table
)
{
PhFreeToFreeList(&PhHandleTableLevel0FreeList, Table);
}
PPH_HANDLE_TABLE_ENTRY *PhpCreateHandleTableLevel1(
_In_ PPH_HANDLE_TABLE HandleTable
)
{
PPH_HANDLE_TABLE_ENTRY *table;
#ifdef PH_HANDLE_TABLE_SAFE
__try
{
table = PhAllocateFromFreeList(&PhHandleTableLevel1FreeList);
}
__except (SIMPLE_EXCEPTION_FILTER(GetExceptionCode() == STATUS_NO_MEMORY))
{
return NULL;
}
#else
table = PhAllocateFromFreeList(&PhHandleTableLevel1FreeList);
#endif
memset(table, 0, sizeof(PPH_HANDLE_TABLE_ENTRY) * PH_HANDLE_TABLE_LEVEL_ENTRIES);
return table;
}
VOID PhpFreeHandleTableLevel1(
_In_ PPH_HANDLE_TABLE_ENTRY *Table
)
{
PhFreeToFreeList(&PhHandleTableLevel1FreeList, Table);
}
PPH_HANDLE_TABLE_ENTRY **PhpCreateHandleTableLevel2(
_In_ PPH_HANDLE_TABLE HandleTable
)
{
PPH_HANDLE_TABLE_ENTRY **table;
#ifdef PH_HANDLE_TABLE_SAFE
table = PhAllocateSafe(sizeof(PPH_HANDLE_TABLE_ENTRY *) * PH_HANDLE_TABLE_LEVEL_ENTRIES);
if (!table)
return NULL;
#else
table = PhAllocate(sizeof(PPH_HANDLE_TABLE_ENTRY *) * PH_HANDLE_TABLE_LEVEL_ENTRIES);
#endif
memset(table, 0, sizeof(PPH_HANDLE_TABLE_ENTRY *) * PH_HANDLE_TABLE_LEVEL_ENTRIES);
return table;
}
VOID PhpFreeHandleTableLevel2(
_In_ PPH_HANDLE_TABLE_ENTRY **Table
)
{
PhFree(Table);
}