#pragma once #if defined(_MSC_VER) #include "optick.config.h" #if USE_OPTICK #include "optick_core.platform.h" namespace Optick { const char* Platform::GetName() { #if defined(OPTICK_PC) return "Windows"; #else return "XBox"; #endif } ThreadID Platform::GetThreadID() { return GetCurrentThreadId(); } ProcessID Platform::GetProcessID() { return GetCurrentProcessId(); } int64 Platform::GetFrequency() { LARGE_INTEGER frequency; QueryPerformanceFrequency(&frequency); return frequency.QuadPart; } int64 Platform::GetTime() { LARGE_INTEGER largeInteger; QueryPerformanceCounter(&largeInteger); return largeInteger.QuadPart; } } #if OPTICK_ENABLE_TRACING #include #include "optick_core.h" /* Event Tracing Functions - API https://msdn.microsoft.com/en-us/library/windows/desktop/aa363795(v=vs.85).aspx */ #define DECLARE_ETW (!OPTICK_PC) #if DECLARE_ETW // Copied from Windows SDK #ifndef WMIAPI #ifndef MIDL_PASS #ifdef _WMI_SOURCE_ #define WMIAPI __stdcall #else #define WMIAPI DECLSPEC_IMPORT __stdcall #endif // _WMI_SOURCE #endif // MIDL_PASS #endif // WMIAPI #define INITGUID #include #if defined(_NTDDK_) || defined(_NTIFS_) || defined(_WMIKM_) #define _EVNTRACE_KERNEL_MODE #endif #if !defined(_EVNTRACE_KERNEL_MODE) #include #endif #if _MSC_VER <= 1600 #define EVENT_DESCRIPTOR_DEF #define EVENT_HEADER_DEF #define EVENT_HEADER_EXTENDED_DATA_ITEM_DEF #define EVENT_RECORD_DEF #endif #ifndef _TRACEHANDLE_DEFINED #define _TRACEHANDLE_DEFINED typedef ULONG64 TRACEHANDLE, *PTRACEHANDLE; #endif // // EventTraceGuid is used to identify a event tracing session // DEFINE_GUID( /* 68fdd900-4a3e-11d1-84f4-0000f80464e3 */ EventTraceGuid, 0x68fdd900, 0x4a3e, 0x11d1, 0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3 ); // // SystemTraceControlGuid. Used to specify event tracing for kernel // DEFINE_GUID( /* 9e814aad-3204-11d2-9a82-006008a86939 */ SystemTraceControlGuid, 0x9e814aad, 0x3204, 0x11d2, 0x9a, 0x82, 0x00, 0x60, 0x08, 0xa8, 0x69, 0x39 ); // // EventTraceConfigGuid. Used to report system configuration records // DEFINE_GUID( /* 01853a65-418f-4f36-aefc-dc0f1d2fd235 */ EventTraceConfigGuid, 0x01853a65, 0x418f, 0x4f36, 0xae, 0xfc, 0xdc, 0x0f, 0x1d, 0x2f, 0xd2, 0x35 ); // // DefaultTraceSecurityGuid. Specifies the default event tracing security // DEFINE_GUID( /* 0811c1af-7a07-4a06-82ed-869455cdf713 */ DefaultTraceSecurityGuid, 0x0811c1af, 0x7a07, 0x4a06, 0x82, 0xed, 0x86, 0x94, 0x55, 0xcd, 0xf7, 0x13 ); /////////////////////////////////////////////////////////////////////////////// #define PROCESS_TRACE_MODE_REAL_TIME 0x00000100 #define PROCESS_TRACE_MODE_RAW_TIMESTAMP 0x00001000 #define PROCESS_TRACE_MODE_EVENT_RECORD 0x10000000 /////////////////////////////////////////////////////////////////////////////// #define EVENT_HEADER_FLAG_EXTENDED_INFO 0x0001 #define EVENT_HEADER_FLAG_PRIVATE_SESSION 0x0002 #define EVENT_HEADER_FLAG_STRING_ONLY 0x0004 #define EVENT_HEADER_FLAG_TRACE_MESSAGE 0x0008 #define EVENT_HEADER_FLAG_NO_CPUTIME 0x0010 #define EVENT_HEADER_FLAG_32_BIT_HEADER 0x0020 #define EVENT_HEADER_FLAG_64_BIT_HEADER 0x0040 #define EVENT_HEADER_FLAG_CLASSIC_HEADER 0x0100 #define EVENT_HEADER_FLAG_PROCESSOR_INDEX 0x0200 /////////////////////////////////////////////////////////////////////////////// #define KERNEL_LOGGER_NAMEW L"NT Kernel Logger" /////////////////////////////////////////////////////////////////////////////// #define EVENT_TRACE_REAL_TIME_MODE 0x00000100 // Real time mode on /////////////////////////////////////////////////////////////////////////////// #define EVENT_TRACE_CONTROL_STOP 1 /////////////////////////////////////////////////////////////////////////////// // // Enable flags for Kernel Events // #define EVENT_TRACE_FLAG_PROCESS 0x00000001 // process start & end #define EVENT_TRACE_FLAG_THREAD 0x00000002 // thread start & end #define EVENT_TRACE_FLAG_IMAGE_LOAD 0x00000004 // image load #define EVENT_TRACE_FLAG_DISK_IO 0x00000100 // physical disk IO #define EVENT_TRACE_FLAG_DISK_FILE_IO 0x00000200 // requires disk IO #define EVENT_TRACE_FLAG_MEMORY_PAGE_FAULTS 0x00001000 // all page faults #define EVENT_TRACE_FLAG_MEMORY_HARD_FAULTS 0x00002000 // hard faults only #define EVENT_TRACE_FLAG_NETWORK_TCPIP 0x00010000 // tcpip send & receive #define EVENT_TRACE_FLAG_REGISTRY 0x00020000 // registry calls #define EVENT_TRACE_FLAG_DBGPRINT 0x00040000 // DbgPrint(ex) Calls // // Enable flags for Kernel Events on Vista and above // #define EVENT_TRACE_FLAG_PROCESS_COUNTERS 0x00000008 // process perf counters #define EVENT_TRACE_FLAG_CSWITCH 0x00000010 // context switches #define EVENT_TRACE_FLAG_DPC 0x00000020 // deffered procedure calls #define EVENT_TRACE_FLAG_INTERRUPT 0x00000040 // interrupts #define EVENT_TRACE_FLAG_SYSTEMCALL 0x00000080 // system calls #define EVENT_TRACE_FLAG_DISK_IO_INIT 0x00000400 // physical disk IO initiation #define EVENT_TRACE_FLAG_ALPC 0x00100000 // ALPC traces #define EVENT_TRACE_FLAG_SPLIT_IO 0x00200000 // split io traces (VolumeManager) #define EVENT_TRACE_FLAG_DRIVER 0x00800000 // driver delays #define EVENT_TRACE_FLAG_PROFILE 0x01000000 // sample based profiling #define EVENT_TRACE_FLAG_FILE_IO 0x02000000 // file IO #define EVENT_TRACE_FLAG_FILE_IO_INIT 0x04000000 // file IO initiation #define EVENT_TRACE_FLAG_PMC_PROFILE 0x80000000 // sample based profiling (PMC) - NOT CONFIRMED! // // Enable flags for Kernel Events on Win7 and above // #define EVENT_TRACE_FLAG_DISPATCHER 0x00000800 // scheduler (ReadyThread) #define EVENT_TRACE_FLAG_VIRTUAL_ALLOC 0x00004000 // VM operations // // Enable flags for Kernel Events on Win8 and above // #define EVENT_TRACE_FLAG_VAMAP 0x00008000 // map/unmap (excluding images) #define EVENT_TRACE_FLAG_NO_SYSCONFIG 0x10000000 // Do not do sys config rundown /////////////////////////////////////////////////////////////////////////////// #pragma warning(push) #pragma warning (disable:4201) #ifndef EVENT_DESCRIPTOR_DEF #define EVENT_DESCRIPTOR_DEF typedef struct _EVENT_DESCRIPTOR { USHORT Id; UCHAR Version; UCHAR Channel; UCHAR Level; UCHAR Opcode; USHORT Task; ULONGLONG Keyword; } EVENT_DESCRIPTOR, *PEVENT_DESCRIPTOR; typedef const EVENT_DESCRIPTOR *PCEVENT_DESCRIPTOR; #endif /////////////////////////////////////////////////////////////////////////////// #ifndef EVENT_HEADER_DEF #define EVENT_HEADER_DEF typedef struct _EVENT_HEADER { USHORT Size; USHORT HeaderType; USHORT Flags; USHORT EventProperty; ULONG ThreadId; ULONG ProcessId; LARGE_INTEGER TimeStamp; GUID ProviderId; EVENT_DESCRIPTOR EventDescriptor; union { struct { ULONG KernelTime; ULONG UserTime; } DUMMYSTRUCTNAME; ULONG64 ProcessorTime; } DUMMYUNIONNAME; GUID ActivityId; } EVENT_HEADER, *PEVENT_HEADER; #endif /////////////////////////////////////////////////////////////////////////////// #ifndef EVENT_HEADER_EXTENDED_DATA_ITEM_DEF #define EVENT_HEADER_EXTENDED_DATA_ITEM_DEF typedef struct _EVENT_HEADER_EXTENDED_DATA_ITEM { USHORT Reserved1; // Reserved for internal use USHORT ExtType; // Extended info type struct { USHORT Linkage : 1; // Indicates additional extended // data item USHORT Reserved2 : 15; }; USHORT DataSize; // Size of extended info data ULONGLONG DataPtr; // Pointer to extended info data } EVENT_HEADER_EXTENDED_DATA_ITEM, *PEVENT_HEADER_EXTENDED_DATA_ITEM; #endif /////////////////////////////////////////////////////////////////////////////// #ifndef ETW_BUFFER_CONTEXT_DEF #define ETW_BUFFER_CONTEXT_DEF typedef struct _ETW_BUFFER_CONTEXT { union { struct { UCHAR ProcessorNumber; UCHAR Alignment; } DUMMYSTRUCTNAME; USHORT ProcessorIndex; } DUMMYUNIONNAME; USHORT LoggerId; } ETW_BUFFER_CONTEXT, *PETW_BUFFER_CONTEXT; #endif /////////////////////////////////////////////////////////////////////////////// #ifndef EVENT_RECORD_DEF #define EVENT_RECORD_DEF typedef struct _EVENT_RECORD { EVENT_HEADER EventHeader; ETW_BUFFER_CONTEXT BufferContext; USHORT ExtendedDataCount; USHORT UserDataLength; PEVENT_HEADER_EXTENDED_DATA_ITEM ExtendedData; PVOID UserData; PVOID UserContext; } EVENT_RECORD, *PEVENT_RECORD; #endif /////////////////////////////////////////////////////////////////////////////// typedef struct _EVENT_TRACE_PROPERTIES { WNODE_HEADER Wnode; // // data provided by caller ULONG BufferSize; // buffer size for logging (kbytes) ULONG MinimumBuffers; // minimum to preallocate ULONG MaximumBuffers; // maximum buffers allowed ULONG MaximumFileSize; // maximum logfile size (in MBytes) ULONG LogFileMode; // sequential, circular ULONG FlushTimer; // buffer flush timer, in seconds ULONG EnableFlags; // trace enable flags union { LONG AgeLimit; // unused LONG FlushThreshold; // Number of buffers to fill before flushing } DUMMYUNIONNAME; // data returned to caller ULONG NumberOfBuffers; // no of buffers in use ULONG FreeBuffers; // no of buffers free ULONG EventsLost; // event records lost ULONG BuffersWritten; // no of buffers written to file ULONG LogBuffersLost; // no of logfile write failures ULONG RealTimeBuffersLost; // no of rt delivery failures HANDLE LoggerThreadId; // thread id of Logger ULONG LogFileNameOffset; // Offset to LogFileName ULONG LoggerNameOffset; // Offset to LoggerName } EVENT_TRACE_PROPERTIES, *PEVENT_TRACE_PROPERTIES; typedef struct _EVENT_TRACE_HEADER { // overlays WNODE_HEADER USHORT Size; // Size of entire record union { USHORT FieldTypeFlags; // Indicates valid fields struct { UCHAR HeaderType; // Header type - internal use only UCHAR MarkerFlags; // Marker - internal use only } DUMMYSTRUCTNAME; } DUMMYUNIONNAME; union { ULONG Version; struct { UCHAR Type; // event type UCHAR Level; // trace instrumentation level USHORT Version; // version of trace record } Class; } DUMMYUNIONNAME2; ULONG ThreadId; // Thread Id ULONG ProcessId; // Process Id LARGE_INTEGER TimeStamp; // time when event happens union { GUID Guid; // Guid that identifies event ULONGLONG GuidPtr; // use with WNODE_FLAG_USE_GUID_PTR } DUMMYUNIONNAME3; union { struct { ULONG KernelTime; // Kernel Mode CPU ticks ULONG UserTime; // User mode CPU ticks } DUMMYSTRUCTNAME; ULONG64 ProcessorTime; // Processor Clock struct { ULONG ClientContext; // Reserved ULONG Flags; // Event Flags } DUMMYSTRUCTNAME2; } DUMMYUNIONNAME4; } EVENT_TRACE_HEADER, *PEVENT_TRACE_HEADER; typedef struct _EVENT_TRACE { EVENT_TRACE_HEADER Header; // Event trace header ULONG InstanceId; // Instance Id of this event ULONG ParentInstanceId; // Parent Instance Id. GUID ParentGuid; // Parent Guid; PVOID MofData; // Pointer to Variable Data ULONG MofLength; // Variable Datablock Length union { ULONG ClientContext; ETW_BUFFER_CONTEXT BufferContext; } DUMMYUNIONNAME; } EVENT_TRACE, *PEVENT_TRACE; typedef struct _TRACE_LOGFILE_HEADER { ULONG BufferSize; // Logger buffer size in Kbytes union { ULONG Version; // Logger version struct { UCHAR MajorVersion; UCHAR MinorVersion; UCHAR SubVersion; UCHAR SubMinorVersion; } VersionDetail; } DUMMYUNIONNAME; ULONG ProviderVersion; // defaults to NT version ULONG NumberOfProcessors; // Number of Processors LARGE_INTEGER EndTime; // Time when logger stops ULONG TimerResolution; // assumes timer is constant!!! ULONG MaximumFileSize; // Maximum in Mbytes ULONG LogFileMode; // specify logfile mode ULONG BuffersWritten; // used to file start of Circular File union { GUID LogInstanceGuid; // For RealTime Buffer Delivery struct { ULONG StartBuffers; // Count of buffers written at start. ULONG PointerSize; // Size of pointer type in bits ULONG EventsLost; // Events losts during log session ULONG CpuSpeedInMHz; // Cpu Speed in MHz } DUMMYSTRUCTNAME; } DUMMYUNIONNAME2; #if defined(_WMIKM_) PWCHAR LoggerName; PWCHAR LogFileName; RTL_TIME_ZONE_INFORMATION TimeZone; #else LPWSTR LoggerName; LPWSTR LogFileName; TIME_ZONE_INFORMATION TimeZone; #endif LARGE_INTEGER BootTime; LARGE_INTEGER PerfFreq; // Reserved LARGE_INTEGER StartTime; // Reserved ULONG ReservedFlags; // ClockType ULONG BuffersLost; } TRACE_LOGFILE_HEADER, *PTRACE_LOGFILE_HEADER; typedef enum _TRACE_QUERY_INFO_CLASS { TraceGuidQueryList, TraceGuidQueryInfo, TraceGuidQueryProcess, TraceStackTracingInfo, // Win7 TraceSystemTraceEnableFlagsInfo, TraceSampledProfileIntervalInfo, TraceProfileSourceConfigInfo, TraceProfileSourceListInfo, TracePmcEventListInfo, TracePmcCounterListInfo, MaxTraceSetInfoClass } TRACE_QUERY_INFO_CLASS, TRACE_INFO_CLASS; typedef struct _CLASSIC_EVENT_ID { GUID EventGuid; UCHAR Type; UCHAR Reserved[7]; } CLASSIC_EVENT_ID, *PCLASSIC_EVENT_ID; typedef struct _TRACE_PROFILE_INTERVAL { ULONG Source; ULONG Interval; } TRACE_PROFILE_INTERVAL, *PTRACE_PROFILE_INTERVAL; typedef struct _EVENT_TRACE_LOGFILEW EVENT_TRACE_LOGFILEW, *PEVENT_TRACE_LOGFILEW; typedef ULONG(WINAPI * PEVENT_TRACE_BUFFER_CALLBACKW) (PEVENT_TRACE_LOGFILEW Logfile); typedef VOID(WINAPI *PEVENT_CALLBACK)(PEVENT_TRACE pEvent); typedef struct _EVENT_RECORD EVENT_RECORD, *PEVENT_RECORD; typedef VOID(WINAPI *PEVENT_RECORD_CALLBACK) (PEVENT_RECORD EventRecord); struct _EVENT_TRACE_LOGFILEW { LPWSTR LogFileName; // Logfile Name LPWSTR LoggerName; // LoggerName LONGLONG CurrentTime; // timestamp of last event ULONG BuffersRead; // buffers read to date union { // Mode of the logfile ULONG LogFileMode; // Processing flags used on Vista and above ULONG ProcessTraceMode; } DUMMYUNIONNAME; EVENT_TRACE CurrentEvent; // Current Event from this stream. TRACE_LOGFILE_HEADER LogfileHeader; // logfile header structure PEVENT_TRACE_BUFFER_CALLBACKW // callback before each buffer BufferCallback; // is read // // following variables are filled for BufferCallback. // ULONG BufferSize; ULONG Filled; ULONG EventsLost; // // following needs to be propaged to each buffer // union { // Callback with EVENT_TRACE PEVENT_CALLBACK EventCallback; // Callback with EVENT_RECORD on Vista and above PEVENT_RECORD_CALLBACK EventRecordCallback; } DUMMYUNIONNAME2; ULONG IsKernelTrace; // TRUE for kernel logfile PVOID Context; // reserved for internal use }; #pragma warning(pop) #define PEVENT_TRACE_BUFFER_CALLBACK PEVENT_TRACE_BUFFER_CALLBACKW #define EVENT_TRACE_LOGFILE EVENT_TRACE_LOGFILEW #define PEVENT_TRACE_LOGFILE PEVENT_TRACE_LOGFILEW #define KERNEL_LOGGER_NAME KERNEL_LOGGER_NAMEW #define GLOBAL_LOGGER_NAME GLOBAL_LOGGER_NAMEW #define EVENT_LOGGER_NAME EVENT_LOGGER_NAMEW EXTERN_C ULONG WMIAPI ProcessTrace( _In_reads_(HandleCount) PTRACEHANDLE HandleArray, _In_ ULONG HandleCount, _In_opt_ LPFILETIME StartTime, _In_opt_ LPFILETIME EndTime ); EXTERN_C ULONG WMIAPI StartTraceW( _Out_ PTRACEHANDLE TraceHandle, _In_ LPCWSTR InstanceName, _Inout_ PEVENT_TRACE_PROPERTIES Properties ); EXTERN_C ULONG WMIAPI ControlTraceW( _In_ TRACEHANDLE TraceHandle, _In_opt_ LPCWSTR InstanceName, _Inout_ PEVENT_TRACE_PROPERTIES Properties, _In_ ULONG ControlCode ); EXTERN_C TRACEHANDLE WMIAPI OpenTraceW( _Inout_ PEVENT_TRACE_LOGFILEW Logfile ); EXTERN_C ULONG WMIAPI CloseTrace( _In_ TRACEHANDLE TraceHandle ); EXTERN_C ULONG WMIAPI TraceSetInformation( _In_ TRACEHANDLE SessionHandle, _In_ TRACE_INFO_CLASS InformationClass, _In_reads_bytes_(InformationLength) PVOID TraceInformation, _In_ ULONG InformationLength ); EXTERN_C ULONG WMIAPI TraceQueryInformation( _In_ TRACEHANDLE SessionHandle, _In_ TRACE_INFO_CLASS InformationClass, _Out_writes_bytes_(InformationLength) PVOID TraceInformation, _In_ ULONG InformationLength, _Out_opt_ PULONG ReturnLength ); ////////////////////////////////////////////////////////////////////////// #define RegisterTraceGuids RegisterTraceGuidsW #define StartTrace StartTraceW #define ControlTrace ControlTraceW #define StopTrace StopTraceW #define QueryTrace QueryTraceW #define UpdateTrace UpdateTraceW #define FlushTrace FlushTraceW #define QueryAllTraces QueryAllTracesW #define OpenTrace OpenTraceW ////////////////////////////////////////////////////////////////////////// #else #define INITGUID // Causes definition of SystemTraceControlGuid in evntrace.h. #include #include #include #include #endif //DECLARE_ETW namespace Optick { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class ETW : public Trace { static const int ETW_BUFFER_SIZE = 1024 << 10; // 1Mb static const int ETW_BUFFER_COUNT = 32; static const int ETW_MAXIMUM_SESSION_NAME = 1024; EVENT_TRACE_PROPERTIES *traceProperties; EVENT_TRACE_LOGFILE logFile; TRACEHANDLE traceSessionHandle; TRACEHANDLE openedHandle; HANDLE processThreadHandle; DWORD currentProcessId; bool isActive; static DWORD WINAPI RunProcessTraceThreadFunction(LPVOID parameter); static void AdjustPrivileges(); unordered_map syscallDescriptions; void ResolveSysCalls(); public: unordered_set activeThreadsIDs; ETW(); ~ETW(); virtual CaptureStatus::Type Start(Mode::Type mode, int frequency, const ThreadList& threads) override; virtual bool Stop() override; DWORD GetProcessID() const { return currentProcessId; } }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// struct CSwitch { // New thread ID after the switch. uint32 NewThreadId; // Previous thread ID. uint32 OldThreadId; // Thread priority of the new thread. int8 NewThreadPriority; // Thread priority of the previous thread. int8 OldThreadPriority; //The index of the C-state that was last used by the processor. A value of 0 represents the lightest idle state with higher values representing deeper C-states. uint8 PreviousCState; // Not used. int8 SpareByte; // Wait reason for the previous thread. The following are the possible values: // 0 Executive // 1 FreePage // 2 PageIn // 3 PoolAllocation // 4 DelayExecution // 5 Suspended // 6 UserRequest // 7 WrExecutive // 8 WrFreePage // 9 WrPageIn // 10 WrPoolAllocation // 11 WrDelayExecution // 12 WrSuspended // 13 WrUserRequest // 14 WrEventPair // 15 WrQueue // 16 WrLpcReceive // 17 WrLpcReply // 18 WrVirtualMemory // 19 WrPageOut // 20 WrRendezvous // 21 WrKeyedEvent // 22 WrTerminated // 23 WrProcessInSwap // 24 WrCpuRateControl // 25 WrCalloutStack // 26 WrKernel // 27 WrResource // 28 WrPushLock // 29 WrMutex // 30 WrQuantumEnd // 31 WrDispatchInt // 32 WrPreempted // 33 WrYieldExecution // 34 WrFastMutex // 35 WrGuardedMutex // 36 WrRundown // 37 MaximumWaitReason int8 OldThreadWaitReason; // Wait mode for the previous thread. The following are the possible values: // 0 KernelMode // 1 UserMode int8 OldThreadWaitMode; // State of the previous thread. The following are the possible state values: // 0 Initialized // 1 Ready // 2 Running // 3 Standby // 4 Terminated // 5 Waiting // 6 Transition // 7 DeferredReady (added for Windows Server 2003) int8 OldThreadState; // Ideal wait time of the previous thread. int8 OldThreadWaitIdealProcessor; // Wait time for the new thread. uint32 NewThreadWaitTime; // Reserved. uint32 Reserved; static const byte OPCODE = 36; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// struct StackWalk_Event { // Original event time stamp from the event header uint64 EventTimeStamp; // The process identifier of the original event uint32 StackProcess; // The thread identifier of the original event uint32 StackThread; // Callstack head uint64 Stack0; static const byte OPCODE = 32; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// struct Thread_TypeGroup1 { // Process identifier of the thread involved in the event. uint32 ProcessId; // Thread identifier of the thread involved in the event. uint32 TThreadId; // Base address of the thread's stack. uint64 StackBase; // Limit of the thread's stack. uint64 StackLimit; // Base address of the thread's user-mode stack. uint64 UserStackBase; // Limit of the thread's user-mode stack. uint64 UserStackLimit; // The set of processors on which the thread is allowed to run. uint32 Affinity; // Starting address of the function to be executed by this thread. uint64 Win32StartAddr; // Thread environment block base address. uint64 TebBase; // Identifies the service if the thread is owned by a service; otherwise, zero. uint32 SubProcessTag; // The scheduler priority of the thread uint8 BasePriority; // A memory page priority hint for memory pages accessed by the thread. uint8 PagePriority; // An IO priority hint for scheduling IOs generated by the thread. uint8 IoPriority; // Not used. uint8 ThreadFlags; enum struct Opcode : uint8 { Start = 1, End = 2, DCStart = 3, DCEnd = 4, }; }; size_t GetSIDSize(uint8* ptr) { size_t result = 0; int sid = *((int*)ptr); if (sid != 0) { size_t tokenSize = 16; ptr += tokenSize; result += tokenSize; result += 8 + (4 * ((SID*)ptr)->SubAuthorityCount); } else { result = 4; } return result; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // https://github.com/Microsoft/perfview/blob/688a8564062d51321bbab53cd71d9e174a77d2ce/src/TraceEvent/TraceEvent.cs struct Process_TypeGroup1 { // The address of the process object in the kernel. uint64 UniqueProcessKey; // Global process identifier that you can use to identify a process. uint32 ProcessId; // Unique identifier of the process that creates this process. uint32 ParentId; // Unique identifier that an operating system generates when it creates a new session. uint32 SessionId; // Exit status of the stopped process. int32 ExitStatus; // The physical address of the page table of the process. uint64 DirectoryTableBase; // (?) uint8 Flags; // object UserSID; // string ImageFileName; // wstring CommandLine; static size_t GetSIDOffset(PEVENT_RECORD pEvent) { if (pEvent->EventHeader.EventDescriptor.Version >= 4) return 36; if (pEvent->EventHeader.EventDescriptor.Version == 3) return 32; return 24; } const char* GetProcessName(PEVENT_RECORD pEvent) const { OPTICK_ASSERT((pEvent->EventHeader.Flags & EVENT_HEADER_FLAG_64_BIT_HEADER) != 0, "32-bit is not supported! Disable OPTICK_ENABLE_TRACING on 32-bit platform if needed!"); size_t sidOffset = GetSIDOffset(pEvent); size_t sidSize = GetSIDSize((uint8*)this + sidOffset); return (char*)this + sidOffset + sidSize; } enum struct Opcode { Start = 1, End = 2, DCStart = 3, DCEnd = 4, Defunct = 39, }; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// struct SampledProfile { uint32 InstructionPointer; uint32 ThreadId; uint32 Count; static const byte OPCODE = 46; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// struct SysCallEnter { uintptr_t SysCallAddress; static const byte OPCODE = 51; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// struct SysCallExit { uint32 SysCallNtStatus; static const byte OPCODE = 52; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // ce1dbfb4-137e-4da6-87b0-3f59aa102cbc DEFINE_GUID(SampledProfileGuid, 0xce1dbfb4, 0x137e, 0x4da6, 0x87, 0xb0, 0x3f, 0x59, 0xaa, 0x10, 0x2c, 0xbc); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 3d6fa8d1-fe05-11d0-9dda-00c04fd7ba7c // https://docs.microsoft.com/en-us/windows/desktop/etw/thread DEFINE_GUID(ThreadGuid, 0x3d6fa8d1, 0xfe05, 0x11d0, 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 3d6fa8d0-fe05-11d0-9dda-00c04fd7ba7c // https://docs.microsoft.com/en-us/windows/desktop/etw/process DEFINE_GUID(ProcessGuid, 0x3d6fa8d0, 0xfe05, 0x11d0, 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const int MAX_CPU_CORES = 256; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// struct ETWRuntime { array activeCores; vector> activeSyscalls; ETWRuntime() { Reset(); } void Reset() { activeCores.fill(INVALID_THREAD_ID); activeSyscalls.resize(0);; } }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ETWRuntime g_ETWRuntime; ETW g_ETW; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void WINAPI OnRecordEvent(PEVENT_RECORD eventRecord) { //static uint8 cpuCoreIsExecutingThreadFromOurProcess[256] = { 0 }; const byte opcode = eventRecord->EventHeader.EventDescriptor.Opcode; if (opcode == CSwitch::OPCODE) { if (sizeof(CSwitch) == eventRecord->UserDataLength) { CSwitch* pSwitchEvent = (CSwitch*)eventRecord->UserData; SwitchContextDesc desc; desc.reason = pSwitchEvent->OldThreadWaitReason; desc.cpuId = eventRecord->BufferContext.ProcessorNumber; desc.oldThreadId = (uint64)pSwitchEvent->OldThreadId; desc.newThreadId = (uint64)pSwitchEvent->NewThreadId; desc.timestamp = eventRecord->EventHeader.TimeStamp.QuadPart; Core::Get().ReportSwitchContext(desc); // Assign ThreadID to the cores if (g_ETW.activeThreadsIDs.find(desc.newThreadId) != g_ETW.activeThreadsIDs.end()) { g_ETWRuntime.activeCores[desc.cpuId] = desc.newThreadId; } else if (g_ETW.activeThreadsIDs.find(desc.oldThreadId) != g_ETW.activeThreadsIDs.end()) { g_ETWRuntime.activeCores[desc.cpuId] = INVALID_THREAD_ID; } } } else if (opcode == StackWalk_Event::OPCODE) { if (eventRecord->UserData && eventRecord->UserDataLength >= sizeof(StackWalk_Event)) { //TODO: Support x86 windows kernels const size_t osKernelPtrSize = sizeof(uint64); StackWalk_Event* pStackWalkEvent = (StackWalk_Event*)eventRecord->UserData; uint32 count = 1 + (eventRecord->UserDataLength - sizeof(StackWalk_Event)) / osKernelPtrSize; if (count && pStackWalkEvent->StackThread != 0) { if (pStackWalkEvent->StackProcess == g_ETW.GetProcessID()) { CallstackDesc desc; desc.threadID = pStackWalkEvent->StackThread; desc.timestamp = pStackWalkEvent->EventTimeStamp; static_assert(osKernelPtrSize == sizeof(uint64), "Incompatible types!"); desc.callstack = &pStackWalkEvent->Stack0; desc.count = (uint8)count; Core::Get().ReportStackWalk(desc); } } } } else if (opcode == SampledProfile::OPCODE) { SampledProfile* pEvent = (SampledProfile*)eventRecord->UserData; OPTICK_UNUSED(pEvent); } else if (opcode == SysCallEnter::OPCODE) { if (eventRecord->UserDataLength >= sizeof(SysCallEnter)) { uint8_t cpuId = eventRecord->BufferContext.ProcessorNumber; uint64_t threadId = g_ETWRuntime.activeCores[cpuId]; if (threadId != INVALID_THREAD_ID) { SysCallEnter* pEventEnter = (SysCallEnter*)eventRecord->UserData; SysCallData& sysCall = Core::Get().syscallCollector.Add(); sysCall.start = eventRecord->EventHeader.TimeStamp.QuadPart; sysCall.finish = EventTime::INVALID_TIMESTAMP; sysCall.threadID = threadId; sysCall.id = pEventEnter->SysCallAddress; sysCall.description = nullptr; g_ETWRuntime.activeSyscalls.push_back(std::make_pair(cpuId, &sysCall)); } } } else if (opcode == SysCallExit::OPCODE) { if (eventRecord->UserDataLength >= sizeof(SysCallExit)) { uint8_t cpuId = eventRecord->BufferContext.ProcessorNumber; if (g_ETWRuntime.activeCores[cpuId] != INVALID_THREAD_ID) { for (int i = (int)g_ETWRuntime.activeSyscalls.size() - 1; i >= 0; --i) { if (g_ETWRuntime.activeSyscalls[i].first == cpuId) { g_ETWRuntime.activeSyscalls[i].second->finish = eventRecord->EventHeader.TimeStamp.QuadPart; g_ETWRuntime.activeSyscalls.erase(g_ETWRuntime.activeSyscalls.begin() + i); break; } } } } } else { // VS TODO: We might have a situation where a thread was deleted and the new thread was created with the same threadID // Ignoring for now - profiling sessions are quite short - not critical if (IsEqualGUID(eventRecord->EventHeader.ProviderId, ThreadGuid)) { if (eventRecord->UserDataLength >= sizeof(Thread_TypeGroup1)) { const Thread_TypeGroup1* pThreadEvent = (const Thread_TypeGroup1*)eventRecord->UserData; Core::Get().RegisterThreadDescription(ThreadDescription("", pThreadEvent->TThreadId, pThreadEvent->ProcessId, 1, pThreadEvent->BasePriority)); } } else if (IsEqualGUID(eventRecord->EventHeader.ProviderId, ProcessGuid)) { if (eventRecord->UserDataLength >= sizeof(Process_TypeGroup1)) { const Process_TypeGroup1* pProcessEvent = (const Process_TypeGroup1*)eventRecord->UserData; Core::Get().RegisterProcessDescription(ProcessDescription(pProcessEvent->GetProcessName(eventRecord), pProcessEvent->ProcessId, pProcessEvent->UniqueProcessKey)); } } } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// static ULONG WINAPI OnBufferRecord(_In_ PEVENT_TRACE_LOGFILE Buffer) { OPTICK_UNUSED(Buffer); return true; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const TRACEHANDLE INVALID_TRACEHANDLE = (TRACEHANDLE)-1; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// DWORD WINAPI ETW::RunProcessTraceThreadFunction(LPVOID parameter) { Core::Get().RegisterThreadDescription(ThreadDescription("[Optick] ETW", GetCurrentThreadId(), GetCurrentProcessId())); ETW* etw = (ETW*)parameter; ULONG status = ProcessTrace(&etw->openedHandle, 1, 0, 0); OPTICK_UNUSED(status); return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void ETW::AdjustPrivileges() { #if OPTICK_PC HANDLE token = 0; if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) { TOKEN_PRIVILEGES tokenPrivileges; memset(&tokenPrivileges, 0, sizeof(tokenPrivileges)); tokenPrivileges.PrivilegeCount = 1; tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; LookupPrivilegeValue(NULL, SE_SYSTEM_PROFILE_NAME, &tokenPrivileges.Privileges[0].Luid); AdjustTokenPrivileges(token, FALSE, &tokenPrivileges, 0, (PTOKEN_PRIVILEGES)NULL, 0); CloseHandle(token); } #endif } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void ETW::ResolveSysCalls() { if (SymbolEngine* symEngine = Platform::GetSymbolEngine()) { Core::Get().syscallCollector.syscallPool.ForEach([this, symEngine](SysCallData& data) { auto it = syscallDescriptions.find(data.id); if (it == syscallDescriptions.end()) { const Symbol* symbol = symEngine->GetSymbol(data.id); if (symbol != nullptr) { string name(symbol->function.begin(), symbol->function.end()); data.description = EventDescription::CreateShared(name.c_str(), "SysCall", (long)data.id); syscallDescriptions.insert(std::pair(data.id, data.description)); } } else { data.description = it->second; } }); } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ETW::ETW() : isActive(false) , traceSessionHandle(INVALID_TRACEHANDLE) , openedHandle(INVALID_TRACEHANDLE) , processThreadHandle(INVALID_HANDLE_VALUE) , traceProperties(nullptr) { currentProcessId = GetCurrentProcessId(); } CaptureStatus::Type ETW::Start(Mode::Type mode, int frequency, const ThreadList& threads) { if (!isActive) { AdjustPrivileges(); g_ETWRuntime.Reset(); activeThreadsIDs.clear(); for (auto it = threads.begin(); it != threads.end(); ++it) { ThreadEntry* entry = *it; if (entry->isAlive) { activeThreadsIDs.insert(entry->description.threadID); } } ULONG bufferSize = sizeof(EVENT_TRACE_PROPERTIES) + (ETW_MAXIMUM_SESSION_NAME + MAX_PATH) * sizeof(WCHAR); if (traceProperties == nullptr) traceProperties = (EVENT_TRACE_PROPERTIES*)Memory::Alloc(bufferSize); ZeroMemory(traceProperties, bufferSize); traceProperties->Wnode.BufferSize = bufferSize; traceProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); StringCchCopyW((LPWSTR)((PCHAR)traceProperties + traceProperties->LoggerNameOffset), ETW_MAXIMUM_SESSION_NAME, KERNEL_LOGGER_NAMEW); traceProperties->EnableFlags = 0; traceProperties->BufferSize = ETW_BUFFER_SIZE; traceProperties->MinimumBuffers = ETW_BUFFER_COUNT; if (mode & Mode::SWITCH_CONTEXT) { traceProperties->EnableFlags |= EVENT_TRACE_FLAG_CSWITCH; } if (mode & Mode::AUTOSAMPLING) { traceProperties->EnableFlags |= EVENT_TRACE_FLAG_PROFILE; } if (mode & Mode::SYS_CALLS) { traceProperties->EnableFlags |= EVENT_TRACE_FLAG_SYSTEMCALL; } if (mode & Mode::OTHER_PROCESSES) { traceProperties->EnableFlags |= EVENT_TRACE_FLAG_PROCESS; traceProperties->EnableFlags |= EVENT_TRACE_FLAG_THREAD; } traceProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; traceProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; // // https://msdn.microsoft.com/en-us/library/windows/desktop/aa364160(v=vs.85).aspx // Clock resolution = QPC traceProperties->Wnode.ClientContext = 1; traceProperties->Wnode.Guid = SystemTraceControlGuid; // ERROR_BAD_LENGTH(24): The Wnode.BufferSize member of Properties specifies an incorrect size. Properties does not have sufficient space allocated to hold a copy of SessionName. // ERROR_ALREADY_EXISTS(183): A session with the same name or GUID is already running. // ERROR_ACCESS_DENIED(5): Only users with administrative privileges, users in the Performance Log Users group, and services running as LocalSystem, LocalService, NetworkService can control event tracing sessions. // ERROR_INVALID_PARAMETER(87) // ERROR_BAD_PATHNAME(161) // ERROR_DISK_FULL(112) // ERROR_NO_SUCH_PRIVILEGE(1313) int retryCount = 4; ULONG status = CaptureStatus::OK; while (--retryCount >= 0) { status = StartTrace(&traceSessionHandle, KERNEL_LOGGER_NAME, traceProperties); switch (status) { case ERROR_NO_SUCH_PRIVILEGE: AdjustPrivileges(); break; case ERROR_ALREADY_EXISTS: ControlTrace(0, KERNEL_LOGGER_NAME, traceProperties, EVENT_TRACE_CONTROL_STOP); break; case ERROR_ACCESS_DENIED: return CaptureStatus::ERR_TRACER_ACCESS_DENIED; case ERROR_SUCCESS: retryCount = 0; break; default: return CaptureStatus::ERR_TRACER_FAILED; } } if (status != ERROR_SUCCESS) { return CaptureStatus::ERR_TRACER_FAILED; } CLASSIC_EVENT_ID callstackSamples[4]; int callstackCountSamplesCount = 0; if (mode & Mode::AUTOSAMPLING) { callstackSamples[callstackCountSamplesCount].EventGuid = SampledProfileGuid; callstackSamples[callstackCountSamplesCount].Type = SampledProfile::OPCODE; ++callstackCountSamplesCount; } if (mode & Mode::SYS_CALLS) { callstackSamples[callstackCountSamplesCount].EventGuid = SampledProfileGuid; callstackSamples[callstackCountSamplesCount].Type = SysCallEnter::OPCODE; ++callstackCountSamplesCount; } /* callstackSamples[callstackCountSamplesCount].EventGuid = CSwitchProfileGuid; callstackSamples[callstackCountSamplesCount].Type = CSwitch::OPCODE; ++callstackCountSamplesCount; */ /* https://msdn.microsoft.com/en-us/library/windows/desktop/dd392328%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 Typically, on 64-bit computers, you cannot capture the kernel stack in certain contexts when page faults are not allowed. To enable walking the kernel stack on x64, set the DisablePagingExecutive Memory Management registry value to 1. The DisablePagingExecutive registry value is located under the following registry key: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Memory Management */ if (callstackCountSamplesCount > 0) { status = TraceSetInformation(traceSessionHandle, TraceStackTracingInfo, &callstackSamples[0], sizeof(CLASSIC_EVENT_ID) * callstackCountSamplesCount); if (status != ERROR_SUCCESS) { OPTICK_FAILED("TraceSetInformation - failed"); return CaptureStatus::ERR_TRACER_FAILED; } } if (mode & Mode::AUTOSAMPLING) { TRACE_PROFILE_INTERVAL itnerval = { 0 }; memset(&itnerval, 0, sizeof(TRACE_PROFILE_INTERVAL)); int step = 10000 * 1000 / frequency; // 1ms = 10000 steps itnerval.Interval = step; // std::max(1221, std::min(step, 10000)); // The SessionHandle is irrelevant for this information class and must be zero, else the function returns ERROR_INVALID_PARAMETER. status = TraceSetInformation(0, TraceSampledProfileIntervalInfo, &itnerval, sizeof(TRACE_PROFILE_INTERVAL)); OPTICK_ASSERT(status == ERROR_SUCCESS, "TraceSetInformation - failed"); } ZeroMemory(&logFile, sizeof(EVENT_TRACE_LOGFILE)); logFile.LoggerName = KERNEL_LOGGER_NAME; logFile.ProcessTraceMode = (PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_RAW_TIMESTAMP); logFile.EventRecordCallback = OnRecordEvent; logFile.BufferCallback = OnBufferRecord; openedHandle = OpenTrace(&logFile); if (openedHandle == INVALID_TRACEHANDLE) { OPTICK_FAILED("OpenTrace - failed"); return CaptureStatus::ERR_TRACER_FAILED; } DWORD threadID; processThreadHandle = CreateThread(0, 0, RunProcessTraceThreadFunction, this, 0, &threadID); isActive = true; } return CaptureStatus::OK; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool ETW::Stop() { if (!isActive) { return false; } ULONG controlTraceResult = ControlTrace(openedHandle, KERNEL_LOGGER_NAME, traceProperties, EVENT_TRACE_CONTROL_STOP); // ERROR_CTX_CLOSE_PENDING(7007L): The call was successful. The ProcessTrace function will stop after it has processed all real-time events in its buffers (it will not receive any new events). // ERROR_BUSY(170L): Prior to Windows Vista, you cannot close the trace until the ProcessTrace function completes. // ERROR_INVALID_HANDLE(6L): One of the following is true: TraceHandle is NULL. TraceHandle is INVALID_HANDLE_VALUE. ULONG closeTraceStatus = CloseTrace(openedHandle); // Wait for ProcessThread to finish WaitForSingleObject(processThreadHandle, INFINITE); BOOL wasThreadClosed = CloseHandle(processThreadHandle); isActive = false; //VS TODO: Disabling resolving of the syscalls - we can't use then as EventDescriptions at the moment //ResolveSysCalls(); activeThreadsIDs.clear(); return wasThreadClosed && (closeTraceStatus == ERROR_SUCCESS) && (controlTraceResult == ERROR_SUCCESS); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ETW::~ETW() { Stop(); Memory::Free(traceProperties); traceProperties = nullptr; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// Trace* Platform::GetTrace() { return &g_ETW; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Symbol Resolving //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #define USE_DBG_HELP (OPTICK_PC) #if USE_DBG_HELP #include #pragma comment( lib, "DbgHelp.Lib" ) #endif #include "optick_serialization.h" #if OPTICK_PC #include #else // Forward declare kernel functions #pragma pack(push,8) typedef struct _MODULEINFO { LPVOID lpBaseOfDll; DWORD SizeOfImage; LPVOID EntryPoint; } MODULEINFO, *LPMODULEINFO; #pragma pack(pop) #ifndef EnumProcessModulesEx #define EnumProcessModulesEx K32EnumProcessModulesEx EXTERN_C DWORD WINAPI K32EnumProcessModulesEx(HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded, DWORD dwFilterFlag); #endif #ifndef GetModuleInformation #define GetModuleInformation K32GetModuleInformation EXTERN_C DWORD WINAPI K32GetModuleInformation(HANDLE hProcess, HMODULE hModule, LPMODULEINFO lpmodinfo, DWORD cb); #endif #ifndef GetModuleFileNameExA #define GetModuleFileNameExA K32GetModuleFileNameExA EXTERN_C DWORD WINAPI K32GetModuleFileNameExA(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize); #endif #endif namespace Optick { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //void ReportLastError() //{ // LPVOID lpMsgBuf; // DWORD dw = GetLastError(); // // FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, // NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // (LPTSTR)&lpMsgBuf, 0, NULL); // // MessageBox(NULL, (LPCTSTR)lpMsgBuf, TEXT("Error"), MB_OK); // LocalFree(lpMsgBuf); //} //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// typedef array CallStackBuffer; typedef unordered_map SymbolCache; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class WinSymbolEngine : public SymbolEngine { HANDLE hProcess; bool isInitialized; bool needRestorePreviousSettings; uint32 previousOptions; static const size_t MAX_SEARCH_PATH_LENGTH = 2048; char previousSearchPath[MAX_SEARCH_PATH_LENGTH]; SymbolCache cache; vector modules; void InitSystemModules(); void InitApplicationModules(); public: WinSymbolEngine(); ~WinSymbolEngine(); void Init(); void Close(); // Get Symbol from PDB file virtual const Symbol * GetSymbol(uint64 dwAddress) override; virtual const vector& GetModules() override; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// WinSymbolEngine::WinSymbolEngine() : isInitialized(false), hProcess(GetCurrentProcess()), needRestorePreviousSettings(false), previousOptions(0) { } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// WinSymbolEngine::~WinSymbolEngine() { Close(); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const Symbol* WinSymbolEngine::GetSymbol(uint64 address) { if (address == 0) return nullptr; Init(); Symbol& symbol = cache[address]; if (symbol.address != 0) return &symbol; if (!isInitialized) return nullptr; symbol.address = address; #if USE_DBG_HELP DWORD64 dwAddress = static_cast(address); // FileName and Line IMAGEHLP_LINEW64 lineInfo; memset(&lineInfo, 0, sizeof(IMAGEHLP_LINEW64)); lineInfo.SizeOfStruct = sizeof(lineInfo); DWORD dwDisp; if (SymGetLineFromAddrW64(hProcess, dwAddress, &dwDisp, &lineInfo)) { symbol.file = lineInfo.FileName; symbol.line = lineInfo.LineNumber; } const size_t length = (sizeof(SYMBOL_INFOW) + MAX_SYM_NAME * sizeof(WCHAR) + sizeof(ULONG64) - 1) / sizeof(ULONG64) + 1; // Function Name ULONG64 buffer[length]; PSYMBOL_INFOW dbgSymbol = (PSYMBOL_INFOW)buffer; memset(dbgSymbol, 0, sizeof(buffer)); dbgSymbol->SizeOfStruct = sizeof(SYMBOL_INFOW); dbgSymbol->MaxNameLen = MAX_SYM_NAME; DWORD64 offset = 0; if (SymFromAddrW(hProcess, dwAddress, &offset, dbgSymbol)) { symbol.function.resize(dbgSymbol->NameLen); memcpy(&symbol.function[0], &dbgSymbol->Name[0], sizeof(WCHAR) * dbgSymbol->NameLen); } symbol.offset = static_cast(offset); #endif return &symbol; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const vector& WinSymbolEngine::GetModules() { if (modules.empty()) { InitSystemModules(); InitApplicationModules(); } return modules; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // const char* USER_SYMBOL_SEARCH_PATH = "http://msdl.microsoft.com/download/symbols"; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void WinSymbolEngine::Init() { if (!isInitialized) { #if USE_DBG_HELP previousOptions = SymGetOptions(); memset(previousSearchPath, 0, MAX_SEARCH_PATH_LENGTH); SymGetSearchPath(hProcess, previousSearchPath, MAX_SEARCH_PATH_LENGTH); SymSetOptions(SymGetOptions() | SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME | SYMOPT_INCLUDE_32BIT_MODULES | SYMOPT_LOAD_ANYTHING); if (!SymInitialize(hProcess, NULL, TRUE)) { needRestorePreviousSettings = true; SymCleanup(hProcess); if (SymInitialize(hProcess, NULL, TRUE)) isInitialized = true; } else { isInitialized = true; } const vector& loadedModules = GetModules(); for (size_t i = 0; i < loadedModules.size(); ++i) { const Module& module = loadedModules[i]; SymLoadModule64(hProcess, NULL, module.path.c_str(), NULL, (DWORD64)module.address, (DWORD)module.size); } #else isInitialized = true; #endif } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// typedef DWORD(__stdcall *pZwQuerySystemInformation)(DWORD, LPVOID, DWORD, DWORD*); #define SystemModuleInformation 11 // SYSTEMINFOCLASS #define MAXIMUM_FILENAME_LENGTH 256 struct SYSTEM_MODULE_INFORMATION { DWORD reserved1; DWORD reserved2; PVOID mappedBase; PVOID imageBase; DWORD imageSize; DWORD flags; WORD loadOrderIndex; WORD initOrderIndex; WORD loadCount; WORD moduleNameOffset; CHAR imageName[MAXIMUM_FILENAME_LENGTH]; }; #pragma warning (push) #pragma warning(disable : 4200) struct MODULE_LIST { DWORD dwModules; SYSTEM_MODULE_INFORMATION pModulesInfo[]; }; #pragma warning (pop) void WinSymbolEngine::InitSystemModules() { ULONG returnLength = 0; ULONG systemInformationLength = 0; MODULE_LIST* pModuleList = nullptr; #pragma warning (push) #pragma warning(disable : 4191) pZwQuerySystemInformation ZwQuerySystemInformation = (pZwQuerySystemInformation)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "ZwQuerySystemInformation"); #pragma warning (pop) ZwQuerySystemInformation(SystemModuleInformation, pModuleList, systemInformationLength, &returnLength); systemInformationLength = returnLength; pModuleList = (MODULE_LIST*)Memory::Alloc(systemInformationLength); DWORD status = ZwQuerySystemInformation(SystemModuleInformation, pModuleList, systemInformationLength, &returnLength); if (status == ERROR_SUCCESS) { char systemRootPath[MAXIMUM_FILENAME_LENGTH] = { 0 }; #if OPTICK_PC ExpandEnvironmentStringsA("%SystemRoot%", systemRootPath, MAXIMUM_FILENAME_LENGTH); #else strcpy_s(systemRootPath, "C:\\Windows"); #endif const char* systemRootPattern = "\\SystemRoot"; modules.reserve(modules.size() + pModuleList->dwModules); for (uint32_t i = 0; i < pModuleList->dwModules; ++i) { SYSTEM_MODULE_INFORMATION& module = pModuleList->pModulesInfo[i]; char path[MAXIMUM_FILENAME_LENGTH] = { 0 }; if (strstr(module.imageName, systemRootPattern) == module.imageName) { strcpy_s(path, systemRootPath); strcat_s(path, module.imageName + strlen(systemRootPattern)); } else { strcpy_s(path, module.imageName); } modules.push_back(Module(path, (void*)module.imageBase, module.imageSize)); } } else { OPTICK_FAILED("Can't query System Module Information!"); } if (pModuleList) { Memory::Free(pModuleList); } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void WinSymbolEngine::InitApplicationModules() { HANDLE processHandle = GetCurrentProcess(); HMODULE hModules[256]; DWORD modulesSize = 0; EnumProcessModulesEx(processHandle, hModules, sizeof(hModules), &modulesSize, 0); int moduleCount = modulesSize / sizeof(HMODULE); modules.reserve(modules.size() + moduleCount); for (int i = 0; i < moduleCount; ++i) { MODULEINFO info = { 0 }; if (GetModuleInformation(processHandle, hModules[i], &info, sizeof(MODULEINFO))) { char name[MAX_PATH] = "UnknownModule"; GetModuleFileNameExA(processHandle, hModules[i], name, MAX_PATH); modules.push_back(Module(name, info.lpBaseOfDll, info.SizeOfImage)); } } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void WinSymbolEngine::Close() { if (isInitialized) { #if USE_DBG_HELP SymCleanup(hProcess); if (needRestorePreviousSettings) { HANDLE currentProcess = GetCurrentProcess(); SymSetOptions(previousOptions); SymSetSearchPath(currentProcess, previousSearchPath); SymInitialize(currentProcess, NULL, TRUE); needRestorePreviousSettings = false; } #endif modules.clear(); isInitialized = false; } } ////////////////////////////////////////////////////////////////////////// SymbolEngine* Platform::GetSymbolEngine() { static WinSymbolEngine pdbSymbolEngine; return &pdbSymbolEngine; } ////////////////////////////////////////////////////////////////////////// } #endif //OPTICK_ENABLE_TRACING #endif //USE_OPTICK #endif //_MSC_VER