Geoff Chappell, Software Analyst
DRAFT: Take more than your usual care.
This function writes a message event to an event tracing session from user mode.
ULONG TraceMessageVa ( TRACEHANDLE SessionHandle, ULONG MessageFlags, GUID *MessageGuid, USHORT MessageNumber, va_list MessageArgList);
The required SessionHandle selects the event tracing session, also called a logger, to which the event is to be written.
The MessageFlags argument directs the logger to add items to the event in addition to the message arguments. The following bits are recognised:
|0x00000001||TRACE_MESSAGE_SEQUENCE||include sequence number as event-specific data|
|0x00000002||TRACE_MESSAGE_GUID||include GUID as event-specific data|
|0x00000004||TRACE_MESSAGE_COMPONENTID||include component ID as event-specific data|
|0x00000008||TRACE_MESSAGE_TIMESTAMP||include time stamp as event-specific data|
|0x00000010||TRACE_MESSAGE_PERFORMANCE_TIMESTAMP||use performance counter for time stamp|
|0x00000020||TRACE_MESSAGE_SYSTEMINFO||include thread and process IDs as event-specific data|
The optional MessageGuid argument supplies either a component ID or a GUID to add to the event as an identifier.
The MessageNumber argument is the primary identifier of the event. The plain intention is that events that have the same MessageNumber, perhaps in combination with the identifier from MessageGuid, are recurrences of the one event and have the same interpretation of message arguments.
The optional MessageArgList is the address of an array of pointer-sized elements that present the message arguments. Successive pairs in the array each provide respectively the address and size of one message argument. This sequence ends with an array element that would represent a NULL address. The concatenation of these message arguments becomes event-specific data to add to the event. The function does not interpret the message arguments.
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 TraceMessageVa function is exported by name from ADVAPI32 in version 5.1 and higher. Starting with version 5.2, it is merely a forward to the NTDLL export EtwTraceMessageVa 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.
Very similar functionality can be arranged in version 5.0 through the TraceEvent function with an MOF_FIELD array for the message arguments, but TraceMessageVa provides distinctly new functionality in one important sense: the events that it writes get a distinctive type of trace header, a MESSAGE_TRACE_HEADER, in contrast to the EVENT_TRACE_HEADER.
This new functionality looks to have been devised as special support for Windows Pre-Processor (WPP) Tracing. This is diagnostic magic that turns drivers, services, DLLs and other low-level program code into event providers that write events for seemingly no more trouble than calling a programmer-defined function in the familiar style of printf from the C Run-Time library. Each source file is assigned a MessageGuid and the trace statements within a source file are each assigned a MessageNumber. The format strings for the trace statements go to the PDB file (with the happy side-effect that the tracing neither bloats the executable nor helps reverse engineers). Interpretation of the message arguments for each combination of MessageGuid and MessageNumber is done by an event consumer that has the PDB file.
The TraceMessageVa function is documented.
Broadly, the TraceMessageVa function creates an event from the function’s inputs and sends it into whichever of the kernel-mode or user-mode event tracing machinery is appropriate for the session.
All the function’s 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.
If the SessionHandle has the 0x01000000 bit set, the tracing session is a user-mode logger—in this version, 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 NtTraceEvent (with ETW_NT_FLAGS_TRACE_MESSAGE set in the Flags). To form the inputs for this call, the function assembles its own inputs into a single packet of event data on the heap. This packet begins as a fixed-size MESSAGE_TRACE_USER header whose Data member in turn begins the variable-size message arguments all assembled into one byte array. The Data member is at offset 0x2C but the header’s size is 0x30 bytes. If the total of 0x30 bytes for the header and of all bytes for the message arguments exceeds 8KB, the function fails, returning ERROR_BUFFER_OVERFLOW. The function also fails, returning ERROR_NOT_ENOUGH_MEMORY (which the documentation says is “Not supported”), if sufficient space cannot be obtained on the heap.
Wherever the event goes, the handling is similar and whatever results is success or failure for the function. Aside from ERROR_NOACCESS from the kernel’s probes of what it expects to be user-mode addresses, the most notable failures are:
What goes into the trace buffers, and which may then persist in an Event Trace Log (ETL) file, is a MESSAGE_TRACE_HEADER (which tells the total size and contains the MessageFlags and MessageNumber) followed by event-specific data in the following order:
|32-bit sequence number||TRACE_MESSAGE_SEQUENCE is set|
|16-byte GUID from MessageGuid||TRACE_MESSAGE_GUID is set and TRACE_MESSAGE_COMPONENTID is clear|
|32-bit component ID from MessageGuid||TRACE_MESSAGE_COMPONENTID is set|
|8-byte time stamp||TRACE_MESSAGE_TIMESTAMP is set|
|32-bit thread and process IDs (in that order)||TRACE_MESSAGE_SYSTEMINFO is set|
|message arguments as byte array|
The time stamp is the high-frequency performance counter if TRACE_MESSAGE_PERFORMANCE_TIMESTAMP is set in the MessageFlags. Otherwise, it is the system time.