Geoff Chappell - Software Analyst
The EVENT_TRACE_SPINLOCK_INFORMATION structure is one of many that the ZwQuerySystemInformation (or NtQuerySystemInformation) and ZwSetSystemInformation (or NtSetSystemInformation) functions expect in their information buffer when given the information class SystemPerformanceTraceInformation (0x1F). This particular structure is selected when the first dword in the information buffer on input is EventTraceSpinlockInformation (0x05).
The remaining members of the structure are parameters that govern the tracing of events that sample the acquisition and release of spin locks. This tracing is enabled for NT Kernel Logger sessions for which the group mask PERF_SPINLOCK (0x20010000) is set. The events have the Hook ID PERFINFO_LOG_TYPE_SPINLOCK (0x0529), with event-specific data in the form of a WMI_SPINLOCK structure. While this tracing is enabled, the kernel tracks the acquisition and release of spin locks, and writes an event whenever a lock’s release satisfies the sampling conditions that are described by this structure’s parameters.
The EVENT_TRACE_SPINLOCK_INFORMATION structure is not documented.
A few public disclosures are known from Microsoft, though not as any sort of plain-English documentation. One is that a previously unpublished header named NTETW.H was published in the original and Version 1511 editions of the Windows Driver Kit (WDK) for Windows 10, and this header contains a C-language definition of the structure.
Were it not for this limited and possibly unintended disclosure of NTETW.H, a practical equivalent of the C-language definition (but missing comments, of course) would anyway be known from type information in symbol files. But this too has the look of an oversight. Type information for this structure has never appeared in any public symbol files for the kernel or for the obvious low-level user-mode DLLs. It has instead slipped out in symbol files for a smattering of higher-level user-mode DLLs, starting with Windows 8. For these few, the readily available symbol files actually are private symbol files and show that the unpublished NTETW.H was included when compiling the corresponding binaries.
Type information also has been published in a statically linked library, named CLFSMGMT.LIB, which Microsoft distributes with the Software Development Kit (SDK) starting for Windows Vista. This does not have the forensic quality as has type information in symbol files for the binaries that ship with an operating system, for although it is as accurate for when the library was built, there is no requirement that the library have been built with the operating system that it targets. There can be, and often is, some discrepancy, and there is anyway not always a fresh library for each service pack.
The EVENT_TRACE_SPINLOCK_INFORMATION is originally 0x10 bytes in both 32-bit and 64-bit Windows. An addition for version 6.3 lengthens the structure to 0x14 bytes. The original structure is defined separately as EVENT_TRACE_SPINLOCK_INFORMATION_V1 in version 6.3 and higher.
Offsets, names and types in the table that follows are from type information in symbol files and libraries, and from the published C-language definition, as described above.
|6.1 and higher|
|6.1 and higher|
|6.1 and higher|
|6.1 and higher|
|6.3 and higher|
Though the structure is presented above as being defined in version 6.1, it has no 32-bit implementation until version 6.2. The 64-bit kernel’s code for spin locks had been in C (or C++) from the start, i.e., for Windows Server 2003 SP1, but the corresponding code in the 32-bit kernel is still in assembly language in Windows 7. Its evolution from Windows NT 3.1 had gone as far as adding hypervisor notifications and, for Windows 7, the maintenance of performance counters in the KPRCB, but there it was left. Not until Windows 8 does 32-bit Windows trace events for spin locks.
To set these members in version 6.2 and higher, the caller must have TRACELOG_GUID_ENABLE access to the NT Kernel Logger.
Constraints on the parameters are enforced when setting information, else the function returns STATUS_INVALID_PARAMETER:
For each, the least permitted value is anyway what the kernel starts with and is thus the default.
A PERFINFO_LOG_TYPE_SPINLOCK event is written whenever a spin lock is released, if PERF_SPINLOCK is enabled and any of the following conditions are met:
Uncontended acquisition of spin locks is highly desirable, hopefully ordinary and correspondingly uninteresting, such that relatively few (if any) are usefully sampled: hence the high minimum for the SpinLockAcquireSampleRate. The default is, in effect, to trace roughly every thousandth uncontended acquisition. See that the rate is an inverse: increasing it means that uncontended acquisitions are sampled less frequently.
The default for contended acquisitions is to trace them all. For some purposes this may be excessive. Occasional contention is expected and ordinary. Tracing them all can generate very many events very quickly. One way to reduce the volume is to increase the SpinLockContentionSampleRate and thus trace only a roughly random sample of contended acquisitions. Another is to raise SpinLockSpinThreshold while keeping the SpinLockContentionSampleRate at 1. This selectively ignores the acquisitions that proceeded with only a few spins in favour of tracing those whose wait was more problematic.
Whether a lock’s acquisition was immediate or contended, and whatever filtering is applied by the original three parameters, it is a serious problem if the time from (finally) acquiring the lock until releasing it is too long. The SpinLockHoldThreshold is in the same processor cycles that are counted by the rdtsc instruction. For processors with clock speeds of a few GHz, the default is an order of magnitude larger than Microsoft’s long-standing warnings that “No routine should hold a spin lock for longer than 25 microseconds.”