Geoff Chappell - Software Analyst
DRAFT: Take more than your usual care.
This function is the simplest of several for writing an event from user mode.
ULONG TraceEvent ( TRACEHANDLE SessionHandle, EVENT_TRACE_HEADER *EventTrace);
The required SessionHandle selects the event tracing session, also called a logger, to which the event is to be written.
The required EventTrace argument describes the event. It is the address of a fixed-size header possibly followed by variable-size event-specific data. The header gets modified.
The function returns zero for success, else a Win32 error code.
The return value is also set as the thread’s last error, such that it can be retrieved by calling GetLastError.
The TraceEvent function is exported by name from ADVAPI32 in version 5.0 and higher. Starting with version 5.2, it is merely a forward to the NTDLL export EtwTraceEvent in its version 5.2 or EtwLogTraceEvent in version 6.0 higher. For the NTDLL implementations, the second of which does behave differently in ways that may be significant, follow the links: this note is concerned only with the function as implemented in ADVAPI32.
The TraceEvent function is documented.
Broadly, the TraceEvent function edits the given header and sends the event into whichever of the kernel-mode or user-mode event tracing machinery is appropriate for the session. This saves the event into a trace buffer and the function is done. What then happens to the event, which will typically be that the trace buffer gets flushed to an Event Trace Log (ETL) file, is not the business of this function.
Note that there is the event as provided and separately the event as written to a trace buffer. The headers in each have the same form but not the same contents.
Without an EventTrace, the function can do nothing and fails, returning ERROR_INVALID_PARAMETER.
All further work is subject to exception handling. If an exception occurs, the exception code is converted to a Win32 error code and becomes the function’s result.
Interpretation, both of the input header and of the event-specific data that may follow it, depends on the Flags in the header. Indeed, some bits that are defined for the Flags tell which type of header is provided. Even where this does not apply directly to this function, it does to the deeper machinery (see below), which in these early versions does double and even triple duty with various types of header. In version 5.0, even this high-level function accepts two types of header: an EVENT_TRACE_HEADER, as documented and declared, or a WNODE_HEADER.
That EventTrace addresses an EVENT_TRACE_HEADER is the only possibility in version 5.1, but version 5.0 expects a set WNODE_FLAG_TRACED_GUID. The Size in the EVENT_TRACE_HEADER is the total, in bytes, of both the header and any event-specific data that follows. If this is not at least enough for the header, the function fails, returning ERROR_INVALID_PARAMETER.
That a caller of version 5.0 intends the header as a WNODE_HEADER is inferred from a set WNODE_FLAG_LOG_WNODE. The BufferSize in the WNODE_HEADER is the total, in bytes, of both the header and any event-specific data that follows. If this is not at least enough for the header, the function fails, returning ERROR_INVALID_PARAMETER.
Version 5.0 requires that either WNODE_FLAG_TRACED_GUID or WNODE_FLAG_LOG_WNODE is set on input. If both are clear, the function returns ERROR_INVALID_FLAG_NUMBER (as still documented in 2018). That both may be set is here put aside as producing undefined behaviour. (If nothing else, it has a quirk that WNODE_FLAG_LOG_WNODE ends up having precedence, yet its 32-bit BufferSize is first interpreted as a 16-bit Size such that the event is rejected early if the low word of the BufferSize is less than 0x0030.)
In sending the event onwards, the function makes the following adjustments in-place:
Of these changes to the input header, most will be undone when the function returns: only the loading of the SessionHandle to offset 0x08 persists as the function’s output.
Note that the EVENT_TRACE_HEADER has no member for the 64-bit SessionHandle. At offset 0x08 there are only the ThreadId and ProcessId members (or just a 64-bit ThreadId in version 5.0), but these are generated by the deeper machinery for the event as written to a trace buffer, not for the event as given for input or seen afterwards as output. Interpret the header instead as a WNODE_HEADER, and this space is defined as a HistoricalContext and is even documented as receiving “the handle to the event tracing session”.
The function forwards the adjusted header and the untouched event-specific data deeper into the event tracing machinery. If the SessionHandle has the 0x01000000 bit set, the tracing session is a user-mode logger—in these versions, the user-mode logger, only one being permitted per process—and the event is written to trace buffers that are maintained by ADVAPI32. Otherwise, the event goes to trace buffers that are maintained by the kernel. The SessionHandle is then a 16-bit logger ID. Zero and 0xFFFF are explicitly invalid and cause the function to return ERROR_INVALID_HANDLE. Communication with the kernel is through Device I/O Control (code 0x0022808F) to the WMI service device in version 5.0 but through NtTraceEvent (with ETW_NT_FLAGS_TRACE_EVENT set in the Flags) in version 5.1.
Wherever the event goes, the handling is similar and whatever results is success or failure for the function. Error codes for the same cause vary with the version and also with whether the event goes to the kernel or to the user-mode logger. Aside from ERROR_NOACCESS from the kernel’s probes of what it expects to be user-mode addresses, the most notable failures are:
The deeper machinery allows for several points of interpretation that are under the caller’s control through the Flags.
The first is new for version 5.1 and applies only if the event goes to the user-mode logger. If WNODE_FLAG_NO_HEADER is set, then nothing matters about the EVENT_TRACE_HEADER except for the Size. This must be at least 0x58, else the function returns ERROR_INVALID_PARAMETER. It’s not, after all, that there’s no header. It’s more that the header continues as an EVENT_TRACE and everything that counts is in the continuation. The MofData and MofLength provide respectively the address and size of a buffer from which to obtain the whole data for the event, header and all, with no further interpretation (and the BufferContext provides a preferred processor).
As an aside, since Microsoft seems never to have documented WNODE_FLAG_NO_HEADER as having any meaning to the TraceEvent function (if to anything), it’s perhaps as well to record what use Microsoft made of this flag in Windows XP. It supported the tracerpt tool’s -merge switch. This reads events from one or more input ETL files and relogs them, uninterpreted and intact, to a merged ETL file. Microsoft has never been the slightest bit shy of writing undocumented functionality so that its ETW tools can do things that others’ can’t.
If WNODE_FLAG_USE_MOF_PTR is set, then the data that follows the input header is not itself the event-specific data but is instead an array of MOF_FIELD structures, which each supplies the address and size of one item of the intended event-specific data. What follows the header as written to a trace buffer is a concatenation of these items in the order of their description by the MOF_FIELD array. The implementation limits the array to 0x0100 bytes: if more than this follows the input header, the function fails, returning STATUS_ARRAY_BOUNDS_EXCEEDED or ERROR_INVALID_DATA.
If WNODE_FLAG_USE_GUID_PTR is set, then the input header is not given with a Guid but instead has a GuidPtr. This is the address from which the implementation is to get the Guid for the event as written to the trace buffer.
In version 5.1, a set WNODE_FLAG_USE_TIMESTAMP indicates that the event should not be stamped with the time of its being writing to a trace buffer but should instead use the TimeStamp from the input header.
Put aside the relogging case, and what goes into the trace buffers, and which may then persist in an Event Trace Log (ETL) file, is a modified EVENT_TRACE_HEADER (or, in version 5.0, WNODE_HEADER) and unmodified event-specific data (possibly assembled from an MOF_FIELD array). If the header is specifically an EVENT_TRACE_HEADER, then in addition to changes that the function makes in the input header, the implementation generates the following for the header in the trace buffer: