NtTraceControl

This function is the central control point for Event Tracing For Windows (ETW). It supports many user-mode API functions for managing tracing sessions. Even the private tracing sessions that are implemented mostly in user mode need some support from the kernel and get it from this function.

Declaration

NTSTATUS 
NtTraceControl (
    ULONG FunctionCode, 
    PVOID InBuffer, 
    ULONG InBufferLen, 
    PVOID OutBuffer, 
    ULONG OutBufferLen, 
    ULONG *ReturnSize);

Parameters

The FunctionCode argument selects from the function’s many operations.

The optional InBuffer and InBufferLen arguments are respectively the address and size (in bytes) of a buffer that provides the function with input. What the function interprets of this input depends on the FunctionCode.

The optional OutBuffer and OutBufferLen arguments are respectively the address and size (in bytes) of a buffer that receives the function’s output. What the function puts into this buffer depends on the FunctionCode.

The required ReturnSize argument is the address of a variable whose value on output tells how many bytes the successful function has put into the output buffer or may tell how much the failed function might have put into the output buffer (had it been large enough).

Return Value

On failure, the function returns a negative error code. The usual return for success is STATUS_SUCCESS. For a few function codes, the function can return some other (positive) indication of success, such as STATUS_MORE_ENTRIES.

Availability

Both the NtTraceControl and ZwTraceControl functions are exported by name from NTDLL in version 6.0 and higher. There, in user mode, the functions are aliases for a stub that transfers execution to the NtTraceControl implementation in kernel mode such that the execution is recognised as originating in user mode.

This NtTraceControl implementation is exported by name from the kernel in version 6.0 and higher. Only in version 10.0 and higher does the kernel also export a ZwTraceControl. The kernel-mode version of ZwTraceControl is also a stub that transfers execution to the NtTraceControl implementation but such that the execution is recognised as originating in kernel mode.

Though no NtTraceControl exists before version 6.0, the kernel in versions 5.1 and 5.2 does export functions that are recognisable as precursors in the sense that each does the work that is later done through an NtTraceControl function code. These earlier functions are WmiStartTrace, WmiStopTrace, WmiQueryTrace, WmiUpdateTrace and WmiFlushTrace. This and other functionality of NtTraceControl is also supported in versions 5.0 to 5.2 as Device I/O Control through the WMI support device. Except for this paragraph to note that NtTraceControl did not arrive out of the blue for Windows Vista, none of this earlier support in any form is any concern here.

Documentation Status

Though the NtTraceControl and ZwTraceControl 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: NtTraceControl in NTWMI.H and ZwTraceControl in ZWAPI.H. Thus are Microsoft’s names and types known for the declaration above.

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, whether to update or to follow through with the history. 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.

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

Buffers

The InBuffer argument can be NULL to provide no input, in which case InBufferLen is ignored (literally, treated as zero). If an input buffer is given, meaning here that InBuffer is not NULL and InBufferLen is not zero, then the whole of the buffer must be in user-mode address space.

The OutBuffer argument can be NULL so that no output is requested, in which case OutBufferLen is ignored (literally, treated as zero). If an output buffer is given, meaning here that OutBuffer is not NULL and OutBufferLen is not zero, then the whole buffer must be in user-mode address space and be writable (at its first byte and also for a byte at each page boundary that is inside the buffer).

Return Size

A variable for learning how much output is or could be produced is required. The variable must be in user-mode address space and be writable. If instead ReturnSize is NULL, the function returns STATUS_INVALID_PARAMETER.

Kernel-Mode Acceptance

If executing for a kernel-mode request, all arguments are trusted as given. This means in particular that behaviour is undefined if a non-zero buffer size is given for a NULL address and there is no rejection of NULL for ReturnSize.

Double-Buffering

Except for the following function codes in the applicable versions

or if given no buffer for either input or output, the function double-buffers. Specifically, it obtains from the paged pool an allocation whose size is the larger of the input and output buffers. If it cannot get this memory, it returns STATUS_NO_MEMORY. If an input buffer is given, the function copies the whole of it to the double buffer so that all further work with the input is from the double buffer, not from the input buffer. If the function prepares output, it does so in the double buffer and copies to the caller-supplied output buffer only when about to return STATUS_SUCCESS.

Exception Handling

The function never accesses InBuffer, OutBuffer or ReturnSize without preparing for exceptions. The occurrence of an exception during such access is fatal to the function, which returns the exception code as its own result.

Valid Function Codes

Microsoft’s names for eight of the valid function codes are known from type information in symbol files that Microsoft first published for Windows 8—though even then, not the symbol files for the kernel, which interprets the codes, nor for the obvious low-level user-mode DLLs that use the codes for their calls to NtTraceControl. Instead, they somehow find their way into symbol files for such things as AppXDeploymentClient.dll.

If only as known to these user-mode modules, the function codes apparently take their values from an enumeration named ETWTRACECONTROLCODE. A formal C-language definition is published in the NTETW.H from the Enterprise WDK for Windows 10 version 1511, but repeats just the eight that had been disclosed in symbol files. This header’s inclusion by source code for some of Microsoft’s user-mode software is presumably where those few symbol files get type information for the enumeration. It is not impossible that the full enumeration is defined for the kernel from some other header and even to give it a different name.

The table below lists the function codes that the function does not dismiss as invalid (after the preceding defences). For all others, the function returns STATUS_INVALID_DEVICE_REQUEST.

Numeric Value Symbolic Name Versions
0x01 EtwStartLoggerCode 6.0 and higher
0x02 EtwStopLoggerCode 6.0 and higher
0x03 EtwQueryLoggerCode 6.0 and higher
0x04 EtwUpdateLoggerCode 6.0 and higher
0x05 EtwFlushLoggerCode 6.0 and higher
0x0B real-time connect 6.0 and higher
0x0C EtwActivityIdCreate 6.0 and higher
0x0D EtwWdiScenarioCode 6.0 and higher
0x0E real-time disconnect consumer by handle 6.0 and higher
0x0F register user-mode GUID 6.0 and higher
0x10 receive notification 6.0 and higher
0x11 send notification 6.0 and higher
0x12 send reply data block 6.0 and higher
0x13 receive reply data block 6.0 and higher
0x14 EtwWdiSemUpdate 6.0 and higher
0x15 get trace GUID list 6.0 and higher
0x16 get trace GUID information 6.0 and higher
0x17 enumerate trace GUIDs 6.0 and higher
0x18 register security provider 6.0 and higher
0x19 query reference time 6.2 and higher
0x1A track provider binary 6.2 and higher
0x1B add notification event 6.3 and higher
0x1C update disallow list 10.0 and higher
0x1E set provider traits 10.0 and higher
0x1F use descriptor type 10.0 and higher
0x20 get trace group list 10.0 and higher
0x21 get trace group information 10.0 and higher
0x22 get disallow list 10.0 and higher
0x23 set compression settings 1607 and higher
0x24 get compression settings 1607 and higher
0x25 update periodic capture state 1703 and higher
0x26 get private session trace handle 1703 and higher
0x27 register private session 1703 and higher
0x28 query session demux object 1703 and higher
0x29 set provider binary tracking 1709 and higher
0x2A   1709 and higher

The function’s behaviour varies greatly with the function code. Follow the links.

Output

For each function code, the function may of course succeed or fail. If it succeeds, it may have prepared output in the double buffer. If so, it copies this output to the caller-supplied OutBuffer. With or without output, the successful function also sets the variable at ReturnSize to the number of bytes it has placed in the output buffer.

The failed function does not produce output but it may set the variable at ReturnSize to show what output it might have produced in different circumstances. The obvious such circumstance is that OutBufferLen was too small. This is indicated by the return of STATUS_BUFFER_TOO_SMALL for the following function codes:

No matter what the error code, the function sets the variable at ReturnSize if the function code is any of: