MESSAGE_TRACE_USER

The MESSAGE_TRACE_USER structure describes an event for a particular case of the NtTraceEvent interface between user mode and kernel mode.

Usage

The types of event that are described to NtTraceEvent by a MESSAGE_TRACE_USER are used mostly for Windows Pre-Processor (WPP) software tracing. This is diagnostic magic that gives low-level programmers the functionality of Event Tracing for Windows (ETW) for seemingly no more trouble than calling a programmer-defined function in the familiar style of printf from the C Run-Time library. The more or less arbitrary arguments of the latter get dressed into an argument list for the documented ADVAPI32 functions TraceMessage and TraceMessageVa or the undocumented NTDLL functions EtwTraceMessage and EtwTraceMessageVa. (The former two are merely forwarded to the latter in version 5.2 and higher.) For transferring the work to the kernel, these functions’ several arguments—the SessionHandle, MessageFlags, MessageGuid, MessageNumber and the more or less arbitrary MessageArgList—are repackaged into a MESSAGE_TRACE_USER.

Documentation Status

The MESSAGE_TRACE_USER structure is not documented, but Microsoft has published a C-language definition in the NTWMI.H from the Enterprise edition of the Windows Driver Kit (WDK) for Windows 10 version 1511.

Were it not for this relatively recent and possibly unintended disclosure, much would anyway be known from type information in symbol files. Curiously though, type information for this structure has never appeared in any public symbol files for the kernel or for the obvious low-level user-mode DLLs. In the whole of Microsoft’s packages of public symbol files, at least to the original Windows 10, relevant type information is unknown before Windows 8 and appears in symbol files only for AppXDeploymentClient.dll, CertEnroll.dll (before Windows 10) and Windows.Storage.ApplicationData.dll.

Layout

Though the MESSAGE_TRACE_USER is shared between kernel and user modes, it evidently is regarded as a private detail. It has changed—first to have some members rearranged and then to have one removed. Importantly, it also changed from being a fixed-size header for variable-size data to being a structure with a pointer to the variable-size data. The MESSAGE_TRACE_USER is 0x30 bytes before version 6.2 and is then 0x28 bytes. It is the same in both 32-bit and 64-bit builds.

Offset Definition Versions Remarks
0x00 (6.0 to 6.1)
TRACEHANDLE SessionHandle;
6.0 to 6.1 previously at 0x10
0x00 (5.1 to 5.2);
0x08 (6.0 to 6.1);
0x00
MESSAGE_TRACE_HEADER MessageHeader;
5.1 and higher  
0x08 (5.1 to 5.2)
ULONG MessageFlags;
5.1 to 5.2 next at 0x20
0x10 (5.1 to 5.2)
TRACEHANDLE SessionHandle;
5.1 to 5.2 next at 0x00
0x18 (5.1 to 5.2);
0x10 (6.0 to 6.1);
0x08
GUID MessageGuid;
5.1 and higher  
0x20 (6.0 to 6.1);
0x18
ULONG MessageFlags;
6.0 and higher previously at 0x08
0x28 (5.1 to 5.2);
0x24 (6.0 to 6.1);
0x1C
ULONG DataSize;
5.1 and higher  
0x2C (5.1 to 5.2);
0x28 (6.0 to 6.1);
0x20
UCHAR Data [1];
5.1 to 5.2  
ULONG64 Data;
6.0 and higher  

The name SessionHandle is proposed for the TRACEHANDLE by analogy with the other members whose names Microsoft duplicates from the TraceMessage arguments. However it’s named, it provides for the whole of the 8-byte SessionHandle argument, presumably to compensate for NtTraceEvent having only a possibly smaller HANDLE as its TraceHandle argument. It is ignored by the kernel-mode implementation, since the low 4 bytes are ample, and was removed for version 6.2.

The MessageTraceHeader has only ever mattered for passing the MessageNumber argument through the MessageNumber member. Except for that, the kernel-mode implementation prepares its own MESSAGE_TRACE_HEADER for the event as written to trace buffers.

Just as the MessageGuid argument addresses either a GUID or a 4-byte component ID, depending on the MessageFlags, so too the MessageGuid can be either a GUID or a 4-byte component ID. It is the latter if TRACE_MESSAGE_COMPONENTID is set in MessageFlags, else it is the former if TRACE_MESSAGE_GUID is set, else it is ignored.

In all versions, the data that originates from a variable-size MessageArgList of pointers and sizes passes into the trace buffer as a simple array of bytes in the variable-size event-specific data after the event’s own MESSAGE_TRACE_HEADER. That this array was assembled from parts is lost. If a consumer is to intepret this data in parts, it is from separate knowledge of what the provider intended. In WPP Tracing, this happens because the consumer has a PDB file from the provider, and this file, not the executable, has the format strings from the provider’s source code.

In versions before 6.0, all interpretation of the argument list is done in user mode, such that NtTraceEvent receives the arbitrary message data already formed into a simple array at offset 0x2C, the size in bytes being given by DataSize at offset 0x28. The name Data is proposed for the array on the grounds that although its type changed for version 6.0, its name needn’t have and perhaps didn’t. The change for version 6.0 is that assembling the message data from the argument list is now the kernel’s work. Though the Data member is formally a ULONG64, it is in fact the 32-bit or 64-bit MessageArgList from the user-mode caller. It is thus a user-mode address of a sequence of user-mode pointers and sizes, in pairs, for successive message arguments, ending with a NULL pointer. The DataSize is the size, in bytes, of this sequence, including the NULL pointer.