Geoff Chappell, Software Analyst
The ETW_HASH_BUCKET structure was introduced for Windows 8 to shorten the lists of ETW_GUID_ENTRY structures that represent the event providers that are currently known to the kernel.
Each time that a provider is registered (for writing events) or is enabled in a tracing session (to read events), the kernel must find the matching ETW_GUID_ENTRY. Earlier versions keep these structures in two double-linked lists, one for each type of event provider that was yet defined: trace providers and notification providers. The list heads are in an array that is indexed by the ETW_GUID_TYPE enumeration, is in the kernel’s own data, and is guarded by one push lock (which these versions acquire exclusively even when searching the lists).
Proliferation of event providers (if only as written by Microsoft’s programmers, many others taking surprisingly long to appreciate the benefits of modernising their event writing) will have seen these lists become unsatisfactorily long. Windows 8 split each of the old double-linked lists into 64, so that each new double-linked list is for event providers whose GUIDs have the same hash. Nothing sophisticated is needed for the hash, just enough to spread the providers reasonably well over the multiple lists: it’s simply the low 6 bits of an XOR of the successive dwords in the GUID. Where Windows Vista and Windows 7 have global variables for an array of two list heads (named EtwpGuidListHead) and for one lock (named EtwpGuidListLock), Windows 8 has one global variable (named EtwpGuidHashTable) for an array of 64 ETW_HASH_BUCKET structures, one for each possible hash. Each structure contains two list heads and a lock.
Windows 10 extends the ETW_GUID_TYPE to allow for event provider groups, thus adding another list head. The double-linked lists are also affected by something more substantial that is new for Windows 10: each event provider exists separately in each silo. For the original release of Windows 10, event providers that have the same GUID and the same ETW_GUID_TYPE but are in different silos have ETW_GUID_ENTRY structures in the same double-linked list.
The expected take-up of silos would tend to lengthen each double-linked list. That each list has structures from different silos was perhaps a concern for how well silos are isolated from one another. The 1511 release of Windows 10 reworks the array of ETW_HASH_BUCKET structures from a global variable to the EtwpGuidHashTable member of the per-silo ETW_SILODRIVERSTATE structure.
That the array of 64 ETW_HASH_BUCKET structures became a member of another structure in the 1511 release of Windows 10 has the side-effect that Microsoft’s names and types are disclosed as type information in public symbol files. Otherwise, the ETW_HASH_BUCKET would be not just undocumented but undisclosed.
|Offset (x86)||Offset (x64)||Definition||Versions|
LIST_ENTRY ListHead [EtwGuidTypeMax];
|6.2 and higher|
|0x10 (6.2 to 6.3);
|0x20 (6.2 to 6.3);
|6.2 and higher|
Introduction of the ETW_HASH_BUCKET has a side-effect for callers of the EnumerateTraceGuidsEx function. When given its information class TraceGuidQueryList, this function produces as its output an array of GUIDs, one for each provider that has an ETW_GUID_ENTRY in the kernel’s lists of providers whose type is EtwTraceGuidType. As with any return of information to user mode, this array can be stale by the time the user-mode caller even begins to examine it. The most a user-mode caller—or a user who is involved in forensic analysis—can expect is that there existed some earlier interval during which the returned information was correct. Before Windows 8, this expectation is met: the providers in the returned array were exactly the providers in the kernel’s one double-linked list while changes to it were guarded by one lock. In Windows 8 and higher, a side-effect of the multiple locks is that there need never have existed any one moment at which all the providers in the returned list actually were in the kernel’s lists.