DRAFT: Take more than your usual care.

ERESOURCE

The ERESOURCE is a compound object with which the Executive supports a more elaborate model of synchronisation than does any one dispatcher object. Threads that contend for access to some protected resource call functions that acquire and release the ERESOURCE. A contending thread may require exclusive access or be satisfied to share its access. Moreover, a thread that already has access may reacquire it. The Executive implements this synchronisation by combining a KEVENT and KSEMAPHORE with various counts and flags protected by a KSPIN_LOCK.

Documentation Status

Though the ERESOURCE has always been documented, its content is explicitly not documented. Relevant functions take the address of an ERESOURCE among their arguments, but callers merely provide sufficient (non-paged) memory for the kernel to initialise what it wants how it wants. The most the documentation says of the content is that “the ERESOURCE structure is opaque: that is, its members are reserved for system use.”

Explicit warning is perhaps necessary because a C-language definition has been provided in every Device Driver Kit (DDK) from as far back as Windows NT 3.51 and presumably before. The apparent purpose is that drivers and other kernel-mode modules that ask the kernel to intialise an ERESOURCE should know how much memory to provide. Since what happens in the space is entirely in the hands of kernel functions that are provided for initialising and then working with this memory, Microsoft might as well have defined the ERESOURCE as containing an array of bytes, with no consequences for programmers at large except if the required size ever grew.

Layout

Yet the size has changed. In all versions since 3.50, the ERESOURCE is 0x38 and 0x68 bytes in 32-bit and 64-bit Windows, respectively, but the structure in version 3.10 is 0x78 bytes.

Original

For whatever reason, the original ERESOURCE from version 3.10 was reworked almost immediately. The very next version, 3.50, has a new set of functions to work with a new ERESOURCE and retains not quite half of the old functions to work with the old ERESOURCE. For all the kerfuffle with new and old functions, the structure didn’t change name, and its most important members in the original implementation are at least recognisable in the new implementation. Indeed, some such correspondence is essential since the two forms of ERESOURCE coexisted up to and including version 5.0 and were both reported for the SystemLocksInformation case of NtQuerySystemInformation, such that they needed at the very least to be reliably distinguishable.

Offset (x86) Definition Versions
0x00
LIST_ENTRY SystemResourcesList;
all
0x08 (3.10 to 5.0)
ERESOURCE_THREAD *OwnerThreads;
3.10 to 5.0
0x0C (3.10 to 5.0)
UCHAR *OwnerCounts;
3.10 to 5.0
0x10 (3.10 to 5.0)
USHORT TableSize;
3.10 to 5.0
0x12 (3.10 to 5.0)
USHORT ActiveCount;
3.10 to 5.0
0x14 (3.10 to 5.0)
USHORT Flag;
3.10 to 5.0
0x16 (3.10 to 5.0)
USHORT TableRover;
3.10 to 5.0
0x18 (3.10 to 5.0)
UCHAR InitialOwnerCounts [4];
3.10 to 5.0
0x1C (3.10 to 5.0)
ERESOURCE_THREAD InitialOwnerThreads [4];
3.10 to 5.0
0x2C (3.10 to 5.0)
ULONG Spare1;
3.10 to 5.0
0x30 (3.10 to 5.0)
ULONG ContentionCount;
3.10 to 5.0
0x34 (3.10 to 5.0)
USHORT NumberOfExclusiveWaiters;
3.10 to 5.0
0x36 (3.10 to 5.0)
USHORT NumberOfSharedWaiters;
3.10 to 5.0
0x38 (3.10 to 5.0)
KSEMAPHORE SharedWaiters;
3.10 to 5.0
0x4C (3.10 to 5.0)
KEVENT ExclusiveWaiters;
3.10 to 5.0
0x5C (3.10 to 5.0)
KSPIN_LOCK SpinLock;
3.10 to 5.0
0x60 (3.10 to 5.0)
ULONG CreatorBackTraceIndex;
3.10 to 5.0
0x64 (3.10 to 5.0)
USHORT Depth;
3.10 to 5.0
0x66 (3.10 to 5.0)
USHORT Reserved;
3.10 to 5.0
0x68 (3.10 to 5.0)
PVOID OwnerBackTrace [4];
3.10 to 5.0

The point to the SystemResourcesList is that initialising an ERESOURCE (unless it is in user-mode address space) gets the ERESOURCE inserted into a double-linked list whose head is in the kernel’s own data. Being in this list is how the ERESOURCE structures are enumerated for NtQuerySystemInformation.

At any given time, an ERESOURCE may have no owners or one exclusive owner or any number of shared owners. The ERESOURCE_THREAD is Microsoft’s type, originally a ULONG but later a ULONG_PTR, for representing an owner thread. What it actually holds is, unsurprisingly, the address of the owner thread’s KTHREAD (or perhaps ETHREAD). Since an owner thread can reacquire the resource multiple times, the ERESOURCE keeps for each owner both an ERESOURCE_THREAD and a count.

The original implementation keeps separate arrays of the owner threads and owner counts, naturally enough named OwnerThreads and OwnerCounts. Together they are an owner table that describes TableSize owners.

In the original implementation, allowance for an exclusive owner or four shared owners is built in as arrays of InitialOwnerCounts and InitialOwnerThreads that are initially pointed to from OwnerCounts and OwnerThreads, respectively. If more than four threads ever share the resource, then memory is found for a growable table of owner threads and counts. The OwnerThreads and OwnerCounts members then point to the threads and counts in the table. The threads are straightforwardly an array at the beginning of the table. The counts are less so. When these are built-in they are simply an array of four byte-sized counts. In the growable table, they follow the array of threads but successive byte-sized counts are spaced 0x10 bytes apart. (No reason is known for this spacing.) The TableSize is the table’s capacity for owners, both exclusive and shared, whether the table is built in or not. This capacity always grows to a multiple of four.

Versions 3.50 to 5.0 retain the original ERESOURCE for use by the original functions, but they do not retain all of these original functions. In particular, no means is provided by which old binaries that still call the old functions can acquire shared ownership. In these versions then, the OwnerThreads in an old ERESOURCE can only ever point to the built-in InitialOwnerThreads. This becomes the mechanism by which the old ERESOURCE and the new can be distinguished: the new retains a pointer at offset 0x08 in such a way that it cannot ever point to offset 0x1C.

The ActiveCount tells how many threads currently have access to the resource. A thread’s recursive reacquisition of the resource does not change this count. This ActiveCount is what’s shown as the resource’s LockCount in the RTL_PROCESS_LOCK_INFORMATION structure when the resource is described for the SystemLocksInformation case of NtQuerySystemInformation.

Bits for the Flag are defined compatibly between the old and new:

Mask Name Versions Remarks
0x0001   3.10 to 5.0 set if any thread is waiting for exclusive access;
debug output suggests ExclusiveWaiter as name
0x0002   3.10 only set if any thread is waiting for shared access;
debug output suggests SharedWaiter as name
0x0004   3.10 only set if using external owner table
0x0008   all set if priority boost disabled
0x0010  ResourceNeverExclusive all  
0x0020 ResourceReleaseByOtherThread all  
0x0080 ResourceOwnedExclusive all debug output in 3.10 to 5.0 suggests IsOwnedExclusive as name

The ContentionCount is of times that threads have been made to wait. Such waits can happen multiple times within any one call to acquire the resource. Though the ContentionCount does not correlate directly with calls to acquire the resource, it does only increase, never decrease (except to wrap around to zero).

Revised

The original ERESOURCE was largely scrapped as soon as the second Windows version. It was retained only with the reduced functionality of not allowing shared ownership and only then until version 5.0. Moreover, the revision seems to have been considered substantial enough that a new set of functions was provided for the new implementation. Old code, not yet rebuilt, could continue to call the old functions such as ExInitializeResource and ExAcquireResourceExclusive to provide the kernel with space for an old ERESOURCE to work with and to get old behaviour. New code would call new functions such as ExInitializeResourceLite and ExAcquireResourceExclusiveLite to provide the kernel with a new ERESOURCE and to get new behaviour. Old code rebuilt with new definitions would get the new behaviour because macros redefine the old functions to use the new names.

The new functionality is perhaps “lite” only in the sense that the new ERESOURCE is smaller, notably for not having a KSEMAPHORE or KEVENT until the corresponding type of access (shared or exclusive, respectively) is requested. Yet precisely because the new ERESOURCE is smaller, old code that was not yet rebuilt but had properly treated the old ERESOURCE as opaque would have been undisturbed by the change of implementation: it would simply have provided more space than the new kernel would use. A reason that would have compelled a name change is that old code was known that did not treat the space as opaque, but this would not explain why the retention of old functions was only of those for exclusive ownership. Whatever the reason, no public explanation from Microsoft is known.

Offset (x86) Offset (x64) Definition Versions
0x00 0x00
LIST_ENTRY SystemResourcesList;
all
0x08 0x10
OWNER_ENTRY *OwnerTable;
3.50 and higher
0x0C 0x18
SHORT ActiveCount;
3.50 and higher
0x0E 0x1A
USHORT Flag;
3.50 to 6.1
union {
    USHORT Flag;
    struct {
        UCHAR ReservedLowFlags;
        UCHAR WaiterPriority;
    };
};
6.2 and higher
0x10 0x20
KSEMAPHORE *SharedWaiters;
3.50 to 6.3
KWAIT_CHAIN SharedWaiters;
10.0 and higher
0x14 0x28
KEVENT *ExclusiveWaiters;
3.50 and higher
0x18 0x30
OWNER_ENTRY OwnerThreads [2];
3.50 to 5.2
OWNER_ENTRY OwnerEntry;
6.0 and higher
0x20 0x40
ULONG ActiveEntries;
6.0 and higher
0x28 (3.50 to 5.2);
0x24
0x50 (5.2);
0x44
ULONG  ContentionCount;
3.50 and higher
0x2C (3.50 to 5.2);
0x28
0x54 (5.2);
0x48
USHORT NumberOfSharedWaiters;
3.50 to 5.2
ULONG NumberOfSharedWaiters;
6.0 and higher
0x2E (3.50 to 5.2);
0x2C
0x56 (5.2);
0x4C
USHORT NumberOfExclusiveWaiters;
3.50 to 5.2
ULONG NumberOfExclusiveWaiters;
6.0 and higher
  0x50
PVOID Reserved2;
6.0 and higher
0x30 0x58
union {
    PVOID Address;
    ULONG_PTR CreatorBackTraceIndex;
};
3.50 and higher
0x34 0x60
KSPIN_LOCK SpinLock;
3.50 and higher

The revision consolidates the tracking of threads and counts into an OWNER_ENTRY structure. As with the original, there is both a built-in allowance and a growable table. In the revision, however, the external OwnerTable does not replace the built-in allowance but adds to it. Whether the ERESOURCE has an OwnerThreads array or just the one OwnerEntry, these built-in provisions remain in use forever.

In all versions, the first built-in OWNER_ENTRY, whether as the first in the OwnerThreads array or just as plain OwnerEntry, is for the exclusive owner whenever the resource has one. For as long as it happens that the resource never has a shared owner, it has no external OwnerTable. Even once the resource has an OwnerTable, the first built-in OWNER_ENTRY remains the one that tracks the exclusive owner.

In those (early) versions that have two built-in OWNER_ENTRY structures, the second is for a shared owner. For as long as it happens that the resource never is owned by two threads concurrently, it has no external OwnerTable. The second built-in OWNER_ENTRY remains available for tracking a shared owner even when more entries are available in an external table.

In all versions, the first OWNER_ENTRY in the external OwnerTable is a dummy. It never tracks a shared owner. Its only meaningful member is the TableSize, which is the number of entries in the table, including the dummy. Versions 5.0 and higher formalise this by having the TableSize in union with the OwnerCount, the former for the first OWNER_ENTRY pointed to from OwnerTable, the latter for all that follow.