mirror of
https://github.com/thefLink/Hunt-Weird-ImageLoads
synced 2026-06-24 08:41:06 +00:00
first commit
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
#include "Detectors.h"
|
||||
#include <string>
|
||||
|
||||
namespace Detectors {
|
||||
|
||||
VOID Detectors::PrivateRX::Check ( HANDLE hProcess, std::vector<ULONG_PTR> stack, DWORD dwPid, std::wstring imageName) {
|
||||
|
||||
SIZE_T s = 0;
|
||||
MEMORY_BASIC_INFORMATION mbi = { 0 };
|
||||
std::string processName;
|
||||
|
||||
for ( ULONG_PTR pAddr : stack ) {
|
||||
|
||||
s = VirtualQueryEx ( hProcess, ( LPCVOID ) pAddr, &mbi, sizeof ( MEMORY_BASIC_INFORMATION ) );
|
||||
if ( s == 0 )
|
||||
continue;
|
||||
|
||||
if ( mbi.Type != MEM_PRIVATE )
|
||||
continue;
|
||||
|
||||
if ( mbi.Protect == PAGE_EXECUTE_READ ) {
|
||||
|
||||
Helpers::ModuleNameFromAddress ( hProcess, NULL, processName );
|
||||
|
||||
wprintf ( L"! Process %S ( %d ) loaded %s and callstack contains RX Page: 0x%p\n", processName.c_str ( ), dwPid, imageName.c_str ( ), ( PVOID ) pAddr );
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
VOID Detectors::PrivateRWX::Check ( HANDLE hProcess, std::vector<ULONG_PTR> stack, DWORD dwPid, std::wstring imageName ) {
|
||||
|
||||
SIZE_T s = 0;
|
||||
MEMORY_BASIC_INFORMATION mbi = { 0 };
|
||||
std::string processName;
|
||||
|
||||
for ( ULONG_PTR pAddr : stack ) {
|
||||
|
||||
s = VirtualQueryEx ( hProcess, ( LPCVOID ) pAddr, &mbi, sizeof ( MEMORY_BASIC_INFORMATION ) );
|
||||
if ( s == 0 )
|
||||
continue;
|
||||
|
||||
if ( mbi.Type != MEM_PRIVATE )
|
||||
continue;
|
||||
|
||||
if ( mbi.Protect == PAGE_EXECUTE_READ ) {
|
||||
|
||||
Helpers::ModuleNameFromAddress ( hProcess, NULL, processName );
|
||||
|
||||
wprintf ( L"! Process %S ( %d ) loaded %s and callstack contains RWX Page: 0x%p\n", processName.c_str ( ), dwPid, imageName.c_str ( ), ( PVOID ) pAddr );
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
VOID Detectors::ModuleStomped::Check ( HANDLE hProcess, std::vector<ULONG_PTR> stack, DWORD dwPid, std::wstring imageName ) {
|
||||
|
||||
std::string stompedModule;
|
||||
std::string processName;
|
||||
|
||||
for ( ULONG_PTR pAddr : stack ) {
|
||||
|
||||
if ( Helpers::IsModuleStomped ( hProcess, ( PVOID ) pAddr ) ) {
|
||||
|
||||
Helpers::ModuleNameFromAddress ( hProcess, ( PVOID ) pAddr, stompedModule );
|
||||
Helpers::ModuleNameFromAddress ( hProcess, NULL, processName );
|
||||
|
||||
wprintf ( L"! Process %S ( %d ) loaded %s and callstack contains stomped module: %S\n", processName.c_str ( ), dwPid, imageName.c_str ( ), stompedModule.c_str ( ) );
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Was ist mit ZwMapViewOfSection ?
|
||||
VOID Detectors::ModuleProxying::Check ( HANDLE hProcess, std::vector<ULONG_PTR> stack, DWORD dwPid, std::wstring imageName ) {
|
||||
|
||||
BOOL bSuccess = FALSE;
|
||||
|
||||
std::string callingmodule;
|
||||
std::string processName;
|
||||
std::string symbol;
|
||||
|
||||
if ( stack.size ( ) < 2 )
|
||||
return;
|
||||
|
||||
for ( auto it = stack.begin ( ); it != stack.end ( ); ++it ) {
|
||||
|
||||
bSuccess = Helpers::SymbolNameFromAddress ( hProcess, ( PVOID ) *it, symbol );
|
||||
if ( bSuccess == FALSE )
|
||||
break;
|
||||
|
||||
if ( symbol.find ( "LoadLibrary" ) != std::string::npos ) {
|
||||
|
||||
bSuccess = Helpers::ModuleNameFromAddress ( hProcess, ( PVOID ) * ( it + 1 ), callingmodule );
|
||||
if ( bSuccess == FALSE )
|
||||
break;
|
||||
|
||||
if ( !_stricmp ( callingmodule.c_str ( ), "ntdll.dll" ) ){
|
||||
|
||||
Helpers::ModuleNameFromAddress ( hProcess, NULL, processName );
|
||||
wprintf ( L"! Process %S ( %d ) loaded %s by proxying loadlibray through ntdll\n", processName.c_str ( ), dwPid, imageName.c_str ( ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
VOID Detectors::DedicatedThread::Check ( HANDLE hProcess, std::vector<ULONG_PTR> stack, DWORD dwPid, PVOID baseAddr) {
|
||||
|
||||
BOOL bSuccess = FALSE;
|
||||
PVOID pLoadLibraryA = NULL, pLoadLibraryW = NULL, pLoadLibraryExA = NULL, pLoadLibraryExW = NULL;
|
||||
|
||||
std::string baseModule;
|
||||
std::string processName;
|
||||
|
||||
bSuccess = Helpers::ModuleNameFromAddress ( hProcess, ( PVOID ) baseAddr, baseModule );
|
||||
if ( bSuccess == FALSE )
|
||||
return;
|
||||
|
||||
pLoadLibraryA = GetProcAddress ( GetModuleHandleA ( "kernel32.dll" ), "LoadLibraryA" );
|
||||
pLoadLibraryW = GetProcAddress ( GetModuleHandleA ( "kernel32.dll" ), "LoadLibraryW" );
|
||||
pLoadLibraryExA = GetProcAddress ( GetModuleHandleA ( "kernel32.dll" ), "LoadLibraryExA" );
|
||||
pLoadLibraryExW = GetProcAddress ( GetModuleHandleA ( "kernel32.dll" ), "LoadLibraryExW" );
|
||||
|
||||
if ( !_stricmp ( baseModule.c_str ( ), "kernel32.dll" ) || !_stricmp ( baseModule.c_str ( ), "kernelbase.dll" ) ) {
|
||||
|
||||
Helpers::ModuleNameFromAddress ( hProcess, NULL, processName );
|
||||
|
||||
wprintf ( L"! Process %S ( %d ) abnormally started a new thread in %S\n", processName.c_str ( ), dwPid, baseModule.c_str ( ) );
|
||||
|
||||
if ( baseAddr == pLoadLibraryA || baseAddr == pLoadLibraryW || baseAddr == pLoadLibraryExA || baseAddr == pLoadLibraryExW )
|
||||
wprintf ( L"\t !! Thread is abnormally started in a LoadLibrary function :o\n" );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "windows.h"
|
||||
#include <vector>
|
||||
|
||||
#include "Helpers.h"
|
||||
|
||||
namespace Detectors {
|
||||
|
||||
class DetectorImageLoad {
|
||||
public:
|
||||
virtual VOID Check ( HANDLE, std::vector<ULONG_PTR>, DWORD, std::wstring ) = 0;
|
||||
};
|
||||
|
||||
class DetectorThreadStart {
|
||||
public:
|
||||
virtual VOID Check ( HANDLE, std::vector<ULONG_PTR>, DWORD, PVOID ) = 0;
|
||||
};
|
||||
|
||||
class PrivateRX : public DetectorImageLoad { public: VOID Check ( HANDLE, std::vector<ULONG_PTR>, DWORD, std::wstring ); };
|
||||
class PrivateRWX : public DetectorImageLoad { public: VOID Check ( HANDLE, std::vector<ULONG_PTR>, DWORD, std::wstring ); };
|
||||
class ModuleStomped : public DetectorImageLoad { public: VOID Check ( HANDLE, std::vector<ULONG_PTR>, DWORD, std::wstring ); };
|
||||
class ModuleProxying : public DetectorImageLoad { public: VOID Check ( HANDLE, std::vector<ULONG_PTR>, DWORD, std::wstring ); };
|
||||
|
||||
class DedicatedThread : public DetectorThreadStart { public: VOID Check ( HANDLE, std::vector<ULONG_PTR>, DWORD, PVOID); };
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
#include "Helpers.h"
|
||||
#include "Dbghelp.h"
|
||||
|
||||
namespace Helpers {
|
||||
|
||||
BOOL IsModuleStomped ( HANDLE hProcess, PVOID pAddr ) {
|
||||
|
||||
SIZE_T s = 0;
|
||||
MEMORY_BASIC_INFORMATION mbi = { 0 };
|
||||
PSAPI_WORKING_SET_EX_INFORMATION workingSets = { 0 };
|
||||
BOOL bSuccess = FALSE;
|
||||
|
||||
s = VirtualQueryEx ( hProcess, ( LPCVOID ) pAddr, &mbi, sizeof ( MEMORY_BASIC_INFORMATION ) );
|
||||
if ( s == 0 )
|
||||
goto Cleanup;
|
||||
|
||||
if ( mbi.Type != MEM_IMAGE || mbi.AllocationProtect == PAGE_NOACCESS )
|
||||
goto Cleanup;
|
||||
|
||||
workingSets.VirtualAddress = mbi.BaseAddress;
|
||||
|
||||
bSuccess = K32QueryWorkingSetEx ( hProcess, &workingSets, sizeof ( PSAPI_WORKING_SET_EX_INFORMATION ) );
|
||||
if ( bSuccess == FALSE )
|
||||
goto Cleanup;
|
||||
|
||||
bSuccess = FALSE;
|
||||
|
||||
if ( workingSets.VirtualAttributes.Shared != 0 )
|
||||
goto Cleanup;
|
||||
|
||||
bSuccess = TRUE;
|
||||
|
||||
Cleanup:
|
||||
|
||||
return bSuccess;
|
||||
|
||||
}
|
||||
|
||||
BOOL SymbolNameFromAddress ( HANDLE hProcess, PVOID pAddr, std::string& symbol ) {
|
||||
|
||||
BOOL bSuccess = FALSE;
|
||||
DWORD64 dw64Displacement = 0;
|
||||
CHAR cSymName [ 256 ] = { 0 };
|
||||
|
||||
PIMAGEHLP_SYMBOL64 pSymbol = NULL;
|
||||
|
||||
pSymbol = ( PIMAGEHLP_SYMBOL64 ) HeapAlloc ( GetProcessHeap ( ), HEAP_ZERO_MEMORY, sizeof ( IMAGEHLP_SYMBOL64 ) + 256 * sizeof ( WCHAR ) );
|
||||
if ( pSymbol == NULL )
|
||||
goto Cleanup;
|
||||
|
||||
pSymbol->SizeOfStruct = sizeof ( IMAGEHLP_SYMBOL64 );
|
||||
pSymbol->MaxNameLength = 255;
|
||||
|
||||
bSuccess = SymGetSymFromAddr64 ( hProcess, ( ULONG64 ) pAddr, &dw64Displacement, pSymbol );
|
||||
if ( bSuccess == FALSE )
|
||||
goto Cleanup;
|
||||
|
||||
UnDecorateSymbolName ( pSymbol->Name, cSymName, 256, UNDNAME_COMPLETE );
|
||||
symbol = std::string ( cSymName );
|
||||
|
||||
Cleanup:
|
||||
|
||||
if ( pSymbol )
|
||||
HeapFree ( GetProcessHeap ( ), 0, pSymbol );
|
||||
|
||||
return bSuccess;
|
||||
|
||||
}
|
||||
|
||||
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 };
|
||||
|
||||
moduleName.clear ( );
|
||||
|
||||
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,14 @@
|
||||
#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 );
|
||||
BOOL IsModuleStomped ( HANDLE, PVOID );
|
||||
BOOL SymbolNameFromAddress ( HANDLE, PVOID, std::string& );
|
||||
}
|
||||
@@ -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-Imageloads", "Hunt-Weird-Imageloads.vcxproj", "{0020C8CD-76A4-4092-9CA5-AF05572CA661}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NewThreadLoadLibrary", "NewThreadLoadLibrary\NewThreadLoadLibrary.vcxproj", "{D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkItemLoadLibrary", "WorkItemLoadLibrary\WorkItemLoadLibrary.vcxproj", "{7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}"
|
||||
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
|
||||
{0020C8CD-76A4-4092-9CA5-AF05572CA661}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{0020C8CD-76A4-4092-9CA5-AF05572CA661}.Debug|x64.Build.0 = Debug|x64
|
||||
{0020C8CD-76A4-4092-9CA5-AF05572CA661}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{0020C8CD-76A4-4092-9CA5-AF05572CA661}.Debug|x86.Build.0 = Debug|Win32
|
||||
{0020C8CD-76A4-4092-9CA5-AF05572CA661}.Release|x64.ActiveCfg = Release|x64
|
||||
{0020C8CD-76A4-4092-9CA5-AF05572CA661}.Release|x64.Build.0 = Release|x64
|
||||
{0020C8CD-76A4-4092-9CA5-AF05572CA661}.Release|x86.ActiveCfg = Release|Win32
|
||||
{0020C8CD-76A4-4092-9CA5-AF05572CA661}.Release|x86.Build.0 = Release|Win32
|
||||
{D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}.Debug|x64.Build.0 = Debug|x64
|
||||
{D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}.Debug|x86.Build.0 = Debug|Win32
|
||||
{D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}.Release|x64.ActiveCfg = Release|x64
|
||||
{D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}.Release|x64.Build.0 = Release|x64
|
||||
{D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}.Release|x86.ActiveCfg = Release|Win32
|
||||
{D819FEE1-5004-4ABF-9B2D-6562FBBBE1C5}.Release|x86.Build.0 = Release|Win32
|
||||
{7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}.Debug|x64.Build.0 = Debug|x64
|
||||
{7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}.Debug|x86.Build.0 = Debug|Win32
|
||||
{7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}.Release|x64.ActiveCfg = Release|x64
|
||||
{7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}.Release|x64.Build.0 = Release|x64
|
||||
{7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}.Release|x86.ActiveCfg = Release|Win32
|
||||
{7EC5D2D4-3E4C-418F-82D3-98C232F6CC9B}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {BE1C43E1-9EF5-4D82-89DE-B7319B283FB1}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,155 @@
|
||||
<?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>{0020c8cd-76a4-4092-9ca5-af05572ca661}</ProjectGuid>
|
||||
<RootNamespace>HuntWeirdImageloads</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>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</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>
|
||||
<AdditionalDependencies>dbghelp.lib;shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</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>
|
||||
<AdditionalDependencies>dbghelp.lib;shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</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,206 @@
|
||||
#include "../libs/krabs/krabs.hpp"
|
||||
#include "dbghelp.h"
|
||||
#include <vector>
|
||||
|
||||
#include "Detectors.h"
|
||||
#include "Helpers.h"
|
||||
|
||||
#define EVENTID_THREADSTART 3
|
||||
#define EVENTID_IMAGELOAD 5
|
||||
|
||||
VOID EnableProcessTracing ( krabs::user_trace& );
|
||||
VOID Help ( VOID );
|
||||
VOID OnImageLoad ( const EVENT_RECORD&, const krabs::trace_context& );
|
||||
VOID OnThreadStart ( const EVENT_RECORD&, const krabs::trace_context& );
|
||||
VOID ParseArgs ( int, char** );
|
||||
|
||||
std::vector<Detectors::DetectorImageLoad *> activeDetectorsImageLoad;
|
||||
std::vector<Detectors::DetectorThreadStart*> activeDetectorsThreadStart;
|
||||
|
||||
VOID EnableProcessTracing ( krabs::user_trace& userTrace ) {
|
||||
|
||||
krabs::provider<>* providerProcess = new krabs::provider<> ( L"Microsoft-Windows-Kernel-Process" );
|
||||
providerProcess->any ( 0x20 | 0x40 ); // WINEVENT_KEYWORD_THREAD | WINEVENT_KEYWORD_IMAGE
|
||||
providerProcess->trace_flags ( providerProcess->trace_flags ( ) | EVENT_ENABLE_PROPERTY_STACK_TRACE );
|
||||
|
||||
krabs::event_filter* imageLoad = new krabs::event_filter ( krabs::predicates::id_is ( EVENTID_IMAGELOAD ) );
|
||||
krabs::event_filter* threadStart = new krabs::event_filter ( krabs::predicates::id_is ( EVENTID_THREADSTART ) );
|
||||
|
||||
imageLoad->add_on_event_callback ( OnImageLoad );
|
||||
threadStart->add_on_event_callback ( OnThreadStart );
|
||||
|
||||
providerProcess->add_filter ( *imageLoad );
|
||||
providerProcess->add_filter ( *threadStart );
|
||||
|
||||
userTrace.enable ( *providerProcess );
|
||||
|
||||
}
|
||||
|
||||
VOID OnThreadStart ( const EVENT_RECORD& record, const krabs::trace_context& trace_context ) {
|
||||
|
||||
krabs::schema schema ( record, trace_context.schema_locator );
|
||||
krabs::parser parser ( schema );
|
||||
|
||||
std::vector<ULONG_PTR> stack = schema.stack_trace ( );
|
||||
|
||||
DWORD pid = parser.parse<DWORD> ( L"ProcessID" );
|
||||
DWORD tid = parser.parse<DWORD> ( L"ThreadID" );
|
||||
|
||||
PVOID startAddr = parser.parse<PVOID> ( L"StartAddr" );
|
||||
|
||||
HANDLE hProcess = NULL;
|
||||
BOOL bIs32Bit;
|
||||
|
||||
hProcess = OpenProcess ( PROCESS_ALL_ACCESS, FALSE, pid );
|
||||
if ( hProcess == NULL )
|
||||
return;
|
||||
|
||||
IsWow64Process ( hProcess, &bIs32Bit );
|
||||
if ( bIs32Bit )
|
||||
goto Cleanup;
|
||||
|
||||
for ( Detectors::DetectorThreadStart* detector : activeDetectorsThreadStart )
|
||||
detector->Check ( hProcess, stack, pid, startAddr );
|
||||
|
||||
Cleanup:
|
||||
|
||||
if ( hProcess )
|
||||
CloseHandle ( hProcess );
|
||||
|
||||
}
|
||||
|
||||
VOID OnImageLoad ( const EVENT_RECORD& record, const krabs::trace_context& trace_context ) {
|
||||
|
||||
krabs::schema schema ( record, trace_context.schema_locator );
|
||||
krabs::parser parser ( schema );
|
||||
|
||||
BOOL bIs32Bit = FALSE;
|
||||
DWORD pid = parser.parse<DWORD> ( L"ProcessID" );
|
||||
PVOID imageBase = parser.parse<PVOID> ( L"ImageBase" );
|
||||
std::wstring imageName = parser.parse<std::wstring> ( L"ImageName" );
|
||||
|
||||
std::vector<ULONG_PTR> stack = schema.stack_trace ( );
|
||||
Helpers::RemoveKernelAddrs ( stack );
|
||||
|
||||
HANDLE hProcess = NULL;
|
||||
|
||||
hProcess = OpenProcess ( PROCESS_ALL_ACCESS, FALSE, pid );
|
||||
if ( hProcess == NULL )
|
||||
return;
|
||||
|
||||
IsWow64Process ( hProcess, &bIs32Bit );
|
||||
if ( bIs32Bit )
|
||||
goto Cleanup;
|
||||
|
||||
SymInitialize ( hProcess, NULL, TRUE );
|
||||
|
||||
for ( Detectors::DetectorImageLoad *detector : activeDetectorsImageLoad )
|
||||
detector->Check ( hProcess, stack, pid, imageName );
|
||||
|
||||
Cleanup:
|
||||
|
||||
if ( hProcess ) {
|
||||
|
||||
SymCleanup ( hProcess );
|
||||
CloseHandle ( hProcess );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
VOID Go ( krabs::user_trace* userTrace ) {
|
||||
userTrace->start ( );
|
||||
}
|
||||
|
||||
VOID Help ( VOID ) {
|
||||
printf ( "Hunt-Weird-ImageLoads.exe\n\n\t--all activates all alerts\n\t--rx alerts on private rx regions in callstack\n\t--rwx alerts on private rwx regions in callstack\n\t--stomped alerts on stomped modules in callstack\n\t--proxy alerts on abnormal calls to kernel32!loadlibrary from ntdll\n\t--dedicatedthread alerts on thread with baseaddr on loadlibrary*\n\n" );
|
||||
exit ( 1 );
|
||||
}
|
||||
|
||||
VOID ParseArgs ( int argc, char** argv ) {
|
||||
|
||||
if ( argc < 2 )
|
||||
Help ( );
|
||||
|
||||
for ( int i = 1; i < argc; i++ ) {
|
||||
|
||||
if ( !_strcmpi ( argv [ i ], "--rx" ) ) {
|
||||
printf ( "\t- Enabling detector RX\n" );
|
||||
activeDetectorsImageLoad.push_back ( new Detectors::PrivateRX ( ) );
|
||||
}
|
||||
|
||||
else if ( !_strcmpi ( argv [ i ], "--rwx" ) ) {
|
||||
printf ( "\t- Enabling detector RWX\n" );
|
||||
activeDetectorsImageLoad.push_back ( new Detectors::PrivateRWX ( ) );
|
||||
}
|
||||
|
||||
else if ( !_strcmpi ( argv [ i ], "--stomped" ) ) {
|
||||
printf ( "\t- Enabling detector stomped\n" );
|
||||
activeDetectorsImageLoad.push_back ( new Detectors::ModuleStomped ( ) );
|
||||
}
|
||||
|
||||
else if ( !_strcmpi ( argv [ i ], "--proxy" ) ) {
|
||||
printf ( "\t- Enabling detector for module proxying\n" );
|
||||
activeDetectorsImageLoad.push_back ( new Detectors::ModuleProxying ( ) );
|
||||
}
|
||||
|
||||
else if ( !_strcmpi ( argv [ i ], "--dedicatedthread" ) ) {
|
||||
printf ( "\t- Enabling detector dedicatedthread\n" );
|
||||
activeDetectorsThreadStart.push_back ( new Detectors::DedicatedThread ( ) );
|
||||
}
|
||||
|
||||
else if ( !_strcmpi ( argv [ i ], "--all" ) ) {
|
||||
|
||||
printf ( "\t- Enabling all detectors\n" );
|
||||
|
||||
activeDetectorsImageLoad.clear ( );
|
||||
|
||||
activeDetectorsImageLoad.push_back ( new Detectors::PrivateRX ( ) );
|
||||
activeDetectorsImageLoad.push_back ( new Detectors::PrivateRWX ( ) );
|
||||
activeDetectorsImageLoad.push_back ( new Detectors::ModuleStomped ( ) );
|
||||
activeDetectorsImageLoad.push_back ( new Detectors::ModuleProxying ( ) );
|
||||
|
||||
activeDetectorsThreadStart.push_back ( new Detectors::DedicatedThread ( ) );
|
||||
|
||||
}
|
||||
|
||||
else
|
||||
Help ( );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main ( int argc, char** argv ) {
|
||||
|
||||
HANDLE traceThread = NULL;
|
||||
|
||||
krabs::user_trace userTrace ( L"Hunt-Weird-Imageloads" );
|
||||
|
||||
if ( !Helpers::IsElevated ( ) ) {
|
||||
printf ( "- Not elevated\n" );
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf ( "* Hunt-Weird-ImageLoads\n" );
|
||||
ParseArgs ( argc, argv );
|
||||
|
||||
printf ( "* Enabling trace, might take a bit ... \n" );
|
||||
|
||||
SymSetOptions ( SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS );
|
||||
|
||||
EnableProcessTracing ( 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,36 @@
|
||||
#include "windows.h"
|
||||
|
||||
#include "stdio.h"
|
||||
|
||||
HMODULE MyLoadLibrary ( PCSTR mName ) {
|
||||
|
||||
HANDLE hThread = NULL;
|
||||
|
||||
hThread = CreateThread ( NULL, 0, ( LPTHREAD_START_ROUTINE ) LoadLibraryA, ( LPVOID ) mName, 0, NULL );
|
||||
if ( hThread == NULL )
|
||||
return 0;
|
||||
|
||||
WaitForSingleObject ( hThread, INFINITE );
|
||||
|
||||
return GetModuleHandleA ( mName );
|
||||
|
||||
}
|
||||
|
||||
int main ( int arg, char** argv ) {
|
||||
|
||||
HMODULE hModule = NULL;
|
||||
|
||||
hModule = GetModuleHandleA ( "dbghelp.dll" );
|
||||
if ( hModule ) {
|
||||
printf ( "Dll already loaded\n" );
|
||||
return 0;
|
||||
}
|
||||
|
||||
hModule = MyLoadLibrary ( "dbghelp.dll" );
|
||||
printf ( "Module at 0x%p\n", hModule );
|
||||
|
||||
getchar ( );
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?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>{d819fee1-5004-4abf-9b2d-6562fbbbe1c5}</ProjectGuid>
|
||||
<RootNamespace>NewThreadLoadLibrary</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>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</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="Main.c" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</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,43 @@
|
||||
#include "windows.h"
|
||||
#include "stdio.h"
|
||||
|
||||
/*
|
||||
|
||||
Based on: https://github.com/rad9800/misc/blob/main/bypasses/WorkItemLoadLibrary.c
|
||||
|
||||
*/
|
||||
|
||||
typedef NTSTATUS ( NTAPI* RtlQueueWorkItem )( PVOID, PVOID, ULONG );
|
||||
|
||||
HMODULE MyLoadLibrary ( PCSTR mName ) {
|
||||
|
||||
HANDLE hThread = NULL;
|
||||
RtlQueueWorkItem _RtlQueueWorkItem = ( RtlQueueWorkItem ) GetProcAddress ( GetModuleHandleA ( "ntdll.dll" ), "RtlQueueWorkItem" );
|
||||
_RtlQueueWorkItem ( LoadLibraryA, ( PVOID ) mName, WT_EXECUTEDEFAULT );
|
||||
|
||||
Sleep ( 1000 ); // Dirty :-)
|
||||
|
||||
return GetModuleHandleA ( mName );
|
||||
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main ( int argc, char** argv ) {
|
||||
|
||||
HMODULE hModule = NULL;
|
||||
|
||||
hModule = GetModuleHandleA ( "wininet.dll" );
|
||||
if ( hModule ) {
|
||||
printf ( "Dll already loaded\n" );
|
||||
return 0;
|
||||
}
|
||||
|
||||
hModule = MyLoadLibrary ( "wininet.dll" );
|
||||
printf ( "Module at 0x%p\n", hModule );
|
||||
|
||||
getchar ( );
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?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>{7ec5d2d4-3e4c-418f-82d3-98c232f6cc9b}</ProjectGuid>
|
||||
<RootNamespace>WorkItemLoadLibrary</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>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</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="Main.c" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</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,34 @@
|
||||
# Hunt-Weird-ImageLoads
|
||||
|
||||
This project was created to play with different IOCs caused by Imageload events.
|
||||
It leverages ETW to monitor for ImageLoad events and walks the callstack to identify some possible IOCs, such as:
|
||||
|
||||
- R(W)X page in callstack
|
||||
- Stomped module in callstack
|
||||
- Module proxying ( ntdll -> kernel32!LoadLibrary ) as described [here](https://github.com/rad9800/misc/blob/main/bypasses/WorkItemLoadLibrary.c) or [here](https://0xdarkvortex.dev/proxying-dll-loads-for-hiding-etwti-stack-tracing)
|
||||
- New thread dedicated to load a library
|
||||
|
||||
There are two sample programs for **module proxying** and **dedicated threads** in this repository.
|
||||
|
||||

|
||||
|
||||
## Conclusion
|
||||
|
||||
In my tests, I had a lot of false positives monitoring for private or module stomped pages in the callstack and this is probably not a valid IOC.
|
||||
However, it seems that both, **module proxying** and **dedicated threads** are quite abnormal, but see yourself.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
--all activates all alerts
|
||||
--rx alerts on private rx regions in callstack
|
||||
--rwx alerts on private rwx regions in callstack
|
||||
--stomped alerts on stomped modules in callstack
|
||||
--proxy alerts on abnormal calls to kernel32!loadlibrary from ntdll
|
||||
--dedicatedthread alerts on thread with baseaddr on loadlibrary*
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
- [@rad9800](https://twitter.com/rad9800) [For an example implementation of LoadLibray via RtlQueueWorkItem](https://github.com/rad9800/misc/blob/main/bypasses/WorkItemLoadLibrary.c)
|
||||
- [@NinjaParanoid](https://twitter.com/NinjaParanoid) [For a super cool blogpost on this topic](https://0xdarkvortex.dev/proxying-dll-loads-for-hiding-etwti-stack-tracing/)
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
Reference in New Issue
Block a user