Geoff Chappell, Software Analyst
DRAFT: Take more than your usual care.
This function is the central switching point for writing an event through Event Tracing For Windows (ETW).
NTSTATUS NtTraceEvent ( HANDLE TraceHandle, ULONG Flags, ULONG FieldSize, PVOID Fields);
The TraceHandle is a handle to an event provider or to a logger, or is NULL. Interpretation depends on the Flags. The interpretation can include that TraceHandle is ignored.
The Flags describe the type of the event and something of how the event is to be handled. Interpretation varies with the Windows version.
The Fields and FieldSize arguments are respectively the address and size, in bytes, of data for the event. Interpretation of this data depends on the Flags. The interpretation can include that FieldSize is ignored.
The function returns STATUS_SUCCESS if successful, else a negative error code.
Both the NtTraceEvent and ZwTraceEvent functions are exported by name from NTDLL in version 5.1 and higher. There, in user mode, the functions are aliases for a stub that transfers execution to the NtTraceEvent implementation in kernel mode such that the execution is recognised as originating in user mode.
This NtTraceEvent implementation is exported by name from the kernel in version 5.1 and higher. Only in version 6.1 and higher does the kernel also export a ZwTraceEvent. The kernel-mode ZwTraceEvent is also a stub that transfers execution to the NtTraceEvent implementation but such that the execution is recognised as originating in kernel mode.
The oldest functionality of NtTraceEvent appeared first in version 5.0 as Device I/O Control (code 0x0022808F) for the WMI service device. In some sense, the introduction of NtTraceEvent for version 5.1 signals the evolution of ETW as its own feature with direct support from the kernel. Access as Device I/O Control for WMI was discontinued in version 6.0.
Though the NtTraceEvent and ZwTraceEvent functions are not documented under either name, C-language declarations have been published by Microsoft in headers from the Enterprise edition of the Windows Driver Kit (WDK) for Windows 10 version 1511: NtTraceEvent in NTWMI.H and ZwTraceEvent in ZWAPI.H.
The following implementation notes are from inspection of the kernel from the
original release of Windows 10 only. They may some day get revised to account for
other versions. Meanwhile, where anything is added about earlier versions, take it not as an attempt at comprehensiveness but as a bonus from my being unable to resist a trip down memory lane or at least a quick look into the history.
If executing for a user-mode request, the function has some general defensiveness about addresses passed as arguments. Failure at any of these defences is failure for the function, which typically returns STATUS_DATATYPE_MISALIGNMENT or STATUS_ACCESS_VIOLATION (showing in kernel mode as raised but handled exceptions).
Starting with version 6.0, the Flags are interpreted as multi-bit fields:
|0x000000FF||ETW_SYSTEM_EVENT_VERSION_MASK||6.1 and higher|
|ETW_NT_TRACE_TYPE_MASK||6.0 and higher|
Masking the Flags by ETW_NT_TRACE_TYPE_MASK produces an event type. Versions before 6.0 test the Flags for single bits but have an event type in effect, since it is an error not to set either of the two defined bits but both cannot usefully be set together (ETW_NT_FLAGS_TRACE_HEADER has precedence).
The table below lists the types that the function does not dismiss as invalid. For all others, the function returns STATUS_INVALID_PARAMETER. The versions that are presently shown for each function code are just from a cursory look for the upper limit that the function applies in different builds.
|Numeric Value||Symbolic Name||Versions|
|0x00000001 (5.1 to 6.0);
|ETW_NT_FLAGS_TRACE_HEADER||5.1 and higher|
|0x00000002 (5.1 to 6.0);
|ETW_NT_FLAGS_TRACE_MESSAGE||5.1 and higher|
|ETW_NT_FLAGS_TRACE_EVENT||6.0 and higher|
|ETW_NT_FLAGS_TRACE_SYSTEM||6.0 and higher|
|ETW_NT_FLAGS_TRACE_SECURITY||6.0 and higher|
|ETW_NT_FLAGS_TRACE_MARK||6.0 and higher|
|0x00000700||ETW_NT_FLAGS_TRACE_EVENT_NOREG||6.1 and higher|
|0x00000800||ETW_NT_FLAGS_TRACE_INSTANCE||6.1 and higher|
|0x00000900||ETW_NT_FLAGS_TRACE_RAW||1511 and higher|
All remaining behaviour varies with the type.
This type of event supports the documented user-mode API functions TraceEvent and (in versions before 6.1) TraceEventInstance. As exports from ADVAPI32, these predate NtTraceEvent. In version 5.0, this case of event tracing is done through Device I/O Control as a WMI feature.
What’s expected for Fields is a fixed-size header possibly followed by variable-size data. All versions ignore FieldSize in favour of learning the total size, in bytes, from the header. Interpretation of this header and of the variable-size data depends on flags in the header. Versions before 6.0 ignore the TraceHandle in favour of learning it from the header. Version 6.0 defaults to this if TraceHandle is NULL. Later versions require the TraceHandle. However the handle is obtained, it is a 16-bit logger ID.
Historically, the header is in general a WNODE_HEADER whose BufferSize, HistoricalContext and Flags members are respectively the total size, trace handle and flags. If the 32-bit BufferSize at the header’s start is implausible for having its highest bit set, the header is instead an EVENT_TRACE_HEADER, an EVENT_INSTANCE_HEADER (in version 5.1) or an EVENT_INSTANCE_GUID_HEADER. These each begin with a 16-bit Size.
Version 6.1 discontinues recognition of a WNODE_HEADER and supports EVENT_INSTANCE_GUID_HEADER through a separate case of the Flags (see ETW_NT_FLAGS_TRACE_INSTANCE), such that the header is necessarily an EVENT_TRACE_HEADER.
This type of event supports the documented user-mode API function TraceMessageVa. The event data described by Fields and FieldSize must be exactly a MESSAGE_TRACE_USER, which is essentially a repackaging of what would ordinarily have been passed as arguments to TraceMessageVa. The Data and DataSize members are respectively the address and size, in bytes, of an argument list for the event. The argument list is in turn a sequence of pointers and sizes, in pairs, each for one argument, ending with a NULL pointer.
This type of event supports the documented user-mode API function TraceEventInstance.
The TraceHandle is a 16-bit logger ID. The special value 0xFFFF for the NT Kernel Logger is explicitly not valid. If the TraceHandle does not select a running logger, the function returns STATUS_INVALID_HANDLE. If the logger has EVENT_TRACE_SECURE_MODE, it does not accept events through this interface even from kernel-mode callers, and the function returns STATUS_ACCESS_DENIED. For a kernel-mode request, the logger must not have the EVENT_TRACE_USE_PAGED_MEMORY mode, else the function fails, returning STATUS_NOT_SUPPORTED.
The Fields argument must be the dword-aligned address of a 0x48-byte EVENT_INSTANCE_GUID_HEADER, possibly followed by variable-size event-specific data. The FieldSize argument, which might be the total size in bytes, is ignored in favour of the Size from the header.
This Size is ordinarily also the size of what the function writes to a trace buffer as the event. The output header is slightly edited from the input but the event-specific data is simply copied.
Special interpretation applies, however, if TRACE_HEADER_FLAG_USE_MOF_PTR is set in the header’s Flags. The data that follows the header on input is not itself the event-specific data but is instead an array of MOF_FIELD structures which may each supply the address and size of one item of event-specific data. All versions limit this array to 0x0100 bytes. If more is allowed by the Size, the function returns STATUS_ARRAY_BOUNDS_EXCEEDED. If the total size of the header and of all items described by whole MOF_FIELD structures in the array overflows 32 bits, the function returns STATUS_BUFFER_OVERFLOW.
If the function cannot obtain space in a trace buffer for the fixed-header and the event-specific data, it returns its choice of STATUS_INTEGER_OVERFLOW, STATUS_BUFFER_OVERFLOW or STATUS_NO_MEMORY.