DRAFT: Take more than your usual care.

NtTraceEvent

This function is the central switching point for writing an event through Event Tracing For Windows (ETW).

Declaration

NTSTATUS 
NtTraceEvent (
    HANDLE TraceHandle, 
    ULONG Flags,
    ULONG FieldSize, 
    PVOID Fields);

Parameters

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.

Return Value

The function returns STATUS_SUCCESS if successful, else a negative error code.

Availability

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.

Documentation Status

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.

Behaviour

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.

User-Mode Defences

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).

Valid Flags

Starting with version 6.0, the Flags are interpreted as multi-bit fields:

Mask Name Versions
0x000000FF ETW_SYSTEM_EVENT_VERSION_MASK 6.1 and higher
0x0000FFFF (6.0);
0x0000FF00
ETW_NT_TRACE_TYPE_MASK 6.0 and higher
0x000F0000 ETW_USER_FRAMES_TO_SKIP_MASK  
0x40000000 ETW_NT_FLAGS_USE_NATIVE_HEADER  
0x80000000 ETW_NT_FLAGS_WOW64_CALL  

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);
0x00000100
ETW_NT_FLAGS_TRACE_HEADER 5.1 and higher
0x00000002 (5.1 to 6.0);
0x00000200
ETW_NT_FLAGS_TRACE_MESSAGE 5.1 and higher
0x00000003 (6.0);
0x00000300
ETW_NT_FLAGS_TRACE_EVENT 6.0 and higher
0x00000004 (6.0);
0x00000400
ETW_NT_FLAGS_TRACE_SYSTEM 6.0 and higher
0x00000005 (6.0);
0x00000500
ETW_NT_FLAGS_TRACE_SECURITY 6.0 and higher
0x00000006 (6.0);
0x00000600
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.

EVENT_NT_FLAGS_TRACE_HEADER

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.

EVENT_NT_FLAGS_TRACE_MESSAGE

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.

EVENT_NT_FLAGS_TRACE_INSTANCE

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.