Geoff Chappell - Software Analyst
DRAFT: Take more than your usual care.
This function is one of several for writing an event from user mode. Events written through this function have instance information for correlation with other events.
ULONG TraceEventInstance ( TRACEHANDLE SessionHandle, EVENT_INSTANCE_HEADER *EventTrace, EVENT_INSTANCE_INFO *pInstInfo, EVENT_INSTANCE_INFO *pParentInstInfo);
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 required pInstInfo and optional pParentInstInfo arguments each provide a registration handle and an instance identifier, the first for this event, the second for a parent event.
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 TraceEventInstance 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 EtwTraceEventInstance in its versions 5.2 and higher. For the NTDLL implementation, which behaves differently in ways that may be significant, follow the link: this note is concerned only with the function as implemented in ADVAPI32.
The TraceEventInstance function is documented. Whether it’s reliably documented is at least debatable. In nearly two decades of versions, wherever implemented, none yet behave quite as documented. The early versions, as covered here, are especially problematic. Since roughly 2010 Microsoft’s documentation has noted “Windows XP: Does not work correctly.”
Broadly, the TraceEventInstance function edits the given header, mostly to add from the EVENT_INSTANCE_INFO inputs, 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.
Without an EventTrace and pInstInfo, 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 of the input header and of the event-specific data that may follow depends on the Flags in the header. This applies not just to this function but also to the deeper machinery (see below), which in these early versions do double and even triple duty with various types of header. Apparently to insist on being given an EVENT_INSTANCE_HEADER rather than a WNODE_HEADER (such as accepted by the closely related TraceEvent function), version 5.0 requires that WNODE_FLAG_TRACED_GUID be set in the Flags on input, else the function fails, returning ERROR_INVALID_FLAGS (as still documented in 2018).
In version 5.1, neither this function nor TraceEvent supports a WNODE_HEADER as input. The function itself proceeds as if given an EVENT_INSTANCE_HEADER, even without WNODE_FLAG_TRACED_GUID. The deeper machinery, however, still supports the WNODE_HEADER for the kernel-mode IoWMIWriteEvent, and code for this is retained even for tracing to the user-mode logger. It requires that either WNODE_FLAG_TRACED_GUID or WNODE_FLAG_LOG_WNODE is set. If both are clear, the function fails, returning ERROR_INVALID_PARAMETER or ERROR_GEN_FAILURE (depending on whether the logger is user-mode or kernel-mode, but either way differing from the documentation).
The Size in the 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.
The pInstInfo and pParentInstInfo, if given, are useless without a RegHandle. Absence is fatal to the function, which returns ERROR_INVALID_PARAMETER.
In sending the event onwards, the function makes the following adjustments:
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”.
See that a RegHandle in an EVENT_INSTANCE_INFO is not directly usable as a RegHandle or ParentRegHandle in the header but is instead a pointer to an opaque structure that contains what’s wanted for the header. It is unclear what Microsoft’s documentation means when saying that callers must set these members of the header in advance.
That said, if pParentInstInfo is NULL, as when the caller wants that the event that can be a parent but does not have one, then the function ignores ParentInstanceId and ParentRegHandle in the header, and the caller might better clear them in advance.
The point to clearing WNODE_FLAG_USE_GUID_PTR is that the deeper implementation, doing double duty with an EVENT_TRACE_HEADER from the TraceEvent function, would otherwise try to interpret a GuidPtr where the EVENT_INSTANCE_HEADER has its RegHandle.
Of these changes to the input header, some will be undone when the function returns, but others persist as the function’s output: the SessionHandle at offset 0x08; and RegHandle, InstanceId, ParentInstanceId and ParentRegHandle.
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 NTDLL. 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:
Some deeper behaviour is 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_INSTANCE_HEADER except for the Size. This must be at least 0x58 bytes, else the function returns ERROR_INVALID_PARAMETER. Whether these 0x58 bytes are an alternate header or continuation of the EVENT_INSTANCE_HEADER is not known. Whichever, it provides at offsets 0x48 and 0x4C the address and size (and at offset 0x50 a preferred processor) of a buffer from which to obtain the whole data for the event, header and all, with no further interpretation.
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.
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.
What goes into the trace buffers, and which may then persist in an Event Trace Log (ETL) file, is the modified EVENT_INSTANCE_HEADER and unmodified event-specific data. The implementation ordinarily adds the following to the event on its way into the trace buffer:
All these EVENT_INSTANCE_HEADER members are irrelevant for input to TraceEventInstance, except in one configurable case: in version 5.1, the caller may set WNODE_FLAG_USE_TIMESTAMP to indicate that the input header already has a TimeStamp to use for the event in the trace buffer in preference to whatever the implementation would otherwise generate.
The function’s loading of RegHandle, InstanceId, ParentInstanceId and ParentRegHandle into the EVENT_INSTANCE_HEADER persists as the function’s output. So too does the SessionHandle at offset 0x08.
The function explicitly preserves the first four bytes of the header (which keeps the FieldTypeFlags reserved and the distinguishing value 0xC00B secret). Version 5.0 preserves the Flags. Version 5.1 does too, except that it sets TRACE_HEADER_FLAG_TRACED_GUID. Perhaps the intention is that if a call to TraceEventInstance fails for not having set this flag, it may succeed if simply repeated.