Skip to content

Instantly share code, notes, and snippets.

@Zhentar
Last active October 3, 2021 19:08
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Zhentar/b12daf5683e6bcff455083f9f4bfa00e to your computer and use it in GitHub Desktop.
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
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