mirror of
https://github.com/thefLink/Hunt-Weird-Syscalls
synced 2026-06-24 08:41:12 +00:00
initial commit
This commit is contained in:
@@ -0,0 +1,76 @@
|
|||||||
|
#include "Detectors.h"
|
||||||
|
|
||||||
|
namespace Detectors {
|
||||||
|
|
||||||
|
std::vector<PCSTR> allowedSyscallmodules = {
|
||||||
|
"ntdll.dll",
|
||||||
|
"win32u.dll",
|
||||||
|
"wow64win.dll"
|
||||||
|
};
|
||||||
|
|
||||||
|
PCSTR SyscallAllowOpenThread = "NtOpenThread";
|
||||||
|
PCSTR SyscallAllowSetThreadContext = "NtSetContextThread";
|
||||||
|
|
||||||
|
VOID DirectSyscall ( DWORD pid, HANDLE hProcess, std::vector<ULONG_PTR> stack ) {
|
||||||
|
|
||||||
|
std::string lastModule;
|
||||||
|
BOOL bSuccess = FALSE;
|
||||||
|
|
||||||
|
bSuccess = Helpers::ModuleNameFromAddress ( hProcess, ( PVOID ) stack.front ( ), lastModule );
|
||||||
|
if ( bSuccess == FALSE )
|
||||||
|
return;
|
||||||
|
|
||||||
|
for ( auto it = allowedSyscallmodules.begin ( ); it != allowedSyscallmodules.end ( ); ++it ) {
|
||||||
|
if ( !_stricmp ( *it, lastModule.c_str ( ) ) )
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf ( "! Direct Syscall detected from process: %d\n", pid );
|
||||||
|
printf ( "\t Syscall from: 0x%p (%s)\n", ( PVOID ) stack.front ( ), lastModule.c_str ( ) );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID InDirectSyscall ( DWORD pid, HANDLE hProcess, std::vector<ULONG_PTR> stack, PCSTR allowedSyscall ) {
|
||||||
|
|
||||||
|
MEMORY_BASIC_INFORMATION mbi = { 0 };
|
||||||
|
|
||||||
|
std::string lastModule;
|
||||||
|
HMODULE hNtdll = NULL;
|
||||||
|
|
||||||
|
BOOL bSuccess = FALSE;
|
||||||
|
SIZE_T s = 0;
|
||||||
|
ULONG_PTR offsetIs = 0, offsetExpected = 0;
|
||||||
|
PVOID returnExpected = NULL;
|
||||||
|
|
||||||
|
bSuccess = Helpers::ModuleNameFromAddress ( hProcess, ( PVOID ) stack.front ( ), lastModule );
|
||||||
|
if ( bSuccess == FALSE )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( _strcmpi ( lastModule.c_str ( ), "ntdll.dll"))
|
||||||
|
return; // Currently only verifying ntdll.dll syscalls
|
||||||
|
|
||||||
|
s = VirtualQueryEx ( hProcess, ( LPCVOID ) stack.front ( ), &mbi, sizeof ( MEMORY_BASIC_INFORMATION ) );
|
||||||
|
if ( s == 0 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
offsetIs = stack.front ( ) - ( ULONG_PTR ) mbi.BaseAddress;
|
||||||
|
|
||||||
|
hNtdll = GetModuleHandleA ( "ntdll.dll" );
|
||||||
|
|
||||||
|
returnExpected = GetProcAddress ( hNtdll, allowedSyscall);
|
||||||
|
s = VirtualQuery ( returnExpected, &mbi, sizeof ( MEMORY_BASIC_INFORMATION ) );
|
||||||
|
if (s == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
offsetExpected = ( ( PBYTE ) returnExpected - ( PBYTE ) mbi.BaseAddress );
|
||||||
|
if ( offsetExpected < offsetIs && offsetIs <= offsetExpected + 23 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf ( "! Indirect Syscall detected from process: %d\n", pid );
|
||||||
|
printf ( "\t Syscall %s expected from: stub at 0x%p but was: 0x%llx\n", allowedSyscall, returnExpected, stack.front ( ) );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "windows.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Helpers.h"
|
||||||
|
|
||||||
|
namespace Detectors {
|
||||||
|
|
||||||
|
extern std::vector<PCSTR> SyscallsAllowOpenProcess;
|
||||||
|
extern PCSTR SyscallAllowOpenThread;
|
||||||
|
extern PCSTR SyscallAllowSetThreadContext;
|
||||||
|
|
||||||
|
VOID DirectSyscall ( DWORD pid, HANDLE hProcess, std::vector<ULONG_PTR> stack );
|
||||||
|
VOID InDirectSyscall ( DWORD pid, HANDLE hProcess, std::vector<ULONG_PTR> stack, PCSTR );
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#include "Helpers.h"
|
||||||
|
|
||||||
|
namespace Helpers {
|
||||||
|
|
||||||
|
BOOL ModuleNameFromAddress ( HANDLE hProcess, PVOID pAddr, std::string& moduleName ) {
|
||||||
|
|
||||||
|
BOOL bSuccess = FALSE;
|
||||||
|
SIZE_T s = 0;
|
||||||
|
MEMORY_BASIC_INFORMATION mbi = { 0 };
|
||||||
|
CHAR cmoduleName [ MAX_PATH ] = { 0 };
|
||||||
|
|
||||||
|
s = VirtualQueryEx ( hProcess, ( LPCVOID ) pAddr, &mbi, sizeof ( MEMORY_BASIC_INFORMATION ) );
|
||||||
|
if ( s == 0 )
|
||||||
|
goto Cleanup;
|
||||||
|
|
||||||
|
bSuccess = K32GetModuleBaseNameA ( hProcess, ( HMODULE ) mbi.AllocationBase, ( LPSTR ) cmoduleName, MAX_PATH );
|
||||||
|
if ( bSuccess == FALSE )
|
||||||
|
goto Cleanup;
|
||||||
|
|
||||||
|
moduleName = std::string ( cmoduleName );
|
||||||
|
|
||||||
|
bSuccess = TRUE;
|
||||||
|
|
||||||
|
Cleanup:
|
||||||
|
|
||||||
|
return bSuccess;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID RemoveKernelAddrs ( std::vector<ULONG_PTR>& stack ) {
|
||||||
|
|
||||||
|
auto it = stack.begin ( );
|
||||||
|
while ( it != stack.end ( ) ) {
|
||||||
|
|
||||||
|
ULONG_PTR addr = *it;
|
||||||
|
if ( addr > 0xFFFF000000000000 ) {
|
||||||
|
it = stack.erase ( it );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//https://github.com/outflanknl/Dumpert/blob/master/Dumpert/Outflank-Dumpert/Dumpert.c Is Elevated() was taken from here :).
|
||||||
|
BOOL IsElevated ( VOID ) {
|
||||||
|
BOOL fRet = FALSE;
|
||||||
|
HANDLE hToken = NULL;
|
||||||
|
if ( OpenProcessToken ( GetCurrentProcess ( ), TOKEN_QUERY, &hToken ) ) {
|
||||||
|
TOKEN_ELEVATION Elevation = { 0 };
|
||||||
|
DWORD cbSize = sizeof ( TOKEN_ELEVATION );
|
||||||
|
if ( GetTokenInformation ( hToken, TokenElevation, &Elevation, sizeof ( Elevation ), &cbSize ) ) {
|
||||||
|
fRet = Elevation.TokenIsElevated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( hToken ) {
|
||||||
|
CloseHandle ( hToken );
|
||||||
|
}
|
||||||
|
return fRet;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "windows.h"
|
||||||
|
#include "psapi.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Helpers {
|
||||||
|
VOID RemoveKernelAddrs ( std::vector<ULONG_PTR>& );
|
||||||
|
BOOL ModuleNameFromAddress ( HANDLE, PVOID, std::string& );
|
||||||
|
BOOL IsElevated ( VOID );
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 16
|
||||||
|
VisualStudioVersion = 16.0.32413.511
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Hunt-Weird-Syscalls", "Hunt-Weird-Syscalls.vcxproj", "{B1446E95-C861-4AD9-8A49-D18B97C3108C}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SetThreadContext_Direct", "SetThreadContext_Direct\SetThreadContext_Direct.vcxproj", "{C49CD6BF-0525-4270-BEF7-7DEC29630191}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SetThreadContext_Indirect", "SetThreadContext_Indirect\SetThreadContext_Indirect.vcxproj", "{77C9B594-A2E6-40AB-BA92-E23FC1EAE781}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{B1446E95-C861-4AD9-8A49-D18B97C3108C}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{B1446E95-C861-4AD9-8A49-D18B97C3108C}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{B1446E95-C861-4AD9-8A49-D18B97C3108C}.Debug|x86.ActiveCfg = Debug|Win32
|
||||||
|
{B1446E95-C861-4AD9-8A49-D18B97C3108C}.Debug|x86.Build.0 = Debug|Win32
|
||||||
|
{B1446E95-C861-4AD9-8A49-D18B97C3108C}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{B1446E95-C861-4AD9-8A49-D18B97C3108C}.Release|x64.Build.0 = Release|x64
|
||||||
|
{B1446E95-C861-4AD9-8A49-D18B97C3108C}.Release|x86.ActiveCfg = Release|Win32
|
||||||
|
{B1446E95-C861-4AD9-8A49-D18B97C3108C}.Release|x86.Build.0 = Release|Win32
|
||||||
|
{C49CD6BF-0525-4270-BEF7-7DEC29630191}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{C49CD6BF-0525-4270-BEF7-7DEC29630191}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{C49CD6BF-0525-4270-BEF7-7DEC29630191}.Debug|x86.ActiveCfg = Debug|Win32
|
||||||
|
{C49CD6BF-0525-4270-BEF7-7DEC29630191}.Debug|x86.Build.0 = Debug|Win32
|
||||||
|
{C49CD6BF-0525-4270-BEF7-7DEC29630191}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{C49CD6BF-0525-4270-BEF7-7DEC29630191}.Release|x64.Build.0 = Release|x64
|
||||||
|
{C49CD6BF-0525-4270-BEF7-7DEC29630191}.Release|x86.ActiveCfg = Release|Win32
|
||||||
|
{C49CD6BF-0525-4270-BEF7-7DEC29630191}.Release|x86.Build.0 = Release|Win32
|
||||||
|
{77C9B594-A2E6-40AB-BA92-E23FC1EAE781}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{77C9B594-A2E6-40AB-BA92-E23FC1EAE781}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{77C9B594-A2E6-40AB-BA92-E23FC1EAE781}.Debug|x86.ActiveCfg = Debug|Win32
|
||||||
|
{77C9B594-A2E6-40AB-BA92-E23FC1EAE781}.Debug|x86.Build.0 = Debug|Win32
|
||||||
|
{77C9B594-A2E6-40AB-BA92-E23FC1EAE781}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{77C9B594-A2E6-40AB-BA92-E23FC1EAE781}.Release|x64.Build.0 = Release|x64
|
||||||
|
{77C9B594-A2E6-40AB-BA92-E23FC1EAE781}.Release|x86.ActiveCfg = Release|Win32
|
||||||
|
{77C9B594-A2E6-40AB-BA92-E23FC1EAE781}.Release|x86.Build.0 = Release|Win32
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {D52D4FC8-D03B-44EE-95E7-EEF1749CA236}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<ProjectConfiguration Include="Debug|Win32">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|Win32">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Debug|x64">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|x64">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<VCProjectVersion>16.0</VCProjectVersion>
|
||||||
|
<Keyword>Win32Proj</Keyword>
|
||||||
|
<ProjectGuid>{b1446e95-c861-4ad9-8a49-d18b97c3108c}</ProjectGuid>
|
||||||
|
<RootNamespace>HuntWeirdSyscalls</RootNamespace>
|
||||||
|
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v142</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v142</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="Shared">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<LinkIncremental>true</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<LinkIncremental>false</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<LinkIncremental>true</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<LinkIncremental>false</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="Detectors.cpp" />
|
||||||
|
<ClCompile Include="Helpers.cpp" />
|
||||||
|
<ClCompile Include="Main.cpp" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="Detectors.h" />
|
||||||
|
<ClInclude Include="Helpers.h" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
</ImportGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup>
|
||||||
|
<Filter Include="Source Files">
|
||||||
|
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||||
|
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Header Files">
|
||||||
|
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||||
|
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Resource Files">
|
||||||
|
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||||
|
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||||
|
</Filter>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="Main.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="Helpers.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="Detectors.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="Helpers.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="Detectors.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup />
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
#include "../libs/krabs/krabs.hpp"
|
||||||
|
|
||||||
|
#include "Detectors.h"
|
||||||
|
#include "Helpers.h"
|
||||||
|
|
||||||
|
#define EVENTID_SETTHTREADCONTEXT 4
|
||||||
|
#define EVENTID_OPENTHREAD 6
|
||||||
|
|
||||||
|
VOID EnableAuditApiTracing ( krabs::user_trace& );
|
||||||
|
VOID OnObservableSyscall ( const EVENT_RECORD&, const krabs::trace_context& );
|
||||||
|
|
||||||
|
VOID EnableAuditApiTracing ( krabs::user_trace& userTrace ) {
|
||||||
|
|
||||||
|
krabs::provider<>* providerApiTracing = new krabs::provider<> ( L"Microsoft-Windows-Kernel-Audit-API-Calls" );
|
||||||
|
|
||||||
|
providerApiTracing->trace_flags ( providerApiTracing->trace_flags ( ) | EVENT_ENABLE_PROPERTY_STACK_TRACE );
|
||||||
|
|
||||||
|
krabs::event_filter* filterOpenThread = new krabs::event_filter ( krabs::predicates::id_is ( EVENTID_OPENTHREAD ) ); // OpenThread
|
||||||
|
krabs::event_filter* filterSetContextThread = new krabs::event_filter ( krabs::predicates::id_is ( EVENTID_SETTHTREADCONTEXT ) ); // OpenThread
|
||||||
|
|
||||||
|
/* For now no distinction between events */
|
||||||
|
filterOpenThread->add_on_event_callback ( OnObservableSyscall );
|
||||||
|
filterSetContextThread->add_on_event_callback ( OnObservableSyscall );
|
||||||
|
|
||||||
|
providerApiTracing->add_filter ( *filterSetContextThread );
|
||||||
|
providerApiTracing->add_filter ( *filterOpenThread );
|
||||||
|
|
||||||
|
userTrace.enable ( *providerApiTracing );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID OnObservableSyscall ( const EVENT_RECORD& record, const krabs::trace_context& trace_context ) {
|
||||||
|
|
||||||
|
BOOL bSuccess = FALSE;
|
||||||
|
HANDLE hProcess = NULL;
|
||||||
|
DWORD pid = 0;
|
||||||
|
|
||||||
|
krabs::schema schema ( record, trace_context.schema_locator );
|
||||||
|
krabs::parser parser ( schema );
|
||||||
|
std::vector<ULONG_PTR> stack;
|
||||||
|
|
||||||
|
pid = record.EventHeader.ProcessId;
|
||||||
|
stack = schema.stack_trace ( );
|
||||||
|
|
||||||
|
if ( pid == GetCurrentProcessId ( ) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
Helpers::RemoveKernelAddrs ( stack );
|
||||||
|
if ( stack.size ( ) == 0 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
hProcess = OpenProcess ( PROCESS_ALL_ACCESS, FALSE, pid );
|
||||||
|
if ( hProcess == NULL )
|
||||||
|
return;
|
||||||
|
|
||||||
|
Detectors::DirectSyscall ( pid, hProcess, stack );
|
||||||
|
|
||||||
|
if ( record.EventHeader.EventDescriptor.Id == EVENTID_OPENTHREAD )
|
||||||
|
Detectors::InDirectSyscall ( pid, hProcess, stack, Detectors::SyscallAllowOpenThread);
|
||||||
|
else if ( record.EventHeader.EventDescriptor.Id == EVENTID_SETTHTREADCONTEXT )
|
||||||
|
Detectors::InDirectSyscall ( pid, hProcess, stack, Detectors::SyscallAllowSetThreadContext );
|
||||||
|
|
||||||
|
Cleanup:
|
||||||
|
|
||||||
|
if ( hProcess )
|
||||||
|
CloseHandle ( hProcess );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID Go ( krabs::user_trace* userTrace ) {
|
||||||
|
userTrace->start ( );
|
||||||
|
}
|
||||||
|
|
||||||
|
int main ( int argc, char** argv ) {
|
||||||
|
|
||||||
|
HANDLE traceThread = NULL;
|
||||||
|
|
||||||
|
krabs::user_trace userTrace ( L"Hunt-Weird-Syscalls" );
|
||||||
|
|
||||||
|
if ( !Helpers::IsElevated ( ) ) {
|
||||||
|
printf ( "- Not elevated\n" );
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf ( "* Enabling trace, might take a bit ... \n" );
|
||||||
|
|
||||||
|
EnableAuditApiTracing ( userTrace );
|
||||||
|
traceThread = CreateThread ( NULL, 0, ( LPTHREAD_START_ROUTINE ) Go, &userTrace, 0, NULL );
|
||||||
|
if ( traceThread == NULL )
|
||||||
|
return 0; // o.0
|
||||||
|
|
||||||
|
printf ( "* Started monitoring, press any key to exit ... \n" );
|
||||||
|
|
||||||
|
getchar ( );
|
||||||
|
printf ( "* exiting ... \n" );
|
||||||
|
userTrace.stop ( );
|
||||||
|
WaitForSingleObject ( traceThread, INFINITE );
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#include "windows.h"
|
||||||
|
#include "threadcontext_embedded.h"
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
|
||||||
|
NtSetContextThread(-1, NULL);
|
||||||
|
|
||||||
|
getchar();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<ProjectConfiguration Include="Debug|Win32">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|Win32">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Debug|x64">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|x64">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<VCProjectVersion>16.0</VCProjectVersion>
|
||||||
|
<Keyword>Win32Proj</Keyword>
|
||||||
|
<ProjectGuid>{c49cd6bf-0525-4270-bef7-7dec29630191}</ProjectGuid>
|
||||||
|
<RootNamespace>SetThreadContextDirect</RootNamespace>
|
||||||
|
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v142</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v142</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.props" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="Shared">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<LinkIncremental>true</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<LinkIncremental>false</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<LinkIncremental>true</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<LinkIncremental>false</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="threadcontext_embedded.h" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="Main.c" />
|
||||||
|
<ClCompile Include="threadcontext_embedded.c" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<MASM Include="threadcontext_embedded_-asm.x64.asm">
|
||||||
|
<FileType>Document</FileType>
|
||||||
|
</MASM>
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
|
||||||
|
</ImportGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup>
|
||||||
|
<Filter Include="Source Files">
|
||||||
|
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||||
|
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Header Files">
|
||||||
|
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||||
|
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Resource Files">
|
||||||
|
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||||
|
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||||
|
</Filter>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="threadcontext_embedded.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="threadcontext_embedded.c">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="Main.c">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<MASM Include="threadcontext_embedded_-asm.x64.asm">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</MASM>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup />
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
#include "threadcontext_embedded.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
//#define DEBUG
|
||||||
|
|
||||||
|
// JUMPER
|
||||||
|
|
||||||
|
#ifdef _M_IX86
|
||||||
|
|
||||||
|
EXTERN_C PVOID internal_cleancall_wow64_gate(VOID) {
|
||||||
|
return (PVOID)__readfsdword(0xC0);
|
||||||
|
}
|
||||||
|
|
||||||
|
__declspec(naked) BOOL local_is_wow64(void)
|
||||||
|
{
|
||||||
|
__asm {
|
||||||
|
mov eax, fs:[0xc0]
|
||||||
|
test eax, eax
|
||||||
|
jne wow64
|
||||||
|
mov eax, 0
|
||||||
|
ret
|
||||||
|
wow64:
|
||||||
|
mov eax, 1
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Code below is adapted from @modexpblog. Read linked article for more details.
|
||||||
|
// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams
|
||||||
|
|
||||||
|
SW3_SYSCALL_LIST SW3_SyscallList;
|
||||||
|
|
||||||
|
// SEARCH_AND_REPLACE
|
||||||
|
#ifdef SEARCH_AND_REPLACE
|
||||||
|
// THIS IS NOT DEFINED HERE; don't know if I'll add it in a future release
|
||||||
|
EXTERN void SearchAndReplace(unsigned char[], unsigned char[]);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DWORD SW3_HashSyscall(PCSTR FunctionName)
|
||||||
|
{
|
||||||
|
DWORD i = 0;
|
||||||
|
DWORD Hash = SW3_SEED;
|
||||||
|
|
||||||
|
while (FunctionName[i])
|
||||||
|
{
|
||||||
|
WORD PartialName = *(WORD*)((ULONG_PTR)FunctionName + i++);
|
||||||
|
Hash ^= PartialName + SW3_ROR8(Hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef JUMPER
|
||||||
|
PVOID SC_Address(PVOID NtApiAddress)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
PVOID SC_Address(PVOID NtApiAddress)
|
||||||
|
{
|
||||||
|
DWORD searchLimit = 512;
|
||||||
|
PVOID SyscallAddress;
|
||||||
|
|
||||||
|
#ifdef _WIN64
|
||||||
|
// If the process is 64-bit on a 64-bit OS, we need to search for syscall
|
||||||
|
BYTE syscall_code[] = { 0x0f, 0x05, 0xc3 };
|
||||||
|
ULONG distance_to_syscall = 0x12;
|
||||||
|
#else
|
||||||
|
// If the process is 32-bit on a 32-bit OS, we need to search for sysenter
|
||||||
|
BYTE syscall_code[] = { 0x0f, 0x34, 0xc3 };
|
||||||
|
ULONG distance_to_syscall = 0x0f;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _M_IX86
|
||||||
|
// If the process is 32-bit on a 64-bit OS, we need to jump to WOW32Reserved
|
||||||
|
if (local_is_wow64())
|
||||||
|
{
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[+] Running 32-bit app on x64 (WOW64)\n");
|
||||||
|
#endif
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// we don't really care if there is a 'jmp' between
|
||||||
|
// NtApiAddress and the 'syscall; ret' instructions
|
||||||
|
SyscallAddress = SW3_RVA2VA(PVOID, NtApiAddress, distance_to_syscall);
|
||||||
|
|
||||||
|
if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code)))
|
||||||
|
{
|
||||||
|
// we can use the original code for this system call :)
|
||||||
|
#if defined(DEBUG)
|
||||||
|
printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
|
||||||
|
#endif
|
||||||
|
return SyscallAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the 'syscall; ret' intructions have not been found,
|
||||||
|
// we will try to use one near it, similarly to HalosGate
|
||||||
|
|
||||||
|
for (ULONG32 num_jumps = 1; num_jumps < searchLimit; num_jumps++)
|
||||||
|
{
|
||||||
|
// let's try with an Nt* API below our syscall
|
||||||
|
SyscallAddress = SW3_RVA2VA(
|
||||||
|
PVOID,
|
||||||
|
NtApiAddress,
|
||||||
|
distance_to_syscall + num_jumps * 0x20);
|
||||||
|
if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code)))
|
||||||
|
{
|
||||||
|
#if defined(DEBUG)
|
||||||
|
printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
|
||||||
|
#endif
|
||||||
|
return SyscallAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// let's try with an Nt* API above our syscall
|
||||||
|
SyscallAddress = SW3_RVA2VA(
|
||||||
|
PVOID,
|
||||||
|
NtApiAddress,
|
||||||
|
distance_to_syscall - num_jumps * 0x20);
|
||||||
|
if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code)))
|
||||||
|
{
|
||||||
|
#if defined(DEBUG)
|
||||||
|
printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
|
||||||
|
#endif
|
||||||
|
return SyscallAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("Syscall Opcodes not found!\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
BOOL SW3_PopulateSyscallList()
|
||||||
|
{
|
||||||
|
// Return early if the list is already populated.
|
||||||
|
if (SW3_SyscallList.Count) return TRUE;
|
||||||
|
|
||||||
|
#ifdef _WIN64
|
||||||
|
PSW3_PEB Peb = (PSW3_PEB)__readgsqword(0x60);
|
||||||
|
#else
|
||||||
|
PSW3_PEB Peb = (PSW3_PEB)__readfsdword(0x30);
|
||||||
|
#endif
|
||||||
|
PSW3_PEB_LDR_DATA Ldr = Peb->Ldr;
|
||||||
|
PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL;
|
||||||
|
PVOID DllBase = NULL;
|
||||||
|
|
||||||
|
// Get the DllBase address of NTDLL.dll. NTDLL is not guaranteed to be the second
|
||||||
|
// in the list, so it's safer to loop through the full list and find it.
|
||||||
|
PSW3_LDR_DATA_TABLE_ENTRY LdrEntry;
|
||||||
|
for (LdrEntry = (PSW3_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW3_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0])
|
||||||
|
{
|
||||||
|
DllBase = LdrEntry->DllBase;
|
||||||
|
PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase;
|
||||||
|
PIMAGE_NT_HEADERS NtHeaders = SW3_RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader->e_lfanew);
|
||||||
|
PIMAGE_DATA_DIRECTORY DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory;
|
||||||
|
DWORD VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
||||||
|
if (VirtualAddress == 0) continue;
|
||||||
|
|
||||||
|
ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)SW3_RVA2VA(ULONG_PTR, DllBase, VirtualAddress);
|
||||||
|
|
||||||
|
// If this is NTDLL.dll, exit loop.
|
||||||
|
PCHAR DllName = SW3_RVA2VA(PCHAR, DllBase, ExportDirectory->Name);
|
||||||
|
|
||||||
|
if ((*(ULONG*)DllName | 0x20202020) != 0x6c64746e) continue;
|
||||||
|
if ((*(ULONG*)(DllName + 4) | 0x20202020) == 0x6c642e6c) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ExportDirectory) return FALSE;
|
||||||
|
|
||||||
|
DWORD NumberOfNames = ExportDirectory->NumberOfNames;
|
||||||
|
PDWORD Functions = SW3_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions);
|
||||||
|
PDWORD Names = SW3_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames);
|
||||||
|
PWORD Ordinals = SW3_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals);
|
||||||
|
|
||||||
|
// Populate SW3_SyscallList with unsorted Zw* entries.
|
||||||
|
DWORD i = 0;
|
||||||
|
PSW3_SYSCALL_ENTRY Entries = SW3_SyscallList.Entries;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
PCHAR FunctionName = SW3_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]);
|
||||||
|
|
||||||
|
// Is this a system call?
|
||||||
|
if (*(USHORT*)FunctionName == 0x775a)
|
||||||
|
{
|
||||||
|
Entries[i].Hash = SW3_HashSyscall(FunctionName);
|
||||||
|
Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]];
|
||||||
|
Entries[i].SyscallAddress = SC_Address(SW3_RVA2VA(PVOID, DllBase, Entries[i].Address));
|
||||||
|
|
||||||
|
i++;
|
||||||
|
if (i == SW3_MAX_ENTRIES) break;
|
||||||
|
}
|
||||||
|
} while (--NumberOfNames);
|
||||||
|
|
||||||
|
// Save total number of system calls found.
|
||||||
|
SW3_SyscallList.Count = i;
|
||||||
|
|
||||||
|
// Sort the list by address in ascending order.
|
||||||
|
for (DWORD i = 0; i < SW3_SyscallList.Count - 1; i++)
|
||||||
|
{
|
||||||
|
for (DWORD j = 0; j < SW3_SyscallList.Count - i - 1; j++)
|
||||||
|
{
|
||||||
|
if (Entries[j].Address > Entries[j + 1].Address)
|
||||||
|
{
|
||||||
|
// Swap entries.
|
||||||
|
SW3_SYSCALL_ENTRY TempEntry;
|
||||||
|
|
||||||
|
TempEntry.Hash = Entries[j].Hash;
|
||||||
|
TempEntry.Address = Entries[j].Address;
|
||||||
|
TempEntry.SyscallAddress = Entries[j].SyscallAddress;
|
||||||
|
|
||||||
|
Entries[j].Hash = Entries[j + 1].Hash;
|
||||||
|
Entries[j].Address = Entries[j + 1].Address;
|
||||||
|
Entries[j].SyscallAddress = Entries[j + 1].SyscallAddress;
|
||||||
|
|
||||||
|
Entries[j + 1].Hash = TempEntry.Hash;
|
||||||
|
Entries[j + 1].Address = TempEntry.Address;
|
||||||
|
Entries[j + 1].SyscallAddress = TempEntry.SyscallAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXTERN_C DWORD SW3_GetSyscallNumber(DWORD FunctionHash)
|
||||||
|
{
|
||||||
|
// Ensure SW3_SyscallList is populated.
|
||||||
|
if (!SW3_PopulateSyscallList()) return -1;
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < SW3_SyscallList.Count; i++)
|
||||||
|
{
|
||||||
|
if (FunctionHash == SW3_SyscallList.Entries[i].Hash)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXTERN_C PVOID SW3_GetSyscallAddress(DWORD FunctionHash)
|
||||||
|
{
|
||||||
|
// Ensure SW3_SyscallList is populated.
|
||||||
|
if (!SW3_PopulateSyscallList()) return NULL;
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < SW3_SyscallList.Count; i++)
|
||||||
|
{
|
||||||
|
if (FunctionHash == SW3_SyscallList.Entries[i].Hash)
|
||||||
|
{
|
||||||
|
return SW3_SyscallList.Entries[i].SyscallAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXTERN_C PVOID SW3_GetRandomSyscallAddress(DWORD FunctionHash)
|
||||||
|
{
|
||||||
|
// Ensure SW3_SyscallList is populated.
|
||||||
|
if (!SW3_PopulateSyscallList()) return NULL;
|
||||||
|
|
||||||
|
DWORD index = ((DWORD) rand()) % SW3_SyscallList.Count;
|
||||||
|
|
||||||
|
while (FunctionHash == SW3_SyscallList.Entries[index].Hash){
|
||||||
|
// Spoofing the syscall return address
|
||||||
|
index = ((DWORD) rand()) % SW3_SyscallList.Count;
|
||||||
|
}
|
||||||
|
return SW3_SyscallList.Entries[index].SyscallAddress;
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Code below is adapted from @modexpblog. Read linked article for more details.
|
||||||
|
// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams
|
||||||
|
|
||||||
|
#ifndef SW3_HEADER_H_
|
||||||
|
#define SW3_HEADER_H_
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#define SW3_SEED 0x3A397AC2
|
||||||
|
#define SW3_ROL8(v) (v << 8 | v >> 24)
|
||||||
|
#define SW3_ROR8(v) (v >> 8 | v << 24)
|
||||||
|
#define SW3_ROX8(v) ((SW3_SEED % 2) ? SW3_ROL8(v) : SW3_ROR8(v))
|
||||||
|
#define SW3_MAX_ENTRIES 500
|
||||||
|
#define SW3_RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva)
|
||||||
|
|
||||||
|
// Typedefs are prefixed to avoid pollution.
|
||||||
|
|
||||||
|
typedef struct _SW3_SYSCALL_ENTRY
|
||||||
|
{
|
||||||
|
DWORD Hash;
|
||||||
|
DWORD Address;
|
||||||
|
PVOID SyscallAddress;
|
||||||
|
} SW3_SYSCALL_ENTRY, *PSW3_SYSCALL_ENTRY;
|
||||||
|
|
||||||
|
typedef struct _SW3_SYSCALL_LIST
|
||||||
|
{
|
||||||
|
DWORD Count;
|
||||||
|
SW3_SYSCALL_ENTRY Entries[SW3_MAX_ENTRIES];
|
||||||
|
} SW3_SYSCALL_LIST, *PSW3_SYSCALL_LIST;
|
||||||
|
|
||||||
|
typedef struct _SW3_PEB_LDR_DATA {
|
||||||
|
BYTE Reserved1[8];
|
||||||
|
PVOID Reserved2[3];
|
||||||
|
LIST_ENTRY InMemoryOrderModuleList;
|
||||||
|
} SW3_PEB_LDR_DATA, *PSW3_PEB_LDR_DATA;
|
||||||
|
|
||||||
|
typedef struct _SW3_LDR_DATA_TABLE_ENTRY {
|
||||||
|
PVOID Reserved1[2];
|
||||||
|
LIST_ENTRY InMemoryOrderLinks;
|
||||||
|
PVOID Reserved2[2];
|
||||||
|
PVOID DllBase;
|
||||||
|
} SW3_LDR_DATA_TABLE_ENTRY, *PSW3_LDR_DATA_TABLE_ENTRY;
|
||||||
|
|
||||||
|
typedef struct _SW3_PEB {
|
||||||
|
BYTE Reserved1[2];
|
||||||
|
BYTE BeingDebugged;
|
||||||
|
BYTE Reserved2[1];
|
||||||
|
PVOID Reserved3[2];
|
||||||
|
PSW3_PEB_LDR_DATA Ldr;
|
||||||
|
} SW3_PEB, *PSW3_PEB;
|
||||||
|
|
||||||
|
DWORD SW3_HashSyscall(PCSTR FunctionName);
|
||||||
|
BOOL SW3_PopulateSyscallList();
|
||||||
|
EXTERN_C DWORD SW3_GetSyscallNumber(DWORD FunctionHash);
|
||||||
|
EXTERN_C PVOID SW3_GetSyscallAddress(DWORD FunctionHash);
|
||||||
|
EXTERN_C PVOID internal_cleancall_wow64_gate(VOID);
|
||||||
|
EXTERN_C NTSTATUS NtSetContextThread(
|
||||||
|
IN HANDLE ThreadHandle,
|
||||||
|
IN PCONTEXT Context);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
.code
|
||||||
|
|
||||||
|
EXTERN SW3_GetSyscallNumber: PROC
|
||||||
|
|
||||||
|
NtSetContextThread PROC
|
||||||
|
mov [rsp +8], rcx ; Save registers.
|
||||||
|
mov [rsp+16], rdx
|
||||||
|
mov [rsp+24], r8
|
||||||
|
mov [rsp+32], r9
|
||||||
|
sub rsp, 28h
|
||||||
|
mov ecx, 0FA5F256Dh ; Load function hash into ECX.
|
||||||
|
call SW3_GetSyscallNumber ; Resolve function hash into syscall number.
|
||||||
|
add rsp, 28h
|
||||||
|
mov rcx, [rsp+8] ; Restore registers.
|
||||||
|
mov rdx, [rsp+16]
|
||||||
|
mov r8, [rsp+24]
|
||||||
|
mov r9, [rsp+32]
|
||||||
|
mov r10, rcx
|
||||||
|
syscall ; Invoke system call.
|
||||||
|
ret
|
||||||
|
NtSetContextThread ENDP
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#include "windows.h"
|
||||||
|
#include "threadcontext_jumper_randomized.h"
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
|
||||||
|
NtSetContextThread(-1, NULL);
|
||||||
|
|
||||||
|
getchar();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<ProjectConfiguration Include="Debug|Win32">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|Win32">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Debug|x64">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|x64">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<VCProjectVersion>16.0</VCProjectVersion>
|
||||||
|
<Keyword>Win32Proj</Keyword>
|
||||||
|
<ProjectGuid>{77c9b594-a2e6-40ab-ba92-e23fc1eae781}</ProjectGuid>
|
||||||
|
<RootNamespace>SetThreadContextIndirect</RootNamespace>
|
||||||
|
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v142</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v142</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.props" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="Shared">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<LinkIncremental>true</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<LinkIncremental>false</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<LinkIncremental>true</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<LinkIncremental>false</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="threadcontext_jumper_randomized.h" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="Main.c" />
|
||||||
|
<ClCompile Include="threadcontext_jumper_randomized.c" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<MASM Include="threadcontext_jumper_randomized_-asm.x64.asm">
|
||||||
|
<FileType>Document</FileType>
|
||||||
|
</MASM>
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
|
||||||
|
</ImportGroup>
|
||||||
|
</Project>
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup>
|
||||||
|
<Filter Include="Source Files">
|
||||||
|
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||||
|
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Header Files">
|
||||||
|
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||||
|
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Resource Files">
|
||||||
|
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||||
|
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||||
|
</Filter>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="threadcontext_jumper_randomized.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="threadcontext_jumper_randomized.c">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="Main.c">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<MASM Include="threadcontext_jumper_randomized_-asm.x64.asm">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</MASM>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup />
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
#include "threadcontext_jumper_randomized.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
//#define DEBUG
|
||||||
|
|
||||||
|
#define JUMPER
|
||||||
|
|
||||||
|
#ifdef _M_IX86
|
||||||
|
|
||||||
|
EXTERN_C PVOID internal_cleancall_wow64_gate(VOID) {
|
||||||
|
return (PVOID)__readfsdword(0xC0);
|
||||||
|
}
|
||||||
|
|
||||||
|
__declspec(naked) BOOL local_is_wow64(void)
|
||||||
|
{
|
||||||
|
__asm {
|
||||||
|
mov eax, fs:[0xc0]
|
||||||
|
test eax, eax
|
||||||
|
jne wow64
|
||||||
|
mov eax, 0
|
||||||
|
ret
|
||||||
|
wow64:
|
||||||
|
mov eax, 1
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Code below is adapted from @modexpblog. Read linked article for more details.
|
||||||
|
// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams
|
||||||
|
|
||||||
|
SW3_SYSCALL_LIST SW3_SyscallList;
|
||||||
|
|
||||||
|
// SEARCH_AND_REPLACE
|
||||||
|
#ifdef SEARCH_AND_REPLACE
|
||||||
|
// THIS IS NOT DEFINED HERE; don't know if I'll add it in a future release
|
||||||
|
EXTERN void SearchAndReplace(unsigned char[], unsigned char[]);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DWORD SW3_HashSyscall(PCSTR FunctionName)
|
||||||
|
{
|
||||||
|
DWORD i = 0;
|
||||||
|
DWORD Hash = SW3_SEED;
|
||||||
|
|
||||||
|
while (FunctionName[i])
|
||||||
|
{
|
||||||
|
WORD PartialName = *(WORD*)((ULONG_PTR)FunctionName + i++);
|
||||||
|
Hash ^= PartialName + SW3_ROR8(Hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef JUMPER
|
||||||
|
PVOID SC_Address(PVOID NtApiAddress)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
PVOID SC_Address(PVOID NtApiAddress)
|
||||||
|
{
|
||||||
|
DWORD searchLimit = 512;
|
||||||
|
PVOID SyscallAddress;
|
||||||
|
|
||||||
|
#ifdef _WIN64
|
||||||
|
// If the process is 64-bit on a 64-bit OS, we need to search for syscall
|
||||||
|
BYTE syscall_code[] = { 0x0f, 0x05, 0xc3 };
|
||||||
|
ULONG distance_to_syscall = 0x12;
|
||||||
|
#else
|
||||||
|
// If the process is 32-bit on a 32-bit OS, we need to search for sysenter
|
||||||
|
BYTE syscall_code[] = { 0x0f, 0x34, 0xc3 };
|
||||||
|
ULONG distance_to_syscall = 0x0f;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _M_IX86
|
||||||
|
// If the process is 32-bit on a 64-bit OS, we need to jump to WOW32Reserved
|
||||||
|
if (local_is_wow64())
|
||||||
|
{
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[+] Running 32-bit app on x64 (WOW64)\n");
|
||||||
|
#endif
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// we don't really care if there is a 'jmp' between
|
||||||
|
// NtApiAddress and the 'syscall; ret' instructions
|
||||||
|
SyscallAddress = SW3_RVA2VA(PVOID, NtApiAddress, distance_to_syscall);
|
||||||
|
|
||||||
|
if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code)))
|
||||||
|
{
|
||||||
|
// we can use the original code for this system call :)
|
||||||
|
#if defined(DEBUG)
|
||||||
|
printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
|
||||||
|
#endif
|
||||||
|
return SyscallAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the 'syscall; ret' intructions have not been found,
|
||||||
|
// we will try to use one near it, similarly to HalosGate
|
||||||
|
|
||||||
|
for (ULONG32 num_jumps = 1; num_jumps < searchLimit; num_jumps++)
|
||||||
|
{
|
||||||
|
// let's try with an Nt* API below our syscall
|
||||||
|
SyscallAddress = SW3_RVA2VA(
|
||||||
|
PVOID,
|
||||||
|
NtApiAddress,
|
||||||
|
distance_to_syscall + num_jumps * 0x20);
|
||||||
|
if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code)))
|
||||||
|
{
|
||||||
|
#if defined(DEBUG)
|
||||||
|
printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
|
||||||
|
#endif
|
||||||
|
return SyscallAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// let's try with an Nt* API above our syscall
|
||||||
|
SyscallAddress = SW3_RVA2VA(
|
||||||
|
PVOID,
|
||||||
|
NtApiAddress,
|
||||||
|
distance_to_syscall - num_jumps * 0x20);
|
||||||
|
if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code)))
|
||||||
|
{
|
||||||
|
#if defined(DEBUG)
|
||||||
|
printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
|
||||||
|
#endif
|
||||||
|
return SyscallAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("Syscall Opcodes not found!\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
BOOL SW3_PopulateSyscallList()
|
||||||
|
{
|
||||||
|
// Return early if the list is already populated.
|
||||||
|
if (SW3_SyscallList.Count) return TRUE;
|
||||||
|
|
||||||
|
#ifdef _WIN64
|
||||||
|
PSW3_PEB Peb = (PSW3_PEB)__readgsqword(0x60);
|
||||||
|
#else
|
||||||
|
PSW3_PEB Peb = (PSW3_PEB)__readfsdword(0x30);
|
||||||
|
#endif
|
||||||
|
PSW3_PEB_LDR_DATA Ldr = Peb->Ldr;
|
||||||
|
PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL;
|
||||||
|
PVOID DllBase = NULL;
|
||||||
|
|
||||||
|
// Get the DllBase address of NTDLL.dll. NTDLL is not guaranteed to be the second
|
||||||
|
// in the list, so it's safer to loop through the full list and find it.
|
||||||
|
PSW3_LDR_DATA_TABLE_ENTRY LdrEntry;
|
||||||
|
for (LdrEntry = (PSW3_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW3_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0])
|
||||||
|
{
|
||||||
|
DllBase = LdrEntry->DllBase;
|
||||||
|
PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase;
|
||||||
|
PIMAGE_NT_HEADERS NtHeaders = SW3_RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader->e_lfanew);
|
||||||
|
PIMAGE_DATA_DIRECTORY DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory;
|
||||||
|
DWORD VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
||||||
|
if (VirtualAddress == 0) continue;
|
||||||
|
|
||||||
|
ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)SW3_RVA2VA(ULONG_PTR, DllBase, VirtualAddress);
|
||||||
|
|
||||||
|
// If this is NTDLL.dll, exit loop.
|
||||||
|
PCHAR DllName = SW3_RVA2VA(PCHAR, DllBase, ExportDirectory->Name);
|
||||||
|
|
||||||
|
if ((*(ULONG*)DllName | 0x20202020) != 0x6c64746e) continue;
|
||||||
|
if ((*(ULONG*)(DllName + 4) | 0x20202020) == 0x6c642e6c) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ExportDirectory) return FALSE;
|
||||||
|
|
||||||
|
DWORD NumberOfNames = ExportDirectory->NumberOfNames;
|
||||||
|
PDWORD Functions = SW3_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions);
|
||||||
|
PDWORD Names = SW3_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames);
|
||||||
|
PWORD Ordinals = SW3_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals);
|
||||||
|
|
||||||
|
// Populate SW3_SyscallList with unsorted Zw* entries.
|
||||||
|
DWORD i = 0;
|
||||||
|
PSW3_SYSCALL_ENTRY Entries = SW3_SyscallList.Entries;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
PCHAR FunctionName = SW3_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]);
|
||||||
|
|
||||||
|
// Is this a system call?
|
||||||
|
if (*(USHORT*)FunctionName == 0x775a)
|
||||||
|
{
|
||||||
|
Entries[i].Hash = SW3_HashSyscall(FunctionName);
|
||||||
|
Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]];
|
||||||
|
Entries[i].SyscallAddress = SC_Address(SW3_RVA2VA(PVOID, DllBase, Entries[i].Address));
|
||||||
|
|
||||||
|
i++;
|
||||||
|
if (i == SW3_MAX_ENTRIES) break;
|
||||||
|
}
|
||||||
|
} while (--NumberOfNames);
|
||||||
|
|
||||||
|
// Save total number of system calls found.
|
||||||
|
SW3_SyscallList.Count = i;
|
||||||
|
|
||||||
|
// Sort the list by address in ascending order.
|
||||||
|
for (DWORD i = 0; i < SW3_SyscallList.Count - 1; i++)
|
||||||
|
{
|
||||||
|
for (DWORD j = 0; j < SW3_SyscallList.Count - i - 1; j++)
|
||||||
|
{
|
||||||
|
if (Entries[j].Address > Entries[j + 1].Address)
|
||||||
|
{
|
||||||
|
// Swap entries.
|
||||||
|
SW3_SYSCALL_ENTRY TempEntry;
|
||||||
|
|
||||||
|
TempEntry.Hash = Entries[j].Hash;
|
||||||
|
TempEntry.Address = Entries[j].Address;
|
||||||
|
TempEntry.SyscallAddress = Entries[j].SyscallAddress;
|
||||||
|
|
||||||
|
Entries[j].Hash = Entries[j + 1].Hash;
|
||||||
|
Entries[j].Address = Entries[j + 1].Address;
|
||||||
|
Entries[j].SyscallAddress = Entries[j + 1].SyscallAddress;
|
||||||
|
|
||||||
|
Entries[j + 1].Hash = TempEntry.Hash;
|
||||||
|
Entries[j + 1].Address = TempEntry.Address;
|
||||||
|
Entries[j + 1].SyscallAddress = TempEntry.SyscallAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXTERN_C DWORD SW3_GetSyscallNumber(DWORD FunctionHash)
|
||||||
|
{
|
||||||
|
// Ensure SW3_SyscallList is populated.
|
||||||
|
if (!SW3_PopulateSyscallList()) return -1;
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < SW3_SyscallList.Count; i++)
|
||||||
|
{
|
||||||
|
if (FunctionHash == SW3_SyscallList.Entries[i].Hash)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXTERN_C PVOID SW3_GetSyscallAddress(DWORD FunctionHash)
|
||||||
|
{
|
||||||
|
// Ensure SW3_SyscallList is populated.
|
||||||
|
if (!SW3_PopulateSyscallList()) return NULL;
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < SW3_SyscallList.Count; i++)
|
||||||
|
{
|
||||||
|
if (FunctionHash == SW3_SyscallList.Entries[i].Hash)
|
||||||
|
{
|
||||||
|
return SW3_SyscallList.Entries[i].SyscallAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXTERN_C PVOID SW3_GetRandomSyscallAddress(DWORD FunctionHash)
|
||||||
|
{
|
||||||
|
// Ensure SW3_SyscallList is populated.
|
||||||
|
if (!SW3_PopulateSyscallList()) return NULL;
|
||||||
|
|
||||||
|
DWORD index = ((DWORD) rand()) % SW3_SyscallList.Count;
|
||||||
|
|
||||||
|
while (FunctionHash == SW3_SyscallList.Entries[index].Hash){
|
||||||
|
// Spoofing the syscall return address
|
||||||
|
index = ((DWORD) rand()) % SW3_SyscallList.Count;
|
||||||
|
}
|
||||||
|
return SW3_SyscallList.Entries[index].SyscallAddress;
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Code below is adapted from @modexpblog. Read linked article for more details.
|
||||||
|
// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams
|
||||||
|
|
||||||
|
#ifndef SW3_HEADER_H_
|
||||||
|
#define SW3_HEADER_H_
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#define SW3_SEED 0xE331CC7E
|
||||||
|
#define SW3_ROL8(v) (v << 8 | v >> 24)
|
||||||
|
#define SW3_ROR8(v) (v >> 8 | v << 24)
|
||||||
|
#define SW3_ROX8(v) ((SW3_SEED % 2) ? SW3_ROL8(v) : SW3_ROR8(v))
|
||||||
|
#define SW3_MAX_ENTRIES 500
|
||||||
|
#define SW3_RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva)
|
||||||
|
|
||||||
|
// Typedefs are prefixed to avoid pollution.
|
||||||
|
|
||||||
|
typedef struct _SW3_SYSCALL_ENTRY
|
||||||
|
{
|
||||||
|
DWORD Hash;
|
||||||
|
DWORD Address;
|
||||||
|
PVOID SyscallAddress;
|
||||||
|
} SW3_SYSCALL_ENTRY, *PSW3_SYSCALL_ENTRY;
|
||||||
|
|
||||||
|
typedef struct _SW3_SYSCALL_LIST
|
||||||
|
{
|
||||||
|
DWORD Count;
|
||||||
|
SW3_SYSCALL_ENTRY Entries[SW3_MAX_ENTRIES];
|
||||||
|
} SW3_SYSCALL_LIST, *PSW3_SYSCALL_LIST;
|
||||||
|
|
||||||
|
typedef struct _SW3_PEB_LDR_DATA {
|
||||||
|
BYTE Reserved1[8];
|
||||||
|
PVOID Reserved2[3];
|
||||||
|
LIST_ENTRY InMemoryOrderModuleList;
|
||||||
|
} SW3_PEB_LDR_DATA, *PSW3_PEB_LDR_DATA;
|
||||||
|
|
||||||
|
typedef struct _SW3_LDR_DATA_TABLE_ENTRY {
|
||||||
|
PVOID Reserved1[2];
|
||||||
|
LIST_ENTRY InMemoryOrderLinks;
|
||||||
|
PVOID Reserved2[2];
|
||||||
|
PVOID DllBase;
|
||||||
|
} SW3_LDR_DATA_TABLE_ENTRY, *PSW3_LDR_DATA_TABLE_ENTRY;
|
||||||
|
|
||||||
|
typedef struct _SW3_PEB {
|
||||||
|
BYTE Reserved1[2];
|
||||||
|
BYTE BeingDebugged;
|
||||||
|
BYTE Reserved2[1];
|
||||||
|
PVOID Reserved3[2];
|
||||||
|
PSW3_PEB_LDR_DATA Ldr;
|
||||||
|
} SW3_PEB, *PSW3_PEB;
|
||||||
|
|
||||||
|
DWORD SW3_HashSyscall(PCSTR FunctionName);
|
||||||
|
BOOL SW3_PopulateSyscallList();
|
||||||
|
EXTERN_C DWORD SW3_GetSyscallNumber(DWORD FunctionHash);
|
||||||
|
EXTERN_C PVOID SW3_GetSyscallAddress(DWORD FunctionHash);
|
||||||
|
EXTERN_C PVOID internal_cleancall_wow64_gate(VOID);
|
||||||
|
EXTERN_C NTSTATUS NtSetContextThread(
|
||||||
|
IN HANDLE ThreadHandle,
|
||||||
|
IN PCONTEXT Context);
|
||||||
|
|
||||||
|
#endif
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
.code
|
||||||
|
|
||||||
|
EXTERN SW3_GetSyscallNumber: PROC
|
||||||
|
|
||||||
|
EXTERN SW3_GetRandomSyscallAddress: PROC
|
||||||
|
|
||||||
|
NtSetContextThread PROC
|
||||||
|
mov [rsp +8], rcx ; Save registers.
|
||||||
|
mov [rsp+16], rdx
|
||||||
|
mov [rsp+24], r8
|
||||||
|
mov [rsp+32], r9
|
||||||
|
sub rsp, 28h
|
||||||
|
mov ecx, 0123E5E95h ; Load function hash into ECX.
|
||||||
|
call SW3_GetRandomSyscallAddress ; Get a syscall offset from a different api.
|
||||||
|
mov r15, rax ; Save the address of the syscall
|
||||||
|
mov ecx, 0123E5E95h ; Re-Load function hash into ECX (optional).
|
||||||
|
call SW3_GetSyscallNumber ; Resolve function hash into syscall number.
|
||||||
|
add rsp, 28h
|
||||||
|
mov rcx, [rsp+8] ; Restore registers.
|
||||||
|
mov rdx, [rsp+16]
|
||||||
|
mov r8, [rsp+24]
|
||||||
|
mov r9, [rsp+32]
|
||||||
|
mov r10, rcx
|
||||||
|
jmp r15 ; Jump to -> Invoke system call.
|
||||||
|
NtSetContextThread ENDP
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Hunt-Weird-Syscalls
|
||||||
|
|
||||||
|
This project aims to identify (some) abnormally conducted syscalls by leveraging ``ETW``.
|
||||||
|
|
||||||
|
``ETW`` providers sitting in the kernel can effectively be leveraged, as the calltraces of the emitted events contain the address from where the syscall was conducted.
|
||||||
|
|
||||||
|
This allows monitoring for two techniques often leveraged by threat actors: ``direct`` and ``indirect`` syscalls:
|
||||||
|
|
||||||
|
1: Does the syscall come from an unusual module? (=direct syscall)
|
||||||
|
2: Does the used syscall stub in ntdll match the appropriate syscall? (=indirect syscall)
|
||||||
|
|
||||||
|
This project uses the Provider: ``Microsoft-Windows-Kernel-Audit-API-Calls`` to monitor for ``OpenThread`` and ``SetContextThread`` events triggered by the syscalls ``NtSetContextThread`` or ``NtOpenThread`` respectively.
|
||||||
|
Calltraces are enabled, using the flag ``EVENT_ENABLE_PROPERTY_STACK_TRACE``.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
This project containts two sample programs using direct and indirect syscalls created using the amazing [SysWhispers3](https://github.com/klezVirus/SysWhispers3).
|
||||||
|
They were generated as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 syswhispers.py -a x64 -m jumper_randomized --functions NtSetContextThread
|
||||||
|
python3 syswhispers.py -a x64 -m embedded --functions NtSetContextThread
|
||||||
|
```
|
||||||
|
|
||||||
|
Upon execution, abnormal syscalls should be identified:
|
||||||
|
|
||||||
|

|
||||||
|
- [SysWhispers3](https://github.com/klezVirus/SysWhispers3)
|
||||||
|
- [etw provider docs by repnz](https://github.com/repnz/etw-providers-docs)
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,25 @@
|
|||||||
|
krabsetw
|
||||||
|
|
||||||
|
Copyright (c) Microsoft Corporation
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the ""Software""), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Krabs Readme
|
||||||
|
|
||||||
|
Important Preprocessor Definitions:
|
||||||
|
|
||||||
|
* `UNICODE` - krabsetw expects the `UNICODE` preprocessor definition to be
|
||||||
|
defined. The code will not successfully compile without this flag. There is
|
||||||
|
no plan to support compilation without `UNICODE` being set.
|
||||||
|
|
||||||
|
* `NDEBUG` - Set this variable in release builds to disable runtime type
|
||||||
|
assertions. You'll still get a runtime error if the size type you're
|
||||||
|
requesting is not the same size as the property in the event schema.
|
||||||
|
|
||||||
|
* `TYPEASSERT` - Set this variable only in debug builds (not `NDEBUG`) to enable
|
||||||
|
strict assertions. This means that if an explicit type check is not defined
|
||||||
|
for a requested type, a `static_assert` is thrown and the code will not
|
||||||
|
compile until one is added. This is mainly used for krabs development to
|
||||||
|
ensure that we don't miss asserts for types that are supported.
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#pragma comment(lib, "advapi32.lib")
|
||||||
|
#pragma comment(lib, "ole32.lib")
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// /\
|
||||||
|
// ( / @ @ ()
|
||||||
|
// \ __| |__ /
|
||||||
|
// -/ " \-
|
||||||
|
// /-| |-\
|
||||||
|
// / /-\ /-\ \
|
||||||
|
// / /-`---'-\ \
|
||||||
|
// / \
|
||||||
|
//
|
||||||
|
// Summary
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Krabs is a wrapper around ETW because ETW is the worst API ever made.
|
||||||
|
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable: 4512) // stupid spurious "can't generate assignment error" warning
|
||||||
|
#pragma warning(disable: 4634) // DocXml comment warnings in native C++
|
||||||
|
#pragma warning(disable: 4635) // DocXml comment warnings in native C++
|
||||||
|
|
||||||
|
#include "krabs/compiler_check.hpp"
|
||||||
|
#include "krabs/ut.hpp"
|
||||||
|
#include "krabs/kt.hpp"
|
||||||
|
#include "krabs/guid.hpp"
|
||||||
|
#include "krabs/trace.hpp"
|
||||||
|
#include "krabs/trace_context.hpp"
|
||||||
|
#include "krabs/client.hpp"
|
||||||
|
#include "krabs/errors.hpp"
|
||||||
|
#include "krabs/schema.hpp"
|
||||||
|
#include "krabs/schema_locator.hpp"
|
||||||
|
#include "krabs/parse_types.hpp"
|
||||||
|
#include "krabs/collection_view.hpp"
|
||||||
|
#include "krabs/size_provider.hpp"
|
||||||
|
#include "krabs/parser.hpp"
|
||||||
|
#include "krabs/property.hpp"
|
||||||
|
#include "krabs/provider.hpp"
|
||||||
|
#include "krabs/etw.hpp"
|
||||||
|
#include "krabs/tdh_helpers.hpp"
|
||||||
|
#include "krabs/kernel_providers.hpp"
|
||||||
|
|
||||||
|
#include "krabs/testing/proxy.hpp"
|
||||||
|
#include "krabs/testing/filler.hpp"
|
||||||
|
#include "krabs/testing/synth_record.hpp"
|
||||||
|
#include "krabs/testing/record_builder.hpp"
|
||||||
|
#include "krabs/testing/event_filter_proxy.hpp"
|
||||||
|
#include "krabs/testing/record_property_thunk.hpp"
|
||||||
|
|
||||||
|
#include "krabs/filtering/view_adapters.hpp"
|
||||||
|
#include "krabs/filtering/comparers.hpp"
|
||||||
|
#include "krabs/filtering/predicates.hpp"
|
||||||
|
#include "krabs/filtering/event_filter.hpp"
|
||||||
|
|
||||||
|
#pragma warning(pop)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RunSettings>
|
||||||
|
<!-- Configurations that affect the Test Framework -->
|
||||||
|
<RunConfiguration>
|
||||||
|
<DeploymentEnabled>False</DeploymentEnabled>
|
||||||
|
<ExecutionThreadApartmentState>MTA</ExecutionThreadApartmentState>
|
||||||
|
<TargetPlatform>x64</TargetPlatform>
|
||||||
|
</RunConfiguration>
|
||||||
|
</RunSettings>
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.2.32317.152
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{5C9E224B-AEB7-456B-B309-92292ACE1A0D}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
.nuget\NuGet.Config = .nuget\NuGet.Config
|
||||||
|
.nuget\NuGet.exe = .nuget\NuGet.exe
|
||||||
|
.nuget\NuGet.targets = .nuget\NuGet.targets
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.O365.Security.Native.ETW", "..\Microsoft.O365.Security.Native.ETW\Microsoft.O365.Security.Native.ETW.vcxproj", "{ED4E6027-541F-440A-A5EE-15DBB7B89423}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "krabstests", "..\tests\krabstests\krabstests.vcxproj", "{880977B8-15CA-421B-BF48-D01626A530A2}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E583602B-48AC-46A6-B0F0-343CE0B6AE33}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
krabs.runsettings = krabs.runsettings
|
||||||
|
MTA.testsettings = MTA.testsettings
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "krabs headers", "krabs headers", "{1FD19105-D67C-492B-B98F-53E00A324269}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
krabs.hpp = krabs.hpp
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "krabs", "krabs", "{371361C8-96EC-4D6D-B80B-2E47E3453264}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
krabs\client.hpp = krabs\client.hpp
|
||||||
|
krabs\collection_view.hpp = krabs\collection_view.hpp
|
||||||
|
krabs\compiler_check.hpp = krabs\compiler_check.hpp
|
||||||
|
krabs\errors.hpp = krabs\errors.hpp
|
||||||
|
krabs\etw.hpp = krabs\etw.hpp
|
||||||
|
krabs\guid.hpp = krabs\guid.hpp
|
||||||
|
krabs\kernel_guids.hpp = krabs\kernel_guids.hpp
|
||||||
|
krabs\kernel_providers.hpp = krabs\kernel_providers.hpp
|
||||||
|
krabs\kt.hpp = krabs\kt.hpp
|
||||||
|
krabs\parser.hpp = krabs\parser.hpp
|
||||||
|
krabs\parse_types.hpp = krabs\parse_types.hpp
|
||||||
|
krabs\perfinfo_groupmask.hpp = krabs\perfinfo_groupmask.hpp
|
||||||
|
krabs\property.hpp = krabs\property.hpp
|
||||||
|
krabs\provider.hpp = krabs\provider.hpp
|
||||||
|
krabs\schema.hpp = krabs\schema.hpp
|
||||||
|
krabs\schema_locator.hpp = krabs\schema_locator.hpp
|
||||||
|
krabs\size_provider.hpp = krabs\size_provider.hpp
|
||||||
|
krabs\tdh_helpers.hpp = krabs\tdh_helpers.hpp
|
||||||
|
krabs\trace.hpp = krabs\trace.hpp
|
||||||
|
krabs\trace_context.hpp = krabs\trace_context.hpp
|
||||||
|
krabs\ut.hpp = krabs\ut.hpp
|
||||||
|
krabs\version_helpers.hpp = krabs\version_helpers.hpp
|
||||||
|
krabs\wstring_convert.hpp = krabs\wstring_convert.hpp
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "filtering", "filtering", "{96FA58B5-A1F6-4107-9FB4-226290F9D696}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
krabs\filtering\comparers.hpp = krabs\filtering\comparers.hpp
|
||||||
|
krabs\filtering\event_filter.hpp = krabs\filtering\event_filter.hpp
|
||||||
|
krabs\filtering\predicates.hpp = krabs\filtering\predicates.hpp
|
||||||
|
krabs\filtering\view_adapters.hpp = krabs\filtering\view_adapters.hpp
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testing", "testing", "{9ED1AE76-2EAA-4CCF-8F01-458BDC8BCD53}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
krabs\testing\event_filter_proxy.hpp = krabs\testing\event_filter_proxy.hpp
|
||||||
|
krabs\testing\extended_data_builder.hpp = krabs\testing\extended_data_builder.hpp
|
||||||
|
krabs\testing\filler.hpp = krabs\testing\filler.hpp
|
||||||
|
krabs\testing\proxy.hpp = krabs\testing\proxy.hpp
|
||||||
|
krabs\testing\record_builder.hpp = krabs\testing\record_builder.hpp
|
||||||
|
krabs\testing\record_property_thunk.hpp = krabs\testing\record_property_thunk.hpp
|
||||||
|
krabs\testing\synth_record.hpp = krabs\testing\synth_record.hpp
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EtwTestsCS", "..\tests\ManagedETWTests\EtwTestsCS.csproj", "{600CFE03-FD84-4323-9439-839D81C31972}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C4AB7F5F-2FB3-4C16-A1F3-F6700C655B02}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{2E00634C-7E8B-4656-9505-78FF2F5D0EDD}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedExamples", "..\examples\ManagedExamples\ManagedExamples.csproj", "{32E71DD0-D11A-44DE-8CA8-572995AF2373}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NativeExamples", "..\examples\NativeExamples\NativeExamples.vcxproj", "{D31B1A4B-8282-4AED-99FC-9AA5974B9134}"
|
||||||
|
ProjectSection(ProjectDependencies) = postProject
|
||||||
|
{ED4E6027-541F-440A-A5EE-15DBB7B89423} = {ED4E6027-541F-440A-A5EE-15DBB7B89423}
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.O365.Security.Native.ETW.NetCore", "..\Microsoft.O365.Security.Native.ETW.NetCore\Microsoft.O365.Security.Native.ETW.NetCore.vcxproj", "{9DE6788C-5759-4A75-B484-ABA4C7EF5F08}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
DebugSigning|x64 = DebugSigning|x64
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
ReleaseSigning|x64 = ReleaseSigning|x64
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{ED4E6027-541F-440A-A5EE-15DBB7B89423}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{ED4E6027-541F-440A-A5EE-15DBB7B89423}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{ED4E6027-541F-440A-A5EE-15DBB7B89423}.DebugSigning|x64.ActiveCfg = DebugSigning|x64
|
||||||
|
{ED4E6027-541F-440A-A5EE-15DBB7B89423}.DebugSigning|x64.Build.0 = DebugSigning|x64
|
||||||
|
{ED4E6027-541F-440A-A5EE-15DBB7B89423}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{ED4E6027-541F-440A-A5EE-15DBB7B89423}.Release|x64.Build.0 = Release|x64
|
||||||
|
{ED4E6027-541F-440A-A5EE-15DBB7B89423}.ReleaseSigning|x64.ActiveCfg = ReleaseSigning|x64
|
||||||
|
{ED4E6027-541F-440A-A5EE-15DBB7B89423}.ReleaseSigning|x64.Build.0 = ReleaseSigning|x64
|
||||||
|
{880977B8-15CA-421B-BF48-D01626A530A2}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{880977B8-15CA-421B-BF48-D01626A530A2}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{880977B8-15CA-421B-BF48-D01626A530A2}.DebugSigning|x64.ActiveCfg = DebugSigning|x64
|
||||||
|
{880977B8-15CA-421B-BF48-D01626A530A2}.DebugSigning|x64.Build.0 = DebugSigning|x64
|
||||||
|
{880977B8-15CA-421B-BF48-D01626A530A2}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{880977B8-15CA-421B-BF48-D01626A530A2}.Release|x64.Build.0 = Release|x64
|
||||||
|
{880977B8-15CA-421B-BF48-D01626A530A2}.ReleaseSigning|x64.ActiveCfg = ReleaseSigning|x64
|
||||||
|
{880977B8-15CA-421B-BF48-D01626A530A2}.ReleaseSigning|x64.Build.0 = ReleaseSigning|x64
|
||||||
|
{600CFE03-FD84-4323-9439-839D81C31972}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{600CFE03-FD84-4323-9439-839D81C31972}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{600CFE03-FD84-4323-9439-839D81C31972}.DebugSigning|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{600CFE03-FD84-4323-9439-839D81C31972}.DebugSigning|x64.Build.0 = Debug|Any CPU
|
||||||
|
{600CFE03-FD84-4323-9439-839D81C31972}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{600CFE03-FD84-4323-9439-839D81C31972}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{600CFE03-FD84-4323-9439-839D81C31972}.ReleaseSigning|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{600CFE03-FD84-4323-9439-839D81C31972}.ReleaseSigning|x64.Build.0 = Release|Any CPU
|
||||||
|
{32E71DD0-D11A-44DE-8CA8-572995AF2373}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{32E71DD0-D11A-44DE-8CA8-572995AF2373}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{32E71DD0-D11A-44DE-8CA8-572995AF2373}.DebugSigning|x64.ActiveCfg = DebugSigning|Any CPU
|
||||||
|
{32E71DD0-D11A-44DE-8CA8-572995AF2373}.DebugSigning|x64.Build.0 = DebugSigning|Any CPU
|
||||||
|
{32E71DD0-D11A-44DE-8CA8-572995AF2373}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{32E71DD0-D11A-44DE-8CA8-572995AF2373}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{32E71DD0-D11A-44DE-8CA8-572995AF2373}.ReleaseSigning|x64.ActiveCfg = ReleaseSigning|Any CPU
|
||||||
|
{32E71DD0-D11A-44DE-8CA8-572995AF2373}.ReleaseSigning|x64.Build.0 = ReleaseSigning|Any CPU
|
||||||
|
{D31B1A4B-8282-4AED-99FC-9AA5974B9134}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{D31B1A4B-8282-4AED-99FC-9AA5974B9134}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{D31B1A4B-8282-4AED-99FC-9AA5974B9134}.DebugSigning|x64.ActiveCfg = DebugSigning|x64
|
||||||
|
{D31B1A4B-8282-4AED-99FC-9AA5974B9134}.DebugSigning|x64.Build.0 = DebugSigning|x64
|
||||||
|
{D31B1A4B-8282-4AED-99FC-9AA5974B9134}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{D31B1A4B-8282-4AED-99FC-9AA5974B9134}.Release|x64.Build.0 = Release|x64
|
||||||
|
{D31B1A4B-8282-4AED-99FC-9AA5974B9134}.ReleaseSigning|x64.ActiveCfg = ReleaseSigning|x64
|
||||||
|
{D31B1A4B-8282-4AED-99FC-9AA5974B9134}.ReleaseSigning|x64.Build.0 = ReleaseSigning|x64
|
||||||
|
{9DE6788C-5759-4A75-B484-ABA4C7EF5F08}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{9DE6788C-5759-4A75-B484-ABA4C7EF5F08}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{9DE6788C-5759-4A75-B484-ABA4C7EF5F08}.DebugSigning|x64.ActiveCfg = DebugSigning|x64
|
||||||
|
{9DE6788C-5759-4A75-B484-ABA4C7EF5F08}.DebugSigning|x64.Build.0 = DebugSigning|x64
|
||||||
|
{9DE6788C-5759-4A75-B484-ABA4C7EF5F08}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{9DE6788C-5759-4A75-B484-ABA4C7EF5F08}.Release|x64.Build.0 = Release|x64
|
||||||
|
{9DE6788C-5759-4A75-B484-ABA4C7EF5F08}.ReleaseSigning|x64.ActiveCfg = ReleaseSigning|x64
|
||||||
|
{9DE6788C-5759-4A75-B484-ABA4C7EF5F08}.ReleaseSigning|x64.Build.0 = ReleaseSigning|x64
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{880977B8-15CA-421B-BF48-D01626A530A2} = {C4AB7F5F-2FB3-4C16-A1F3-F6700C655B02}
|
||||||
|
{371361C8-96EC-4D6D-B80B-2E47E3453264} = {1FD19105-D67C-492B-B98F-53E00A324269}
|
||||||
|
{96FA58B5-A1F6-4107-9FB4-226290F9D696} = {371361C8-96EC-4D6D-B80B-2E47E3453264}
|
||||||
|
{9ED1AE76-2EAA-4CCF-8F01-458BDC8BCD53} = {371361C8-96EC-4D6D-B80B-2E47E3453264}
|
||||||
|
{600CFE03-FD84-4323-9439-839D81C31972} = {C4AB7F5F-2FB3-4C16-A1F3-F6700C655B02}
|
||||||
|
{32E71DD0-D11A-44DE-8CA8-572995AF2373} = {2E00634C-7E8B-4656-9505-78FF2F5D0EDD}
|
||||||
|
{D31B1A4B-8282-4AED-99FC-9AA5974B9134} = {2E00634C-7E8B-4656-9505-78FF2F5D0EDD}
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {82BAA012-2EF9-4303-A429-CDA3655D5009}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
#include "ut.hpp"
|
||||||
|
#include "kt.hpp"
|
||||||
|
#include "trace.hpp"
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Specialization of the base trace class for user traces.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
typedef krabs::trace<krabs::details::ut> user_trace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Specialization of the base trace class for kernel traces.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
typedef krabs::trace<krabs::details::kt> kernel_trace;
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a range of a collection starting at the location
|
||||||
|
* specified by the begin iterator and ending a the location
|
||||||
|
* specified by the end iterator. The underlying items are
|
||||||
|
* left in-place and should be considered const
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
struct collection_view
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
const T beg_;
|
||||||
|
const T end_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Construct a new view for the range specified by the
|
||||||
|
* iterators 'begin' and 'end'
|
||||||
|
*/
|
||||||
|
collection_view(const T begin, const T end)
|
||||||
|
: beg_(begin)
|
||||||
|
, end_(end)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the iterator for the beginning of the view range
|
||||||
|
*/
|
||||||
|
const T begin() const
|
||||||
|
{
|
||||||
|
return beg_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the iterator for the end of the view range
|
||||||
|
*/
|
||||||
|
const T end() const
|
||||||
|
{
|
||||||
|
return end_;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a view over the range specified by iterators 'begin' and 'end'
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
inline collection_view<T> view(const T& begin, const T& end)
|
||||||
|
{
|
||||||
|
return{ begin, end };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a const_iterator view over the specified string
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
inline collection_view<typename std::basic_string<T>::const_iterator> view(const std::basic_string<T>& string)
|
||||||
|
{
|
||||||
|
return{ string.cbegin(), string.cend() };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a const view over the range starting at 'begin' extending 'length' items
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
inline collection_view<const T*> view(const T* begin, size_t length)
|
||||||
|
{
|
||||||
|
return{ begin, begin + length };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a const view over the specified array
|
||||||
|
*/
|
||||||
|
template <typename T, size_t n>
|
||||||
|
inline collection_view<const T*> view(const T(&arr)[n])
|
||||||
|
{
|
||||||
|
return{ arr, arr + n };
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace krabs */
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if (_MSC_VER < 1900)
|
||||||
|
#error "krabsetw is only supported with Visual Studio 2015 and above (MSVC++ 14.0)"
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
class trace_already_registered : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
trace_already_registered()
|
||||||
|
: std::runtime_error("The trace session has already been registered")
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
class invalid_parameter : public std::logic_error {
|
||||||
|
public:
|
||||||
|
invalid_parameter()
|
||||||
|
: std::logic_error("Invalid parameter given")
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
class open_trace_failure : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
open_trace_failure()
|
||||||
|
: std::runtime_error("Failure to open trace")
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
class need_to_be_admin_failure : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
need_to_be_admin_failure()
|
||||||
|
: std::runtime_error("Need to be an admin")
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
class could_not_find_schema : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
could_not_find_schema()
|
||||||
|
: std::runtime_error("Could not find the schema")
|
||||||
|
{}
|
||||||
|
|
||||||
|
could_not_find_schema(const std::string& context)
|
||||||
|
: std::runtime_error(std::string("Could not find the schema: ") + context)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
class type_mismatch_assert : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
type_mismatch_assert(
|
||||||
|
const char* property,
|
||||||
|
const char* actual,
|
||||||
|
const char* requested)
|
||||||
|
: std::runtime_error(std::string("Attempt to read property '") +
|
||||||
|
property + "' type " + actual + " as " + requested)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
class no_trace_sessions_remaining : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
no_trace_sessions_remaining()
|
||||||
|
: std::runtime_error("No more trace sessions available.")
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
class function_not_supported : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
function_not_supported()
|
||||||
|
: std::runtime_error("This function is not supported on this system.")
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
class unexpected_error : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
unexpected_error(ULONG status)
|
||||||
|
: std::runtime_error(std::string("An unexpected error occurred: status_code=") +
|
||||||
|
std::to_string(status))
|
||||||
|
{}
|
||||||
|
|
||||||
|
unexpected_error(const std::string &context)
|
||||||
|
: std::runtime_error(std::string("An unexpected error occurred: ") + context)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::string get_status_and_record_context(ULONG status, const EVENT_RECORD& record)
|
||||||
|
{
|
||||||
|
std::stringstream message;
|
||||||
|
message << "status_code="
|
||||||
|
<< status
|
||||||
|
<< " provider_id="
|
||||||
|
<< std::to_string(record.EventHeader.ProviderId)
|
||||||
|
<< " event_id="
|
||||||
|
<< record.EventHeader.EventDescriptor.Id;
|
||||||
|
|
||||||
|
return message.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>Checks for common ETW API error codes.</summary>
|
||||||
|
*/
|
||||||
|
inline void error_check_common_conditions(ULONG status)
|
||||||
|
{
|
||||||
|
if (status == ERROR_SUCCESS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case ERROR_ALREADY_EXISTS:
|
||||||
|
throw krabs::trace_already_registered();
|
||||||
|
case ERROR_INVALID_PARAMETER:
|
||||||
|
throw krabs::invalid_parameter();
|
||||||
|
case ERROR_ACCESS_DENIED:
|
||||||
|
throw krabs::need_to_be_admin_failure();
|
||||||
|
case ERROR_NOT_FOUND:
|
||||||
|
throw krabs::could_not_find_schema();
|
||||||
|
case ERROR_NO_SYSTEM_RESOURCES:
|
||||||
|
throw krabs::no_trace_sessions_remaining();
|
||||||
|
case ERROR_NOT_SUPPORTED:
|
||||||
|
throw krabs::function_not_supported();
|
||||||
|
default:
|
||||||
|
throw krabs::unexpected_error(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>Checks for common ETW API error codes and includes properties from the event record.</summary>
|
||||||
|
*/
|
||||||
|
inline void error_check_common_conditions(ULONG status, const EVENT_RECORD &record)
|
||||||
|
{
|
||||||
|
if (status == ERROR_SUCCESS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto context = get_status_and_record_context(status, record);
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case ERROR_ALREADY_EXISTS:
|
||||||
|
throw krabs::trace_already_registered();
|
||||||
|
case ERROR_INVALID_PARAMETER:
|
||||||
|
throw krabs::invalid_parameter();
|
||||||
|
case ERROR_ACCESS_DENIED:
|
||||||
|
throw krabs::need_to_be_admin_failure();
|
||||||
|
case ERROR_NOT_FOUND:
|
||||||
|
throw krabs::could_not_find_schema(context);
|
||||||
|
case ERROR_NO_SYSTEM_RESOURCES:
|
||||||
|
throw krabs::no_trace_sessions_remaining();
|
||||||
|
case ERROR_NOT_SUPPORTED:
|
||||||
|
throw krabs::function_not_supported();
|
||||||
|
default:
|
||||||
|
throw krabs::unexpected_error(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,403 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
// Interface for ETW.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define INITGUID
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
#include "trace.hpp"
|
||||||
|
#include "errors.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include <evntrace.h>
|
||||||
|
#include <evntcons.h>
|
||||||
|
|
||||||
|
namespace krabs { namespace details {
|
||||||
|
|
||||||
|
// The ETW API requires that we reserve enough memory behind
|
||||||
|
// an EVENT_TRACE_PROPERTIES buffer in order to store an ETW trace name
|
||||||
|
// and an optional ETW log file name. The easiest way to do this is to
|
||||||
|
// use a struct to reserve this space -- the alternative is to malloc
|
||||||
|
// the bytes at runtime (ew).
|
||||||
|
class trace_info {
|
||||||
|
public:
|
||||||
|
EVENT_TRACE_PROPERTIES properties;
|
||||||
|
wchar_t traceName[MAX_PATH];
|
||||||
|
wchar_t logfileName[MAX_PATH];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Used to implement starting and stopping traces.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
class trace_manager {
|
||||||
|
public:
|
||||||
|
trace_manager(T &trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Starts the ETW trace identified by the info in the trace type.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Stops the ETW trace identified by the info in the trace type.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Opens the ETW trace identified by the info in the trace type.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
EVENT_TRACE_LOGFILE open();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Starts processing the ETW trace identified by the info in the trace type.
|
||||||
|
* open() needs to called for this to work first.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
void process();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Queries the ETW trace identified by the info in the trace type.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
EVENT_TRACE_PROPERTIES query();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Configures the ETW trace session settings.
|
||||||
|
* See https://docs.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-tracesetinformation.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
void set_trace_information(
|
||||||
|
TRACE_INFO_CLASS information_class,
|
||||||
|
PVOID trace_information,
|
||||||
|
ULONG information_length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Notifies the underlying trace of the buffers that were processed.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
void set_buffers_processed(size_t processed);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Notifies the underlying trace that an event occurred.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
void on_event(const EVENT_RECORD &record);
|
||||||
|
|
||||||
|
private:
|
||||||
|
trace_info fill_trace_info();
|
||||||
|
EVENT_TRACE_LOGFILE fill_logfile();
|
||||||
|
void close_trace();
|
||||||
|
void register_trace();
|
||||||
|
EVENT_TRACE_PROPERTIES query_trace();
|
||||||
|
void stop_trace();
|
||||||
|
EVENT_TRACE_LOGFILE open_trace();
|
||||||
|
void process_trace();
|
||||||
|
void enable_providers();
|
||||||
|
|
||||||
|
private:
|
||||||
|
T &trace_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Called by ETW when an event occurs, forwards calls to the
|
||||||
|
* appropriate instance.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* A pointer to the instance is stored in the UserContext
|
||||||
|
* field of the EVENT_RECORD. This is set via the Context field of the
|
||||||
|
* EVENT_TRACE_LOGFILE structure.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
static void __stdcall trace_callback_thunk(EVENT_RECORD *pRecord)
|
||||||
|
{
|
||||||
|
auto *pUserTrace = (T*)(pRecord->UserContext);
|
||||||
|
trace_manager<T> trace(*pUserTrace);
|
||||||
|
trace.on_event(*pRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Called by ETW after the events for each buffer are delivered, gives
|
||||||
|
* statistics like the number of buffers processed and the number of
|
||||||
|
* events dropped.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* A pointer to the instance is stored in the UserContext
|
||||||
|
* field of the EVENT_RECORD. This is set via the Context field of the
|
||||||
|
* EVENT_TRACE_LOGFILE structure.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
static ULONG __stdcall trace_buffer_callback(EVENT_TRACE_LOGFILE *pLogFile)
|
||||||
|
{
|
||||||
|
auto *pTrace = (T*)(pLogFile->Context);
|
||||||
|
trace_manager<T> trace(*pTrace);
|
||||||
|
|
||||||
|
// NOTE: EventsLost is not set on this type
|
||||||
|
trace.set_buffers_processed(pLogFile->BuffersRead);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
trace_manager<T>::trace_manager(T &trace)
|
||||||
|
: trace_(trace)
|
||||||
|
{}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace_manager<T>::start()
|
||||||
|
{
|
||||||
|
if (trace_.sessionHandle_ == INVALID_PROCESSTRACE_HANDLE) {
|
||||||
|
(void)open();
|
||||||
|
}
|
||||||
|
process_trace();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
EVENT_TRACE_LOGFILE trace_manager<T>::open()
|
||||||
|
{
|
||||||
|
register_trace();
|
||||||
|
enable_providers();
|
||||||
|
return open_trace();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace_manager<T>::process()
|
||||||
|
{
|
||||||
|
process_trace();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
EVENT_TRACE_PROPERTIES trace_manager<T>::query()
|
||||||
|
{
|
||||||
|
return query_trace();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace_manager<T>::set_trace_information(
|
||||||
|
TRACE_INFO_CLASS information_class,
|
||||||
|
PVOID trace_information,
|
||||||
|
ULONG information_length)
|
||||||
|
{
|
||||||
|
ULONG status = TraceSetInformation(
|
||||||
|
trace_.registrationHandle_,
|
||||||
|
information_class,
|
||||||
|
trace_information,
|
||||||
|
information_length);
|
||||||
|
|
||||||
|
error_check_common_conditions(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace_manager<T>::stop()
|
||||||
|
{
|
||||||
|
stop_trace();
|
||||||
|
close_trace();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace_manager<T>::set_buffers_processed(size_t processed)
|
||||||
|
{
|
||||||
|
trace_.buffersRead_ = processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace_manager<T>::on_event(const EVENT_RECORD &record)
|
||||||
|
{
|
||||||
|
trace_.on_event(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
trace_info trace_manager<T>::fill_trace_info()
|
||||||
|
{
|
||||||
|
trace_info info = {};
|
||||||
|
info.properties.Wnode.BufferSize = sizeof(trace_info);
|
||||||
|
info.properties.Wnode.Guid = T::trace_type::get_trace_guid();
|
||||||
|
info.properties.Wnode.Flags = WNODE_FLAG_TRACED_GUID;
|
||||||
|
info.properties.Wnode.ClientContext = 1; // QPC clock resolution
|
||||||
|
info.properties.BufferSize = trace_.properties_.BufferSize;
|
||||||
|
info.properties.MinimumBuffers = trace_.properties_.MinimumBuffers;
|
||||||
|
info.properties.MaximumBuffers = trace_.properties_.MaximumBuffers;
|
||||||
|
info.properties.FlushTimer = trace_.properties_.FlushTimer;
|
||||||
|
|
||||||
|
if (trace_.properties_.LogFileMode)
|
||||||
|
info.properties.LogFileMode = trace_.properties_.LogFileMode;
|
||||||
|
else
|
||||||
|
info.properties.LogFileMode = EVENT_TRACE_REAL_TIME_MODE
|
||||||
|
| EVENT_TRACE_NO_PER_PROCESSOR_BUFFERING;
|
||||||
|
|
||||||
|
info.properties.LogFileMode |= T::trace_type::augment_file_mode();
|
||||||
|
info.properties.LoggerNameOffset = offsetof(trace_info, logfileName);
|
||||||
|
info.properties.EnableFlags = T::trace_type::construct_enable_flags(trace_);
|
||||||
|
assert(info.traceName[0] == '\0');
|
||||||
|
assert(info.logfileName[0] == '\0');
|
||||||
|
trace_.name_._Copy_s(info.traceName, ARRAYSIZE(info.traceName), trace_.name_.length());
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
EVENT_TRACE_LOGFILE trace_manager<T>::fill_logfile()
|
||||||
|
{
|
||||||
|
EVENT_TRACE_LOGFILE file = {};
|
||||||
|
|
||||||
|
if (!trace_.logFilename_.empty())
|
||||||
|
{
|
||||||
|
file.LogFileName = const_cast<wchar_t*>(trace_.logFilename_.c_str());
|
||||||
|
file.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
file.LoggerName = const_cast<wchar_t*>(trace_.name_.c_str());
|
||||||
|
file.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD |
|
||||||
|
PROCESS_TRACE_MODE_REAL_TIME;
|
||||||
|
}
|
||||||
|
file.Context = (void *)&trace_;
|
||||||
|
file.EventRecordCallback = trace_callback_thunk<T>;
|
||||||
|
file.BufferCallback = trace_buffer_callback<T>;
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace_manager<T>::stop_trace()
|
||||||
|
{
|
||||||
|
trace_info info = fill_trace_info();
|
||||||
|
ULONG status = ControlTrace(
|
||||||
|
NULL,
|
||||||
|
trace_.name_.c_str(),
|
||||||
|
&info.properties,
|
||||||
|
EVENT_TRACE_CONTROL_STOP);
|
||||||
|
|
||||||
|
if (status != ERROR_WMI_INSTANCE_NOT_FOUND) {
|
||||||
|
error_check_common_conditions(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
EVENT_TRACE_PROPERTIES trace_manager<T>::query_trace()
|
||||||
|
{
|
||||||
|
trace_info info = fill_trace_info();
|
||||||
|
ULONG status = ControlTrace(
|
||||||
|
NULL,
|
||||||
|
trace_.name_.c_str(),
|
||||||
|
&info.properties,
|
||||||
|
EVENT_TRACE_CONTROL_QUERY);
|
||||||
|
|
||||||
|
if (status != ERROR_WMI_INSTANCE_NOT_FOUND) {
|
||||||
|
error_check_common_conditions(status);
|
||||||
|
|
||||||
|
return info.properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { };
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace_manager<T>::register_trace()
|
||||||
|
{
|
||||||
|
trace_info info = fill_trace_info();
|
||||||
|
|
||||||
|
ULONG status = StartTrace(&trace_.registrationHandle_,
|
||||||
|
trace_.name_.c_str(),
|
||||||
|
&info.properties);
|
||||||
|
if (status == ERROR_ALREADY_EXISTS) {
|
||||||
|
try {
|
||||||
|
stop_trace();
|
||||||
|
status = StartTrace(&trace_.registrationHandle_,
|
||||||
|
trace_.name_.c_str(),
|
||||||
|
&info.properties);
|
||||||
|
}
|
||||||
|
catch (need_to_be_admin_failure) {
|
||||||
|
(void)open_trace();
|
||||||
|
close_trace();
|
||||||
|
// insufficient privilege to stop/configure
|
||||||
|
// but if open/close didn't throw also
|
||||||
|
// then we're okay to process events
|
||||||
|
status = ERROR_SUCCESS;
|
||||||
|
// we also invalidate the registrationHandle_
|
||||||
|
// StartTrace() actually sets this to 0 on failure
|
||||||
|
trace_.registrationHandle_ = INVALID_PROCESSTRACE_HANDLE;
|
||||||
|
}
|
||||||
|
catch (invalid_parameter) {
|
||||||
|
// In some versions, the error code is 87 when using
|
||||||
|
// SystemTraceControlGuid session. If open/close doesn't
|
||||||
|
// throw, then we can continually processing events.
|
||||||
|
(void)open_trace();
|
||||||
|
close_trace();
|
||||||
|
status = ERROR_SUCCESS;
|
||||||
|
trace_.registrationHandle_ = INVALID_PROCESSTRACE_HANDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error_check_common_conditions(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
EVENT_TRACE_LOGFILE trace_manager<T>::open_trace()
|
||||||
|
{
|
||||||
|
auto file = fill_logfile();
|
||||||
|
trace_.sessionHandle_ = OpenTrace(&file);
|
||||||
|
if (trace_.sessionHandle_ == INVALID_PROCESSTRACE_HANDLE) {
|
||||||
|
throw open_trace_failure();
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace_manager<T>::process_trace()
|
||||||
|
{
|
||||||
|
if (trace_.sessionHandle_ == INVALID_PROCESSTRACE_HANDLE) {
|
||||||
|
throw open_trace_failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refactoring warning.
|
||||||
|
// During the testing of the (slower) C++/CLI implementation it became evident that
|
||||||
|
// EnableTraceEx2(EVENT_CONTROL_CODE_CAPTURE_STATE) must be called very shortly
|
||||||
|
// before ProcessTrace() in order for the rundown events to be generated.
|
||||||
|
T::trace_type::enable_rundown(trace_);
|
||||||
|
|
||||||
|
ULONG status = ProcessTrace(&trace_.sessionHandle_, 1, NULL, NULL);
|
||||||
|
error_check_common_conditions(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace_manager<T>::close_trace()
|
||||||
|
{
|
||||||
|
if (trace_.sessionHandle_ != INVALID_PROCESSTRACE_HANDLE) {
|
||||||
|
ULONG status = CloseTrace(trace_.sessionHandle_);
|
||||||
|
trace_.sessionHandle_ = INVALID_PROCESSTRACE_HANDLE;
|
||||||
|
|
||||||
|
if (status != ERROR_CTX_CLOSE_PENDING) {
|
||||||
|
error_check_common_conditions(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace_manager<T>::enable_providers()
|
||||||
|
{
|
||||||
|
T::trace_type::enable_providers(trace_);
|
||||||
|
}
|
||||||
|
} /* namespace details */ } /* namespace krabs */
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "../compiler_check.hpp"
|
||||||
|
|
||||||
|
namespace krabs { namespace predicates {
|
||||||
|
|
||||||
|
namespace comparers {
|
||||||
|
|
||||||
|
// Algorithms
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator based equals
|
||||||
|
*/
|
||||||
|
template <typename Comparer>
|
||||||
|
struct equals
|
||||||
|
{
|
||||||
|
template <typename Iter1, typename Iter2>
|
||||||
|
bool operator()(Iter1 first1, Iter1 last1, Iter2 first2, Iter2 last2) const
|
||||||
|
{
|
||||||
|
return std::equal(first1, last1, first2, last2, Comparer());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator based search
|
||||||
|
*/
|
||||||
|
template <typename Comparer>
|
||||||
|
struct contains
|
||||||
|
{
|
||||||
|
template <typename Iter1, typename Iter2>
|
||||||
|
bool operator()(Iter1 first1, Iter1 last1, Iter2 first2, Iter2 last2) const
|
||||||
|
{
|
||||||
|
// empty test range always contained, even when input range empty
|
||||||
|
return first2 == last2
|
||||||
|
|| std::search(first1, last1, first2, last2, Comparer()) != last1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator based starts_with
|
||||||
|
*/
|
||||||
|
template <typename Comparer>
|
||||||
|
struct starts_with
|
||||||
|
{
|
||||||
|
template <typename Iter1, typename Iter2>
|
||||||
|
bool operator()(Iter1 first1, Iter1 last1, Iter2 first2, Iter2 last2) const
|
||||||
|
{
|
||||||
|
const auto first_nonequal = std::mismatch(first1, last1, first2, last2, Comparer());
|
||||||
|
return first_nonequal.second == last2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator based ends_with
|
||||||
|
*/
|
||||||
|
template <typename Comparer>
|
||||||
|
struct ends_with
|
||||||
|
{
|
||||||
|
template <typename Iter1, typename Iter2>
|
||||||
|
bool operator()(Iter1 first1, Iter1 last1, Iter2 first2, Iter2 last2) const
|
||||||
|
{
|
||||||
|
const auto dist1 = std::distance(first1, last1);
|
||||||
|
const auto dist2 = std::distance(first2, last2);
|
||||||
|
|
||||||
|
if (dist2 > dist1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const auto suffix_begin = std::next(first1, dist1 - dist2);
|
||||||
|
return std::equal(suffix_begin, last1, first2, last2, Comparer());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Custom Comparison
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct iequal_to
|
||||||
|
{
|
||||||
|
bool operator()(const T& a, const T& b) const
|
||||||
|
{
|
||||||
|
static_assert(sizeof(T) == 0,
|
||||||
|
"iequal_to needs a specialized overload for type");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Binary predicate for comparing two wide characters case insensitively
|
||||||
|
* Does not handle all locales
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template <>
|
||||||
|
struct iequal_to<wchar_t>
|
||||||
|
{
|
||||||
|
bool operator()(const wchar_t& a, const wchar_t& b) const
|
||||||
|
{
|
||||||
|
return towupper(a) == towupper(b);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Binary predicate for comparing two characters case insensitively
|
||||||
|
* Does not handle all locales
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template <>
|
||||||
|
struct iequal_to<char>
|
||||||
|
{
|
||||||
|
bool operator()(const char& a, const char& b) const
|
||||||
|
{
|
||||||
|
return toupper(a) == toupper(b);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} /* namespace comparers */
|
||||||
|
|
||||||
|
} /* namespace predicates */ } /* namespace krabs */
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <evntcons.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <deque>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../compiler_check.hpp"
|
||||||
|
#include "../trace_context.hpp"
|
||||||
|
|
||||||
|
namespace krabs { namespace testing {
|
||||||
|
class event_filter_proxy;
|
||||||
|
} /* namespace testing */} /* namespace krabs */
|
||||||
|
|
||||||
|
namespace krabs { namespace details {
|
||||||
|
template <typename T> class base_provider;
|
||||||
|
} /* namespace details */} /* namespace krabs */
|
||||||
|
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
typedef void(*c_provider_callback)(const EVENT_RECORD &, const krabs::trace_context &);
|
||||||
|
typedef void(*c_provider_error_callback)(const EVENT_RECORD&, const std::string&);
|
||||||
|
typedef std::function<void(const EVENT_RECORD &, const krabs::trace_context &)> provider_event_callback;
|
||||||
|
typedef std::function<void(const EVENT_RECORD&, const std::string&)> provider_error_callback;
|
||||||
|
typedef std::function<bool(const EVENT_RECORD &, const krabs::trace_context &)> filter_predicate;
|
||||||
|
|
||||||
|
template <typename T> class provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Use this to provide event filtering before an event bubbles to
|
||||||
|
* specific callbacks.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* Each event_filter has a single predicate (which can do complicated
|
||||||
|
* checks and logic on the event). All callbacks registered under the
|
||||||
|
* filter are invoked only if the predicate returns true for a given
|
||||||
|
* event.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
class event_filter {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs an event_filter that applies the given predicate to all
|
||||||
|
* events.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
event_filter(filter_predicate predicate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs an event_filter that applies event id filtering by event_id
|
||||||
|
* which will be added to list of filtered event ids in ETW API.
|
||||||
|
* This way is more effective from performance point of view.
|
||||||
|
* Given optional predicate will be applied to ETW API filtered results
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
event_filter(unsigned short event_id, filter_predicate predicate=nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs an event_filter that applies event id filtering by event_id
|
||||||
|
* which will be added to list of filtered event ids in ETW API.
|
||||||
|
* This way is more effective from performance point of view.
|
||||||
|
* Given optional predicate will be applied to ETW API filtered results
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
event_filter(std::vector<unsigned short> event_ids, filter_predicate predicate = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Adds a function to call when an event for this filter is fired.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
void add_on_event_callback(c_provider_callback callback);
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
void add_on_event_callback(U &callback);
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
void add_on_event_callback(const U &callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Adds a function to call when an error occurs.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
void add_on_error_callback(c_provider_error_callback callback);
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
void add_on_error_callback(U& callback);
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
void add_on_error_callback(const U& callback);
|
||||||
|
|
||||||
|
const std::vector<unsigned short>& provider_filter_event_ids() const
|
||||||
|
{
|
||||||
|
return provider_filter_event_ids_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Called when an event occurs, forwards to callbacks if the event
|
||||||
|
* satisfies the predicate.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Called when an error occurs, forwards to the error callback
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
void on_error(const EVENT_RECORD& record, const std::string& error_message) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::deque<provider_event_callback> event_callbacks_;
|
||||||
|
std::deque<provider_error_callback> error_callbacks_;
|
||||||
|
filter_predicate predicate_{ nullptr };
|
||||||
|
std::vector<unsigned short> provider_filter_event_ids_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename T>
|
||||||
|
friend class details::base_provider;
|
||||||
|
|
||||||
|
friend class krabs::testing::event_filter_proxy;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline event_filter::event_filter(filter_predicate predicate)
|
||||||
|
: predicate_(predicate)
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline event_filter::event_filter(std::vector<unsigned short> event_ids, filter_predicate predicate/*=nullptr*/)
|
||||||
|
: provider_filter_event_ids_{ event_ids },
|
||||||
|
predicate_(predicate)
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline event_filter::event_filter(unsigned short event_id, filter_predicate predicate/*=nullptr*/)
|
||||||
|
: provider_filter_event_ids_{ event_id },
|
||||||
|
predicate_(predicate)
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline void event_filter::add_on_event_callback(c_provider_callback callback)
|
||||||
|
{
|
||||||
|
// C function pointers don't interact well with std::ref, so we
|
||||||
|
// overload to take care of this scenario.
|
||||||
|
event_callbacks_.push_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
void event_filter::add_on_event_callback(U &callback)
|
||||||
|
{
|
||||||
|
// std::function copies its argument -- because our callbacks list
|
||||||
|
// is a list of std::function, this causes problems when a user
|
||||||
|
// intended for their particular instance to be called.
|
||||||
|
// std::ref lets us get around this and point to a specific instance
|
||||||
|
// that they handed us.
|
||||||
|
event_callbacks_.push_back(std::ref(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
void event_filter::add_on_event_callback(const U &callback)
|
||||||
|
{
|
||||||
|
// This is where temporaries bind to. Temporaries can't be wrapped in
|
||||||
|
// a std::ref because they'll go away very quickly. We are forced to
|
||||||
|
// actually copy these.
|
||||||
|
event_callbacks_.push_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void event_filter::add_on_error_callback(c_provider_error_callback callback)
|
||||||
|
{
|
||||||
|
// C function pointers don't interact well with std::ref, so we
|
||||||
|
// overload to take care of this scenario.
|
||||||
|
error_callbacks_.push_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
void event_filter::add_on_error_callback(U& callback)
|
||||||
|
{
|
||||||
|
// std::function copies its argument -- because our callbacks list
|
||||||
|
// is a list of std::function, this causes problems when a user
|
||||||
|
// intended for their particular instance to be called.
|
||||||
|
// std::ref lets us get around this and point to a specific instance
|
||||||
|
// that they handed us.
|
||||||
|
error_callbacks_.push_back(std::ref(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
void event_filter::add_on_error_callback(const U& callback)
|
||||||
|
{
|
||||||
|
// This is where temporaries bind to. Temporaries can't be wrapped in
|
||||||
|
// a std::ref because they'll go away very quickly. We are forced to
|
||||||
|
// actually copy these.
|
||||||
|
error_callbacks_.push_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void event_filter::on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) const
|
||||||
|
{
|
||||||
|
if (event_callbacks_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (predicate_ != nullptr && !predicate_(record, trace_context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& callback : event_callbacks_) {
|
||||||
|
callback(record, trace_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const krabs::could_not_find_schema& ex)
|
||||||
|
{
|
||||||
|
// this occurs when a predicate is applied to an event for which
|
||||||
|
// no schema exists. instead of allowing the exception to halt
|
||||||
|
// the entire trace, send a notification to the filter's error callback
|
||||||
|
for (auto& error_callback : error_callbacks_) {
|
||||||
|
error_callback(record, ex.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} /* namespace krabs */
|
||||||
@@ -0,0 +1,552 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <evntcons.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "../compiler_check.hpp"
|
||||||
|
#include "comparers.hpp"
|
||||||
|
#include "../trace_context.hpp"
|
||||||
|
#include "view_adapters.hpp"
|
||||||
|
|
||||||
|
using namespace krabs::predicates::adapters;
|
||||||
|
using namespace krabs::predicates::comparers;
|
||||||
|
|
||||||
|
namespace krabs { namespace predicates {
|
||||||
|
|
||||||
|
namespace details {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* The base predicate struct, use to create a vector or list of
|
||||||
|
* Arbitrary predicate types
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct predicate_base
|
||||||
|
{
|
||||||
|
virtual bool operator()(const EVENT_RECORD&, const krabs::trace_context&) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns true for any event.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct any_event : predicate_base {
|
||||||
|
bool operator()(const EVENT_RECORD &, const krabs::trace_context &) const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns false for any event.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct no_event : predicate_base {
|
||||||
|
bool operator()(const EVENT_RECORD &, const krabs::trace_context &) const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Performs a logical AND on two filters.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
struct and_filter : predicate_base {
|
||||||
|
and_filter(const T1 &t1, const T2 &t2)
|
||||||
|
: t1_(t1)
|
||||||
|
, t2_(t2)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool operator()(const EVENT_RECORD &record, const krabs::trace_context &trace_context) const
|
||||||
|
{
|
||||||
|
return (t1_(record, trace_context) && t2_(record, trace_context));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const T1 t1_;
|
||||||
|
const T2 t2_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Performs a logical OR on two filters.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
struct or_filter : predicate_base {
|
||||||
|
or_filter(const T1 &t1, const T2 &t2)
|
||||||
|
: t1_(t1)
|
||||||
|
, t2_(t2)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool operator()(const EVENT_RECORD &record, const krabs::trace_context &trace_context) const
|
||||||
|
{
|
||||||
|
return (t1_(record, trace_context) || t2_(record, trace_context));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const T1 t1_;
|
||||||
|
const T2 t2_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Performs a logical NOT on a filter.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template <typename T1>
|
||||||
|
struct not_filter : predicate_base {
|
||||||
|
not_filter(const T1 &t1)
|
||||||
|
: t1_(t1)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool operator()(const EVENT_RECORD &record, const krabs::trace_context &trace_context) const
|
||||||
|
{
|
||||||
|
return !t1_(record, trace_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const T1 t1_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns true if the event property matches the expected value.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
struct property_is : predicate_base {
|
||||||
|
property_is(const std::wstring &property, const T &expected)
|
||||||
|
: property_(property)
|
||||||
|
, expected_(expected)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool operator()(const EVENT_RECORD &record, const krabs::trace_context &trace_context) const
|
||||||
|
{
|
||||||
|
krabs::schema schema(record, trace_context.schema_locator);
|
||||||
|
krabs::parser parser(schema);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return (expected_ == parser.parse<T>(property_));
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::wstring property_;
|
||||||
|
const T expected_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Gets a collection_view of a property using the specified adapter
|
||||||
|
* and executes the specified predicate against the view.
|
||||||
|
* This is used to provide type-specialization for properties
|
||||||
|
* that can be represented by the collection_view.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template <typename T, typename Adapter, typename Predicate>
|
||||||
|
struct property_view_predicate : details::predicate_base
|
||||||
|
{
|
||||||
|
property_view_predicate(
|
||||||
|
const std::wstring &property,
|
||||||
|
const T &expected,
|
||||||
|
Adapter adapter,
|
||||||
|
Predicate predicate)
|
||||||
|
: property_(property)
|
||||||
|
, expected_(expected)
|
||||||
|
, adapter_(adapter)
|
||||||
|
, predicate_(predicate)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
//bool operator()(const EVENT_RECORD &record, const krabs::trace_context &trace_context)
|
||||||
|
bool operator()(const EVENT_RECORD& record, const krabs::trace_context& trace_context) const
|
||||||
|
{
|
||||||
|
krabs::schema schema(record, trace_context.schema_locator);
|
||||||
|
krabs::parser parser(schema);
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto view = parser.view_of(property_, adapter_);
|
||||||
|
return predicate_(view.begin(), view.end(), expected_.begin(), expected_.end());
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::wstring property_;
|
||||||
|
const T expected_;
|
||||||
|
Adapter adapter_;
|
||||||
|
Predicate predicate_;
|
||||||
|
};
|
||||||
|
} /* namespace details */
|
||||||
|
|
||||||
|
// Filter factory functions
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* A simple filter that accepts any event.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
static details::any_event any_event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* A simple filter that accepts no events.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
static details::no_event no_event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Accepts an event if its ID matches the expected value.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct id_is : details::predicate_base {
|
||||||
|
id_is(size_t expected)
|
||||||
|
: expected_(USHORT(expected))
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool operator()(const EVENT_RECORD &record, const krabs::trace_context &) const
|
||||||
|
{
|
||||||
|
return (record.EventHeader.EventDescriptor.Id == expected_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
USHORT expected_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Accepts an event if its opcode matches the expected value.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct opcode_is : details::predicate_base {
|
||||||
|
opcode_is(size_t expected)
|
||||||
|
: expected_(USHORT(expected))
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool operator()(const EVENT_RECORD &record, const krabs::trace_context &) const
|
||||||
|
{
|
||||||
|
return (record.EventHeader.EventDescriptor.Opcode == expected_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
USHORT expected_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Accepts an event if any of the predicates in the vector matches
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct any_of : details::predicate_base {
|
||||||
|
any_of(std::vector<details::predicate_base*> list)
|
||||||
|
: list_(list)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool operator()(const EVENT_RECORD &record, const krabs::trace_context &trace_context) const
|
||||||
|
{
|
||||||
|
for (auto &item : list_) {
|
||||||
|
if (item->operator()(record, trace_context)) {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::vector<details::predicate_base*> list_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Accepts an event if all of the predicates in the vector matches
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct all_of : details::predicate_base {
|
||||||
|
all_of(std::vector<details::predicate_base*> list)
|
||||||
|
: list_(list)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool operator()(const EVENT_RECORD& record, const krabs::trace_context& trace_context) const
|
||||||
|
{
|
||||||
|
if (list_.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (auto& item : list_) {
|
||||||
|
if (!item->operator()(record, trace_context)) {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::vector<details::predicate_base*> list_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Accepts an event only if none of the predicates in the vector match
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct none_of : details::predicate_base {
|
||||||
|
none_of(std::vector<details::predicate_base*> list)
|
||||||
|
: list_(list)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool operator()(const EVENT_RECORD& record, const krabs::trace_context& trace_context) const
|
||||||
|
{
|
||||||
|
for (auto& item : list_) {
|
||||||
|
if (item->operator()(record, trace_context)) {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::vector<details::predicate_base*> list_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Accepts an event if its version matches the expected value.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct version_is : details::predicate_base {
|
||||||
|
version_is(size_t expected)
|
||||||
|
: expected_(USHORT(expected))
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool operator()(const EVENT_RECORD &record, const krabs::trace_context &) const
|
||||||
|
{
|
||||||
|
return (record.EventHeader.EventDescriptor.Version == expected_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
USHORT expected_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Accepts an event if its PID matches the expected value.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct process_id_is : details::predicate_base {
|
||||||
|
process_id_is(size_t expected)
|
||||||
|
: expected_(ULONG(expected))
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool operator()(const EVENT_RECORD &record, const krabs::trace_context &) const
|
||||||
|
{
|
||||||
|
return (record.EventHeader.ProcessId == expected_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ULONG expected_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Accepts an event if the named property matches the expected value.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
details::property_is<T> property_is(
|
||||||
|
const std::wstring &prop,
|
||||||
|
const T &expected)
|
||||||
|
{
|
||||||
|
return details::property_is<T>(prop, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Explicit specialization for string arrays, because C++.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
inline details::property_is<std::wstring> property_is(
|
||||||
|
const std::wstring &prop,
|
||||||
|
const wchar_t *expected)
|
||||||
|
{
|
||||||
|
return details::property_is<std::wstring>(prop, std::wstring(expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline details::property_is<std::string> property_is(
|
||||||
|
const std::wstring &prop,
|
||||||
|
const char *expected)
|
||||||
|
{
|
||||||
|
return details::property_is<std::string>(prop, std::string(expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
// View-based filters
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View based filters work on ranges of data in-place in the etw record.
|
||||||
|
* By default, these expect a null terminated string property in the
|
||||||
|
* record, but if an adapter is specified, any range of data can be
|
||||||
|
* compared. The comparison functor must support taking two iterator
|
||||||
|
* pairs that iterate the value_type specified by the adapter. Because
|
||||||
|
* comparisons use iterators, it's possible to compare various flavors
|
||||||
|
* of strings as well as arrays and binary data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts events if property exactly matches the expected value
|
||||||
|
*/
|
||||||
|
template <
|
||||||
|
typename Adapter = adapters::generic_string<wchar_t>,
|
||||||
|
typename T,
|
||||||
|
typename Comparer = equals<std::equal_to<typename Adapter::value_type>>>
|
||||||
|
details::property_view_predicate<T, Adapter, Comparer> property_equals(
|
||||||
|
const std::wstring &prop,
|
||||||
|
const T& expected)
|
||||||
|
{
|
||||||
|
return { prop, expected, Adapter(), Comparer() };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts events if property case insensitive matches the expected value
|
||||||
|
*/
|
||||||
|
template <
|
||||||
|
typename Adapter = adapters::generic_string<wchar_t>,
|
||||||
|
typename T,
|
||||||
|
typename Comparer = equals<iequal_to<typename Adapter::value_type>>>
|
||||||
|
details::property_view_predicate<T, Adapter, Comparer> property_iequals(
|
||||||
|
const std::wstring &prop,
|
||||||
|
const T& expected)
|
||||||
|
{
|
||||||
|
return{ prop, expected, Adapter(), Comparer() };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts events if property contains expected value
|
||||||
|
*/
|
||||||
|
template <
|
||||||
|
typename Adapter = adapters::generic_string<wchar_t>,
|
||||||
|
typename T,
|
||||||
|
typename Comparer = contains<std::equal_to<typename Adapter::value_type>>>
|
||||||
|
details::property_view_predicate<T, Adapter, Comparer> property_contains(
|
||||||
|
const std::wstring &prop,
|
||||||
|
const T& expected)
|
||||||
|
{
|
||||||
|
return {prop, expected, Adapter(), Comparer()};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts events if property case insensitive contains expected value
|
||||||
|
*/
|
||||||
|
template <
|
||||||
|
typename Adapter = adapters::generic_string<wchar_t>,
|
||||||
|
typename T,
|
||||||
|
typename Comparer = contains<iequal_to<typename Adapter::value_type>>>
|
||||||
|
details::property_view_predicate<T, Adapter, Comparer> property_icontains(
|
||||||
|
const std::wstring &prop,
|
||||||
|
const T& expected)
|
||||||
|
{
|
||||||
|
return{ prop, expected, Adapter(), Comparer() };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts events if property starts with expected value
|
||||||
|
*/
|
||||||
|
template <
|
||||||
|
typename Adapter = adapters::generic_string<wchar_t>,
|
||||||
|
typename T,
|
||||||
|
typename Comparer = starts_with<std::equal_to<typename Adapter::value_type>>>
|
||||||
|
details::property_view_predicate<T, Adapter, Comparer> property_starts_with(
|
||||||
|
const std::wstring &prop,
|
||||||
|
const T& expected)
|
||||||
|
{
|
||||||
|
return{ prop, expected, Adapter(), Comparer() };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts events if property case insensitive starts with expected value
|
||||||
|
*/
|
||||||
|
template <
|
||||||
|
typename Adapter = adapters::generic_string<wchar_t>,
|
||||||
|
typename T,
|
||||||
|
typename Comparer = starts_with<iequal_to<typename Adapter::value_type>>>
|
||||||
|
details::property_view_predicate<T, Adapter, Comparer> property_istarts_with(
|
||||||
|
const std::wstring &prop,
|
||||||
|
const T& expected)
|
||||||
|
{
|
||||||
|
return{ prop, expected, Adapter(), Comparer() };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts events if property ends with expected value
|
||||||
|
*/
|
||||||
|
template <
|
||||||
|
typename Adapter = adapters::generic_string<wchar_t>,
|
||||||
|
typename T,
|
||||||
|
typename Comparer = ends_with<std::equal_to<typename Adapter::value_type>>>
|
||||||
|
details::property_view_predicate<T, Adapter, Comparer> property_ends_with(
|
||||||
|
const std::wstring &prop,
|
||||||
|
const T& expected)
|
||||||
|
{
|
||||||
|
return{ prop, expected, Adapter(), Comparer() };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts events if property case insensitive ends with expected value
|
||||||
|
*/
|
||||||
|
template <
|
||||||
|
typename Adapter = adapters::generic_string<wchar_t>,
|
||||||
|
typename T,
|
||||||
|
typename Comparer = ends_with<iequal_to<typename Adapter::value_type>>>
|
||||||
|
details::property_view_predicate<T, Adapter, Comparer> property_iends_with(
|
||||||
|
const std::wstring &prop,
|
||||||
|
const T& expected)
|
||||||
|
{
|
||||||
|
return{ prop, expected, Adapter(), Comparer() };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Accepts an event if its two component filters both accept the event.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
details::and_filter<T1, T2> and_filter(const T1 &t1, const T2 &t2)
|
||||||
|
{
|
||||||
|
return details::and_filter<T1, T2>(t1, t2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Accepts an event if either of its two component filters accept the event.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
details::or_filter<T1, T2> or_filter(const T1 &t1, const T2 &t2)
|
||||||
|
{
|
||||||
|
return details::or_filter<T1, T2>(t1, t2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Negates the filter that is given to it.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template <typename T1>
|
||||||
|
details::not_filter<T1> not_filter(const T1 &t1)
|
||||||
|
{
|
||||||
|
return details::not_filter<T1>(t1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace predicates */ } /* namespace krabs */
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "../compiler_check.hpp"
|
||||||
|
#include "../parser.hpp"
|
||||||
|
|
||||||
|
namespace krabs { namespace predicates {
|
||||||
|
|
||||||
|
namespace adapters {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View adapter for counted_string strings
|
||||||
|
*/
|
||||||
|
struct counted_string
|
||||||
|
{
|
||||||
|
using value_type = krabs::counted_string::value_type;
|
||||||
|
using const_iterator = krabs::counted_string::const_iterator;
|
||||||
|
|
||||||
|
collection_view<const_iterator> operator()(const property_info& propInfo) const
|
||||||
|
{
|
||||||
|
auto cs_ptr = reinterpret_cast<const krabs::counted_string*>(propInfo.pPropertyIndex_);
|
||||||
|
return krabs::view(cs_ptr->string(), cs_ptr->length());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View adapter for fixed width and null-terminated strings
|
||||||
|
*/
|
||||||
|
template <typename ElemT>
|
||||||
|
struct generic_string
|
||||||
|
{
|
||||||
|
using value_type = ElemT;
|
||||||
|
using const_iterator = const value_type*;
|
||||||
|
|
||||||
|
collection_view<const_iterator> operator()(const property_info& propInfo) const
|
||||||
|
{
|
||||||
|
auto pString = reinterpret_cast<const value_type*>(propInfo.pPropertyIndex_);
|
||||||
|
auto length = get_string_content_length(pString, propInfo.length_);
|
||||||
|
|
||||||
|
return krabs::view(pString, length);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} /* namespace adapters */
|
||||||
|
|
||||||
|
} /* namespace predicates */ } /* namespace krabs */
|
||||||
@@ -0,0 +1,345 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <objbase.h>
|
||||||
|
|
||||||
|
#include <new>
|
||||||
|
#include <memory>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
/** <summary>
|
||||||
|
* Represents a GUID, allowing simplified construction from a string or
|
||||||
|
* Windows GUID structure.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
class guid {
|
||||||
|
public:
|
||||||
|
guid(GUID guid);
|
||||||
|
guid(const std::wstring &guid);
|
||||||
|
|
||||||
|
bool operator==(const guid &rhs) const;
|
||||||
|
bool operator==(const GUID &rhs) const;
|
||||||
|
|
||||||
|
bool operator<(const guid &rhs) const;
|
||||||
|
bool operator<(const GUID &rhs) const;
|
||||||
|
|
||||||
|
operator GUID() const;
|
||||||
|
operator const GUID*() const;
|
||||||
|
|
||||||
|
/** <summary>
|
||||||
|
* Constructs a new random guid.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
static inline guid random_guid();
|
||||||
|
|
||||||
|
private:
|
||||||
|
GUID guid_;
|
||||||
|
|
||||||
|
friend struct std::hash<guid>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** <summary>
|
||||||
|
* Helper functions for parsing GUID's.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
class guid_parser {
|
||||||
|
// Implementing in Krabs instead of Lobsters so that we can test it in unmanaged code.
|
||||||
|
private:
|
||||||
|
// Number of characters in the UUID's 8-4-4-4-12 string format.
|
||||||
|
static const size_t UUID_STRING_LENGTH = 36;
|
||||||
|
|
||||||
|
static const unsigned char DELIMITER = '-';
|
||||||
|
|
||||||
|
// Expected character positions of runs of hex digits in 8-4-4-4-12 format, e.g.
|
||||||
|
// 00000000-0000-0000-0000-000000000000
|
||||||
|
// Names correspond to struct members of GUID.
|
||||||
|
static const size_t STR_POSITION_DATA1 = 0;
|
||||||
|
static const size_t STR_POSITION_DATA2 = 8 + 1;
|
||||||
|
static const size_t STR_POSITION_DATA3 = STR_POSITION_DATA2 + 4 + 1;
|
||||||
|
static const size_t STR_POSITION_DATA4_PART1 = STR_POSITION_DATA3 + 4 + 1;
|
||||||
|
static const size_t STR_POSITION_DATA4_PART2 = STR_POSITION_DATA4_PART1 + 4 + 1;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// str_input must have at least 2 valid chars in allocated buffer
|
||||||
|
static bool hex_octet_to_byte(const char* str_input, unsigned char& byte_output);
|
||||||
|
// str_input must have at least 2*sizeof(T) valid chars in allocated buffer
|
||||||
|
template<typename T>
|
||||||
|
static bool hex_string_to_number(const char* str_input, T& int_output);
|
||||||
|
// str_input must have at least 2*byte_count valid chars in allocated buffer,
|
||||||
|
// and byte_output must have at least byte_count bytes in allocated buffer
|
||||||
|
static bool hex_string_to_bytes(const char* str_input, unsigned char* byte_output, size_t byte_count);
|
||||||
|
|
||||||
|
/** <summary>
|
||||||
|
* Parses GUID of "D" format. For example, the nil GUID would be "00000000-0000-0000-0000-000000000000".
|
||||||
|
* See: https://docs.microsoft.com/en-us/dotnet/api/system.guid.tostring?view=netframework-4.8
|
||||||
|
*
|
||||||
|
* (str) must have at least (length) valid characters for memory safety. A null terminator is not
|
||||||
|
* required. Instead, (length) is used for the bounds check.
|
||||||
|
*
|
||||||
|
* Returns the parsed GUID. Throws a std::runtime_error if there is a bounds error or format error.
|
||||||
|
*
|
||||||
|
* This function is for performance, to help deal with container ID extended data, which has no null
|
||||||
|
* terminator, which would force us to clone the data to append a null terminator in order to use
|
||||||
|
* existing GUID parsing functions.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
static GUID parse_guid(const char* str, unsigned int length);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline guid::guid(GUID guid)
|
||||||
|
: guid_(guid)
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline guid::guid(const std::wstring &guid)
|
||||||
|
{
|
||||||
|
HRESULT hr = CLSIDFromString(guid.c_str(), &guid_);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable: 4244) // narrowing guid wchar_t to char for this error message
|
||||||
|
std::string guidStr(guid.begin(), guid.end());
|
||||||
|
#pragma warning(pop)
|
||||||
|
std::stringstream stream;
|
||||||
|
stream << "Error in constructing guid from string (";
|
||||||
|
stream << guidStr;
|
||||||
|
stream << "), hr = 0x";
|
||||||
|
stream << std::hex << hr;
|
||||||
|
throw std::runtime_error(stream.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool guid::operator==(const guid &rhs) const
|
||||||
|
{
|
||||||
|
return (0 == memcmp(&guid_, &rhs.guid_, sizeof(GUID)));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool guid::operator==(const GUID &rhs) const
|
||||||
|
{
|
||||||
|
return (0 == memcmp(&guid_, &rhs, sizeof(GUID)));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool guid::operator<(const guid &rhs) const
|
||||||
|
{
|
||||||
|
return (memcmp(&guid_, &rhs.guid_, sizeof(guid_)) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool guid::operator<(const GUID &rhs) const
|
||||||
|
{
|
||||||
|
return (memcmp(&guid_, &rhs, sizeof(guid_)) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline guid::operator GUID() const
|
||||||
|
{
|
||||||
|
return guid_;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline guid::operator const GUID*() const
|
||||||
|
{
|
||||||
|
return &guid_;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline guid guid::random_guid()
|
||||||
|
{
|
||||||
|
GUID tmpGuid;
|
||||||
|
CoCreateGuid(&tmpGuid);
|
||||||
|
return guid(tmpGuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CoTaskMemDeleter {
|
||||||
|
void operator()(wchar_t *mem) {
|
||||||
|
CoTaskMemFree(mem);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool guid_parser::hex_octet_to_byte(const char* str_input, unsigned char& byte_output)
|
||||||
|
{
|
||||||
|
// Accepts chars '0' through '9' (0x30 to 0x39),
|
||||||
|
// 'A' through 'F' (0x41 to 0x46)
|
||||||
|
// 'a' through 'f' (0x61 to 0x66)
|
||||||
|
|
||||||
|
// Narrow the value later, for safety checking.
|
||||||
|
auto value = 0;
|
||||||
|
// most significant digit in the octet
|
||||||
|
auto msd = str_input[0];
|
||||||
|
// least significant digit in the octet
|
||||||
|
auto lsd = str_input[1];
|
||||||
|
|
||||||
|
if (msd >= '0' && msd <= '9')
|
||||||
|
{
|
||||||
|
value |= ((int)msd & 0x0F) << 4;
|
||||||
|
}
|
||||||
|
else if ((msd >= 'A' && msd <= 'F') || (msd >= 'a' && msd <= 'f'))
|
||||||
|
{
|
||||||
|
value |= (((int)msd & 0x0F) + 9) << 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lsd >= '0' && lsd <= '9')
|
||||||
|
{
|
||||||
|
value |= ((int)lsd & 0x0F);
|
||||||
|
}
|
||||||
|
else if ((lsd >= 'A' && lsd <= 'F') || (lsd >= 'a' && lsd <= 'f'))
|
||||||
|
{
|
||||||
|
value |= (((int)lsd & 0x0F) + 9);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(value >= 0 && value <= UCHAR_MAX);
|
||||||
|
byte_output = static_cast<unsigned char>(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool guid_parser::hex_string_to_number(const char* str_input, T& int_output)
|
||||||
|
{
|
||||||
|
auto byte_count = sizeof(T);
|
||||||
|
T value = 0;
|
||||||
|
unsigned char byte = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < byte_count; i++)
|
||||||
|
{
|
||||||
|
if (!guid_parser::hex_octet_to_byte(str_input + i * 2, byte))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = (value << 8) | static_cast<T>(byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
int_output = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool guid_parser::hex_string_to_bytes(const char* str_input, unsigned char* byte_output, size_t byte_count)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < byte_count; i++)
|
||||||
|
{
|
||||||
|
if (!hex_octet_to_byte(str_input + (i * 2), byte_output[i]))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** <summary>
|
||||||
|
* Parses GUID of "D" format. For example, the nil GUID would be "00000000-0000-0000-0000-000000000000".
|
||||||
|
* See: https://docs.microsoft.com/en-us/dotnet/api/system.guid.tostring?view=netframework-4.8
|
||||||
|
*
|
||||||
|
* (str) must have at least (length) valid characters for memory safety. A null terminator is not
|
||||||
|
* required. Instead, (length) is used for the bounds check.
|
||||||
|
*
|
||||||
|
* Returns the parsed GUID. Throws a std::runtime_error if there is a bounds error or format error.
|
||||||
|
*
|
||||||
|
* This function is for performance, to help deal with container ID extended data, which has no null
|
||||||
|
* terminator, which would force us to clone the data to append a null terminator in order to use
|
||||||
|
* existing GUID parsing functions.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
inline GUID guid_parser::parse_guid(const char* str, unsigned int length)
|
||||||
|
{
|
||||||
|
if (length != UUID_STRING_LENGTH)
|
||||||
|
{
|
||||||
|
std::stringstream message;
|
||||||
|
message << "Input data has incorrect length. Expected "
|
||||||
|
<< UUID_STRING_LENGTH
|
||||||
|
<< ", got "
|
||||||
|
<< length;
|
||||||
|
throw std::runtime_error(message.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
GUID guid = { 0 };
|
||||||
|
|
||||||
|
// Check that hyphens are in expected places as a formatting issue.
|
||||||
|
if (str[STR_POSITION_DATA2 - 1] != DELIMITER ||
|
||||||
|
str[STR_POSITION_DATA3 - 1] != DELIMITER ||
|
||||||
|
str[STR_POSITION_DATA4_PART1 - 1] != DELIMITER ||
|
||||||
|
str[STR_POSITION_DATA4_PART2 - 1] != DELIMITER)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Missing a hyphen where one was expected.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use from_hex_string for Data1, Data2, and Data3 because of endianness of the data
|
||||||
|
// Use hex_string_to_bytes for Data4's array elements because it's byte by byte instead
|
||||||
|
auto success = guid_parser::hex_string_to_number(str + STR_POSITION_DATA1, guid.Data1)
|
||||||
|
&& guid_parser::hex_string_to_number(str + STR_POSITION_DATA2, guid.Data2)
|
||||||
|
&& guid_parser::hex_string_to_number(str + STR_POSITION_DATA3, guid.Data3)
|
||||||
|
&& guid_parser::hex_string_to_bytes(str + STR_POSITION_DATA4_PART1, reinterpret_cast<unsigned char*>(&guid.Data4[0]), 2)
|
||||||
|
&& guid_parser::hex_string_to_bytes(str + STR_POSITION_DATA4_PART2, reinterpret_cast<unsigned char*>(&guid.Data4[2]), 6);
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("GUID string contains non-hex digits where hex digits are expected.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return guid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace std
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Converts a krabs GUID to a wide string
|
||||||
|
*/
|
||||||
|
inline std::wstring to_wstring(const krabs::guid& guid)
|
||||||
|
{
|
||||||
|
wchar_t* guidString;
|
||||||
|
HRESULT hr = StringFromCLSID(guid, &guidString);
|
||||||
|
|
||||||
|
if (FAILED(hr)) throw std::bad_alloc();
|
||||||
|
|
||||||
|
std::unique_ptr<wchar_t, krabs::CoTaskMemDeleter> managed(guidString);
|
||||||
|
|
||||||
|
return { managed.get() };
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Converts a Windows GUID to a C string
|
||||||
|
*/
|
||||||
|
inline std::string to_string(const GUID& guid)
|
||||||
|
{
|
||||||
|
char guid_string[37]; // 32 hex chars + 4 hyphens + null terminator
|
||||||
|
snprintf(
|
||||||
|
guid_string, sizeof(guid_string),
|
||||||
|
"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
||||||
|
guid.Data1, guid.Data2, guid.Data3,
|
||||||
|
guid.Data4[0], guid.Data4[1], guid.Data4[2],
|
||||||
|
guid.Data4[3], guid.Data4[4], guid.Data4[5],
|
||||||
|
guid.Data4[6], guid.Data4[7]);
|
||||||
|
return guid_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct std::hash<krabs::guid>
|
||||||
|
{
|
||||||
|
size_t operator()(const krabs::guid& guid) const
|
||||||
|
{
|
||||||
|
// This algorithm comes from .NET's reference source for Guid.GetHashCode()
|
||||||
|
return guid.guid_.Data1 ^
|
||||||
|
((guid.guid_.Data2 << 16) | guid.guid_.Data3 ) ^
|
||||||
|
((guid.guid_.Data4[2] << 24) | guid.guid_.Data4[7]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <guiddef.h>
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
|
||||||
|
namespace krabs { namespace guids {
|
||||||
|
|
||||||
|
DEFINE_GUID ( /* 45d8cccd-539f-4b72-a8b7-5c683142609a */
|
||||||
|
alpc,
|
||||||
|
0x45d8cccd,
|
||||||
|
0x539f,
|
||||||
|
0x4b72,
|
||||||
|
0xa8, 0xb7, 0x5c, 0x68, 0x31, 0x42, 0x60, 0x9a
|
||||||
|
);
|
||||||
|
|
||||||
|
DEFINE_GUID ( /* 13976d09-a327-438c-950b-7f03192815c7 */
|
||||||
|
debug,
|
||||||
|
0x13976d09,
|
||||||
|
0xa327,
|
||||||
|
0x438c,
|
||||||
|
0x95, 0x0b, 0x7f, 0x03, 0x19, 0x28, 0x15, 0xc7
|
||||||
|
);
|
||||||
|
|
||||||
|
DEFINE_GUID ( /* 3d6fa8d4-fe05-11d0-9dda-00c04fd7ba7c */
|
||||||
|
disk_io,
|
||||||
|
0x3d6fa8d4,
|
||||||
|
0xfe05,
|
||||||
|
0x11d0,
|
||||||
|
0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c
|
||||||
|
);
|
||||||
|
|
||||||
|
DEFINE_GUID ( /* 01853a65-418f-4f36-aefc-dc0f1d2fd235 */
|
||||||
|
event_trace_config,
|
||||||
|
0x01853a65,
|
||||||
|
0x418f,
|
||||||
|
0x4f36,
|
||||||
|
0xae, 0xfc, 0xdc, 0x0f, 0x1d, 0x2f, 0xd2, 0x35
|
||||||
|
);
|
||||||
|
|
||||||
|
DEFINE_GUID ( /* 90cbdc39-4a3e-11d1-84f4-0000f80464e3 */
|
||||||
|
file_io,
|
||||||
|
0x90cbdc39,
|
||||||
|
0x4a3e,
|
||||||
|
0x11d1,
|
||||||
|
0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3
|
||||||
|
);
|
||||||
|
|
||||||
|
DEFINE_GUID ( /* 2cb15d1d-5fc1-11d2-abe1-00a0c911f518 */
|
||||||
|
image_load,
|
||||||
|
0x2cb15d1d,
|
||||||
|
0x5fc1,
|
||||||
|
0x11d2,
|
||||||
|
0xab, 0xe1, 0x00, 0xa0, 0xc9, 0x11, 0xf5, 0x18
|
||||||
|
);
|
||||||
|
|
||||||
|
DEFINE_GUID ( /* 3d6fa8d3-fe05-11d0-9dda-00c04fd7ba7c */
|
||||||
|
page_fault,
|
||||||
|
0x3d6fa8d3,
|
||||||
|
0xfe05,
|
||||||
|
0x11d0,
|
||||||
|
0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c
|
||||||
|
);
|
||||||
|
|
||||||
|
DEFINE_GUID ( /* ce1dbfb4-137e-4da6-87b0-3f59aa102cbc */
|
||||||
|
perf_info,
|
||||||
|
0xce1dbfb4,
|
||||||
|
0x137e,
|
||||||
|
0x4da6,
|
||||||
|
0x87, 0xb0, 0x3f, 0x59, 0xaa, 0x10, 0x2c, 0xbc
|
||||||
|
);
|
||||||
|
|
||||||
|
DEFINE_GUID ( /* 3d6fa8d0-fe05-11d0-9dda-00c04fd7ba7c */
|
||||||
|
process,
|
||||||
|
0x3d6fa8d0,
|
||||||
|
0xfe05,
|
||||||
|
0x11d0,
|
||||||
|
0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c
|
||||||
|
);
|
||||||
|
|
||||||
|
DEFINE_GUID ( /* AE53722E-C863-11d2-8659-00C04FA321A1 */
|
||||||
|
registry,
|
||||||
|
0xae53722e,
|
||||||
|
0xc863,
|
||||||
|
0x11d2,
|
||||||
|
0x86, 0x59, 0x0, 0xc0, 0x4f, 0xa3, 0x21, 0xa1
|
||||||
|
);
|
||||||
|
|
||||||
|
DEFINE_GUID ( /* d837ca92-12b9-44a5-ad6a-3a65b3578aa8 */
|
||||||
|
split_io,
|
||||||
|
0xd837ca92,
|
||||||
|
0x12b9,
|
||||||
|
0x44a5,
|
||||||
|
0xad, 0x6a, 0x3a, 0x65, 0xb3, 0x57, 0x8a, 0xa8
|
||||||
|
);
|
||||||
|
|
||||||
|
DEFINE_GUID ( /* 9a280ac0-c8e0-11d1-84e2-00c04fb998a2 */
|
||||||
|
tcp_ip,
|
||||||
|
0x9a280ac0,
|
||||||
|
0xc8e0,
|
||||||
|
0x11d1,
|
||||||
|
0x84, 0xe2, 0x00, 0xc0, 0x4f, 0xb9, 0x98, 0xa2
|
||||||
|
);
|
||||||
|
|
||||||
|
DEFINE_GUID ( /* 3d6fa8d1-fe05-11d0-9dda-00c04fd7ba7c */
|
||||||
|
thread,
|
||||||
|
0x3d6fa8d1,
|
||||||
|
0xfe05,
|
||||||
|
0x11d0,
|
||||||
|
0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c
|
||||||
|
);
|
||||||
|
|
||||||
|
DEFINE_GUID ( /* bf3a50c5-a9c9-4988-a005-2df0b7c80f80 */
|
||||||
|
udp_ip,
|
||||||
|
0xbf3a50c5,
|
||||||
|
0xa9c9,
|
||||||
|
0x4988,
|
||||||
|
0xa0, 0x05, 0x2d, 0xf0, 0xb7, 0xc8, 0x0f, 0x80
|
||||||
|
);
|
||||||
|
|
||||||
|
DEFINE_GUID ( /* 9e814aad-3204-11d2-9a82-006008a86939 */
|
||||||
|
system_trace,
|
||||||
|
0x9e814aad,
|
||||||
|
0x3204,
|
||||||
|
0x11d2,
|
||||||
|
0x9a, 0x82, 0x00, 0x60, 0x08, 0xa8, 0x69, 0x39);
|
||||||
|
|
||||||
|
DEFINE_GUID( /* 89497f50-effe-4440-8cf2-ce6b1cdcaca7 */
|
||||||
|
ob_trace,
|
||||||
|
0x89497f50,
|
||||||
|
0xeffe,
|
||||||
|
0x4440,
|
||||||
|
0x8c, 0xf2, 0xce, 0x6b, 0x1c, 0xdc, 0xac, 0xa7);
|
||||||
|
|
||||||
|
DEFINE_GUID( /* 0268a8b6-74fd-4302-9dd0-6e8f1795c0cf */
|
||||||
|
pool_trace,
|
||||||
|
0x0268a8b6,
|
||||||
|
0x74fd,
|
||||||
|
0x4302,
|
||||||
|
0x9d, 0xd0, 0x6e, 0x8f, 0x17, 0x95, 0xc0, 0xcf);
|
||||||
|
|
||||||
|
DEFINE_GUID( /* 68fdd900-4a3e-11d1-84f4-0000f80464e3 */
|
||||||
|
event_trace,
|
||||||
|
0x68fdd900,
|
||||||
|
0x4a3e,
|
||||||
|
0x11d1,
|
||||||
|
0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3);
|
||||||
|
|
||||||
|
DEFINE_GUID( /* 6a399ae0-4bc6-4de9-870b-3657f8947e7e */
|
||||||
|
lost_event,
|
||||||
|
0x6a399ae0,
|
||||||
|
0x4bc6,
|
||||||
|
0x4de9,
|
||||||
|
0x87, 0x0b, 0x36, 0x57, 0xf8, 0x94, 0x7e, 0x7e);
|
||||||
|
|
||||||
|
DEFINE_GUID( /* 9aec974b-5b8e-4118-9b92-3186d8002ce5 */
|
||||||
|
ums_event,
|
||||||
|
0x9aec974b,
|
||||||
|
0x5b8e,
|
||||||
|
0x4118,
|
||||||
|
0x9b, 0x92, 0x31, 0x86, 0xd8, 0x00, 0x2c, 0xe5);
|
||||||
|
|
||||||
|
DEFINE_GUID( /* def2fe46-7bd6-4b80-bd94-f57fe20d0ce3 */
|
||||||
|
stack_walk,
|
||||||
|
0xdef2fe46,
|
||||||
|
0x7bd6,
|
||||||
|
0x4b80,
|
||||||
|
0xbd, 0x94, 0xf5, 0x7f, 0xe2, 0x0d, 0x0c, 0xe3);
|
||||||
|
|
||||||
|
DEFINE_GUID( /* e43445e0-0903-48c3-b878-ff0fccebdd04 */
|
||||||
|
power,
|
||||||
|
0xe43445e0,
|
||||||
|
0x0903,
|
||||||
|
0x48c3,
|
||||||
|
0xb8, 0x78, 0xff, 0x0f, 0xcc, 0xeb, 0xdd, 0x04);
|
||||||
|
|
||||||
|
DEFINE_GUID( /* f8f10121-b617-4a56-868b-9df1b27fe32c */
|
||||||
|
mmcss_trace,
|
||||||
|
0xf8f10121,
|
||||||
|
0xb617,
|
||||||
|
0x4a56,
|
||||||
|
0x86, 0x8b, 0x9d, 0xf1, 0xb2, 0x7f, 0xe3, 0x2c);
|
||||||
|
|
||||||
|
DEFINE_GUID( /* 3b9c9951-3480-4220-9377-9c8e5184f5cd */
|
||||||
|
rundown,
|
||||||
|
0x3b9c9951,
|
||||||
|
0x3480,
|
||||||
|
0x4220,
|
||||||
|
0x93, 0x77, 0x9c, 0x8e, 0x51, 0x84, 0xf5, 0xcd);
|
||||||
|
|
||||||
|
} /* namespace guids */ } /* namespace krabs */
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
#include "kernel_guids.hpp"
|
||||||
|
#include "perfinfo_groupmask.hpp"
|
||||||
|
#include "provider.hpp"
|
||||||
|
|
||||||
|
#define INITGUID
|
||||||
|
#include <Evntrace.h>
|
||||||
|
|
||||||
|
namespace krabs { namespace kernel {
|
||||||
|
|
||||||
|
#define CREATE_CONVENIENCE_KERNEL_PROVIDER(__name__, __value__, __guid__) \
|
||||||
|
struct __name__ : public krabs::kernel_provider \
|
||||||
|
{ \
|
||||||
|
__name__() \
|
||||||
|
: krabs::kernel_provider(__value__, __guid__) \
|
||||||
|
{} \
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CREATE_CONVENIENCE_KERNEL_PROVIDER_MASK(__name__, __guid__, __mask__) \
|
||||||
|
struct __name__ : public krabs::kernel_provider \
|
||||||
|
{ \
|
||||||
|
__name__() \
|
||||||
|
: krabs::kernel_provider(__guid__, __mask__) \
|
||||||
|
{} \
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables ALPC events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
alpc_provider,
|
||||||
|
EVENT_TRACE_FLAG_ALPC,
|
||||||
|
krabs::guids::alpc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables context switch events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
context_switch_provider,
|
||||||
|
EVENT_TRACE_FLAG_CSWITCH,
|
||||||
|
krabs::guids::thread);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables debug print events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
debug_print_provider,
|
||||||
|
EVENT_TRACE_FLAG_DBGPRINT,
|
||||||
|
krabs::guids::debug);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables file I/O name events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
disk_file_io_provider,
|
||||||
|
EVENT_TRACE_FLAG_DISK_FILE_IO,
|
||||||
|
krabs::guids::file_io);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables disk I/O completion events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
disk_io_provider,
|
||||||
|
EVENT_TRACE_FLAG_DISK_IO,
|
||||||
|
krabs::guids::disk_io);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables disk I/O start events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
disk_init_io_provider,
|
||||||
|
EVENT_TRACE_FLAG_DISK_IO_INIT,
|
||||||
|
krabs::guids::disk_io);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables file I/O completion events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
file_io_provider,
|
||||||
|
EVENT_TRACE_FLAG_FILE_IO,
|
||||||
|
krabs::guids::file_io);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables file I/O start events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
file_init_io_provider,
|
||||||
|
EVENT_TRACE_FLAG_FILE_IO_INIT,
|
||||||
|
krabs::guids::file_io);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables thread dispatch events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
thread_dispatch_provider,
|
||||||
|
EVENT_TRACE_FLAG_DISPATCHER,
|
||||||
|
krabs::guids::thread);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables device deferred procedure call events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
dpc_provider,
|
||||||
|
EVENT_TRACE_FLAG_DPC,
|
||||||
|
krabs::guids::perf_info);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables driver events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
driver_provider,
|
||||||
|
EVENT_TRACE_FLAG_DRIVER,
|
||||||
|
krabs::guids::disk_io);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables image load events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
image_load_provider,
|
||||||
|
EVENT_TRACE_FLAG_IMAGE_LOAD,
|
||||||
|
krabs::guids::image_load);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables interrupt events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
interrupt_provider,
|
||||||
|
EVENT_TRACE_FLAG_INTERRUPT,
|
||||||
|
krabs::guids::perf_info);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables memory hard fault events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
memory_hard_fault_provider,
|
||||||
|
EVENT_TRACE_FLAG_MEMORY_HARD_FAULTS,
|
||||||
|
krabs::guids::page_fault);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables memory page fault events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
memory_page_fault_provider,
|
||||||
|
EVENT_TRACE_FLAG_MEMORY_PAGE_FAULTS,
|
||||||
|
krabs::guids::page_fault);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables network tcp/ip events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
network_tcpip_provider,
|
||||||
|
EVENT_TRACE_FLAG_NETWORK_TCPIP,
|
||||||
|
krabs::guids::tcp_ip);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables process events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
process_provider,
|
||||||
|
EVENT_TRACE_FLAG_PROCESS,
|
||||||
|
krabs::guids::process);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables process counter events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
process_counter_provider,
|
||||||
|
EVENT_TRACE_FLAG_PROCESS_COUNTERS,
|
||||||
|
krabs::guids::process);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables profiling events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
profile_provider,
|
||||||
|
EVENT_TRACE_FLAG_PROFILE,
|
||||||
|
krabs::guids::perf_info);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables registry events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
registry_provider,
|
||||||
|
EVENT_TRACE_FLAG_REGISTRY,
|
||||||
|
krabs::guids::registry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables split I/O events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
split_io_provider,
|
||||||
|
EVENT_TRACE_FLAG_SPLIT_IO,
|
||||||
|
krabs::guids::split_io);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables system call events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
system_call_provider,
|
||||||
|
EVENT_TRACE_FLAG_SYSTEMCALL,
|
||||||
|
krabs::guids::perf_info);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables thread start and stop events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
thread_provider,
|
||||||
|
EVENT_TRACE_FLAG_THREAD,
|
||||||
|
krabs::guids::thread);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables file map and unmap (excluding images) events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
vamap_provider,
|
||||||
|
EVENT_TRACE_FLAG_VAMAP,
|
||||||
|
krabs::guids::file_io);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables VirtualAlloc and VirtualFree events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER(
|
||||||
|
virtual_alloc_provider,
|
||||||
|
EVENT_TRACE_FLAG_VIRTUAL_ALLOC,
|
||||||
|
krabs::guids::page_fault);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>A provider that enables Object Manager events.</summary>
|
||||||
|
*/
|
||||||
|
CREATE_CONVENIENCE_KERNEL_PROVIDER_MASK(
|
||||||
|
object_manager_provider,
|
||||||
|
krabs::guids::ob_trace,
|
||||||
|
PERF_OB_HANDLE);
|
||||||
|
|
||||||
|
#undef CREATE_CONVENIENCE_KERNEL_PROVIDER
|
||||||
|
#undef CREATE_CONVENIENCE_KERNEL_PROVIDER_MASK
|
||||||
|
|
||||||
|
} /* namespace kernel */ } /* namespace krabs */
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
#include "kernel_guids.hpp"
|
||||||
|
#include "perfinfo_groupmask.hpp"
|
||||||
|
#include "provider.hpp"
|
||||||
|
#include "trace.hpp"
|
||||||
|
#include "ut.hpp"
|
||||||
|
#include "version_helpers.hpp"
|
||||||
|
|
||||||
|
#include <Evntrace.h>
|
||||||
|
|
||||||
|
namespace krabs { namespace details {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Used as a template argument to a trace instance. This class implements
|
||||||
|
* code paths for kernel traces. Should never be used or seen by client
|
||||||
|
* code.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct kt {
|
||||||
|
|
||||||
|
typedef krabs::kernel_provider provider_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Used to assign a name to the trace instance that is being
|
||||||
|
* instantiated.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* In pre-Win8 days, there could only be a single kernel trace
|
||||||
|
* instance on an entire machine, and that instance had to be named
|
||||||
|
* a particular name. This restriction was loosened in Win8, but
|
||||||
|
* the trace still needs to do the right thing on older OSes.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
static const std::wstring enforce_name_policy(
|
||||||
|
const std::wstring &name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Generates a value that fills the EnableFlags field in an
|
||||||
|
* EVENT_TRACE_PROPERTIES structure. This controls the providers that
|
||||||
|
* get enabled for a kernel trace.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
static const unsigned long construct_enable_flags(
|
||||||
|
const krabs::trace<krabs::details::kt> &trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Enables the providers that are attached to the given trace.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
static void enable_providers(
|
||||||
|
const krabs::trace<krabs::details::kt> &trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Enables the configured kernel rundown flags.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* This ETW feature is undocumented and should be used with caution.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
static void enable_rundown(
|
||||||
|
const krabs::trace<krabs::details::kt>& trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Decides to forward an event to any of the providers in the trace.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
static void forward_events(
|
||||||
|
const EVENT_RECORD &record,
|
||||||
|
const krabs::trace<krabs::details::kt> &trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Sets the ETW trace log file mode.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
static unsigned long augment_file_mode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns the GUID of the trace session.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
static krabs::guid get_trace_guid();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline const std::wstring kt::enforce_name_policy(
|
||||||
|
const std::wstring &name_hint)
|
||||||
|
{
|
||||||
|
if (IsWindows8OrGreater()) {
|
||||||
|
return krabs::details::ut::enforce_name_policy(name_hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return KERNEL_LOGGER_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const unsigned long kt::construct_enable_flags(
|
||||||
|
const krabs::trace<krabs::details::kt> &trace)
|
||||||
|
{
|
||||||
|
unsigned long flags = 0;
|
||||||
|
for (auto &provider : trace.providers_) {
|
||||||
|
flags |= provider.get().flags();
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void kt::enable_providers(
|
||||||
|
const krabs::trace<krabs::details::kt> &trace)
|
||||||
|
{
|
||||||
|
EVENT_TRACE_GROUPMASK_INFORMATION gmi = { 0 };
|
||||||
|
gmi.EventTraceInformationClass = EventTraceGroupMaskInformation;
|
||||||
|
gmi.TraceHandle = trace.registrationHandle_;
|
||||||
|
|
||||||
|
// initialise EventTraceGroupMasks to the values that have been enabled via the trace flags
|
||||||
|
ULONG status = NtQuerySystemInformation(SystemPerformanceTraceInformation, &gmi, sizeof(gmi), nullptr);
|
||||||
|
error_check_common_conditions(status);
|
||||||
|
|
||||||
|
auto group_mask_set = false;
|
||||||
|
for (auto& provider : trace.providers_) {
|
||||||
|
auto group = provider.get().group_mask();
|
||||||
|
PERFINFO_OR_GROUP_WITH_GROUPMASK(group, &(gmi.EventTraceGroupMasks));
|
||||||
|
group_mask_set |= (group != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group_mask_set) {
|
||||||
|
// This will fail on Windows 7, so only call it if truly neccessary
|
||||||
|
status = NtSetSystemInformation(SystemPerformanceTraceInformation, &gmi, sizeof(gmi));
|
||||||
|
error_check_common_conditions(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void kt::enable_rundown(
|
||||||
|
const krabs::trace<krabs::details::kt>& trace)
|
||||||
|
{
|
||||||
|
bool rundown_enabled = false;
|
||||||
|
ULONG rundown_flags = 0;
|
||||||
|
for (auto& provider : trace.providers_) {
|
||||||
|
rundown_enabled |= provider.get().rundown_enabled();
|
||||||
|
rundown_flags |= provider.get().rundown_flags();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rundown_enabled) {
|
||||||
|
ULONG status = EnableTraceEx2(trace.registrationHandle_,
|
||||||
|
&krabs::guids::rundown,
|
||||||
|
EVENT_CONTROL_CODE_ENABLE_PROVIDER,
|
||||||
|
0,
|
||||||
|
rundown_flags,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
NULL);
|
||||||
|
error_check_common_conditions(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inline void kt::forward_events(
|
||||||
|
const EVENT_RECORD &record,
|
||||||
|
const krabs::trace<krabs::details::kt> &trace)
|
||||||
|
{
|
||||||
|
for (auto &provider : trace.providers_) {
|
||||||
|
if (provider.get().id() == record.EventHeader.ProviderId) {
|
||||||
|
provider.get().on_event(record, trace.context_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trace.default_callback_ != nullptr)
|
||||||
|
trace.default_callback_(record, trace.context_);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned long kt::augment_file_mode()
|
||||||
|
{
|
||||||
|
if (IsWindows8OrGreater()) {
|
||||||
|
return EVENT_TRACE_SYSTEM_LOGGER_MODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline krabs::guid kt::get_trace_guid()
|
||||||
|
{
|
||||||
|
if (IsWindows8OrGreater()) {
|
||||||
|
return krabs::guid::random_guid();
|
||||||
|
}
|
||||||
|
|
||||||
|
return krabs::guid(SystemTraceControlGuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace details */ } /* namespace krabs */
|
||||||
@@ -0,0 +1,330 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <sddl.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Provided entirely for code clarity purposes.
|
||||||
|
* Indicates that the number is intended to be used as an ID
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* This should be turned into an _id user defined literal when our
|
||||||
|
* compiler decides to catch up to the times.
|
||||||
|
* </remarks>
|
||||||
|
* <example>
|
||||||
|
* id(1000);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
T id(T n)
|
||||||
|
{
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Provided entirely for code clarity purposes.
|
||||||
|
* Indicates that the number is intended to be used as a version
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* This should be turned into a _vers user defined literal when our
|
||||||
|
* compiler decides to catch up to the times.
|
||||||
|
* </remarks>
|
||||||
|
* <example>
|
||||||
|
* id(1000);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
T version(T n)
|
||||||
|
{
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Provided entirely for code clarity purposes.
|
||||||
|
* Indicates that the number is intended to be used as an opcode
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* This should be turned into a _opcode user defined literal when our
|
||||||
|
* compiler decides to catch up to the times.
|
||||||
|
* </remarks>
|
||||||
|
* <example>
|
||||||
|
* opcode(1000);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
T opcode(T n)
|
||||||
|
{
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Used to discriminate between hex ints and regular ints in ETW events.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* Q: Why in the world? I can't even.
|
||||||
|
* A: ETW differentiates between hexints and regular ints. When
|
||||||
|
* record_builder validates that the input type matches the type
|
||||||
|
* specified in the schema, getting this wrong will cause an
|
||||||
|
* exception. A quick little type wrapper like this lets us
|
||||||
|
* discriminate based on the type and everything turns out better.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
struct hexint32 {
|
||||||
|
hexint32(int v)
|
||||||
|
: value(v)
|
||||||
|
{}
|
||||||
|
|
||||||
|
int value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hexint64 {
|
||||||
|
hexint64(long long v)
|
||||||
|
: value(v)
|
||||||
|
{}
|
||||||
|
|
||||||
|
long long value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Used to support parsing and creation of binary ETW fields.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct binary {
|
||||||
|
public:
|
||||||
|
binary() : bytes_() { }
|
||||||
|
|
||||||
|
binary(const BYTE* start, size_t n)
|
||||||
|
: bytes_(start, start + n)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
const std::vector<BYTE>& bytes() const
|
||||||
|
{
|
||||||
|
return bytes_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<BYTE> bytes_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
binary make_binary(const T& value, size_t n)
|
||||||
|
{
|
||||||
|
const auto start = (BYTE*)&value;
|
||||||
|
return binary(start, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Used to handle parsing of IPv4 and IPv6 fields in an ETW record.
|
||||||
|
* This is used in the parser class in a template specialization.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct ip_address {
|
||||||
|
union {
|
||||||
|
DWORD v4;
|
||||||
|
BYTE v6[16];
|
||||||
|
};
|
||||||
|
bool is_ipv6;
|
||||||
|
|
||||||
|
static ip_address from_ipv6(const BYTE* bytes)
|
||||||
|
{
|
||||||
|
ip_address addr;
|
||||||
|
addr.is_ipv6 = true;
|
||||||
|
memcpy_s(addr.v6, 16, bytes, 16);
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ip_address from_ipv4(DWORD val)
|
||||||
|
{
|
||||||
|
ip_address addr;
|
||||||
|
addr.is_ipv6 = false;
|
||||||
|
addr.v4 = val;
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ip_address() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Used to handle parsing of socket addresses in
|
||||||
|
* network order. This union is a convenient wrapper
|
||||||
|
* around the type IPv4 and IPv6 types provided by
|
||||||
|
* the Winsock (v2) APIs.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct socket_address {
|
||||||
|
union {
|
||||||
|
struct sockaddr sa;
|
||||||
|
struct sockaddr_in sa_in;
|
||||||
|
struct sockaddr_in6 sa_in6;
|
||||||
|
struct sockaddr_storage sa_stor;
|
||||||
|
};
|
||||||
|
size_t size;
|
||||||
|
|
||||||
|
static socket_address from_bytes(const BYTE* bytes, size_t size_in_bytes)
|
||||||
|
{
|
||||||
|
socket_address sa;
|
||||||
|
memcpy_s(&(sa.sa_stor), sizeof sa.sa_stor, bytes, size_in_bytes);
|
||||||
|
sa.size = size_in_bytes;
|
||||||
|
return sa;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Holds information about an property extracted from the etw schema
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct property_info {
|
||||||
|
const BYTE *pPropertyIndex_;
|
||||||
|
const EVENT_PROPERTY_INFO *pEventPropertyInfo_;
|
||||||
|
ULONG length_;
|
||||||
|
|
||||||
|
property_info(
|
||||||
|
const BYTE *offset,
|
||||||
|
const EVENT_PROPERTY_INFO &evtPropInfo,
|
||||||
|
ULONG length)
|
||||||
|
: pPropertyIndex_(offset)
|
||||||
|
, pEventPropertyInfo_(&evtPropInfo)
|
||||||
|
, length_(length)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
property_info()
|
||||||
|
: pPropertyIndex_(nullptr)
|
||||||
|
, pEventPropertyInfo_(nullptr)
|
||||||
|
, length_(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
inline bool found() const
|
||||||
|
{
|
||||||
|
return pPropertyIndex_ != nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Used to handle parsing of SIDs from either a
|
||||||
|
* SID or WBEMSID property
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct sid {
|
||||||
|
// SIDs are variable-length
|
||||||
|
// So the 'best' way to store them is to convert to a string
|
||||||
|
// The other-end can either print the string or call ConvertStringSidToSidA
|
||||||
|
// to get the SID back
|
||||||
|
std::string sid_string;
|
||||||
|
|
||||||
|
static sid from_bytes(const BYTE* bytes, size_t size_in_bytes)
|
||||||
|
{
|
||||||
|
sid ws;
|
||||||
|
LPSTR temp_sid_string;
|
||||||
|
UNREFERENCED_PARAMETER(size_in_bytes);
|
||||||
|
|
||||||
|
if (!ConvertSidToStringSidA((PSID)bytes, &temp_sid_string)) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Failed to get a SID from a property");
|
||||||
|
}
|
||||||
|
ws.sid_string = temp_sid_string;
|
||||||
|
LocalFree(temp_sid_string);
|
||||||
|
return ws;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Used to handle parsing of Pointer Address types.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct pointer {
|
||||||
|
/**
|
||||||
|
* We store the pointer as an uint64_t, as it is highly unlikley
|
||||||
|
* to be pointing to somewhere accessible to our process
|
||||||
|
*/
|
||||||
|
uint64_t address;
|
||||||
|
|
||||||
|
static pointer from_bytes(const BYTE* bytes, size_t size_in_bytes)
|
||||||
|
{
|
||||||
|
pointer pt;
|
||||||
|
|
||||||
|
// If 32-Bit, first parse as a uint32
|
||||||
|
// Then we can 'cast' that to our uint64_t
|
||||||
|
if (size_in_bytes == sizeof(uint32_t)) {
|
||||||
|
pt.address = *reinterpret_cast<const uint32_t*>(bytes);
|
||||||
|
}
|
||||||
|
else if (size_in_bytes == sizeof(uint64_t)) {
|
||||||
|
pt.address = *reinterpret_cast<const uint64_t*>(bytes);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Failed to get a POINTER from a property");
|
||||||
|
}
|
||||||
|
|
||||||
|
return pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Used to handle parsing of CountedStrings in an ETW Record.
|
||||||
|
* This is used in the parser class in a template specialization.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
#pragma pack(push,1)
|
||||||
|
struct counted_string {
|
||||||
|
using value_type = wchar_t;
|
||||||
|
using reference = value_type&;
|
||||||
|
using pointer = value_type*;
|
||||||
|
using const_reference = const value_type&;
|
||||||
|
using const_pointer = const value_type*;
|
||||||
|
using iterator = value_type*;
|
||||||
|
using const_iterator = const value_type*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* size of the string in bytes
|
||||||
|
*/
|
||||||
|
uint16_t size_;
|
||||||
|
wchar_t string_[1];
|
||||||
|
|
||||||
|
const_pointer string() const
|
||||||
|
{
|
||||||
|
return string_;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t length() const
|
||||||
|
{
|
||||||
|
return size_ / sizeof(value_type);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
static_assert(std::is_trivial<counted_string>::value && std::is_standard_layout<counted_string>::value , "Do not modify counted_string");
|
||||||
|
|
||||||
|
} /* namespace krabs */
|
||||||
@@ -0,0 +1,431 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <deque>
|
||||||
|
#include <utility>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
#include "collection_view.hpp"
|
||||||
|
#include "property.hpp"
|
||||||
|
#include "parse_types.hpp"
|
||||||
|
#include "size_provider.hpp"
|
||||||
|
#include "tdh_helpers.hpp"
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
class schema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Used to parse specific properties out of an event schema.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* The parser class dodges the task of trying to validate that the expected
|
||||||
|
* type of a field matches the actual type of a field -- the onus is on
|
||||||
|
* client code to get this right.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
class parser {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs an event parser from an event schema.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* void on_event(const EVENT_RECORD &record)
|
||||||
|
* {
|
||||||
|
* krabs::schema schema(record);
|
||||||
|
* krabs::parser parser(schema);
|
||||||
|
* }
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
parser(const schema &);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns an iterator that returns each property in the event.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* void on_event(const EVENT_RECORD &record)
|
||||||
|
* {
|
||||||
|
* krabs::schema schema(record);
|
||||||
|
* krabs::parser parser(schema);
|
||||||
|
* for (property &property : parser.properties())
|
||||||
|
* {
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
property_iterator properties() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Attempts to retrieve the given property by name and type.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* Type hinting here is taken as the authoritative source. There is no
|
||||||
|
* validation that the request for type is correct.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
bool try_parse(const std::wstring &name, T &out);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Attempts to parse the given property by name and type. If the
|
||||||
|
* property does not exist, an exception is thrown.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
T parse(const std::wstring &name);
|
||||||
|
|
||||||
|
template <typename Adapter>
|
||||||
|
auto view_of(const std::wstring &name, Adapter &adapter) -> collection_view<typename Adapter::const_iterator>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
property_info find_property(const std::wstring &name);
|
||||||
|
void cache_property(const wchar_t *name, property_info info);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const schema &schema_;
|
||||||
|
const BYTE *pEndBuffer_;
|
||||||
|
BYTE *pBufferIndex_;
|
||||||
|
ULONG lastPropertyIndex_;
|
||||||
|
|
||||||
|
// Maintain a mapping from property name to blob data index.
|
||||||
|
std::deque<std::pair<const wchar_t *, property_info>> propertyCache_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline parser::parser(const schema &s)
|
||||||
|
: schema_(s)
|
||||||
|
, pEndBuffer_((BYTE*)s.record_.UserData + s.record_.UserDataLength)
|
||||||
|
, pBufferIndex_((BYTE*)s.record_.UserData)
|
||||||
|
, lastPropertyIndex_(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline property_iterator parser::properties() const
|
||||||
|
{
|
||||||
|
return property_iterator(schema_);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline property_info parser::find_property(const std::wstring &name)
|
||||||
|
{
|
||||||
|
// A schema contains a collection of properties that are keyed by name.
|
||||||
|
// These properties are stored in a blob of bytes that needs to be
|
||||||
|
// interpreted according to information that is packaged up in the
|
||||||
|
// schema and that can be retrieved using the Tdh* APIs. This format
|
||||||
|
// requires a linear traversal over the blob, incrementing according to
|
||||||
|
// the contents within it. This is janky, so our strategy is to
|
||||||
|
// minimize this as much as possible via caching.
|
||||||
|
|
||||||
|
// The first step is to use our cache for the property to see if we've
|
||||||
|
// discovered it already.
|
||||||
|
for (auto &item : propertyCache_) {
|
||||||
|
if (name == item.first) {
|
||||||
|
return item.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ULONG totalPropCount = schema_.pSchema_->PropertyCount;
|
||||||
|
|
||||||
|
assert((pBufferIndex_ <= pEndBuffer_ && pBufferIndex_ >= schema_.record_.UserData) &&
|
||||||
|
"invariant: we should've already thrown for falling off the edge");
|
||||||
|
|
||||||
|
// accept that last property can be omitted from buffer. this happens if last property
|
||||||
|
// is string but empty and the provider strips the null terminator
|
||||||
|
assert((pBufferIndex_ == pEndBuffer_ ? ((totalPropCount - lastPropertyIndex_) <= 1)
|
||||||
|
: true)
|
||||||
|
&& "invariant: if we've exhausted our buffer, then we must've"
|
||||||
|
"exhausted the properties as well");
|
||||||
|
|
||||||
|
// We've not looked up this property before, so we have to do the work
|
||||||
|
// to find it. While we're going through the blob to find it, we'll
|
||||||
|
// remember what we've seen to save time later.
|
||||||
|
//
|
||||||
|
// Question: Why don't we just populate the cache before looking up any
|
||||||
|
// properties and simplify our code (less state, etc)?
|
||||||
|
//
|
||||||
|
// Answer: Doing that introduces overhead in the case that only a
|
||||||
|
// subset of properties are needed. While this code is a bit
|
||||||
|
// more complicated, we introduce no additional performance
|
||||||
|
// overhead at runtime.
|
||||||
|
for (auto &i = lastPropertyIndex_; i < totalPropCount; ++i) {
|
||||||
|
|
||||||
|
auto ¤tPropInfo = schema_.pSchema_->EventPropertyInfoArray[i];
|
||||||
|
const wchar_t *pName = reinterpret_cast<const wchar_t*>(
|
||||||
|
reinterpret_cast<BYTE*>(schema_.pSchema_) +
|
||||||
|
currentPropInfo.NameOffset);
|
||||||
|
|
||||||
|
ULONG propertyLength = size_provider::get_property_size(
|
||||||
|
pBufferIndex_,
|
||||||
|
pName,
|
||||||
|
schema_.record_,
|
||||||
|
currentPropInfo);
|
||||||
|
|
||||||
|
// verify that the length of the property doesn't exceed the buffer
|
||||||
|
if (pBufferIndex_ + propertyLength > pEndBuffer_) {
|
||||||
|
throw std::out_of_range("Property length past end of property buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
property_info propInfo(pBufferIndex_, currentPropInfo, propertyLength);
|
||||||
|
cache_property(pName, propInfo);
|
||||||
|
|
||||||
|
// advance the buffer index since we've already processed this property
|
||||||
|
pBufferIndex_ += propertyLength;
|
||||||
|
|
||||||
|
// The property was found, return it
|
||||||
|
if (name == pName) {
|
||||||
|
// advance the index since we've already processed this property
|
||||||
|
++i;
|
||||||
|
return propInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// property wasn't found, return an empty propInfo
|
||||||
|
return property_info();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void parser::cache_property(const wchar_t *name, property_info propInfo)
|
||||||
|
{
|
||||||
|
propertyCache_.push_front(std::make_pair(name, propInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void throw_if_property_not_found(const property_info &propInfo)
|
||||||
|
{
|
||||||
|
if (!propInfo.found()) {
|
||||||
|
throw std::runtime_error("Property with the given name does not exist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
size_t get_string_content_length(const T* string, size_t lengthBytes)
|
||||||
|
{
|
||||||
|
// for some string types the length includes the null terminator
|
||||||
|
// so we need to find the length of just the content part
|
||||||
|
|
||||||
|
T nullChar {0};
|
||||||
|
auto length = lengthBytes / sizeof(T);
|
||||||
|
|
||||||
|
for (auto i = length; i >= 1; --i)
|
||||||
|
if (string[i - 1] != nullChar) return i;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try_parse
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool parser::try_parse(const std::wstring &name, T &out)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
out = parse<T>(name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
// in debug builds we want any mismatch asserts
|
||||||
|
// to get back to the caller. This is removed
|
||||||
|
// in release builds.
|
||||||
|
catch (const krabs::type_mismatch_assert&) {
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
#endif // NDEBUG
|
||||||
|
|
||||||
|
catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T parser::parse(const std::wstring &name)
|
||||||
|
{
|
||||||
|
auto propInfo = find_property(name);
|
||||||
|
throw_if_property_not_found(propInfo);
|
||||||
|
|
||||||
|
krabs::debug::assert_valid_assignment<T>(name, propInfo);
|
||||||
|
|
||||||
|
// ensure that size of the type we are requesting is
|
||||||
|
// the same size of the property in the event
|
||||||
|
if (sizeof(T) != propInfo.length_)
|
||||||
|
throw std::runtime_error("Property size doesn't match requested size");
|
||||||
|
|
||||||
|
return *(T*)propInfo.pPropertyIndex_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline bool parser::parse<bool>(const std::wstring& name)
|
||||||
|
{
|
||||||
|
auto propInfo = find_property(name);
|
||||||
|
throw_if_property_not_found(propInfo);
|
||||||
|
|
||||||
|
krabs::debug::assert_valid_assignment<bool>(name, propInfo);
|
||||||
|
|
||||||
|
// Boolean in ETW is 4 bytes long
|
||||||
|
return static_cast<bool>(*(unsigned*)propInfo.pPropertyIndex_);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline std::wstring parser::parse<std::wstring>(const std::wstring &name)
|
||||||
|
{
|
||||||
|
auto propInfo = find_property(name);
|
||||||
|
throw_if_property_not_found(propInfo);
|
||||||
|
|
||||||
|
krabs::debug::assert_valid_assignment<std::wstring>(name, propInfo);
|
||||||
|
|
||||||
|
auto string = reinterpret_cast<const wchar_t*>(propInfo.pPropertyIndex_);
|
||||||
|
auto length = get_string_content_length(string, propInfo.length_);
|
||||||
|
|
||||||
|
return std::wstring(string, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline std::string parser::parse<std::string>(const std::wstring &name)
|
||||||
|
{
|
||||||
|
auto propInfo = find_property(name);
|
||||||
|
throw_if_property_not_found(propInfo);
|
||||||
|
|
||||||
|
krabs::debug::assert_valid_assignment<std::string>(name, propInfo);
|
||||||
|
|
||||||
|
auto string = reinterpret_cast<const char*>(propInfo.pPropertyIndex_);
|
||||||
|
auto length = get_string_content_length(string, propInfo.length_);
|
||||||
|
|
||||||
|
return std::string(string, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline const counted_string* parser::parse<const counted_string*>(const std::wstring &name)
|
||||||
|
{
|
||||||
|
auto propInfo = find_property(name);
|
||||||
|
throw_if_property_not_found(propInfo);
|
||||||
|
|
||||||
|
krabs::debug::assert_valid_assignment<const counted_string*>(name, propInfo);
|
||||||
|
|
||||||
|
return reinterpret_cast<const counted_string*>(propInfo.pPropertyIndex_);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline binary parser::parse<binary>(const std::wstring &name)
|
||||||
|
{
|
||||||
|
auto propInfo = find_property(name);
|
||||||
|
throw_if_property_not_found(propInfo);
|
||||||
|
|
||||||
|
// no type asserts for binary - anything can be read as binary
|
||||||
|
|
||||||
|
return binary(propInfo.pPropertyIndex_, propInfo.length_);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline ip_address parser::parse<ip_address>(
|
||||||
|
const std::wstring &name)
|
||||||
|
{
|
||||||
|
auto propInfo = find_property(name);
|
||||||
|
throw_if_property_not_found(propInfo);
|
||||||
|
|
||||||
|
krabs::debug::assert_valid_assignment<ip_address>(name, propInfo);
|
||||||
|
|
||||||
|
auto outType = propInfo.pEventPropertyInfo_->nonStructType.OutType;
|
||||||
|
|
||||||
|
switch (outType) {
|
||||||
|
case TDH_OUTTYPE_IPV6:
|
||||||
|
return ip_address::from_ipv6(propInfo.pPropertyIndex_);
|
||||||
|
|
||||||
|
case TDH_OUTTYPE_IPV4:
|
||||||
|
return ip_address::from_ipv4(*(DWORD*)propInfo.pPropertyIndex_);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("IP Address was not IPV4 or IPV6");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline socket_address parser::parse<socket_address>(
|
||||||
|
const std::wstring &name)
|
||||||
|
{
|
||||||
|
auto propInfo = find_property(name);
|
||||||
|
throw_if_property_not_found(propInfo);
|
||||||
|
|
||||||
|
krabs::debug::assert_valid_assignment<socket_address>(name, propInfo);
|
||||||
|
|
||||||
|
return socket_address::from_bytes(propInfo.pPropertyIndex_, propInfo.length_);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline sid parser::parse<sid>(
|
||||||
|
const std::wstring& name)
|
||||||
|
{
|
||||||
|
auto propInfo = find_property(name);
|
||||||
|
throw_if_property_not_found(propInfo);
|
||||||
|
|
||||||
|
krabs::debug::assert_valid_assignment<sid>(name, propInfo);
|
||||||
|
auto InType = propInfo.pEventPropertyInfo_->nonStructType.InType;
|
||||||
|
|
||||||
|
// A WBEMSID is actually a TOKEN_USER structure followed by the SID.
|
||||||
|
// We only care about the SID. From MSDN:
|
||||||
|
//
|
||||||
|
// The size of the TOKEN_USER structure differs
|
||||||
|
// depending on whether the events were generated on a 32 - bit
|
||||||
|
// or 64 - bit architecture. Also the structure is aligned
|
||||||
|
// on an 8 - byte boundary, so its size is 8 bytes on a
|
||||||
|
// 32 - bit computer and 16 bytes on a 64 - bit computer.
|
||||||
|
// Doubling the pointer size handles both cases.
|
||||||
|
ULONG sid_start = 16;
|
||||||
|
if (EVENT_HEADER_FLAG_32_BIT_HEADER == (schema_.record_.EventHeader.Flags & EVENT_HEADER_FLAG_32_BIT_HEADER)) {
|
||||||
|
sid_start = 8;
|
||||||
|
}
|
||||||
|
switch (InType) {
|
||||||
|
case TDH_INTYPE_SID:
|
||||||
|
return sid::from_bytes(propInfo.pPropertyIndex_, propInfo.length_);
|
||||||
|
case TDH_INTYPE_WBEMSID:
|
||||||
|
// Safety measure to make sure we don't overflow
|
||||||
|
if (propInfo.length_ <= sid_start) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Requested a WBEMSID property but data is too small");
|
||||||
|
}
|
||||||
|
return sid::from_bytes(propInfo.pPropertyIndex_ + sid_start, propInfo.length_ - sid_start);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("SID was not a SID or WBEMSID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline pointer parser::parse<pointer>(const std::wstring& name)
|
||||||
|
{
|
||||||
|
auto propInfo = find_property(name);
|
||||||
|
throw_if_property_not_found(propInfo);
|
||||||
|
|
||||||
|
krabs::debug::assert_valid_assignment<pointer>(name, propInfo);
|
||||||
|
|
||||||
|
return pointer::from_bytes(propInfo.pPropertyIndex_, propInfo.length_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// view_of
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template <typename Adapter>
|
||||||
|
auto parser::view_of(const std::wstring &name, Adapter &adapter)
|
||||||
|
-> collection_view<typename Adapter::const_iterator>
|
||||||
|
{
|
||||||
|
auto propInfo = find_property(name);
|
||||||
|
throw_if_property_not_found(propInfo);
|
||||||
|
|
||||||
|
// TODO: type asserts?
|
||||||
|
|
||||||
|
return adapter(propInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#ifndef PERFINFO_GROUPMASK_HPP
|
||||||
|
#define PERFINFO_GROUPMASK_HPP
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#pragma comment(lib, "ntdll")
|
||||||
|
|
||||||
|
// https://geoffchappell.com/studies/windows/km/ntoskrnl/api/etw/tracesup/perfinfo_groupmask.htm
|
||||||
|
|
||||||
|
#define PERF_MASK_INDEX (0xe0000000)
|
||||||
|
#define PERF_MASK_GROUP (~PERF_MASK_INDEX)
|
||||||
|
#define PERF_NUM_MASKS 8
|
||||||
|
|
||||||
|
typedef ULONG PERFINFO_MASK;
|
||||||
|
typedef struct _PERFINFO_GROUPMASK {
|
||||||
|
ULONG Masks[PERF_NUM_MASKS];
|
||||||
|
} PERFINFO_GROUPMASK, *PPERFINFO_GROUPMASK;
|
||||||
|
|
||||||
|
#define PERF_GET_MASK_INDEX(GM) (((GM) & PERF_MASK_INDEX) >> 29)
|
||||||
|
#define PERF_GET_MASK_GROUP(GM) ((GM) & PERF_MASK_GROUP)
|
||||||
|
#define PERFINFO_OR_GROUP_WITH_GROUPMASK(Group, pGroupMask) \
|
||||||
|
(pGroupMask)->Masks[PERF_GET_MASK_INDEX(Group)] |= PERF_GET_MASK_GROUP(Group);
|
||||||
|
|
||||||
|
// Masks[0]
|
||||||
|
#define PERF_PROCESS EVENT_TRACE_FLAG_PROCESS
|
||||||
|
#define PERF_THREAD EVENT_TRACE_FLAG_THREAD
|
||||||
|
#define PERF_PROC_THREAD EVENT_TRACE_FLAG_PROCESS | EVENT_TRACE_FLAG_THREAD
|
||||||
|
#define PERF_LOADER EVENT_TRACE_FLAG_IMAGE_LOAD
|
||||||
|
#define PERF_PERF_COUNTER EVENT_TRACE_FLAG_PROCESS_COUNTERS
|
||||||
|
#define PERF_FILENAME EVENT_TRACE_FLAG_DISK_FILE_IO
|
||||||
|
#define PERF_DISK_IO EVENT_TRACE_FLAG_DISK_FILE_IO | EVENT_TRACE_FLAG_DISK_IO
|
||||||
|
#define PERF_DISK_IO_INIT EVENT_TRACE_FLAG_DISK_IO_INIT
|
||||||
|
#define PERF_ALL_FAULTS EVENT_TRACE_FLAG_MEMORY_PAGE_FAULTS
|
||||||
|
#define PERF_HARD_FAULTS EVENT_TRACE_FLAG_MEMORY_HARD_FAULTS
|
||||||
|
#define PERF_VAMAP EVENT_TRACE_FLAG_VAMAP
|
||||||
|
#define PERF_NETWORK EVENT_TRACE_FLAG_NETWORK_TCPIP
|
||||||
|
#define PERF_REGISTRY EVENT_TRACE_FLAG_REGISTRY
|
||||||
|
#define PERF_DBGPRINT EVENT_TRACE_FLAG_DBGPRINT
|
||||||
|
#define PERF_JOB EVENT_TRACE_FLAG_JOB
|
||||||
|
#define PERF_ALPC EVENT_TRACE_FLAG_ALPC
|
||||||
|
#define PERF_SPLIT_IO EVENT_TRACE_FLAG_SPLIT_IO
|
||||||
|
#define PERF_DEBUG_EVENTS EVENT_TRACE_FLAG_DEBUG_EVENTS
|
||||||
|
#define PERF_FILE_IO EVENT_TRACE_FLAG_FILE_IO
|
||||||
|
#define PERF_FILE_IO_INIT EVENT_TRACE_FLAG_FILE_IO_INIT
|
||||||
|
#define PERF_NO_SYSCONFIG EVENT_TRACE_FLAG_NO_SYSCONFIG
|
||||||
|
|
||||||
|
// Masks[1]
|
||||||
|
#define PERF_MEMORY 0x20000001
|
||||||
|
#define PERF_PROFILE 0x20000002 // equivalent to EVENT_TRACE_FLAG_PROFILE
|
||||||
|
#define PERF_CONTEXT_SWITCH 0x20000004 // equivalent to EVENT_TRACE_FLAG_CSWITCH
|
||||||
|
#define PERF_FOOTPRINT 0x20000008
|
||||||
|
#define PERF_DRIVERS 0x20000010 // equivalent to EVENT_TRACE_FLAG_DRIVER
|
||||||
|
#define PERF_REFSET 0x20000020
|
||||||
|
#define PERF_POOL 0x20000040
|
||||||
|
#define PERF_POOLTRACE 0x20000041
|
||||||
|
#define PERF_DPC 0x20000080 // equivalent to EVENT_TRACE_FLAG_DPC
|
||||||
|
#define PERF_COMPACT_CSWITCH 0x20000100
|
||||||
|
#define PERF_DISPATCHER 0x20000200 // equivalent to EVENT_TRACE_FLAG_DISPATCHER
|
||||||
|
#define PERF_PMC_PROFILE 0x20000400
|
||||||
|
#define PERF_PROFILING 0x20000402
|
||||||
|
#define PERF_PROCESS_INSWAP 0x20000800
|
||||||
|
#define PERF_AFFINITY 0x20001000
|
||||||
|
#define PERF_PRIORITY 0x20002000
|
||||||
|
#define PERF_INTERRUPT 0x20004000 // equivalent to EVENT_TRACE_FLAG_INTERRUPT
|
||||||
|
#define PERF_VIRTUAL_ALLOC 0x20008000 // equivalent to EVENT_TRACE_FLAG_VIRTUAL_ALLOC
|
||||||
|
#define PERF_SPINLOCK 0x20010000
|
||||||
|
#define PERF_SYNC_OBJECTS 0x20020000
|
||||||
|
#define PERF_DPC_QUEUE 0x20040000
|
||||||
|
#define PERF_MEMINFO 0x20080000
|
||||||
|
#define PERF_CONTMEM_GEN 0x20100000
|
||||||
|
#define PERF_SPINLOCK_CNTRS 0x20200000
|
||||||
|
#define PERF_SPININSTR 0x20210000
|
||||||
|
#define PERF_SESSION 0x20400000
|
||||||
|
#define PERF_PFSECTION 0x20400000
|
||||||
|
#define PERF_MEMINFO_WS 0x20800000
|
||||||
|
#define PERF_KERNEL_QUEUE 0x21000000
|
||||||
|
#define PERF_INTERRUPT_STEER 0x22000000
|
||||||
|
#define PERF_SHOULD_YIELD 0x24000000
|
||||||
|
#define PERF_WS 0x28000000
|
||||||
|
|
||||||
|
// Masks[2]
|
||||||
|
#define PERF_ANTI_STARVATION 0x40000001
|
||||||
|
#define PERF_PROCESS_FREEZE 0x40000002
|
||||||
|
#define PERF_PFN_LIST 0x40000004
|
||||||
|
#define PERF_WS_DETAIL 0x40000008
|
||||||
|
#define PERF_WS_ENTRY 0x40000010
|
||||||
|
#define PERF_HEAP 0x40000020
|
||||||
|
#define PERF_SYSCALL 0x40000040 // equivalent to EVENT_TRACE_FLAG_SYSTEMCALL
|
||||||
|
#define PERF_UMS 0x40000080
|
||||||
|
#define PERF_BACKTRACE 0x40000100
|
||||||
|
#define PERF_VULCAN 0x40000200
|
||||||
|
#define PERF_OBJECTS 0x40000400
|
||||||
|
#define PERF_EVENTS 0x40000800
|
||||||
|
#define PERF_FULLTRACE 0x40001000
|
||||||
|
#define PERF_DFSS 0x40002000
|
||||||
|
#define PERF_PREFETCH 0x40004000
|
||||||
|
#define PERF_PROCESSOR_IDLE 0x40008000
|
||||||
|
#define PERF_CPU_CONFIG 0x40010000
|
||||||
|
#define PERF_TIMER 0x40020000
|
||||||
|
#define PERF_CLOCK_INTERRUPT 0x40040000
|
||||||
|
#define PERF_LOAD_BALANCER 0x40080000
|
||||||
|
#define PERF_CLOCK_TIMER 0x40100000
|
||||||
|
#define PERF_IDLE_SELECTION 0x40200000
|
||||||
|
#define PERF_IPI 0x40400000
|
||||||
|
#define PERF_IO_TIMER 0x40800000
|
||||||
|
#define PERF_REG_HIVE 0x41000000
|
||||||
|
#define PERF_REG_NOTIF 0x42000000
|
||||||
|
#define PERF_PPM_EXIT_LATENCY 0x44000000
|
||||||
|
#define PERF_WORKER_THREAD 0x48000000
|
||||||
|
|
||||||
|
// Masks[4]
|
||||||
|
#define PERF_OPTICAL_IO 0x80000001
|
||||||
|
#define PERF_OPTICAL_IO_INIT 0x80000002
|
||||||
|
#define PERF_DLL_INFO 0x80000008
|
||||||
|
#define PERF_DLL_FLUSH_WS 0x80000010
|
||||||
|
#define PERF_OB_HANDLE 0x80000040
|
||||||
|
#define PERF_OB_OBJECT 0x80000080
|
||||||
|
#define PERF_WAKE_DROP 0x80000200
|
||||||
|
#define PERF_WAKE_EVENT 0x80000400
|
||||||
|
#define PERF_DEBUGGER 0x80000800
|
||||||
|
#define PERF_PROC_ATTACH 0x80001000
|
||||||
|
#define PERF_WAKE_COUNTER 0x80002000
|
||||||
|
#define PERF_POWER 0x80008000
|
||||||
|
#define PERF_SOFT_TRIM 0x80010000
|
||||||
|
#define PERF_CC 0x80020000
|
||||||
|
#define PERF_FLT_IO_INIT 0x80080000
|
||||||
|
#define PERF_FLT_IO 0x80100000
|
||||||
|
#define PERF_FLT_FASTIO 0x80200000
|
||||||
|
#define PERF_FLT_IO_FAILURE 0x80400000
|
||||||
|
#define PERF_HV_PROFILE 0x80800000
|
||||||
|
#define PERF_WDF_DPC 0x81000000
|
||||||
|
#define PERF_WDF_INTERRUPT 0x82000000
|
||||||
|
#define PERF_CACHE_FLUSH 0x84000000
|
||||||
|
|
||||||
|
// Masks[5]
|
||||||
|
#define PERF_HIBER_RUNDOWN 0xA0000001
|
||||||
|
|
||||||
|
// Masks[6]
|
||||||
|
#define PERF_SYSCFG_SYSTEM 0xC0000001
|
||||||
|
#define PERF_SYSCFG_GRAPHICS 0xC0000002
|
||||||
|
#define PERF_SYSCFG_STORAGE 0xC0000004
|
||||||
|
#define PERF_SYSCFG_NETWORK 0xC0000008
|
||||||
|
#define PERF_SYSCFG_SERVICES 0xC0000010
|
||||||
|
#define PERF_SYSCFG_PNP 0xC0000020
|
||||||
|
#define PERF_SYSCFG_OPTICAL 0xC0000040
|
||||||
|
#define PERF_SYSCFG_ALL 0xDFFFFFFF
|
||||||
|
|
||||||
|
// Masks[7] - Control Mask. All flags that change system behavior go here.
|
||||||
|
#define PERF_CLUSTER_OFF 0xE0000001
|
||||||
|
#define PERF_MEMORY_CONTROL 0xE0000002
|
||||||
|
|
||||||
|
// TraceQueryInformation wasn't introduced until Windows 8, so we need to use
|
||||||
|
// NtQuerySystemInformation instead in order to maintain support for Windows 7.
|
||||||
|
// This requires the below additional definitions.
|
||||||
|
|
||||||
|
typedef enum _EVENT_TRACE_INFORMATION_CLASS {
|
||||||
|
EventTraceKernelVersionInformation,
|
||||||
|
EventTraceGroupMaskInformation,
|
||||||
|
EventTracePerformanceInformation,
|
||||||
|
EventTraceTimeProfileInformation,
|
||||||
|
EventTraceSessionSecurityInformation,
|
||||||
|
EventTraceSpinlockInformation,
|
||||||
|
EventTraceStackTracingInformation,
|
||||||
|
EventTraceExecutiveResourceInformation,
|
||||||
|
EventTraceHeapTracingInformation,
|
||||||
|
EventTraceHeapSummaryTracingInformation,
|
||||||
|
EventTracePoolTagFilterInformation,
|
||||||
|
EventTracePebsTracingInformation,
|
||||||
|
EventTraceProfileConfigInformation,
|
||||||
|
EventTraceProfileSourceListInformation,
|
||||||
|
EventTraceProfileEventListInformation,
|
||||||
|
EventTraceProfileCounterListInformation,
|
||||||
|
EventTraceStackCachingInformation,
|
||||||
|
EventTraceObjectTypeFilterInformation,
|
||||||
|
MaxEventTraceInfoClass
|
||||||
|
} EVENT_TRACE_INFORMATION_CLASS;
|
||||||
|
|
||||||
|
typedef struct _EVENT_TRACE_GROUPMASK_INFORMATION {
|
||||||
|
EVENT_TRACE_INFORMATION_CLASS EventTraceInformationClass;
|
||||||
|
TRACEHANDLE TraceHandle;
|
||||||
|
PERFINFO_GROUPMASK EventTraceGroupMasks;
|
||||||
|
} EVENT_TRACE_GROUPMASK_INFORMATION, * PEVENT_TRACE_GROUPMASK_INFORMATION;
|
||||||
|
|
||||||
|
#ifndef _WINTERNL_
|
||||||
|
|
||||||
|
typedef enum _SYSTEM_INFORMATION_CLASS {
|
||||||
|
} SYSTEM_INFORMATION_CLASS;
|
||||||
|
|
||||||
|
typedef LONG NTSTATUS;
|
||||||
|
|
||||||
|
extern "C" NTSTATUS NTAPI NtQuerySystemInformation(
|
||||||
|
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
|
||||||
|
_Out_writes_bytes_to_opt_(SystemInformationLength, *ReturnLength) PVOID SystemInformation,
|
||||||
|
_In_ ULONG SystemInformationLength,
|
||||||
|
_Out_opt_ PULONG ReturnLength
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif // _WINTERNL_
|
||||||
|
|
||||||
|
extern "C" NTSTATUS NTAPI NtSetSystemInformation(
|
||||||
|
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
|
||||||
|
_In_reads_bytes_opt_(SystemInformationLength) PVOID SystemInformation,
|
||||||
|
_In_ ULONG SystemInformationLength
|
||||||
|
);
|
||||||
|
|
||||||
|
constexpr auto SystemPerformanceTraceInformation{ static_cast<SYSTEM_INFORMATION_CLASS>(0x1f) };
|
||||||
|
|
||||||
|
#endif // PERFINFO_GROUPMASK_HPP
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define INITGUID
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
#include "schema.hpp"
|
||||||
|
#include "errors.hpp"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <tdh.h>
|
||||||
|
#include <evntrace.h>
|
||||||
|
|
||||||
|
#pragma comment(lib, "tdh.lib")
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Represents a single property of the record schema.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* Noticeably absent from this property is the ability to ask what its
|
||||||
|
* value is. The reason for this is that this property instance is
|
||||||
|
* intended to work with synth_records, which don't always have data to
|
||||||
|
* correspond with properties. This class *cannot* return a value because
|
||||||
|
* there isn't always a value to return.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
class property {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs a property.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* This should be instantiated by client code -- let the parser
|
||||||
|
* object do this for you with its `properties` method.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
property(const std::wstring &name, _TDH_IN_TYPE type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Retrieves the name of the property.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
const std::wstring &name() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Retrieves the Tdh type of the property.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
_TDH_IN_TYPE type() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::wstring name_;
|
||||||
|
_TDH_IN_TYPE type_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Iterates the properties in a given event record.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
class property_iterator {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs a new iterator that lazily retrieves the properties of
|
||||||
|
* the given event record.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* Don't construct this yourself. Let the `parser` class do it for you.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
property_iterator(const schema &s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns an iterator that hasn't yielded any properties yet.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
std::vector<property>::iterator begin();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns an iterator that has yielded all properties.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
std::vector<property>::iterator end();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs a property instance out of the raw data of the
|
||||||
|
* given property.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
property get_property(size_t index) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Collects the names of the properties in the schema.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* This is a little lazy of us, as we end up iterating the properties
|
||||||
|
* entirely before allowing enumeration by the client. Because this
|
||||||
|
* code is most likely called in a non-critical path, there's not
|
||||||
|
* much to worry about here.
|
||||||
|
*/
|
||||||
|
std::vector<property> enum_properties() const;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
const krabs::schema &schema_;
|
||||||
|
size_t numProperties_;
|
||||||
|
std::vector<property> properties_;
|
||||||
|
std::vector<property>::iterator beg_;
|
||||||
|
std::vector<property>::iterator end_;
|
||||||
|
std::vector<property>::iterator curr_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline property::property(const std::wstring &name, _TDH_IN_TYPE type)
|
||||||
|
: name_(name)
|
||||||
|
, type_(type)
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline const std::wstring &property::name() const
|
||||||
|
{
|
||||||
|
return name_;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline _TDH_IN_TYPE property::type() const
|
||||||
|
{
|
||||||
|
return type_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline property_iterator::property_iterator(const schema &s)
|
||||||
|
: schema_(s)
|
||||||
|
, numProperties_(s.pSchema_->TopLevelPropertyCount)
|
||||||
|
, properties_(enum_properties())
|
||||||
|
, beg_(properties_.begin())
|
||||||
|
, end_(properties_.end())
|
||||||
|
, curr_(properties_.begin())
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline std::vector<property>::iterator property_iterator::begin()
|
||||||
|
{
|
||||||
|
return beg_;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::vector<property>::iterator property_iterator::end()
|
||||||
|
{
|
||||||
|
return end_;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline property property_iterator::get_property(size_t index) const
|
||||||
|
{
|
||||||
|
const auto &curr_prop = schema_.pSchema_->EventPropertyInfoArray[index];
|
||||||
|
|
||||||
|
const wchar_t *pName = reinterpret_cast<const wchar_t*>(
|
||||||
|
reinterpret_cast<BYTE*>(schema_.pSchema_) +
|
||||||
|
curr_prop.NameOffset);
|
||||||
|
|
||||||
|
auto tdh_type = (_TDH_IN_TYPE)curr_prop.nonStructType.InType;
|
||||||
|
|
||||||
|
return property(pName, tdh_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::vector<property> property_iterator::enum_properties() const
|
||||||
|
{
|
||||||
|
std::vector<property> props;
|
||||||
|
for (size_t i = 0; i < numProperties_; ++i) {
|
||||||
|
props.emplace_back(get_property(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} /* namespace krabs */
|
||||||
@@ -0,0 +1,666 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define INITGUID
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
#include "filtering/event_filter.hpp"
|
||||||
|
#include "perfinfo_groupmask.hpp"
|
||||||
|
#include "trace_context.hpp"
|
||||||
|
#include "wstring_convert.hpp"
|
||||||
|
|
||||||
|
#include <evntcons.h>
|
||||||
|
#include <guiddef.h>
|
||||||
|
#include <pla.h>
|
||||||
|
#include <comutil.h>
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
#pragma comment(lib, "comsuppwd.lib")
|
||||||
|
#else
|
||||||
|
#pragma comment(lib, "comsuppw.lib")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace krabs { namespace details {
|
||||||
|
template <typename T> class trace_manager;
|
||||||
|
|
||||||
|
struct kt;
|
||||||
|
struct ut;
|
||||||
|
|
||||||
|
} /* namespace details */ } /* namespace krabs */
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class trace;
|
||||||
|
|
||||||
|
typedef void(*c_provider_callback)(const EVENT_RECORD &, const krabs::trace_context &);
|
||||||
|
typedef void(*c_provider_error_callback)(const EVENT_RECORD&, const std::string&);
|
||||||
|
typedef std::function<void(const EVENT_RECORD &, const krabs::trace_context &)> provider_callback;
|
||||||
|
typedef std::function<void(const EVENT_RECORD&, const std::string&)> provider_error_callback;
|
||||||
|
|
||||||
|
namespace details {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Serves as a base for providers and kernel_providers. Handles event
|
||||||
|
* registration and forwarding.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
class base_provider {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Adds a function to call when an event for this provider is fired.
|
||||||
|
* </summary>
|
||||||
|
*
|
||||||
|
* <param name="callback">the function to call into</param>
|
||||||
|
* <example>
|
||||||
|
* void my_fun(const EVENT_RECORD &record, const krabs::trace_context &trace_context) { ... }
|
||||||
|
* // ...
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* provider<> powershell(id);
|
||||||
|
* provider.add_on_event_callback(my_fun);
|
||||||
|
* </example>
|
||||||
|
*
|
||||||
|
* <example>
|
||||||
|
* auto fun = [&](const EVENT_RECORD &record, const krabs::trace_context &trace_context) {...}
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* provider<> powershell(id);
|
||||||
|
* provider.add_on_event_callback(fun);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void add_on_event_callback(c_provider_callback callback);
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
void add_on_event_callback(U &callback);
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
void add_on_event_callback(const U &callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Adds a function to call when an error occurs when this provider handles an event.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
void add_on_error_callback(c_provider_error_callback callback);
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
void add_on_error_callback(U& callback);
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
void add_on_error_callback(const U& callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Adds a new filter to a provider, where the filter is expected
|
||||||
|
* to have callbacks attached to it.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* krabs::provider<> powershell(id);
|
||||||
|
* krabs::event_filter filter(krabs::filtering::any_event);
|
||||||
|
* filter.add_on_event_callback([&](const EVENT_RECORD &record, const krabs::trace_context &trace_context) {...});
|
||||||
|
* powershell.add_filter(filter);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void add_filter(const event_filter &f);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Called when an event occurs, forwards to callbacks and filters.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
void on_event(const EVENT_RECORD &record, const krabs::trace_context &context) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::deque<provider_callback> callbacks_;
|
||||||
|
std::deque<provider_error_callback> error_callbacks_;
|
||||||
|
std::deque<event_filter> filters_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename T>
|
||||||
|
friend class details::trace_manager;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
friend class krabs::trace;
|
||||||
|
|
||||||
|
template <typename S>
|
||||||
|
friend class base_provider;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace details
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Used to enable specific types of events from specific event sources in
|
||||||
|
* ETW. Corresponds tightly with the concept of an ETW provider. Used for
|
||||||
|
* user trace instances (not kernel trace instances).
|
||||||
|
* </summary>
|
||||||
|
*
|
||||||
|
* <param name='T'>
|
||||||
|
* The type of flags to use when filtering event types via any and all.
|
||||||
|
* There is an implicitly requirement that T can be downcasted to a
|
||||||
|
* ULONGLONG.
|
||||||
|
* </param>
|
||||||
|
*/
|
||||||
|
template <typename T = ULONGLONG>
|
||||||
|
class provider : public details::base_provider<T> {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs a provider with the given guid identifier.
|
||||||
|
* </summary>
|
||||||
|
*
|
||||||
|
* <param name="id">the GUID that identifies the provider.</param>
|
||||||
|
* <example>
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* provider<> powershell(id);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
provider(GUID id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs a provider with the given a provider name.
|
||||||
|
* </summary>
|
||||||
|
*
|
||||||
|
* <param name="id">the provider name.</param>
|
||||||
|
* <example>
|
||||||
|
* krabs::guid id(L"Microsoft-Windows-WinINet");
|
||||||
|
* provider<> winINet(id);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
provider(const std::wstring &providerName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Sets the "any" flag of the provider.
|
||||||
|
* </summary>
|
||||||
|
*
|
||||||
|
* <param name="value">the value to set <c>any</c> to.</param>
|
||||||
|
* <example>
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* provider<> powershell(id);
|
||||||
|
* powershell.any(0xFF00);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void any(T any);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Sets the "all" flag of the provider.
|
||||||
|
* </summary>
|
||||||
|
*
|
||||||
|
* <param name="value">the value to set <c>all</c> to.</param>
|
||||||
|
* <example>
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* provider<> powershell(id);
|
||||||
|
* powershell.all(0xFF00);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void all(T all);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Sets the "level" flag of the provider. Valid values are 0~255 (0xFF).
|
||||||
|
* </summary>
|
||||||
|
*
|
||||||
|
* <param name="value">the value to set <c>level</c> to.</param>
|
||||||
|
* <example>
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* provider<> powershell(id);
|
||||||
|
* powershell.level(0x00);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void level(T level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Sets the "EnableProperty" flag on the ENABLE_TRACE_PARAMETER struct.
|
||||||
|
* These properties configure behaviours for a specified user-mode provider.
|
||||||
|
* Valid values can be found here:
|
||||||
|
* https://msdn.microsoft.com/en-us/library/windows/desktop/dd392306(v=vs.85).aspx
|
||||||
|
* </summary>
|
||||||
|
*
|
||||||
|
* <param name="value">the value to set</param>
|
||||||
|
* <example>
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* provider<> powershell(id);
|
||||||
|
* powershell.trace_flags(EVENT_ENABLE_PROPERTY_STACK_TRACE);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void trace_flags(T trace_flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Gets the configured value for the "EnableProperty" flag on the
|
||||||
|
* ENABLE_TRACE_PARAMETER struct. See void trace_flags(T trace_flags)
|
||||||
|
* for details on what the values mean.
|
||||||
|
* </summary>
|
||||||
|
* <returns>The value to set when the provider is enabled.</returns>
|
||||||
|
* <example>
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* provider<> powershell(id);
|
||||||
|
* powershell.trace_flags(EVENT_ENABLE_PROPERTY_STACK_TRACE);
|
||||||
|
* auto flags = powershell.get_trace_flags();
|
||||||
|
* assert(flags == EVENT_ENABLE_PROPERTY_STACK_TRACE);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
T trace_flags() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Requests that the provider log its state information. See:
|
||||||
|
* https://docs.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-enabletraceex2
|
||||||
|
* </summary>
|
||||||
|
*
|
||||||
|
* <example>
|
||||||
|
* krabs::provider<> process_provider(L"Microsoft-Windows-Kernel-Process");
|
||||||
|
* process_provider.any(0x10); // WINEVENT_KEYWORD_PROCESS
|
||||||
|
* process_provider.enable_rundown_events();
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void enable_rundown_events();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Turns a strongly typed provider<T> to provider<> (useful for
|
||||||
|
* creating collections of providers).
|
||||||
|
* </summary>
|
||||||
|
*
|
||||||
|
* <example>
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* provider<CustomFlagType> powershell(id);
|
||||||
|
* provider<> blah(powershell);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
operator provider<>() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
GUID guid_;
|
||||||
|
T any_;
|
||||||
|
T all_;
|
||||||
|
T level_;
|
||||||
|
T trace_flags_;
|
||||||
|
bool rundown_enabled_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename T>
|
||||||
|
friend class details::trace_manager;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
friend class krabs::trace;
|
||||||
|
|
||||||
|
template <typename S>
|
||||||
|
friend class base_provider;
|
||||||
|
|
||||||
|
friend struct details::ut;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Used to enable specific types of event sources from an ETW kernel
|
||||||
|
* trace.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
class kernel_provider : public details::base_provider<ULONGLONG> {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs a kernel_provider that enables events of the given
|
||||||
|
* flags.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
kernel_provider(unsigned long flags, const GUID &id)
|
||||||
|
: p_(flags)
|
||||||
|
, id_(id)
|
||||||
|
, gm_(0)
|
||||||
|
, r_(0)
|
||||||
|
, rundown_enabled_(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs a kernel_provider that enables events of the given
|
||||||
|
* group mask.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* Only supported on Windows 8 and newer.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
kernel_provider(const GUID& id, PERFINFO_MASK group_mask)
|
||||||
|
: p_(0)
|
||||||
|
, id_(id)
|
||||||
|
, gm_(group_mask)
|
||||||
|
, r_(0)
|
||||||
|
, rundown_enabled_(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Retrieves the GUID associated with this provider.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
const krabs::guid &id() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Sets flags to be enabled for the kernel rundown GUID.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* This ETW feature is undocumented and should be used with caution.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
void set_rundown_flags(unsigned long rundown_flags) {
|
||||||
|
r_ = rundown_flags;
|
||||||
|
rundown_enabled_ = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Retrieves the flag value associated with this provider.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
unsigned long flags() const { return p_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Retrieves the group mask value associated with this provider.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
PERFINFO_MASK group_mask() const { return gm_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Retrieves the rundown flag value associated with this provider.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
unsigned long rundown_flags() const { return r_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Have rundown flags been set for this this provider?
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
bool rundown_enabled() const { return rundown_enabled_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned long p_;
|
||||||
|
const krabs::guid id_;
|
||||||
|
PERFINFO_MASK gm_;
|
||||||
|
unsigned long r_;
|
||||||
|
bool rundown_enabled_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend struct details::kt;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace details {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void base_provider<T>::add_on_event_callback(c_provider_callback callback)
|
||||||
|
{
|
||||||
|
// C function pointers don't interact well with std::ref, so we
|
||||||
|
// overload to take care of this scenario.
|
||||||
|
callbacks_.push_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
template <typename U>
|
||||||
|
void base_provider<T>::add_on_event_callback(U &callback)
|
||||||
|
{
|
||||||
|
// std::function copies its argument -- because our callbacks list
|
||||||
|
// is a list of std::function, this causes problems when a user
|
||||||
|
// intended for their particular instance to be called.
|
||||||
|
// std::ref lets us get around this and point to a specific instance
|
||||||
|
// that they handed us.
|
||||||
|
callbacks_.push_back(std::ref(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
template <typename U>
|
||||||
|
void base_provider<T>::add_on_event_callback(const U &callback)
|
||||||
|
{
|
||||||
|
// This is where temporaries bind to. Temporaries can't be wrapped in
|
||||||
|
// a std::ref because they'll go away very quickly. We are forced to
|
||||||
|
// actually copy these.
|
||||||
|
callbacks_.push_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void base_provider<T>::add_on_error_callback(c_provider_error_callback callback)
|
||||||
|
{
|
||||||
|
// C function pointers don't interact well with std::ref, so we
|
||||||
|
// overload to take care of this scenario.
|
||||||
|
error_callbacks_.push_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
template <typename U>
|
||||||
|
void base_provider<T>::add_on_error_callback(U& callback)
|
||||||
|
{
|
||||||
|
// std::function copies its argument -- because our callbacks list
|
||||||
|
// is a list of std::function, this causes problems when a user
|
||||||
|
// intended for their particular instance to be called.
|
||||||
|
// std::ref lets us get around this and point to a specific instance
|
||||||
|
// that they handed us.
|
||||||
|
error_callbacks_.push_back(std::ref(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
template <typename U>
|
||||||
|
void base_provider<T>::add_on_error_callback(const U& callback)
|
||||||
|
{
|
||||||
|
// This is where temporaries bind to. Temporaries can't be wrapped in
|
||||||
|
// a std::ref because they'll go away very quickly. We are forced to
|
||||||
|
// actually copy these.
|
||||||
|
error_callbacks_.push_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void base_provider<T>::add_filter(const event_filter &f)
|
||||||
|
{
|
||||||
|
filters_.push_back(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void base_provider<T>::on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context) const
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (auto& callback : callbacks_) {
|
||||||
|
callback(record, trace_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& filter : filters_) {
|
||||||
|
filter.on_event(record, trace_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (krabs::could_not_find_schema& ex)
|
||||||
|
{
|
||||||
|
for (auto& error_callback : error_callbacks_) {
|
||||||
|
error_callback(record, ex.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace details
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static const GUID emptyGuid = { 0 };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
provider<T>::provider(GUID id)
|
||||||
|
: guid_(id)
|
||||||
|
, any_(0)
|
||||||
|
, all_(0)
|
||||||
|
, level_(5)
|
||||||
|
, trace_flags_(0)
|
||||||
|
, rundown_enabled_(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
|
||||||
|
inline void check_com_hr(HRESULT hr) {
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
std::stringstream stream;
|
||||||
|
stream << "Error in creating instance of trace providers";
|
||||||
|
stream << ", hr = 0x";
|
||||||
|
stream << std::hex << hr;
|
||||||
|
throw std::runtime_error(stream.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void check_provider_hr(HRESULT hr, const std::wstring &providerName) {
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
std::stringstream stream;
|
||||||
|
stream << "Error in constructing guid from provider name (";
|
||||||
|
stream << from_wstring(providerName);
|
||||||
|
stream << "), hr = 0x";
|
||||||
|
stream << std::hex << hr;
|
||||||
|
throw std::runtime_error(stream.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
provider<T>::provider(const std::wstring &providerName)
|
||||||
|
{
|
||||||
|
ITraceDataProviderCollection *allProviders;
|
||||||
|
|
||||||
|
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||||
|
check_com_hr(hr);
|
||||||
|
{
|
||||||
|
hr = CoCreateInstance(
|
||||||
|
CLSID_TraceDataProviderCollection,
|
||||||
|
NULL,
|
||||||
|
CLSCTX_SERVER,
|
||||||
|
IID_ITraceDataProviderCollection,
|
||||||
|
(void**)&allProviders);
|
||||||
|
check_com_hr(hr);
|
||||||
|
|
||||||
|
auto release_ptr = [](IUnknown* ptr) { ptr->Release(); };
|
||||||
|
std::unique_ptr<ITraceDataProviderCollection, decltype(release_ptr)> allProvidersPtr(allProviders, release_ptr);
|
||||||
|
|
||||||
|
hr = allProviders->GetTraceDataProviders(NULL);
|
||||||
|
check_provider_hr(hr, providerName);
|
||||||
|
|
||||||
|
ULONG count;
|
||||||
|
hr = allProviders->get_Count((long*)&count);
|
||||||
|
check_provider_hr(hr, providerName);
|
||||||
|
|
||||||
|
VARIANT index;
|
||||||
|
index.vt = VT_UI4;
|
||||||
|
|
||||||
|
GUID providerGuid = { 0 };
|
||||||
|
|
||||||
|
for (index.ulVal = 0; index.ulVal < count; index.ulVal++){
|
||||||
|
ITraceDataProvider *provider;
|
||||||
|
hr = allProviders->get_Item(index, &provider);
|
||||||
|
check_provider_hr(hr, providerName);
|
||||||
|
|
||||||
|
std::unique_ptr<ITraceDataProvider, decltype(release_ptr)> providerPtr(provider, release_ptr);
|
||||||
|
|
||||||
|
_bstr_t name;
|
||||||
|
hr = provider->get_DisplayName(name.GetAddress());
|
||||||
|
check_provider_hr(hr, providerName);
|
||||||
|
|
||||||
|
if (wcscmp(name, providerName.c_str()) == 0){
|
||||||
|
hr = provider->get_Guid(&providerGuid);
|
||||||
|
check_provider_hr(hr, providerName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp((void*)&providerGuid, (void*)&emptyGuid, sizeof(emptyGuid)) == 0)
|
||||||
|
{
|
||||||
|
std::stringstream stream;
|
||||||
|
stream << "Provider name does not exist. (";
|
||||||
|
stream << from_wstring(providerName);
|
||||||
|
stream << "), hr = 0x";
|
||||||
|
stream << std::hex << hr;
|
||||||
|
throw std::runtime_error(stream.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
guid_ = providerGuid;
|
||||||
|
any_ = 0;
|
||||||
|
all_ = 0;
|
||||||
|
level_ = 5;
|
||||||
|
trace_flags_ = 0;
|
||||||
|
rundown_enabled_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CoUninitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void provider<T>::any(T any)
|
||||||
|
{
|
||||||
|
any_ = any;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void provider<T>::all(T all)
|
||||||
|
{
|
||||||
|
all_ = all;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void provider<T>::level(T level)
|
||||||
|
{
|
||||||
|
level_ = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void provider<T>::trace_flags(T trace_flags)
|
||||||
|
{
|
||||||
|
trace_flags_ = trace_flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T provider<T>::trace_flags() const
|
||||||
|
{
|
||||||
|
return static_cast<T>(trace_flags_);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void provider<T>::enable_rundown_events()
|
||||||
|
{
|
||||||
|
rundown_enabled_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
provider<T>::operator provider<>() const
|
||||||
|
{
|
||||||
|
provider<> tmp(guid_);
|
||||||
|
tmp.any_ = static_cast<ULONGLONG>(any_);
|
||||||
|
tmp.all_ = static_cast<ULONGLONG>(all_);
|
||||||
|
tmp.level_ = static_cast<UCHAR>(level_);
|
||||||
|
tmp.trace_flags_ = static_cast<ULONG>(trace_flags_);
|
||||||
|
tmp.callbacks_ = this.callbacks_;
|
||||||
|
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const krabs::guid &kernel_provider::id() const
|
||||||
|
{
|
||||||
|
return id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,420 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define INITGUID
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <tdh.h>
|
||||||
|
#include <evntrace.h>
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
#include "schema_locator.hpp"
|
||||||
|
|
||||||
|
#pragma comment(lib, "tdh.lib")
|
||||||
|
|
||||||
|
|
||||||
|
namespace krabs { namespace testing {
|
||||||
|
class record_builder;
|
||||||
|
} /* namespace testing */ } /* namespace krabs */
|
||||||
|
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
class schema;
|
||||||
|
class parser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Used to query events for detailed information. Creation is rather
|
||||||
|
* costly, so client code should try hard to delay creation of this.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
class schema {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs a schema from an event record instance
|
||||||
|
* using the provided schema_locator.
|
||||||
|
* </summary>
|
||||||
|
*
|
||||||
|
* <example>
|
||||||
|
* void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context)
|
||||||
|
* {
|
||||||
|
* krabs::schema schema(record, trace_context.schema_locator);
|
||||||
|
* }
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
schema(const EVENT_RECORD &, const krabs::schema_locator &);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>Compares two schemas for equality.<summary>
|
||||||
|
*
|
||||||
|
* <example>
|
||||||
|
* schema1 == schema2;
|
||||||
|
* schema1 != schema2;
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
bool operator==(const schema &other) const;
|
||||||
|
bool operator!=(const schema &other) const;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* <summary>
|
||||||
|
* Returns the name of an event via its schema.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context)
|
||||||
|
* {
|
||||||
|
* krabs::schema schema(record, trace_context.schema_locator);
|
||||||
|
* std::wstring name = krabs::event_name(schema);
|
||||||
|
* }
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
const wchar_t *event_name() const;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* <summary>
|
||||||
|
* Returns the name of an opcode via its schema.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context)
|
||||||
|
* {
|
||||||
|
* krabs::schema schema(record, trace_context.schema_locator);
|
||||||
|
* std::wstring name = krabs::opcode_name(schema);
|
||||||
|
* }
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
const wchar_t* opcode_name() const;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* <summary>
|
||||||
|
* Returns the taskname of an event via its schema.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context)
|
||||||
|
* {
|
||||||
|
* krabs::schema schema(record, trace_context.schema_locator);
|
||||||
|
* std::wstring name = krabs::task_name(schema);
|
||||||
|
* }
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
const wchar_t *task_name() const;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* <summary>
|
||||||
|
* Returns the DECODING_SOURCE of an event via its schema.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context)
|
||||||
|
* {
|
||||||
|
* krabs::schema schema(record, trace_context.schema_locator);
|
||||||
|
* DECODING_SOURCE source = krabs::decoding_source(schema);
|
||||||
|
* }
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
DECODING_SOURCE decoding_source() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns the event ID via its schema.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context)
|
||||||
|
* {
|
||||||
|
* krabs::schema schema(record, trace_context.schema_locator);
|
||||||
|
* int id = schema.event_id();
|
||||||
|
* }
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
int event_id() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns the event opcode.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context)
|
||||||
|
* {
|
||||||
|
* krabs::schema schema(record, trace_context.schema_locator);
|
||||||
|
* int opcode = schema.event_opcode();
|
||||||
|
* }
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
int event_opcode() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns the version of the event.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
unsigned int event_version() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns the flags of the event.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
unsigned int event_flags() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns the provider name of an event via its schema.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context)
|
||||||
|
* {
|
||||||
|
* krabs::schema schema(record, trace_context.schema_locator);
|
||||||
|
* std::wstring name = krabs::provider_name(schema);
|
||||||
|
* }
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
const wchar_t *provider_name() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns the PID associated with the event via its schema.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context)
|
||||||
|
* {
|
||||||
|
* krabs::schema schema(record, trace_context.schema_locator);
|
||||||
|
* unsigned int name = krabs::process_id(schema);
|
||||||
|
* }
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
unsigned int process_id() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns the Thread ID associated with the event via its schema.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context)
|
||||||
|
* {
|
||||||
|
* krabs::schema schema(record, trace_context.schema_locator);
|
||||||
|
* unsigned int name = krabs::thread_id(schema);
|
||||||
|
* }
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
unsigned int thread_id() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns the timestamp associated with the event via its schema.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context)
|
||||||
|
* {
|
||||||
|
* krabs::schema schema(record, trace_context.schema_locator);
|
||||||
|
* LARGE_INTEGER time = krabs::timestamp(schema);
|
||||||
|
* }
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
LARGE_INTEGER timestamp() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns the Activity ID associated with the event via its schema.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context)
|
||||||
|
* {
|
||||||
|
* krabs::schema schema(record, trace_context.schema_locator);
|
||||||
|
* GUID activity_id = krabs::activity_id(schema);
|
||||||
|
* }
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
GUID activity_id() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Retrieves the call stack associated with the record, if enabled.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* void on_event(const EVENT_RECORD &record, const krabs::trace_context &trace_context)
|
||||||
|
* {
|
||||||
|
* krabs::schema schema(record, trace_context.schema_locator);
|
||||||
|
* std::vector<ULONG_PTR> stack_trace = schema.stack_trace();
|
||||||
|
* }
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
std::vector<ULONG_PTR> stack_trace() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const EVENT_RECORD &record_;
|
||||||
|
TRACE_EVENT_INFO *pSchema_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend std::wstring event_name(const schema &);
|
||||||
|
friend std::wstring opcode_name(const schema &);
|
||||||
|
friend std::wstring task_name(const schema &);
|
||||||
|
friend DECODING_SOURCE decoding_source(const schema &);
|
||||||
|
friend std::wstring provider_name(const schema &);
|
||||||
|
friend unsigned int process_id(const schema &);
|
||||||
|
friend LARGE_INTEGER timestamp(const schema &);
|
||||||
|
friend GUID activity_id(const schema&);
|
||||||
|
friend int event_id(const EVENT_RECORD &);
|
||||||
|
friend int event_id(const schema &);
|
||||||
|
friend std::vector<ULONG_PTR> stack_trace(const schema&);
|
||||||
|
friend std::vector<ULONG_PTR> stack_trace(const EVENT_RECORD&);
|
||||||
|
|
||||||
|
friend class parser;
|
||||||
|
friend class property_iterator;
|
||||||
|
friend class record_builder;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline schema::schema(const EVENT_RECORD &record, const krabs::schema_locator &schema_locator)
|
||||||
|
: record_(record)
|
||||||
|
, pSchema_(schema_locator.get_event_schema(record))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
inline bool schema::operator==(const schema &other) const
|
||||||
|
{
|
||||||
|
return (pSchema_->ProviderGuid == other.pSchema_->ProviderGuid &&
|
||||||
|
pSchema_->EventDescriptor.Id == other.pSchema_->EventDescriptor.Id &&
|
||||||
|
pSchema_->EventDescriptor.Version == other.pSchema_->EventDescriptor.Version);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool schema::operator!=(const schema &other) const
|
||||||
|
{
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const wchar_t *schema::event_name() const
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
EventNameOffset will be 0 if the event does not have an assigned name or
|
||||||
|
if this event is decoded on a system that does not support decoding
|
||||||
|
manifest event names. Event name decoding is supported on Windows
|
||||||
|
10 Fall Creators Update (2017) and later.
|
||||||
|
*/
|
||||||
|
if (pSchema_->EventNameOffset != 0) {
|
||||||
|
return reinterpret_cast<const wchar_t*>(
|
||||||
|
reinterpret_cast<const char*>(pSchema_) +
|
||||||
|
pSchema_->EventNameOffset);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return L"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const wchar_t* schema::opcode_name() const
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
In WPP Traces OpcodeName is not used
|
||||||
|
*/
|
||||||
|
if (pSchema_->OpcodeNameOffset != 0) {
|
||||||
|
return reinterpret_cast<const wchar_t*>(
|
||||||
|
reinterpret_cast<const char*>(pSchema_) +
|
||||||
|
pSchema_->OpcodeNameOffset);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return L"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const wchar_t *schema::task_name() const
|
||||||
|
{
|
||||||
|
if (pSchema_->TaskNameOffset != 0) {
|
||||||
|
return reinterpret_cast<const wchar_t*>(
|
||||||
|
reinterpret_cast<const char*>(pSchema_) +
|
||||||
|
pSchema_->TaskNameOffset);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return L"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline DECODING_SOURCE schema::decoding_source() const
|
||||||
|
{
|
||||||
|
return pSchema_->DecodingSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int schema::event_id() const
|
||||||
|
{
|
||||||
|
return record_.EventHeader.EventDescriptor.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int schema::event_opcode() const
|
||||||
|
{
|
||||||
|
return record_.EventHeader.EventDescriptor.Opcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int schema::event_version() const
|
||||||
|
{
|
||||||
|
return record_.EventHeader.EventDescriptor.Version;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int schema::event_flags() const
|
||||||
|
{
|
||||||
|
return record_.EventHeader.Flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const wchar_t *schema::provider_name() const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<const wchar_t*>(
|
||||||
|
reinterpret_cast<const char*>(pSchema_) +
|
||||||
|
pSchema_->ProviderNameOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int schema::process_id() const
|
||||||
|
{
|
||||||
|
return record_.EventHeader.ProcessId;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int schema::thread_id() const
|
||||||
|
{
|
||||||
|
return record_.EventHeader.ThreadId;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline LARGE_INTEGER schema::timestamp() const
|
||||||
|
{
|
||||||
|
return record_.EventHeader.TimeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline GUID schema::activity_id() const
|
||||||
|
{
|
||||||
|
return record_.EventHeader.ActivityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::vector<ULONG_PTR> schema::stack_trace() const
|
||||||
|
{
|
||||||
|
std::vector<ULONG_PTR> call_stack;
|
||||||
|
if (record_.ExtendedDataCount != 0) {
|
||||||
|
for (USHORT i = 0; i < record_.ExtendedDataCount; i++)
|
||||||
|
{
|
||||||
|
auto item = record_.ExtendedData[i];
|
||||||
|
if (item.ExtType == EVENT_HEADER_EXT_TYPE_STACK_TRACE64) {
|
||||||
|
auto stacktrace = reinterpret_cast<PEVENT_EXTENDED_ITEM_STACK_TRACE64>(item.DataPtr);
|
||||||
|
auto stack_length = (item.DataSize - sizeof(ULONG64)) / sizeof(ULONG64);
|
||||||
|
for (size_t j = 0; j < stack_length; j++)
|
||||||
|
{
|
||||||
|
call_stack.push_back(stacktrace->Address[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.ExtType == EVENT_HEADER_EXT_TYPE_STACK_TRACE32) {
|
||||||
|
auto stacktrace = reinterpret_cast<PEVENT_EXTENDED_ITEM_STACK_TRACE32>(item.DataPtr);
|
||||||
|
auto stack_length = (item.DataSize - sizeof(ULONG64)) / sizeof(ULONG);
|
||||||
|
for (size_t j = 0; j < stack_length; j++)
|
||||||
|
{
|
||||||
|
call_stack.push_back(stacktrace->Address[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return call_stack;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define INITGUID
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <tdh.h>
|
||||||
|
#include <evntrace.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
#include "errors.hpp"
|
||||||
|
#include "guid.hpp"
|
||||||
|
|
||||||
|
#pragma comment(lib, "tdh.lib")
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Type used as the key for cache lookup in a schema_locator.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct schema_key
|
||||||
|
{
|
||||||
|
guid provider;
|
||||||
|
uint16_t id;
|
||||||
|
uint8_t opcode;
|
||||||
|
uint8_t version;
|
||||||
|
uint8_t level;
|
||||||
|
|
||||||
|
schema_key(const EVENT_RECORD &record)
|
||||||
|
: provider(record.EventHeader.ProviderId)
|
||||||
|
, id(record.EventHeader.EventDescriptor.Id)
|
||||||
|
, opcode(record.EventHeader.EventDescriptor.Opcode)
|
||||||
|
, level(record.EventHeader.EventDescriptor.Level)
|
||||||
|
, version(record.EventHeader.EventDescriptor.Version) { }
|
||||||
|
|
||||||
|
bool operator==(const schema_key &rhs) const
|
||||||
|
{
|
||||||
|
return provider == rhs.provider &&
|
||||||
|
id == rhs.id &&
|
||||||
|
opcode == rhs.opcode &&
|
||||||
|
level == rhs.level &&
|
||||||
|
version == rhs.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const schema_key &rhs) const { return !(*this == rhs); }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Builds a hash code for a schema_key
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template<>
|
||||||
|
struct std::hash<krabs::schema_key>
|
||||||
|
{
|
||||||
|
size_t operator()(const krabs::schema_key &key) const
|
||||||
|
{
|
||||||
|
// Shift-Add-XOR hash - good enough for the small sets we deal with
|
||||||
|
size_t h = 2166136261;
|
||||||
|
|
||||||
|
h ^= (h << 5) + (h >> 2) + std::hash<krabs::guid>()(key.provider);
|
||||||
|
h ^= (h << 5) + (h >> 2) + key.id;
|
||||||
|
h ^= (h << 5) + (h >> 2) + key.opcode;
|
||||||
|
h ^= (h << 5) + (h >> 2) + key.version;
|
||||||
|
h ^= (h << 5) + (h >> 2) + key.level;
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Get event schema from TDH.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
std::unique_ptr<char[]> get_event_schema_from_tdh(const EVENT_RECORD &);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Fetches and caches schemas from TDH.
|
||||||
|
* NOTE: this cache also reduces the number of managed to native transitions
|
||||||
|
* when krabs is compiled into a managed assembly.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
class schema_locator {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Retrieves the event schema from the cache or falls back to
|
||||||
|
* TDH to load the schema.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
const PTRACE_EVENT_INFO get_event_schema(const EVENT_RECORD &record) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable std::unordered_map<schema_key, std::unique_ptr<char[]>> cache_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline const PTRACE_EVENT_INFO schema_locator::get_event_schema(const EVENT_RECORD &record) const
|
||||||
|
{
|
||||||
|
// check the cache
|
||||||
|
auto key = schema_key(record);
|
||||||
|
auto& buffer = cache_[key];
|
||||||
|
|
||||||
|
if (!buffer) {
|
||||||
|
auto temp = get_event_schema_from_tdh(record);
|
||||||
|
buffer.swap(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (PTRACE_EVENT_INFO)(buffer.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::unique_ptr<char[]> get_event_schema_from_tdh(const EVENT_RECORD &record)
|
||||||
|
{
|
||||||
|
// get required size
|
||||||
|
ULONG bufferSize = 0;
|
||||||
|
ULONG status = TdhGetEventInformation(
|
||||||
|
(PEVENT_RECORD)&record,
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
&bufferSize);
|
||||||
|
|
||||||
|
if (status != ERROR_INSUFFICIENT_BUFFER) {
|
||||||
|
error_check_common_conditions(status, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate and fill the schema from TDH
|
||||||
|
auto buffer = std::unique_ptr<char[]>(new char[bufferSize]);
|
||||||
|
|
||||||
|
error_check_common_conditions(
|
||||||
|
TdhGetEventInformation(
|
||||||
|
(PEVENT_RECORD)&record,
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
(PTRACE_EVENT_INFO)buffer.get(),
|
||||||
|
&bufferSize),
|
||||||
|
record);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <tdh.h>
|
||||||
|
#include <evntrace.h>
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
// TODO: I don't like this interface - it's too tightly
|
||||||
|
// coupled to parser.hpp mainly because the code was
|
||||||
|
// lifted directly out of parser::find_property.
|
||||||
|
|
||||||
|
class size_provider {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Get the size of the specified property from the specified record.
|
||||||
|
* </summary>
|
||||||
|
* BYTE* offset into the user data buffer where the property starts
|
||||||
|
* wchar_t* name of the property to query
|
||||||
|
* EVENT_RECORD& record to query
|
||||||
|
* EVENT_PROPERTY_INFO& property info for the property to query
|
||||||
|
*/
|
||||||
|
static ULONG get_property_size(
|
||||||
|
const BYTE*,
|
||||||
|
const wchar_t*,
|
||||||
|
const EVENT_RECORD&,
|
||||||
|
const EVENT_PROPERTY_INFO&);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static ULONG get_heuristic_size(
|
||||||
|
const BYTE*,
|
||||||
|
const EVENT_PROPERTY_INFO&,
|
||||||
|
const EVENT_RECORD&);
|
||||||
|
|
||||||
|
static ULONG get_tdh_size(
|
||||||
|
const wchar_t*,
|
||||||
|
const EVENT_RECORD&);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline ULONG size_provider::get_property_size(
|
||||||
|
const BYTE* propertyStart,
|
||||||
|
const wchar_t* propertyName,
|
||||||
|
const EVENT_RECORD& record,
|
||||||
|
const EVENT_PROPERTY_INFO& propertyInfo)
|
||||||
|
{
|
||||||
|
// The values of the event are essentially stored as an ad-hoc
|
||||||
|
// variant. In order to determine how far we need to advance the
|
||||||
|
// seeking pointer, we need to know the size of the property that
|
||||||
|
// we've just looked at. For certain variable-sized types (like a
|
||||||
|
// string), we need to ask Tdh* to determine the length of the
|
||||||
|
// property. For others, the size is immediately accessible in
|
||||||
|
// the schema structure.
|
||||||
|
|
||||||
|
if ((propertyInfo.Flags & PropertyParamLength) == 0 &&
|
||||||
|
propertyInfo.length > 0)
|
||||||
|
{
|
||||||
|
// length is a union that may refer to another field for a length
|
||||||
|
// value. In that case, defer to TDH for the value otherwise
|
||||||
|
// use the length value directly.
|
||||||
|
|
||||||
|
// For pointers check header instead of size, see PointerSize at
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/api/tdh/nf-tdh-tdhformatproperty
|
||||||
|
// for details
|
||||||
|
if (propertyInfo.nonStructType.InType == TDH_INTYPE_POINTER)
|
||||||
|
{
|
||||||
|
return record.EventHeader.Flags & EVENT_HEADER_FLAG_32_BIT_HEADER ? 4 : 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
return propertyInfo.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
ULONG propertyLength = 0;
|
||||||
|
|
||||||
|
// If no flags are set on the property, attempt to use the length
|
||||||
|
// field. If that field is 0, try using our heuristic.
|
||||||
|
if (propertyInfo.Flags == 0)
|
||||||
|
{
|
||||||
|
if (propertyInfo.length > 0)
|
||||||
|
propertyLength = propertyInfo.length;
|
||||||
|
else
|
||||||
|
propertyLength = get_heuristic_size(propertyStart, propertyInfo, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couldn't get the length from the 'length' field or
|
||||||
|
// the heuristic for size failed -> ask Tdh.
|
||||||
|
if (propertyLength == 0)
|
||||||
|
propertyLength = get_tdh_size(propertyName, record);
|
||||||
|
|
||||||
|
return propertyLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ULONG size_provider::get_heuristic_size(
|
||||||
|
const BYTE* propertyStart,
|
||||||
|
const EVENT_PROPERTY_INFO& propertyInfo,
|
||||||
|
const EVENT_RECORD& record)
|
||||||
|
{
|
||||||
|
ULONG propertyLength = 0;
|
||||||
|
PBYTE pRecordEnd = (PBYTE)record.UserData + record.UserDataLength;
|
||||||
|
|
||||||
|
// The calls to Tdh are kind of expensive, especially when krabs is
|
||||||
|
// included in a managed assembly as this call will be a thunk.
|
||||||
|
// The following _very_ common property types can be short-circuited
|
||||||
|
// to prevent the expensive call.
|
||||||
|
|
||||||
|
// Be careful! Check IN and OUT types before making an assumption.
|
||||||
|
|
||||||
|
// Strings that appear at the end of a record may not be null-terminated.
|
||||||
|
// If a string is null-terminated, propertyLength includes the null character.
|
||||||
|
// If a string is not-null terminated, propertyLength includes all bytes up
|
||||||
|
// to the end of the record buffer.
|
||||||
|
|
||||||
|
if (propertyInfo.nonStructType.OutType == TDH_OUTTYPE_STRING)
|
||||||
|
{
|
||||||
|
if (propertyInfo.nonStructType.InType == TDH_INTYPE_UNICODESTRING)
|
||||||
|
{
|
||||||
|
auto p = (const wchar_t*)propertyStart;
|
||||||
|
auto pEnd = (const wchar_t*)pRecordEnd;
|
||||||
|
while (p < pEnd) {
|
||||||
|
if (!*p++) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
propertyLength = static_cast<ULONG>(((PBYTE)p) - propertyStart);
|
||||||
|
}
|
||||||
|
else if (propertyInfo.nonStructType.InType == TDH_INTYPE_ANSISTRING)
|
||||||
|
{
|
||||||
|
auto p = (const char*)propertyStart;
|
||||||
|
auto pEnd = (const char*)pRecordEnd;
|
||||||
|
while (p < pEnd) {
|
||||||
|
if (!*p++) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
propertyLength = static_cast<ULONG>(((PBYTE)p) - propertyStart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return propertyLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ULONG size_provider::get_tdh_size(
|
||||||
|
const wchar_t* propertyName,
|
||||||
|
const EVENT_RECORD& record)
|
||||||
|
{
|
||||||
|
ULONG propertyLength = 0;
|
||||||
|
|
||||||
|
PROPERTY_DATA_DESCRIPTOR desc;
|
||||||
|
desc.PropertyName = (ULONGLONG)propertyName;
|
||||||
|
desc.ArrayIndex = ULONG_MAX;
|
||||||
|
|
||||||
|
TdhGetPropertySize((PEVENT_RECORD)&record, 0, NULL, 1, &desc, &propertyLength);
|
||||||
|
|
||||||
|
return propertyLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define INITGUID
|
||||||
|
|
||||||
|
#include <tdh.h>
|
||||||
|
#include <string>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
#include "parse_types.hpp"
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
#define CASE_TYPE(enum) case TDH_INTYPE_##enum: return #enum
|
||||||
|
|
||||||
|
inline const char* in_type_to_string(_TDH_IN_TYPE type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
CASE_TYPE(NULL);
|
||||||
|
CASE_TYPE(UNICODESTRING);
|
||||||
|
CASE_TYPE(ANSISTRING);
|
||||||
|
CASE_TYPE(INT8);
|
||||||
|
CASE_TYPE(UINT8);
|
||||||
|
CASE_TYPE(INT16);
|
||||||
|
CASE_TYPE(UINT16);
|
||||||
|
CASE_TYPE(INT32);
|
||||||
|
CASE_TYPE(UINT32);
|
||||||
|
CASE_TYPE(INT64);
|
||||||
|
CASE_TYPE(UINT64);
|
||||||
|
CASE_TYPE(FLOAT);
|
||||||
|
CASE_TYPE(DOUBLE);
|
||||||
|
CASE_TYPE(BOOLEAN);
|
||||||
|
CASE_TYPE(BINARY);
|
||||||
|
CASE_TYPE(GUID);
|
||||||
|
CASE_TYPE(POINTER);
|
||||||
|
CASE_TYPE(FILETIME);
|
||||||
|
CASE_TYPE(SYSTEMTIME);
|
||||||
|
CASE_TYPE(SID);
|
||||||
|
CASE_TYPE(HEXINT32);
|
||||||
|
CASE_TYPE(HEXINT64);
|
||||||
|
CASE_TYPE(COUNTEDSTRING);
|
||||||
|
CASE_TYPE(COUNTEDANSISTRING);
|
||||||
|
CASE_TYPE(REVERSEDCOUNTEDSTRING);
|
||||||
|
CASE_TYPE(REVERSEDCOUNTEDANSISTRING);
|
||||||
|
CASE_TYPE(NONNULLTERMINATEDSTRING);
|
||||||
|
CASE_TYPE(NONNULLTERMINATEDANSISTRING);
|
||||||
|
CASE_TYPE(UNICODECHAR);
|
||||||
|
CASE_TYPE(ANSICHAR);
|
||||||
|
CASE_TYPE(SIZET);
|
||||||
|
CASE_TYPE(HEXDUMP);
|
||||||
|
CASE_TYPE(WBEMSID);
|
||||||
|
default: return "<INVALID VALUE>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef CASE_TYPE
|
||||||
|
|
||||||
|
namespace debug {
|
||||||
|
|
||||||
|
// this function provides a user-friendly compiler error
|
||||||
|
// which shows the type in question in the error message.
|
||||||
|
template <typename T>
|
||||||
|
inline void missing_assert_specialization_for()
|
||||||
|
{
|
||||||
|
static_assert(sizeof(T) == 0, __FUNCSIG__);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The "catch-all" implementation of assert_valid_assignment just
|
||||||
|
// throws in debug to let us know that we are trying to parse a
|
||||||
|
// type that does not have any assignment validation. This compiles
|
||||||
|
// to a no-op in release.
|
||||||
|
template <typename T>
|
||||||
|
inline void assert_valid_assignment(const std::wstring&, const property_info&)
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
|
||||||
|
// NOTE: if you want compile time assignment assertion define TYPEASSERT
|
||||||
|
// in the preprocessor or undefine it to disable compilation errors
|
||||||
|
|
||||||
|
#ifdef TYPEASSERT
|
||||||
|
missing_assert_specialization_for<T>();
|
||||||
|
#endif // TYPEASSERT
|
||||||
|
#endif // NDEBUG
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
|
||||||
|
// These specializations will be removed in release builds and compilation
|
||||||
|
// will fall back to the unspecialized version which is a no-op in release.
|
||||||
|
|
||||||
|
inline void throw_if_invalid(
|
||||||
|
const std::wstring& name,
|
||||||
|
const property_info& info,
|
||||||
|
_TDH_IN_TYPE requested)
|
||||||
|
{
|
||||||
|
auto actual = (_TDH_IN_TYPE)info.pEventPropertyInfo_->nonStructType.InType;
|
||||||
|
|
||||||
|
if (requested == actual) return;
|
||||||
|
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable: 4244) // narrowing property name wchar_t to char for this error message
|
||||||
|
std::string ansiName(name.begin(), name.end());
|
||||||
|
#pragma warning(pop)
|
||||||
|
|
||||||
|
throw type_mismatch_assert(
|
||||||
|
ansiName.c_str(),
|
||||||
|
in_type_to_string(actual),
|
||||||
|
in_type_to_string(requested));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The macro below generates a specialized version of assert_valid_assignment
|
||||||
|
// only in debug builds. The specialized overload will be selected instead
|
||||||
|
// of the unspecialized version defined above. This allows us to have
|
||||||
|
// type-driven assertions only in debug builds.
|
||||||
|
|
||||||
|
#define BUILD_ASSERT(type, tdh_type) \
|
||||||
|
template <> \
|
||||||
|
inline void assert_valid_assignment<type>( \
|
||||||
|
const std::wstring& name, const property_info& info) \
|
||||||
|
{ \
|
||||||
|
throw_if_invalid(name, info, tdh_type); \
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: don't just blindly add assertions here, some types
|
||||||
|
// that seem trivial (e.g. bool) are not because of differences
|
||||||
|
// between the representation in C++ and the representation in ETW.
|
||||||
|
// Ensure that type sizes match and that the ETW form isn't
|
||||||
|
// a variant or variable length. A type that requires a specialized
|
||||||
|
// assertion will also require a specialized parser.
|
||||||
|
|
||||||
|
// strings
|
||||||
|
BUILD_ASSERT(std::wstring, TDH_INTYPE_UNICODESTRING);
|
||||||
|
BUILD_ASSERT(std::string, TDH_INTYPE_ANSISTRING);
|
||||||
|
BUILD_ASSERT(const counted_string*, TDH_INTYPE_COUNTEDSTRING);
|
||||||
|
|
||||||
|
// integers
|
||||||
|
BUILD_ASSERT(int8_t, TDH_INTYPE_INT8);
|
||||||
|
BUILD_ASSERT(uint8_t, TDH_INTYPE_UINT8);
|
||||||
|
BUILD_ASSERT(int16_t, TDH_INTYPE_INT16);
|
||||||
|
BUILD_ASSERT(uint16_t, TDH_INTYPE_UINT16);
|
||||||
|
BUILD_ASSERT(int32_t, TDH_INTYPE_INT32);
|
||||||
|
BUILD_ASSERT(uint32_t, TDH_INTYPE_UINT32);
|
||||||
|
BUILD_ASSERT(int64_t, TDH_INTYPE_INT64);
|
||||||
|
BUILD_ASSERT(uint64_t, TDH_INTYPE_UINT64);
|
||||||
|
|
||||||
|
// floating
|
||||||
|
BUILD_ASSERT(float, TDH_INTYPE_FLOAT);
|
||||||
|
BUILD_ASSERT(double, TDH_INTYPE_DOUBLE);
|
||||||
|
|
||||||
|
// FILETIME
|
||||||
|
BUILD_ASSERT(::FILETIME, TDH_INTYPE_FILETIME);
|
||||||
|
BUILD_ASSERT(::SYSTEMTIME, TDH_INTYPE_SYSTEMTIME);
|
||||||
|
|
||||||
|
#undef BUILD_ASSERT
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline void assert_valid_assignment<ip_address>(
|
||||||
|
const std::wstring&, const property_info& info)
|
||||||
|
{
|
||||||
|
auto outType = info.pEventPropertyInfo_->nonStructType.OutType;
|
||||||
|
|
||||||
|
if (outType != TDH_OUTTYPE_IPV6 &&
|
||||||
|
outType != TDH_OUTTYPE_IPV4) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Requested an IP address from non-IP address property");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline void assert_valid_assignment<socket_address>(
|
||||||
|
const std::wstring&, const property_info& info)
|
||||||
|
{
|
||||||
|
auto outType = info.pEventPropertyInfo_->nonStructType.OutType;
|
||||||
|
|
||||||
|
if (outType != TDH_OUTTYPE_SOCKETADDRESS) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Requested a socket address from property that does not contain a socket address");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline void assert_valid_assignment<sid>(
|
||||||
|
const std::wstring&, const property_info& info)
|
||||||
|
{
|
||||||
|
auto inType = info.pEventPropertyInfo_->nonStructType.InType;
|
||||||
|
|
||||||
|
if (inType != TDH_INTYPE_WBEMSID && inType != TDH_INTYPE_SID) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Requested a SID but was neither a SID nor WBEMSID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline void assert_valid_assignment<pointer>(
|
||||||
|
const std::wstring&, const property_info& info)
|
||||||
|
{
|
||||||
|
auto inType = info.pEventPropertyInfo_->nonStructType.InType;
|
||||||
|
|
||||||
|
if (inType != TDH_INTYPE_POINTER) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Requested a POINTER from property that is not one");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline void assert_valid_assignment<bool>(
|
||||||
|
const std::wstring&, const property_info& info)
|
||||||
|
{
|
||||||
|
auto inType = info.pEventPropertyInfo_->nonStructType.InType;
|
||||||
|
|
||||||
|
if (inType != TDH_INTYPE_BOOLEAN) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Requested a BOOLEAN from property that is not one");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // NDEBUG
|
||||||
|
|
||||||
|
} /* namespace debug */
|
||||||
|
|
||||||
|
} /* namespace krabs */
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define INITGUID
|
||||||
|
|
||||||
|
|
||||||
|
#include "../compiler_check.hpp"
|
||||||
|
#include "../filtering/event_filter.hpp"
|
||||||
|
#include "synth_record.hpp"
|
||||||
|
|
||||||
|
namespace krabs { namespace testing {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Serves as a fill-in for the event_filter class for testing purposes.
|
||||||
|
* It acts as a liason for the actual filter instance and allows for forced event
|
||||||
|
* testing.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
class event_filter_proxy {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs a proxy for the given event_filter.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* krabs::event_filter event_filter;
|
||||||
|
* krabs::testing::event_filter_proxy proxy(event_filter);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
event_filter_proxy(krabs::event_filter &filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Pushes an event through to the proxied filter instance.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* krabs::event_filter event_filter;
|
||||||
|
* krabs::testing::event_filter_proxy proxy(event_filter);
|
||||||
|
*
|
||||||
|
* krabs::guid powershell(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* krabs::testing::record_builder builder(powershell, krabs::id(7942), krabs::version(1));
|
||||||
|
*
|
||||||
|
* builder.add_properties()
|
||||||
|
* (L"ClassName", L"FakeETWEventForRealz")
|
||||||
|
* (L"Message", L"This message is completely faked");
|
||||||
|
*
|
||||||
|
* auto record = builder.pack_incomplete();
|
||||||
|
* proxy.push_event(record);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void push_event(const synth_record &record);
|
||||||
|
|
||||||
|
private:
|
||||||
|
krabs::event_filter &event_filter_;
|
||||||
|
krabs::trace_context trace_context_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline event_filter_proxy::event_filter_proxy(krabs::event_filter &event_filter)
|
||||||
|
: event_filter_(event_filter)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void event_filter_proxy::push_event(const synth_record &record)
|
||||||
|
{
|
||||||
|
event_filter_.on_event(record, trace_context_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace testing */ } /* namespace krabs */
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <evntcons.h>
|
||||||
|
#include <WinDef.h>
|
||||||
|
#include <objbase.h>
|
||||||
|
|
||||||
|
// TODO: Remove this #define once Krabs starts using Windows SDK v. 10.0.19041.0 or later.
|
||||||
|
// From evntcons.h starting in Windows SDK v. 10.0.19041.0.
|
||||||
|
#ifndef EVENT_HEADER_EXT_TYPE_CONTAINER_ID
|
||||||
|
#define EVENT_HEADER_EXT_TYPE_CONTAINER_ID 16
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace krabs { namespace testing {
|
||||||
|
class extended_data_builder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Since extended data items have to be packed later, we have to hold onto the data
|
||||||
|
* until we're ready to pack it.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
class extended_data_thunk
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
extended_data_thunk(USHORT ext_type, BYTE* data, size_t data_length);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Intentionally not defined.
|
||||||
|
extended_data_thunk();
|
||||||
|
|
||||||
|
USHORT ext_type_;
|
||||||
|
std::vector<BYTE> bytes_;
|
||||||
|
|
||||||
|
friend class extended_data_builder;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Generates fake packed EVENT_HEADER_EXTENDED_DATA_ITEM structures to later add into test
|
||||||
|
* synth_record objects. These are not guaranteed to be indistinguishable from the real
|
||||||
|
* thing, just good enough to unit test code that reads/interprets extended data.
|
||||||
|
*
|
||||||
|
* Note for testing: this builder just appends extended data structures, it won't stop you
|
||||||
|
* from breaking any API invariants, such as only one of a specific extended data item type.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
class extended_data_builder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static constexpr size_t GUID_STRING_LENGTH_NO_BRACES = 36;
|
||||||
|
static constexpr size_t GUID_STRING_LENGTH_WITH_BRACES = GUID_STRING_LENGTH_NO_BRACES + 2;
|
||||||
|
|
||||||
|
|
||||||
|
extended_data_builder()
|
||||||
|
: items_()
|
||||||
|
{}
|
||||||
|
|
||||||
|
// Mocks a container ID type extended data item.
|
||||||
|
void add_container_id(const GUID& container_id);
|
||||||
|
|
||||||
|
// This generates a contiguous buffer holding all of the data for
|
||||||
|
// the extended data items. Non-trivial because the actual structs
|
||||||
|
// have to be a contiguous array, and they each contain pointers,
|
||||||
|
// not offsets, to dynamically sized data buffers.
|
||||||
|
std::pair<std::shared_ptr<BYTE[]>, size_t> pack() const;
|
||||||
|
|
||||||
|
// Returns the value that should correspond with EVENT_RECORD.ExtendedDataCount
|
||||||
|
inline size_t count() const { return items_.size(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<extended_data_thunk> items_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline extended_data_thunk::extended_data_thunk(USHORT ext_type, BYTE* data, size_t data_length)
|
||||||
|
: ext_type_(ext_type)
|
||||||
|
, bytes_()
|
||||||
|
{
|
||||||
|
bytes_.assign(data, data + data_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void extended_data_builder::add_container_id(const GUID& container_id)
|
||||||
|
{
|
||||||
|
// With null terminator
|
||||||
|
wchar_t wide_guid_buffer[GUID_STRING_LENGTH_WITH_BRACES + 1] = {};
|
||||||
|
|
||||||
|
// No null terminator
|
||||||
|
BYTE guid_data[GUID_STRING_LENGTH_NO_BRACES] = {};
|
||||||
|
|
||||||
|
StringFromGUID2(container_id, wide_guid_buffer, GUID_STRING_LENGTH_WITH_BRACES + 1);
|
||||||
|
|
||||||
|
for (int i = 0; i < GUID_STRING_LENGTH_NO_BRACES; i++)
|
||||||
|
{
|
||||||
|
// Offset by 1 to ignore the wrapping braces.
|
||||||
|
guid_data[i] = static_cast<BYTE>(wide_guid_buffer[i + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
items_.emplace_back(static_cast<USHORT>(EVENT_HEADER_EXT_TYPE_CONTAINER_ID), guid_data, GUID_STRING_LENGTH_NO_BRACES);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::pair<std::shared_ptr<BYTE[]>, size_t> extended_data_builder::pack() const
|
||||||
|
{
|
||||||
|
// Return null for buffer if there are no extended data items.
|
||||||
|
if (items_.size() == 0)
|
||||||
|
{
|
||||||
|
return std::make_pair(std::shared_ptr<BYTE[]>(nullptr), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
BYTE* data_buffer = nullptr;
|
||||||
|
size_t data_buffer_size = 0;
|
||||||
|
|
||||||
|
// Step 1: compute the required buffer size
|
||||||
|
size_t array_part_size = sizeof(EVENT_HEADER_EXTENDED_DATA_ITEM) * items_.size();
|
||||||
|
size_t data_part_size = 0;
|
||||||
|
|
||||||
|
for (const extended_data_thunk& item : items_)
|
||||||
|
{
|
||||||
|
data_part_size += item.bytes_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate the buffer and zero it
|
||||||
|
data_buffer = new BYTE[array_part_size + data_part_size];
|
||||||
|
data_buffer_size = array_part_size + data_part_size;
|
||||||
|
ZeroMemory(data_buffer, data_buffer_size);
|
||||||
|
|
||||||
|
// Step 2: Fill the buffer. For each extended data item, write the object into the buffer at the back.
|
||||||
|
auto array_ptr = reinterpret_cast<EVENT_HEADER_EXTENDED_DATA_ITEM*>(data_buffer);
|
||||||
|
auto data_ptr = data_buffer + array_part_size;
|
||||||
|
|
||||||
|
for (int i = 0; i < items_.size(); i++)
|
||||||
|
{
|
||||||
|
// 2a: write the struct
|
||||||
|
auto& destination = array_ptr[i];
|
||||||
|
const auto& thunk = items_[i];
|
||||||
|
const size_t thunk_size = thunk.bytes_.size();
|
||||||
|
|
||||||
|
destination.ExtType = thunk.ext_type_;
|
||||||
|
destination.DataSize = static_cast<USHORT>(thunk_size);
|
||||||
|
// Assert that the conversion did not truncate thunk_size.
|
||||||
|
assert(static_cast<size_t>(destination.DataSize) == thunk_size);
|
||||||
|
|
||||||
|
// 2b: Write the data
|
||||||
|
assert((data_buffer + data_buffer_size) > data_ptr); // prevent wraparound with unsigned int math
|
||||||
|
size_t remaining = (data_buffer + data_buffer_size) - (data_ptr);
|
||||||
|
// Assert that we will not truncate the data due to not allocating enough space in the buffer.
|
||||||
|
assert(remaining >= thunk_size);
|
||||||
|
// Make sure we rather not copy all of the data than overrun the buffer.
|
||||||
|
memcpy_s(data_ptr, std::min<size_t>(remaining, thunk_size), thunk.bytes_.data(), thunk_size);
|
||||||
|
|
||||||
|
// 2c: point the DataPtr field at the data
|
||||||
|
destination.DataPtr = reinterpret_cast<ULONGLONG>(data_ptr);
|
||||||
|
|
||||||
|
// 2d: increment the pointer for where to write the next piece of data
|
||||||
|
data_ptr += destination.DataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_pair(std::shared_ptr<BYTE[]>(data_buffer), data_buffer_size);
|
||||||
|
}
|
||||||
|
} }
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "../compiler_check.hpp"
|
||||||
|
#include "../parse_types.hpp"
|
||||||
|
|
||||||
|
namespace krabs { namespace testing { namespace details {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Defines how much padding to inject into a synth_record when a property
|
||||||
|
* is not filled by calling code.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
inline size_t how_many_bytes_to_fill(_TDH_IN_TYPE type)
|
||||||
|
{
|
||||||
|
static_assert(sizeof(float) == 4, "sizeof(float) must be 4, defined on MSDN");
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case TDH_INTYPE_NULL: throw std::runtime_error("supposed to be unusued -- something horrible is happening");
|
||||||
|
case TDH_INTYPE_UNICODESTRING: return sizeof(wchar_t);
|
||||||
|
case TDH_INTYPE_ANSISTRING: return sizeof(char);
|
||||||
|
case TDH_INTYPE_INT8: return sizeof(int8_t);
|
||||||
|
case TDH_INTYPE_UINT8: return sizeof(uint8_t);
|
||||||
|
case TDH_INTYPE_INT16: return sizeof(int16_t);
|
||||||
|
case TDH_INTYPE_UINT16: return sizeof(uint16_t);
|
||||||
|
case TDH_INTYPE_INT32: return sizeof(int32_t);
|
||||||
|
case TDH_INTYPE_UINT32: return sizeof(uint32_t);
|
||||||
|
case TDH_INTYPE_INT64: return sizeof(int64_t);
|
||||||
|
case TDH_INTYPE_UINT64: return sizeof(int64_t);
|
||||||
|
case TDH_INTYPE_FLOAT: return sizeof(float);
|
||||||
|
case TDH_INTYPE_DOUBLE: return sizeof(double);
|
||||||
|
case TDH_INTYPE_BOOLEAN: return sizeof(uint32_t); // 4-byte bool, defined on MSDN
|
||||||
|
case TDH_INTYPE_BINARY: return sizeof(char);
|
||||||
|
case TDH_INTYPE_GUID: return sizeof(GUID);
|
||||||
|
case TDH_INTYPE_POINTER: return sizeof(char*);
|
||||||
|
case TDH_INTYPE_FILETIME: return sizeof(FILETIME);
|
||||||
|
case TDH_INTYPE_SYSTEMTIME: return sizeof(SYSTEMTIME);
|
||||||
|
case TDH_INTYPE_SID: return sizeof(PSID);
|
||||||
|
case TDH_INTYPE_HEXINT32: return sizeof(uint32_t);
|
||||||
|
case TDH_INTYPE_HEXINT64: return sizeof(uint64_t);
|
||||||
|
default: break;
|
||||||
|
};
|
||||||
|
|
||||||
|
throw std::runtime_error("Unexpected fill type");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Maps C++ types to TDH types. Used to do runtime type checking of packed
|
||||||
|
* synthetic properties.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct tdh_morphism {
|
||||||
|
// This doesn't have a value field, so compilation will fail when we
|
||||||
|
// try to use a type in our record_builder that isn't recognized.
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct tdh_morphism<T*> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_POINTER;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<std::wstring> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_UNICODESTRING;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<std::string> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_ANSISTRING;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<char> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_INT8;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<unsigned char> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_UINT8;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<short> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_INT16;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<unsigned short> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_UINT16;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<int> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_INT32;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<unsigned int> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_UINT32;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<long long> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_INT64;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<unsigned long long> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_UINT64;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<float> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_FLOAT;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<double> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_DOUBLE;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<bool> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_BOOLEAN;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<GUID> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_GUID;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<krabs::guid> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_GUID;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<FILETIME> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_FILETIME;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<SYSTEMTIME> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_SYSTEMTIME;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<krabs::hexint32> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_HEXINT32;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<krabs::hexint64> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_HEXINT64;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<SID> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_SID;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tdh_morphism<krabs::binary> {
|
||||||
|
static const _TDH_IN_TYPE value = TDH_INTYPE_BINARY;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} /* namespace details */ } /* namespace testing */ } /* namespace krabs */
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define INITGUID
|
||||||
|
|
||||||
|
#include "../compiler_check.hpp"
|
||||||
|
#include "../trace.hpp"
|
||||||
|
#include "../client.hpp"
|
||||||
|
#include "../testing/synth_record.hpp"
|
||||||
|
|
||||||
|
namespace krabs { namespace testing {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Serves as a fill-in for the trace class for testing purposes. It acts
|
||||||
|
* as a liason for the actual trace instance and allows for forced event
|
||||||
|
* testing.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
class trace_proxy {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs a proxy for the given trace.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* krabs::user_trace trace;
|
||||||
|
* krabs::testing::trace_proxy proxy(trace);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
|
||||||
|
trace_proxy(T &trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Mocks starting the underlying trace.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* krabs::user_trace trace;
|
||||||
|
* krabs::testing::trace_proxy proxy(trace);
|
||||||
|
* proxy.start(); // do not call trace.start()
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Pushes an event through to the proxied trace instance.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* This is the primary mechanism for testing providers and their
|
||||||
|
* callbacks. Create a fake event with an record_builder instance
|
||||||
|
* and then push the created synth_record through the object
|
||||||
|
* graph.
|
||||||
|
* </remarks>
|
||||||
|
* <example>
|
||||||
|
* krabs::user_trace trace;
|
||||||
|
* krabs::testing::trace_proxy proxy(trace);
|
||||||
|
* proxy.start(); // do not call trace.start()
|
||||||
|
*
|
||||||
|
* krabs::guid powershell(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* krabs::testing::record_builder builder(powershell, krabs::id(7942), krabs::version(1));
|
||||||
|
*
|
||||||
|
* builder.add_properties()
|
||||||
|
* (L"ClassName", L"FakeETWEventForRealz")
|
||||||
|
* (L"Message", L"This message is completely faked");
|
||||||
|
*
|
||||||
|
* auto record = builder.pack_incomplete();
|
||||||
|
* proxy.push_event(record);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void push_event(const synth_record &record);
|
||||||
|
|
||||||
|
private:
|
||||||
|
T &trace_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>Specific instantiation for user traces.</summary>
|
||||||
|
*/
|
||||||
|
typedef trace_proxy<krabs::user_trace> user_trace_proxy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>Specific instantiation for kernel traces.</summary>
|
||||||
|
*/
|
||||||
|
typedef trace_proxy<krabs::kernel_trace> kernel_trace_proxy;
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
trace_proxy<T>::trace_proxy(T &trace)
|
||||||
|
: trace_(trace)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace_proxy<T>::start()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace_proxy<T>::push_event(const synth_record &record)
|
||||||
|
{
|
||||||
|
trace_.on_event(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace testing */ } /* namespace krabs */
|
||||||
@@ -0,0 +1,373 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define INITGUID
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <sstream>
|
||||||
|
#include <utility>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
#include "../compiler_check.hpp"
|
||||||
|
#include "../guid.hpp"
|
||||||
|
#include "../schema.hpp"
|
||||||
|
#include "../parser.hpp"
|
||||||
|
#include "../tdh_helpers.hpp"
|
||||||
|
#include "filler.hpp"
|
||||||
|
#include "synth_record.hpp"
|
||||||
|
#include "record_property_thunk.hpp"
|
||||||
|
#include "extended_data_builder.hpp"
|
||||||
|
|
||||||
|
namespace krabs { namespace testing {
|
||||||
|
|
||||||
|
class record_builder;
|
||||||
|
|
||||||
|
namespace details {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Provides a convenient syntax for adding properties to a
|
||||||
|
* record_builder.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* Really shouldn't be used by client code. An instance of this should
|
||||||
|
* be handed out by a specific record_builder's `add_properties`
|
||||||
|
* method.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
struct property_adder {
|
||||||
|
public:
|
||||||
|
property_adder(record_builder &builder);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Allows chaining of property addition.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* record_builder builder;
|
||||||
|
* builder.add_properties()
|
||||||
|
* (L"Name", L"Bjarne Stroustrup")
|
||||||
|
* (L"Level", 9001);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
property_adder &operator()(const std::wstring &name, T &&value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
record_builder &builder_;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Enables creation of synthetic events in order to test client code.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* This beast of a class enables the creation of EVENT_RECORD events
|
||||||
|
* for testing. The class accepts a collection of keyed pairs that are
|
||||||
|
* then packed into the event according to the schema on the local
|
||||||
|
* machine. Because a lot of this is Dark Arts kind of stuff, there
|
||||||
|
* really isn't a guarantee that this code works perfectly. Please
|
||||||
|
* file bugs.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
class record_builder {
|
||||||
|
private:
|
||||||
|
public:
|
||||||
|
record_builder(
|
||||||
|
const krabs::guid &providerId,
|
||||||
|
size_t id,
|
||||||
|
size_t version,
|
||||||
|
size_t opcode = 0,
|
||||||
|
size_t level = 0,
|
||||||
|
bool trim_string_null_terminator = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>Enables adding new properties to the builder.</summary>
|
||||||
|
* <example>
|
||||||
|
* record_builder builder;
|
||||||
|
* builder.add_properties()
|
||||||
|
* (L"Name", L"Bjarne Stroustrup")
|
||||||
|
* (L"Level", 9001);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
details::property_adder add_properties();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>Packs the event properties into an EVENT_RECORD.</summary>
|
||||||
|
* <example>
|
||||||
|
* record_builder builder;
|
||||||
|
* builder.add_properties()
|
||||||
|
* (L"Name", L"Bjarne Stroustrup")
|
||||||
|
* (L"Level", 9001);
|
||||||
|
* auto event = builder.pack();
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
krabs::testing::synth_record pack() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Packs the event properties into an EVENT_RECORD, but
|
||||||
|
* doesn't throw when the properties are not complete.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* record_builder builder;
|
||||||
|
* builder.add_properties()
|
||||||
|
* (L"Name", L"Grumpy Gills");
|
||||||
|
* auto event = builder.pack_incomplete();
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
krabs::testing::synth_record pack_incomplete() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Provides access to the properties that have been added.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* record_builder builder;
|
||||||
|
* builder.add_properties()(L"Foo", 10);
|
||||||
|
* for (auto &prop : builder.properties()) {
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
const std::vector<record_property_thunk> &properties() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Adds extended data representing a GUID for an Windows container ID
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
void add_container_id_extended_data(const GUID& container_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Gives direct access to the EVENT_HEADER that will be packed into
|
||||||
|
* the faked record.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
EVENT_HEADER &header();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Fills an EVENT_RECORD with the info necessary to grab its schema
|
||||||
|
* via Tdh.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
EVENT_RECORD create_stub_record() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Does the dirty work of packing up an event record's user data.
|
||||||
|
* </summary>
|
||||||
|
* <returns>
|
||||||
|
* A pair, where the first item is the packed user data and
|
||||||
|
* the second is the properties that were not filled (because the
|
||||||
|
* user never specified them).
|
||||||
|
* </returns>
|
||||||
|
*/
|
||||||
|
std::pair<std::vector<BYTE>, std::vector<std::wstring>>
|
||||||
|
pack_impl(const EVENT_RECORD &record) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const krabs::guid &providerId_;
|
||||||
|
const size_t id_;
|
||||||
|
const size_t version_;
|
||||||
|
const size_t opcode_;
|
||||||
|
const size_t level_;
|
||||||
|
EVENT_HEADER header_;
|
||||||
|
std::vector<record_property_thunk> properties_;
|
||||||
|
bool trim_string_null_terminator_;
|
||||||
|
extended_data_builder extended_data_;
|
||||||
|
|
||||||
|
friend struct details::property_adder;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline details::property_adder::property_adder(record_builder &builder)
|
||||||
|
: builder_(builder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
details::property_adder &details::property_adder::operator()(
|
||||||
|
const std::wstring &name,
|
||||||
|
T &&value)
|
||||||
|
{
|
||||||
|
builder_.properties_.emplace_back(name, value);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline record_builder::record_builder(
|
||||||
|
const krabs::guid &providerId,
|
||||||
|
size_t id,
|
||||||
|
size_t version,
|
||||||
|
size_t opcode,
|
||||||
|
size_t level,
|
||||||
|
bool trim_string_null_terminator)
|
||||||
|
: providerId_(providerId)
|
||||||
|
, id_(id)
|
||||||
|
, version_(version)
|
||||||
|
, opcode_(opcode)
|
||||||
|
, level_(level)
|
||||||
|
, trim_string_null_terminator_(trim_string_null_terminator)
|
||||||
|
{
|
||||||
|
ZeroMemory(&header_, sizeof(EVENT_HEADER));
|
||||||
|
header_.EventDescriptor.Id = static_cast<USHORT>(id_);
|
||||||
|
header_.EventDescriptor.Version = static_cast<UCHAR>(version_);
|
||||||
|
header_.EventDescriptor.Opcode = static_cast<UCHAR>(opcode_);
|
||||||
|
header_.EventDescriptor.Level = static_cast<UCHAR>(level_);
|
||||||
|
memcpy(&header_.ProviderId, (const GUID *)&providerId_, sizeof(GUID));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline EVENT_HEADER &record_builder::header()
|
||||||
|
{
|
||||||
|
return header_;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline details::property_adder record_builder::add_properties()
|
||||||
|
{
|
||||||
|
return details::property_adder(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline synth_record record_builder::pack() const
|
||||||
|
{
|
||||||
|
EVENT_RECORD record = create_stub_record();
|
||||||
|
|
||||||
|
auto results = pack_impl(record);
|
||||||
|
if (!results.second.empty()) {
|
||||||
|
std::string msg = "Not all the properties of the event were filled:";
|
||||||
|
|
||||||
|
for (auto& s : results.second) {
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable: 4244) // narrowing property name wchar_t to char for this error message
|
||||||
|
msg += " " + std::string(s.begin(), s.end());
|
||||||
|
#pragma warning(pop)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::invalid_argument(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a size 0 list, pack() will return (nullptr, 0) and no buffer is allocated.
|
||||||
|
auto extended_data_buffer = extended_data_.pack();
|
||||||
|
record.ExtendedData = reinterpret_cast<EVENT_HEADER_EXTENDED_DATA_ITEM*>(extended_data_buffer.first.get());
|
||||||
|
record.ExtendedDataCount = static_cast<USHORT>(extended_data_.count());
|
||||||
|
|
||||||
|
// Pass shared_ptr of the extended data buffer to make sure the buffer isn't deleted before the synth_record is.
|
||||||
|
return krabs::testing::synth_record(record, results.first, extended_data_buffer.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline synth_record record_builder::pack_incomplete() const
|
||||||
|
{
|
||||||
|
EVENT_RECORD record = create_stub_record();
|
||||||
|
auto results = pack_impl(record);
|
||||||
|
|
||||||
|
// If it's a size 0 list, pack() will return (nullptr, 0) and no buffer is allocated.
|
||||||
|
auto extended_data_buffer = extended_data_.pack();
|
||||||
|
record.ExtendedData = reinterpret_cast<EVENT_HEADER_EXTENDED_DATA_ITEM*>(extended_data_buffer.first.get());
|
||||||
|
record.ExtendedDataCount = static_cast<USHORT>(extended_data_.count());
|
||||||
|
|
||||||
|
// Pass shared_ptr of the extended data buffer to make sure the buffer isn't deleted before the synth_record is.
|
||||||
|
return krabs::testing::synth_record(record, results.first, extended_data_buffer.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline EVENT_RECORD record_builder::create_stub_record() const
|
||||||
|
{
|
||||||
|
EVENT_RECORD record = {0};
|
||||||
|
memcpy(&record.EventHeader, &header_, sizeof(EVENT_HEADER));
|
||||||
|
if (record.EventHeader.Size == 0) {
|
||||||
|
record.EventHeader.Size = sizeof(record.EventHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const std::vector<record_property_thunk> &
|
||||||
|
record_builder::properties() const
|
||||||
|
{
|
||||||
|
return properties_;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void record_builder::add_container_id_extended_data(const GUID& container_id)
|
||||||
|
{
|
||||||
|
extended_data_.add_container_id(container_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::pair<std::vector<BYTE>, std::vector<std::wstring>>
|
||||||
|
record_builder::pack_impl(const EVENT_RECORD &record) const
|
||||||
|
{
|
||||||
|
std::pair<std::vector<BYTE>, std::vector<std::wstring>> results;
|
||||||
|
krabs::schema_locator schema_locator;
|
||||||
|
krabs::schema event_schema(record, schema_locator);
|
||||||
|
krabs::parser event_parser(event_schema);
|
||||||
|
|
||||||
|
// When the last property in a record is of string type (ansi or unicode),
|
||||||
|
// ETW may omit the string NULL terminator. bytes_to_trim below will eventually be
|
||||||
|
// set to the number of bytes that can be trimmed from the generated buffer.
|
||||||
|
|
||||||
|
auto bytes_to_trim = 0;
|
||||||
|
for (auto prop : event_parser.properties()) {
|
||||||
|
bytes_to_trim = 0;
|
||||||
|
|
||||||
|
auto found_prop = std::find_if(properties_.begin(),
|
||||||
|
properties_.end(),
|
||||||
|
[&](const record_property_thunk &thunk) {
|
||||||
|
return prop.name() == thunk.name();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (found_prop != properties_.end()) {
|
||||||
|
|
||||||
|
// Verify that the user-provided property data matches the type
|
||||||
|
// that the schema expects.
|
||||||
|
if (found_prop->type() != prop.type()) {
|
||||||
|
std::string ansi(prop.name().begin(), prop.name().end());
|
||||||
|
auto msg = std::string(
|
||||||
|
"Invalid property type given for property " + ansi +
|
||||||
|
" Expected: " + krabs::in_type_to_string(prop.type()) +
|
||||||
|
" Received: " + krabs::in_type_to_string(found_prop->type()));
|
||||||
|
|
||||||
|
throw std::invalid_argument(msg.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this is a string type, we could trim the null terminator
|
||||||
|
// (assuming that there are no other properties after this one)
|
||||||
|
if (prop.type() == TDH_INTYPE_UNICODESTRING) {
|
||||||
|
bytes_to_trim = sizeof(L'\0');
|
||||||
|
}
|
||||||
|
else if (prop.type() == TDH_INTYPE_ANSISTRING) {
|
||||||
|
bytes_to_trim = sizeof('\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
std::copy(found_prop->bytes().begin(),
|
||||||
|
found_prop->bytes().end(),
|
||||||
|
std::back_inserter(results.first));
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// If the property wasn't filled by the user's tests, we fill
|
||||||
|
// it with empty data that is the size that is expected
|
||||||
|
// according to the schema. We also remember these properties,
|
||||||
|
// because it may be considered an error to not fill all
|
||||||
|
// properties manually.
|
||||||
|
results.second.emplace_back(prop.name());
|
||||||
|
std::fill_n(std::back_inserter(results.first),
|
||||||
|
details::how_many_bytes_to_fill(prop.type()), static_cast<UCHAR>(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trim_string_null_terminator_) {
|
||||||
|
results.first.resize(results.first.size() - bytes_to_trim);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
} /* namespace testing */ } /* namespace krabs */
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../compiler_check.hpp"
|
||||||
|
#include "filler.hpp"
|
||||||
|
|
||||||
|
namespace krabs { namespace testing {
|
||||||
|
|
||||||
|
class record_builder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Takes any value and turns it into a sequence of serialized bytes.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* When we're composing an event, we need to store heterogeneous types in
|
||||||
|
* a collection while we wait until we know exactly how to pack the actual
|
||||||
|
* event. Because the actual EVENT_RECORD structure properties are packed
|
||||||
|
* into a byte collection, we take our cue from that and do similarly. We
|
||||||
|
* keep all of the random property byte blobs separate until we know the
|
||||||
|
* particular order to stash them in so we have less futzing to do later.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
class record_property_thunk {
|
||||||
|
public:
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
record_property_thunk(const std::wstring &property, const T &value);
|
||||||
|
|
||||||
|
record_property_thunk(const std::wstring &property, const wchar_t *value);
|
||||||
|
record_property_thunk(const std::wstring &property, const char *value);
|
||||||
|
record_property_thunk(const std::wstring &property, bool value);
|
||||||
|
|
||||||
|
const std::wstring &name() const;
|
||||||
|
const std::vector<BYTE> &bytes() const;
|
||||||
|
const _TDH_IN_TYPE type() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// We need this because we don't have delegating constructors in VS 2012.
|
||||||
|
template <typename T>
|
||||||
|
void common_string_init(const std::wstring &property, const T &value);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void common_init(const std::wstring &property, const T &value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::wstring name_;
|
||||||
|
std::vector<BYTE> bytes_;
|
||||||
|
_TDH_IN_TYPE type_;
|
||||||
|
|
||||||
|
friend class record_builder;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
template <typename T>
|
||||||
|
inline record_property_thunk::record_property_thunk(
|
||||||
|
const std::wstring &property,
|
||||||
|
const T &value)
|
||||||
|
{
|
||||||
|
common_init(property, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specialization for wstrings
|
||||||
|
template <>
|
||||||
|
inline record_property_thunk::record_property_thunk(
|
||||||
|
const std::wstring &property,
|
||||||
|
const std::wstring &value)
|
||||||
|
{
|
||||||
|
common_string_init(property, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specialization for strings
|
||||||
|
template <>
|
||||||
|
inline record_property_thunk::record_property_thunk(
|
||||||
|
const std::wstring &property,
|
||||||
|
const std::string &value)
|
||||||
|
{
|
||||||
|
common_string_init(property, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overload for wchar_t strings.
|
||||||
|
inline record_property_thunk::record_property_thunk(
|
||||||
|
const std::wstring &property,
|
||||||
|
const wchar_t *value)
|
||||||
|
{
|
||||||
|
common_string_init(property, std::move(std::wstring(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overload for char strings.
|
||||||
|
inline record_property_thunk::record_property_thunk(
|
||||||
|
const std::wstring &property,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
common_string_init(property, std::move(std::string(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specialization for binary blobs
|
||||||
|
template <>
|
||||||
|
inline record_property_thunk::record_property_thunk(
|
||||||
|
const std::wstring &property,
|
||||||
|
const krabs::binary &bin)
|
||||||
|
: name_(property)
|
||||||
|
, bytes_(bin.bytes())
|
||||||
|
, type_(krabs::testing::details::tdh_morphism<krabs::binary>::value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overload for booleans
|
||||||
|
inline record_property_thunk::record_property_thunk(
|
||||||
|
const std::wstring &property,
|
||||||
|
bool value)
|
||||||
|
{
|
||||||
|
common_init(property, (int)value);
|
||||||
|
type_ = krabs::testing::details::tdh_morphism<bool>::value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void record_property_thunk::common_init(
|
||||||
|
const std::wstring &property,
|
||||||
|
const T &value)
|
||||||
|
{
|
||||||
|
name_ = property;
|
||||||
|
bytes_ = std::move(std::vector<BYTE>((BYTE*)&value, (BYTE*)&value + sizeof(T)));
|
||||||
|
type_ = krabs::testing::details::tdh_morphism<T>::value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void record_property_thunk::common_string_init(
|
||||||
|
const std::wstring &property,
|
||||||
|
const T &value)
|
||||||
|
{
|
||||||
|
name_ = property;
|
||||||
|
|
||||||
|
const size_t size = value.size() * sizeof(typename T::value_type);
|
||||||
|
bytes_ = std::move(std::vector<BYTE>((BYTE*)&value[0], (BYTE*)&value[0] + size));
|
||||||
|
type_ = krabs::testing::details::tdh_morphism<T>::value;
|
||||||
|
|
||||||
|
// Null terminate the string
|
||||||
|
for (size_t i = 0; i < sizeof(typename T::value_type); ++i) {
|
||||||
|
bytes_.push_back('\0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const std::wstring &record_property_thunk::name() const
|
||||||
|
{
|
||||||
|
return name_;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const std::vector<BYTE> &record_property_thunk::bytes() const
|
||||||
|
{
|
||||||
|
return bytes_;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const _TDH_IN_TYPE record_property_thunk::type() const
|
||||||
|
{
|
||||||
|
return type_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace testing */ } /* namespace krabs */
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define INITGUID
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "../compiler_check.hpp"
|
||||||
|
#include "../guid.hpp"
|
||||||
|
#include "../schema.hpp"
|
||||||
|
#include "../parser.hpp"
|
||||||
|
|
||||||
|
#include <evntrace.h>
|
||||||
|
#include <evntcons.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace krabs { namespace testing {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Represents a property that is faked -- one that is built by hand for the
|
||||||
|
* purpose of testing event reaction code.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
class synth_record {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs a synthetic property, given a partially filled
|
||||||
|
* EVENT_RECORD and a packed sequence of bytes that represent the
|
||||||
|
* event's user data.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* This class should not be directly instantiated -- an record_builder
|
||||||
|
* should return this with its `pack` methods.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
synth_record(const EVENT_RECORD& record,
|
||||||
|
const std::vector<BYTE>& user_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs a synthetic property, given a partially filled
|
||||||
|
* EVENT_RECORD and a packed sequence of bytes that represent the
|
||||||
|
* event's user data.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* This class should not be directly instantiated -- an record_builder
|
||||||
|
* should return this with its `pack` methods.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
synth_record(const EVENT_RECORD &record,
|
||||||
|
const std::vector<BYTE> &user_data,
|
||||||
|
const std::shared_ptr<BYTE[]> &extended_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Copies a synth_record and updates the pointers
|
||||||
|
* in the EVENT_RECORD appropriately.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
synth_record(const synth_record& other);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Moves a synth_record into a new instance.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
synth_record(synth_record&& other);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Assigns a synth_record to another.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>by value to take advantage of move ctor</remarks>
|
||||||
|
*/
|
||||||
|
synth_record& operator=(synth_record);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Allows implicit casts to an EVENT_RECORD.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
operator const EVENT_RECORD&() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Swaps two synth_records.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
friend void swap(synth_record& left, synth_record& right)
|
||||||
|
{
|
||||||
|
using std::swap; // ADL
|
||||||
|
|
||||||
|
swap(left.record_, right.record_);
|
||||||
|
swap(left.data_, right.data_);
|
||||||
|
swap(left.extended_data_, right.extended_data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
synth_record()
|
||||||
|
: record_()
|
||||||
|
, data_() { }
|
||||||
|
|
||||||
|
EVENT_RECORD record_;
|
||||||
|
std::vector<BYTE> data_;
|
||||||
|
|
||||||
|
// extended_data shared PTR is passed around to make sure that the data
|
||||||
|
// buffer is only deleted after all dependent synth_records are deleted.
|
||||||
|
// since the extended data structure uses direct pointers to data
|
||||||
|
// instead of offsets, we can't pass around a vector<BYTE> unless we
|
||||||
|
// also want to redo the pointers every time the buffer is copied.
|
||||||
|
std::shared_ptr<BYTE[]> extended_data_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline synth_record::synth_record(const EVENT_RECORD& record,
|
||||||
|
const std::vector<BYTE>& user_data)
|
||||||
|
: synth_record(record, user_data, std::shared_ptr<BYTE[]>())
|
||||||
|
{
|
||||||
|
// Empty shared_ptr is fine here because there's no concern
|
||||||
|
// about managing lifetime of an extended data buffer if there
|
||||||
|
// is no extended data buffer.
|
||||||
|
}
|
||||||
|
|
||||||
|
inline synth_record::synth_record(
|
||||||
|
const EVENT_RECORD &record,
|
||||||
|
const std::vector<BYTE> &user_data,
|
||||||
|
const std::shared_ptr<BYTE[]> &extended_data)
|
||||||
|
: record_(record)
|
||||||
|
, data_(user_data)
|
||||||
|
, extended_data_(extended_data)
|
||||||
|
{
|
||||||
|
if (data_.size() > 0) {
|
||||||
|
record_.UserData = &data_[0];
|
||||||
|
} else {
|
||||||
|
record_.UserData = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
record_.UserDataLength = static_cast<USHORT>(data_.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline synth_record::synth_record(const synth_record& other)
|
||||||
|
: synth_record(other.record_, other.data_, other.extended_data_)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
inline synth_record::synth_record(synth_record&& other)
|
||||||
|
: synth_record()
|
||||||
|
{
|
||||||
|
swap(*this, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline synth_record& synth_record::operator=(synth_record other)
|
||||||
|
{
|
||||||
|
swap(*this, other);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline synth_record::operator const EVENT_RECORD&() const
|
||||||
|
{
|
||||||
|
return record_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace testing */ } /* namespace krabs */
|
||||||
@@ -0,0 +1,446 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
#include "guid.hpp"
|
||||||
|
#include "provider.hpp"
|
||||||
|
#include "trace_context.hpp"
|
||||||
|
#include "etw.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
namespace krabs { namespace details {
|
||||||
|
template <typename T> class trace_manager;
|
||||||
|
} /* namespace details */ } /* namespace krabs */
|
||||||
|
|
||||||
|
namespace krabs { namespace testing {
|
||||||
|
template <typename T> class trace_proxy;
|
||||||
|
} /* namespace testing */ } /* namespace krabs */
|
||||||
|
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Selected statistics about an ETW trace
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
class trace_stats
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
const uint32_t buffersCount;
|
||||||
|
const uint32_t buffersFree;
|
||||||
|
const uint32_t buffersWritten;
|
||||||
|
const uint32_t buffersLost;
|
||||||
|
const uint64_t eventsTotal;
|
||||||
|
const uint64_t eventsHandled;
|
||||||
|
const uint32_t eventsLost;
|
||||||
|
|
||||||
|
trace_stats(uint64_t eventsHandled, const EVENT_TRACE_PROPERTIES& props)
|
||||||
|
: buffersCount(props.NumberOfBuffers)
|
||||||
|
, buffersFree(props.FreeBuffers)
|
||||||
|
, buffersWritten(props.BuffersWritten)
|
||||||
|
, buffersLost(props.RealTimeBuffersLost)
|
||||||
|
, eventsTotal(eventsHandled + props.EventsLost)
|
||||||
|
, eventsHandled(eventsHandled)
|
||||||
|
, eventsLost(props.EventsLost)
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Represents a single trace session that can have multiple
|
||||||
|
* enabled providers. Ideally, there should only need to be a
|
||||||
|
* single trace instance for all ETW user traces.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
class trace {
|
||||||
|
public:
|
||||||
|
|
||||||
|
typedef T trace_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Constructs a trace with an optional trace name, which can be
|
||||||
|
* any arbitrary, unique name.
|
||||||
|
* </summary>
|
||||||
|
*
|
||||||
|
* <example>
|
||||||
|
* trace trace;
|
||||||
|
* trace namedTrace(L"Some special name");
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
trace(const std::wstring &name);
|
||||||
|
trace(const wchar_t *name = L"");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Destructs the trace session and unregisters the session, if
|
||||||
|
* applicable.
|
||||||
|
* </summary>
|
||||||
|
*
|
||||||
|
* <example>
|
||||||
|
* trace trace;
|
||||||
|
* // ~trace implicitly called
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
~trace();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Sets the trace properties for a session.
|
||||||
|
* Must be called before open()/start().
|
||||||
|
* See https://docs.microsoft.com/en-us/windows/win32/etw/event-trace-properties
|
||||||
|
* for important details and restrictions.
|
||||||
|
* Configurable properties are ->
|
||||||
|
* - BufferSize. In KB. The maximum buffer size is 1024 KB.
|
||||||
|
* - MinimumBuffers. Minimum number of buffers is two per processor*.
|
||||||
|
* - MaximumBuffers.
|
||||||
|
* - FlushTimer. How often, in seconds, the trace buffers are forcibly flushed.
|
||||||
|
* - LogFileMode. EVENT_TRACE_NO_PER_PROCESSOR_BUFFERING simulates a *single* sequential processor.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* krabs::trace trace;
|
||||||
|
* EVENT_TRACE_PROPERTIES properties = { 0 };
|
||||||
|
* properties.BufferSize = 256;
|
||||||
|
* properties.MinimumBuffers = 12;
|
||||||
|
* properties.MaximumBuffers = 48;
|
||||||
|
* properties.FlushTimer = 1;
|
||||||
|
* properties.LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
|
||||||
|
* trace.set_trace_properties(&properties);
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* provider<> powershell(id);
|
||||||
|
* trace.enable(powershell);
|
||||||
|
* trace.start();
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void set_trace_properties(const PEVENT_TRACE_PROPERTIES properties);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Configures trace session settings.
|
||||||
|
* Must be called after open().
|
||||||
|
* See https://docs.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-tracesetinformation
|
||||||
|
* for more information.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* krabs::trace trace;
|
||||||
|
* // Adjust SE_SYSTEM_PROFILE_NAME token privilege through AdjustTokenPrivileges(...)
|
||||||
|
* // to enable stack tracing (not done in this example). Then:
|
||||||
|
* STACK_TRACING_EVENT_ID event_id = {0};
|
||||||
|
* event_id.EventGuid = krabs::guids::perf_info;
|
||||||
|
* event_id.Type = 46; // SampleProfile
|
||||||
|
* trace.open();
|
||||||
|
* trace.set_trace_information(TraceStackTracingInfo, &event_id, sizeof(STACK_TRACING_EVENT_ID));
|
||||||
|
* krabs::kernel_provider stack_walk_provider(EVENT_TRACE_FLAG_PROFILE, krabs::guids::stack_walk);
|
||||||
|
* trace.enable(stack_walk_provider);
|
||||||
|
* trace.process();
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void set_trace_information(
|
||||||
|
TRACE_INFO_CLASS information_class,
|
||||||
|
PVOID trace_information,
|
||||||
|
ULONG information_length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Configures trace to read from a file instead of realtime
|
||||||
|
* Must be called before open().
|
||||||
|
* See https://docs.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-tracesetinformation
|
||||||
|
* for more information.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* krabs::trace trace;
|
||||||
|
* trace.set_trace_filename(L"C:\merged.etl");
|
||||||
|
* trace.process();
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void set_trace_filename(const std::wstring& filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Enables the provider on the given user trace.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* krabs::trace trace;
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* provider<> powershell(id);
|
||||||
|
* trace.enable(powershell);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void enable(const typename T::provider_type &p);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Starts a trace session.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* krabs::trace trace;
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* provider<> powershell(id);
|
||||||
|
* trace.enable(powershell);
|
||||||
|
* trace.start();
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Closes a trace session.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* krabs::trace trace;
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* provider<> powershell(id);
|
||||||
|
* trace.enable(powershell);
|
||||||
|
* trace.start();
|
||||||
|
* trace.stop();
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Opens a trace session.
|
||||||
|
* This is an optional call before start() if you need the trace
|
||||||
|
* registered with the ETW subsystem before you start processing events.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* krabs::trace trace;
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* provider<> powershell(id);
|
||||||
|
* trace.enable(powershell);
|
||||||
|
* auto logfile = trace.open();
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
EVENT_TRACE_LOGFILE open();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Start processing events for an already opened session.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* krabs::trace trace;
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* provider<> powershell(id);
|
||||||
|
* trace.enable(powershell);
|
||||||
|
* trace.open();
|
||||||
|
* trace.process();
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void process();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Queries the trace session to get stats about
|
||||||
|
* events lost and buffers handled.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
trace_stats query_stats();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns the number of buffers that were processed.
|
||||||
|
* </summary>
|
||||||
|
* <example>
|
||||||
|
* krabs::trace trace;
|
||||||
|
* krabs::guid id(L"{A0C1853B-5C40-4B15-8766-3CF1C58F985A}");
|
||||||
|
* provider<> powershell(id);
|
||||||
|
* trace.enable(powershell);
|
||||||
|
* trace.start();
|
||||||
|
* trace.stop();
|
||||||
|
* std::wcout << trace.buffers_processed() << std::endl;
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
size_t buffers_processed() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Adds a function to call when an event is fired which has no corresponding provider.
|
||||||
|
* </summary>
|
||||||
|
*
|
||||||
|
* <param name="callback">the function to call into</param>
|
||||||
|
* <example>
|
||||||
|
* void my_fun(const EVENT_RECORD &record) { ... }
|
||||||
|
* // ...
|
||||||
|
* krabs::trace trace;
|
||||||
|
* trace.set_default_event_callback(my_fun);
|
||||||
|
* </example>
|
||||||
|
*
|
||||||
|
* <example>
|
||||||
|
* auto fun = [&](const EVENT_RECORD &record) {...}
|
||||||
|
* krabs::trace trace;
|
||||||
|
* trace.set_default_event_callback(fun);
|
||||||
|
* </example>
|
||||||
|
*/
|
||||||
|
void set_default_event_callback(c_provider_callback callback);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Invoked when an event occurs in the underlying ETW session.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
void on_event(const EVENT_RECORD &);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::wstring name_;
|
||||||
|
std::wstring logFilename_;
|
||||||
|
std::deque<std::reference_wrapper<const typename T::provider_type>> providers_;
|
||||||
|
|
||||||
|
TRACEHANDLE registrationHandle_;
|
||||||
|
TRACEHANDLE sessionHandle_;
|
||||||
|
|
||||||
|
size_t buffersRead_;
|
||||||
|
uint64_t eventsHandled_;
|
||||||
|
|
||||||
|
EVENT_TRACE_PROPERTIES properties_;
|
||||||
|
|
||||||
|
const trace_context context_;
|
||||||
|
|
||||||
|
provider_callback default_callback_ = nullptr;
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename T>
|
||||||
|
friend class details::trace_manager;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
friend class testing::trace_proxy;
|
||||||
|
|
||||||
|
friend typename T;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
trace<T>::trace(const std::wstring &name)
|
||||||
|
: registrationHandle_(INVALID_PROCESSTRACE_HANDLE)
|
||||||
|
, sessionHandle_(INVALID_PROCESSTRACE_HANDLE)
|
||||||
|
, eventsHandled_(0)
|
||||||
|
, buffersRead_(0)
|
||||||
|
, context_()
|
||||||
|
{
|
||||||
|
name_ = T::enforce_name_policy(name);
|
||||||
|
ZeroMemory(&properties_, sizeof(EVENT_TRACE_PROPERTIES));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
trace<T>::trace(const wchar_t *name)
|
||||||
|
: registrationHandle_(INVALID_PROCESSTRACE_HANDLE)
|
||||||
|
, sessionHandle_(INVALID_PROCESSTRACE_HANDLE)
|
||||||
|
, eventsHandled_(0)
|
||||||
|
, buffersRead_(0)
|
||||||
|
, context_()
|
||||||
|
{
|
||||||
|
name_ = T::enforce_name_policy(name);
|
||||||
|
ZeroMemory(&properties_, sizeof(EVENT_TRACE_PROPERTIES));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
trace<T>::~trace()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace<T>::set_trace_properties(const PEVENT_TRACE_PROPERTIES properties)
|
||||||
|
{
|
||||||
|
properties_.BufferSize = properties->BufferSize;
|
||||||
|
properties_.MinimumBuffers = properties->MinimumBuffers;
|
||||||
|
properties_.MaximumBuffers = properties->MaximumBuffers;
|
||||||
|
properties_.FlushTimer = properties->FlushTimer;
|
||||||
|
properties_.LogFileMode = properties->LogFileMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace<T>::set_trace_information(
|
||||||
|
TRACE_INFO_CLASS information_class,
|
||||||
|
PVOID trace_information,
|
||||||
|
ULONG information_length)
|
||||||
|
{
|
||||||
|
details::trace_manager<trace> manager(*this);
|
||||||
|
manager.set_trace_information(information_class, trace_information, information_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace<T>::set_trace_filename(const std::wstring& filename)
|
||||||
|
{
|
||||||
|
logFilename_ = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace<T>::on_event(const EVENT_RECORD &record)
|
||||||
|
{
|
||||||
|
++eventsHandled_;
|
||||||
|
T::forward_events(record, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace<T>::enable(const typename T::provider_type &p)
|
||||||
|
{
|
||||||
|
providers_.push_back(std::ref(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace<T>::start()
|
||||||
|
{
|
||||||
|
eventsHandled_ = 0;
|
||||||
|
|
||||||
|
details::trace_manager<trace> manager(*this);
|
||||||
|
manager.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace<T>::stop()
|
||||||
|
{
|
||||||
|
details::trace_manager<trace> manager(*this);
|
||||||
|
manager.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
EVENT_TRACE_LOGFILE trace<T>::open()
|
||||||
|
{
|
||||||
|
eventsHandled_ = 0;
|
||||||
|
|
||||||
|
details::trace_manager<trace> manager(*this);
|
||||||
|
return manager.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace<T>::process()
|
||||||
|
{
|
||||||
|
eventsHandled_ = 0;
|
||||||
|
|
||||||
|
details::trace_manager<trace> manager(*this);
|
||||||
|
manager.process();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
trace_stats trace<T>::query_stats()
|
||||||
|
{
|
||||||
|
details::trace_manager<trace> manager(*this);
|
||||||
|
return { eventsHandled_, manager.query() };
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
size_t trace<T>::buffers_processed() const
|
||||||
|
{
|
||||||
|
return buffersRead_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void trace<T>::set_default_event_callback(c_provider_callback callback)
|
||||||
|
{
|
||||||
|
default_callback_ = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "schema_locator.hpp"
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Additional ETW trace context passed to event callbacks
|
||||||
|
* to enable processing.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct trace_context
|
||||||
|
{
|
||||||
|
const schema_locator schema_locator;
|
||||||
|
/* Add additional trace context here. */
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "compiler_check.hpp"
|
||||||
|
#include "trace.hpp"
|
||||||
|
#include "provider.hpp"
|
||||||
|
|
||||||
|
namespace krabs { namespace details {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Used as a template argument to a trace instance. This class implements
|
||||||
|
* code paths for user traces. Should never be used or seen by client
|
||||||
|
* code.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
struct ut {
|
||||||
|
|
||||||
|
typedef krabs::provider<> provider_type;
|
||||||
|
|
||||||
|
struct filter_flags {
|
||||||
|
UCHAR level_;
|
||||||
|
ULONGLONG any_;
|
||||||
|
ULONGLONG all_;
|
||||||
|
ULONG trace_flags_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct filter_settings{
|
||||||
|
std::set<unsigned short> provider_filter_event_ids_;
|
||||||
|
filter_flags filter_flags_{};
|
||||||
|
bool rundown_enabled_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::map<krabs::guid, filter_settings> provider_filter_settings;
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Used to assign a name to the trace instance that is being
|
||||||
|
* instantiated.
|
||||||
|
* </summary>
|
||||||
|
* <remarks>
|
||||||
|
* There really isn't a name policy to enforce with user traces, but
|
||||||
|
* kernel traces do have specific naming requirements.
|
||||||
|
* </remarks>
|
||||||
|
*/
|
||||||
|
static const std::wstring enforce_name_policy(
|
||||||
|
const std::wstring &name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Generates a value that fills the EnableFlags field in an
|
||||||
|
* EVENT_TRACE_PROPERTIES structure. This controls the providers that
|
||||||
|
* get enabled for a kernel trace. For a user trace, it doesn't do
|
||||||
|
* much of anything.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
static const unsigned long construct_enable_flags(
|
||||||
|
const krabs::trace<krabs::details::ut> &trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Enables the providers that are attached to the given trace.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
static void enable_providers(
|
||||||
|
const krabs::trace<krabs::details::ut> &trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Enables the configured rundown events for each provider.
|
||||||
|
* Should be called immediately prior to ProcessTrace.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
static void enable_rundown(
|
||||||
|
const krabs::trace<krabs::details::ut>& trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Decides to forward an event to any of the providers in the trace.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
static void forward_events(
|
||||||
|
const EVENT_RECORD &record,
|
||||||
|
const krabs::trace<krabs::details::ut> &trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Sets the ETW trace log file mode.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
static unsigned long augment_file_mode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <summary>
|
||||||
|
* Returns the GUID of the trace session.
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
static krabs::guid get_trace_guid();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Implementation
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline const std::wstring ut::enforce_name_policy(
|
||||||
|
const std::wstring &name_hint)
|
||||||
|
{
|
||||||
|
if (name_hint.empty()) {
|
||||||
|
return std::to_wstring(krabs::guid::random_guid());
|
||||||
|
}
|
||||||
|
|
||||||
|
return name_hint;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const unsigned long ut::construct_enable_flags(
|
||||||
|
const krabs::trace<krabs::details::ut> &)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ut::enable_providers(
|
||||||
|
const krabs::trace<krabs::details::ut> &trace)
|
||||||
|
{
|
||||||
|
if (trace.registrationHandle_ == INVALID_PROCESSTRACE_HANDLE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
provider_filter_settings provider_flags;
|
||||||
|
|
||||||
|
// This function essentially takes the union of all the provider flags
|
||||||
|
// for a given provider GUID. This comes about when multiple providers
|
||||||
|
// for the same GUID are provided and request different provider flags.
|
||||||
|
// TODO: Only forward the calls that are requested to each provider.
|
||||||
|
for (auto &provider : trace.providers_) {
|
||||||
|
auto& settings = provider_flags[provider.get().guid_];
|
||||||
|
settings.filter_flags_.level_ |= provider.get().level_;
|
||||||
|
settings.filter_flags_.any_ |= provider.get().any_;
|
||||||
|
settings.filter_flags_.all_ |= provider.get().all_;
|
||||||
|
settings.filter_flags_.trace_flags_ |= provider.get().trace_flags_;
|
||||||
|
settings.rundown_enabled_ |= provider.get().rundown_enabled_;
|
||||||
|
|
||||||
|
for (const auto& filter : provider.get().filters_) {
|
||||||
|
settings.provider_filter_event_ids_.insert(
|
||||||
|
filter.provider_filter_event_ids().begin(),
|
||||||
|
filter.provider_filter_event_ids().end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &provider : provider_flags) {
|
||||||
|
ENABLE_TRACE_PARAMETERS parameters;
|
||||||
|
parameters.ControlFlags = 0;
|
||||||
|
parameters.Version = ENABLE_TRACE_PARAMETERS_VERSION_2;
|
||||||
|
parameters.SourceId = provider.first;
|
||||||
|
|
||||||
|
GUID guid = provider.first;
|
||||||
|
auto& settings = provider.second;
|
||||||
|
|
||||||
|
parameters.EnableProperty = settings.filter_flags_.trace_flags_;
|
||||||
|
parameters.EnableFilterDesc = nullptr;
|
||||||
|
parameters.FilterDescCount = 0;
|
||||||
|
EVENT_FILTER_DESCRIPTOR filterDesc{};
|
||||||
|
std::vector<BYTE> filterEventIdBuffer;
|
||||||
|
auto filterEventIdCount = settings.provider_filter_event_ids_.size();
|
||||||
|
|
||||||
|
if (filterEventIdCount > 0) {
|
||||||
|
//event filters existing, set native filters using API
|
||||||
|
parameters.FilterDescCount = 1;
|
||||||
|
filterDesc.Type = EVENT_FILTER_TYPE_EVENT_ID;
|
||||||
|
|
||||||
|
//allocate + size of expected events in filter
|
||||||
|
DWORD size = FIELD_OFFSET(EVENT_FILTER_EVENT_ID, Events[filterEventIdCount]);
|
||||||
|
filterEventIdBuffer.resize(size, 0);
|
||||||
|
|
||||||
|
auto filterEventIds = reinterpret_cast<PEVENT_FILTER_EVENT_ID>(&(filterEventIdBuffer[0]));
|
||||||
|
filterEventIds->FilterIn = TRUE;
|
||||||
|
filterEventIds->Count = static_cast<USHORT>(filterEventIdCount);
|
||||||
|
|
||||||
|
auto index = 0;
|
||||||
|
for (auto filter : settings.provider_filter_event_ids_) {
|
||||||
|
filterEventIds->Events[index] = filter;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
filterDesc.Ptr = reinterpret_cast<ULONGLONG>(filterEventIds);
|
||||||
|
filterDesc.Size = size;
|
||||||
|
|
||||||
|
parameters.EnableFilterDesc = &filterDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
ULONG status = EnableTraceEx2(trace.registrationHandle_,
|
||||||
|
&guid,
|
||||||
|
EVENT_CONTROL_CODE_ENABLE_PROVIDER,
|
||||||
|
settings.filter_flags_.level_,
|
||||||
|
settings.filter_flags_.any_,
|
||||||
|
settings.filter_flags_.all_,
|
||||||
|
0,
|
||||||
|
¶meters);
|
||||||
|
error_check_common_conditions(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ut::enable_rundown(
|
||||||
|
const krabs::trace<krabs::details::ut>& trace)
|
||||||
|
{
|
||||||
|
if (trace.registrationHandle_ == INVALID_PROCESSTRACE_HANDLE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (auto& provider : trace.providers_) {
|
||||||
|
if (!provider.get().rundown_enabled_)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ULONG status = EnableTraceEx2(trace.registrationHandle_,
|
||||||
|
&provider.get().guid_,
|
||||||
|
EVENT_CONTROL_CODE_CAPTURE_STATE,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
NULL);
|
||||||
|
error_check_common_conditions(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ut::forward_events(
|
||||||
|
const EVENT_RECORD &record,
|
||||||
|
const krabs::trace<krabs::details::ut> &trace)
|
||||||
|
{
|
||||||
|
// for manifest providers, EventHeader.ProviderId is the Provider GUID
|
||||||
|
for (auto& provider : trace.providers_) {
|
||||||
|
if (record.EventHeader.ProviderId == provider.get().guid_) {
|
||||||
|
provider.get().on_event(record, trace.context_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for MOF providers, EventHeader.Provider is the *Message* GUID
|
||||||
|
// we need to ask TDH for event information in order to determine the
|
||||||
|
// correct provider to pass this event to
|
||||||
|
auto schema = get_event_schema_from_tdh(record);
|
||||||
|
auto eventInfo = reinterpret_cast<PTRACE_EVENT_INFO>(schema.get());
|
||||||
|
for (auto& provider : trace.providers_) {
|
||||||
|
if (eventInfo->ProviderGuid == provider.get().guid_) {
|
||||||
|
provider.get().on_event(record, trace.context_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trace.default_callback_ != nullptr)
|
||||||
|
trace.default_callback_(record, trace.context_);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned long ut::augment_file_mode()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline krabs::guid ut::get_trace_guid()
|
||||||
|
{
|
||||||
|
return krabs::guid::random_guid();
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace details */ } /* namespace krabs */
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
// We manually include this file because it doesn't exist for VS2012.
|
||||||
|
|
||||||
|
#ifndef _versionhelpers_H_INCLUDED_
|
||||||
|
#define _versionhelpers_H_INCLUDED_
|
||||||
|
|
||||||
|
#include <winapifamily.h>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma once
|
||||||
|
#endif // _MSC_VER
|
||||||
|
|
||||||
|
#pragma region Application Family
|
||||||
|
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
|
||||||
|
|
||||||
|
#include <specstrings.h> // for _In_, etc.
|
||||||
|
|
||||||
|
#if !defined(__midl) && !defined(SORTPP_PASS)
|
||||||
|
|
||||||
|
#if (NTDDI_VERSION >= NTDDI_WINXP)
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
#define VERSIONHELPERAPI inline bool
|
||||||
|
|
||||||
|
#else // __cplusplus
|
||||||
|
|
||||||
|
#define VERSIONHELPERAPI FORCEINLINE BOOL
|
||||||
|
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
#ifndef NTDDI_WINBLUE
|
||||||
|
#define NTDDI_WINBLUE 0x06030000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _WIN32_WINNT_WINBLUE
|
||||||
|
#define _WIN32_WINNT_WINBLUE 0x0602
|
||||||
|
#endif
|
||||||
|
|
||||||
|
VERSIONHELPERAPI
|
||||||
|
IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor)
|
||||||
|
{
|
||||||
|
OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 };
|
||||||
|
DWORDLONG const dwlConditionMask = VerSetConditionMask(
|
||||||
|
VerSetConditionMask(
|
||||||
|
VerSetConditionMask(
|
||||||
|
0, VER_MAJORVERSION, VER_GREATER_EQUAL),
|
||||||
|
VER_MINORVERSION, VER_GREATER_EQUAL),
|
||||||
|
VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
|
||||||
|
|
||||||
|
osvi.dwMajorVersion = wMajorVersion;
|
||||||
|
osvi.dwMinorVersion = wMinorVersion;
|
||||||
|
osvi.wServicePackMajor = wServicePackMajor;
|
||||||
|
|
||||||
|
return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSIONHELPERAPI
|
||||||
|
IsWindowsXPOrGreater()
|
||||||
|
{
|
||||||
|
return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSIONHELPERAPI
|
||||||
|
IsWindowsXPSP1OrGreater()
|
||||||
|
{
|
||||||
|
return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSIONHELPERAPI
|
||||||
|
IsWindowsXPSP2OrGreater()
|
||||||
|
{
|
||||||
|
return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSIONHELPERAPI
|
||||||
|
IsWindowsXPSP3OrGreater()
|
||||||
|
{
|
||||||
|
return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSIONHELPERAPI
|
||||||
|
IsWindowsVistaOrGreater()
|
||||||
|
{
|
||||||
|
return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSIONHELPERAPI
|
||||||
|
IsWindowsVistaSP1OrGreater()
|
||||||
|
{
|
||||||
|
return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSIONHELPERAPI
|
||||||
|
IsWindowsVistaSP2OrGreater()
|
||||||
|
{
|
||||||
|
return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSIONHELPERAPI
|
||||||
|
IsWindows7OrGreater()
|
||||||
|
{
|
||||||
|
return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), LOBYTE(_WIN32_WINNT_WIN7), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSIONHELPERAPI
|
||||||
|
IsWindows7SP1OrGreater()
|
||||||
|
{
|
||||||
|
return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), LOBYTE(_WIN32_WINNT_WIN7), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSIONHELPERAPI
|
||||||
|
IsWindows8OrGreater()
|
||||||
|
{
|
||||||
|
return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSIONHELPERAPI
|
||||||
|
IsWindows8Point1OrGreater()
|
||||||
|
{
|
||||||
|
return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINBLUE), LOBYTE(_WIN32_WINNT_WINBLUE), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSIONHELPERAPI
|
||||||
|
IsWindowsServer()
|
||||||
|
{
|
||||||
|
OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0, 0, VER_NT_WORKSTATION };
|
||||||
|
DWORDLONG const dwlConditionMask = VerSetConditionMask( 0, VER_PRODUCT_TYPE, VER_EQUAL );
|
||||||
|
|
||||||
|
return !VerifyVersionInfoW(&osvi, VER_PRODUCT_TYPE, dwlConditionMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // NTDDI_VERSION
|
||||||
|
|
||||||
|
#endif // defined(__midl)
|
||||||
|
|
||||||
|
#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) */
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
#endif // _VERSIONHELPERS_H_INCLUDED_
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
namespace krabs {
|
||||||
|
|
||||||
|
/** <summary>
|
||||||
|
* Converts std::wstring argument to std::string using UTF-8 codepage
|
||||||
|
* Returns empty string if translation fails or input string is empty
|
||||||
|
* </summary>
|
||||||
|
*/
|
||||||
|
inline std::string from_wstring(const std::wstring& wstr, UINT codePage = CP_UTF8)
|
||||||
|
{
|
||||||
|
if (wstr.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const auto requiredLen = WideCharToMultiByte(codePage, 0, wstr.data(), static_cast<int>(wstr.size()),
|
||||||
|
nullptr, 0, nullptr, nullptr);
|
||||||
|
if (0 == requiredLen)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::string result(requiredLen, 0);
|
||||||
|
const auto convertedLen = WideCharToMultiByte(codePage, 0, wstr.data(), static_cast<int>(wstr.size()),
|
||||||
|
&result[0], requiredLen, nullptr, nullptr);
|
||||||
|
if (0 == convertedLen)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user