Last active
October 3, 2021 19:08
-
-
Save Zhentar/b12daf5683e6bcff455083f9f4bfa00e to your computer and use it in GitHub Desktop.
Unlocking the secrets of ETW: How to turn on COMPACT_CSWITCH and other kernel loggers that are undocumented outside of xperf/WPR
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Runtime.InteropServices; | |
namespace StartTraceExtended | |
{ | |
static class Program | |
{ | |
static unsafe void Main() | |
{ | |
var trace_props = new EVENT_TRACE_PROPERTIES(); | |
trace_props.Wnode.BufferSize = (uint)sizeof(EVENT_TRACE_PROPERTIES); | |
trace_props.Wnode.Flags = 0x20000; | |
trace_props.LoggerNameOffset = 0x78 /*offsetof(LoggerName)*/; | |
trace_props.LogFileNameOffset = 0x280 /*offsetof(LogFileName)*/; | |
trace_props.EnableFlags = 0x8000_0000 //extended flags enable flag | |
| 0xFF_0000 //Either: Length (in DWORDs) of the extended flag space, OR -1 means it goes to the end of the struct (thus being calculated from wnode.BufferSize) | |
| 0x_0488; /*offsetof(ExtendedFlagsTotalDWords)*/ //Beginning of the extended flags; | |
trace_props.ExtendedFlagsTotalDWords = 1; | |
//system trace control guid | |
trace_props.Wnode.Guid = new Guid(0x9E814AAD, 0x3204, 0x11D2, 0x9A, 0x82, 0x00, 0x60, 0x08, 0xA8, 0x69, 0x39); | |
//I don't think these are particularly important, but they match what xperf uses | |
trace_props.Wnode.ClientContext = 1; | |
trace_props.BufferSize = 0x40; | |
trace_props.MinimumBuffers = 0x40; | |
trace_props.MaximumBuffers = 0x140; | |
trace_props.LogFileMode = 1; | |
trace_props.AgeLimit = 15; | |
var logFileName = @"\kernel.etl"; | |
for (int i = 0; i < logFileName.Length; i++) | |
{ | |
trace_props.LogFileName[i] = logFileName[i]; | |
} | |
var loggerName = "NT Kernel Logger"; | |
for (int i = 0; i < loggerName.Length; i++) | |
{ | |
trace_props.LoggerName[i] = loggerName[i]; | |
} | |
//Set up the group mask: | |
trace_props.ExtendedFlagsTotalDWords += (ushort)(sizeof(ExtendedFlags_GroupMask) / 4); | |
trace_props.ExtendedFlagsTotalCount++; | |
trace_props.GroupMask = ExtendedFlags_GroupMask.GetNewGroupMaskEntry(); | |
//Now, the individual flags... | |
//See a full(?) list of kernel logger flags here: https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/etw/tracesup/perfinfo_groupmask.htm | |
//The top 3 bits of each define determine which mask field they go in, and aren't actually part of the masks | |
//So CSWITCH is 0x20000004, and COMPACT_CSWITCH 0x20000100, which means they go in the second mask as 0x4 | 0x100 | |
trace_props.GroupMask.Masks[1] = 0x0104; //CSWITCH+COMPACT_CSWITCH | |
trace_props.GroupMask.Masks[2] = 0x10000; //CPU_CONFIG - xperf always adds this one if it isn't set, I assume for a good reason | |
ulong handle = 0; | |
var result = StartTrace(ref handle, trace_props.LoggerName, &trace_props); | |
if (result != 0) | |
{ | |
Console.WriteLine(result.ToString("X8")); | |
Console.ReadLine(); | |
} | |
} | |
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] | |
public static unsafe extern uint StartTrace(ref ulong SessionHandle, void* SessionName, void* Properties); | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
public unsafe struct EVENT_TRACE_PROPERTIES | |
{ | |
public WNODE_HEADER Wnode; // Timer Resolution determined by the Wnode.ClientContext. | |
public uint BufferSize; | |
public uint MinimumBuffers; | |
public uint MaximumBuffers; | |
public uint MaximumFileSize; | |
public uint LogFileMode; | |
public uint FlushTimer; | |
public uint EnableFlags; | |
public int AgeLimit; | |
public uint NumberOfBuffers; | |
public uint FreeBuffers; | |
public uint EventsLost; | |
public uint BuffersWritten; | |
public uint LogBuffersLost; | |
public uint RealTimeBuffersLost; | |
public ulong LoggerThreadId; | |
public uint LogFileNameOffset; | |
public uint LoggerNameOffset; | |
public fixed char LoggerName[260]; | |
public fixed char LogFileName[260]; | |
//Secret extended flags struct. It's a list structure that can hold structs of varying size | |
//It starts with a header that describes the total populated size (header included), along | |
//with the count of discrete entries in the list. The entries can be walked by reading the size | |
//from each entry's header, stopping after total count entries. | |
public ushort ExtendedFlagsTotalDWords; | |
public ushort ExtendedFlagsTotalCount; | |
//The buffer should start here, but I only know the full structure for the one type of entry, | |
//so I'll just hardcode it here to simplify things. | |
public ExtendedFlags_GroupMask GroupMask; | |
//xperf can conditionally use two other types of extended flags, with Type values of 3 & 4 | |
//They appear to be heap tracing related | |
} | |
public struct ExtendedFlags_Header | |
{ | |
public ushort SizeInDWords; //The size of this flag struct, including the header, in 4-byte units | |
public ushort Type; //The enum type of this struct. Defined values unknown | |
} | |
public unsafe struct ExtendedFlags_GroupMask | |
{ | |
ExtendedFlags_Header Header; | |
public fixed uint Masks[8]; | |
public static ExtendedFlags_GroupMask GetNewGroupMaskEntry() | |
{ | |
var header = new ExtendedFlags_Header {SizeInDWords = (ushort) (sizeof(ExtendedFlags_GroupMask) / 4), Type = 1}; | |
return new ExtendedFlags_GroupMask {Header = header}; | |
} | |
} | |
public struct WNODE_HEADER | |
{ | |
public uint BufferSize; | |
public uint ProviderId; | |
public ulong HistoricalContext; | |
public ulong TimeStamp; | |
public Guid Guid; | |
public uint ClientContext; // Determines the time stamp resolution | |
public uint Flags; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment