--- /dev/null
+cmake_minimum_required(VERSION 2.8.12.2)
+
+project(CoreProfiler)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wno-null-conversion")
+
+set(CLR_CMAKE_PLATFORM_UNIX 1)
+
+# Architecture specific files folder name
+if (CLR_CMAKE_TARGET_ARCH_AMD64)
+ set(ARCH_SOURCES_DIR amd64)
+ set(CLR_CMAKE_PLATFORM_UNIX_AMD64)
+ set(LIB64 TRUE)
+elseif (CLR_CMAKE_TARGET_ARCH_ARM64)
+ set(ARCH_SOURCES_DIR arm64)
+ set(CLR_CMAKE_PLATFORM_UNIX_ARM64)
+ set(LIB64 TRUE)
+elseif (CLR_CMAKE_TARGET_ARCH_ARM)
+ set(ARCH_SOURCES_DIR arm)
+ set(CLR_CMAKE_PLATFORM_UNIX_ARM)
+ set(LIB64 FALSE)
+elseif (CLR_CMAKE_TARGET_ARCH_I386)
+ set(ARCH_SOURCES_DIR i386)
+ set(CLR_CMAKE_PLATFORM_UNIX_I386)
+ set(LIB64 FALSE)
+else ()
+ clr_unknown_arch()
+endif ()
+
+include(clrdefinitions.cmake)
+include(compileoptions.cmake)
+
+add_subdirectory(src)
--- /dev/null
+if (CLR_CMAKE_TARGET_ARCH_AMD64)
+ if (CLR_CMAKE_PLATFORM_UNIX)
+ add_definitions(-DDBG_TARGET_AMD64_UNIX)
+ endif()
+ add_definitions(-D_TARGET_AMD64_=1)
+ add_definitions(-DDBG_TARGET_64BIT=1)
+ add_definitions(-DDBG_TARGET_AMD64=1)
+ add_definitions(-DDBG_TARGET_WIN64=1)
+ add_definitions(-D_AMD64_)
+ add_definitions(-D_WIN64)
+ add_definitions(-DAMD64)
+ add_definitions(-DBIT64=1)
+elseif (CLR_CMAKE_TARGET_ARCH_ARM64)
+ if (CLR_CMAKE_PLATFORM_UNIX)
+ add_definitions(-DDBG_TARGET_ARM64_UNIX)
+ endif()
+ add_definitions(-D_TARGET_ARM64_=1)
+ add_definitions(-DDBG_TARGET_64BIT=1)
+ add_definitions(-DDBG_TARGET_ARM64=1)
+ add_definitions(-DDBG_TARGET_WIN64=1)
+ add_definitions(-DFEATURE_MULTIREG_RETURN)
+ add_definitions(-D_ARM64_)
+ add_definitions(-DARM64)
+ add_definitions(-D_WIN64)
+ add_definitions(-DBIT64=1)
+elseif (CLR_CMAKE_TARGET_ARCH_ARM)
+ if (CLR_CMAKE_PLATFORM_UNIX)
+ add_definitions(-DDBG_TARGET_ARM_UNIX)
+ elseif (WIN32 AND NOT DEFINED CLR_CROSS_COMPONENTS_BUILD)
+ # Set this to ensure we can use Arm SDK for Desktop binary linkage when doing native (Arm32) build
+ add_definitions(-D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE=1)
+ add_definitions(-D_ARM_WORKAROUND_)
+ endif (CLR_CMAKE_PLATFORM_UNIX)
+ add_definitions(-D_TARGET_ARM_=1)
+ add_definitions(-DDBG_TARGET_32BIT=1)
+ add_definitions(-DDBG_TARGET_ARM=1)
+ add_definitions(-D_ARM_)
+ add_definitions(-DARM)
+elseif (CLR_CMAKE_TARGET_ARCH_I386)
+ add_definitions(-D_TARGET_X86_=1)
+ add_definitions(-DDBG_TARGET_32BIT=1)
+ add_definitions(-DDBG_TARGET_X86=1)
+ add_definitions(-D_X86_)
+else ()
+ clr_unknown_arch()
+endif (CLR_CMAKE_TARGET_ARCH_AMD64)
+
--- /dev/null
+# Disable frame pointer optimizations so profilers can get better call stacks
+add_compile_options(-fno-omit-frame-pointer)
+
+# The -fms-extensions enable the stuff like __if_exists, __declspec(uuid()), etc.
+add_compile_options(-fms-extensions )
+#-fms-compatibility Enable full Microsoft Visual C++ compatibility
+#-fms-extensions Accept some non-standard constructs supported by the Microsoft compiler
+
+# Make signed arithmetic overflow of addition, subtraction, and multiplication wrap around
+# using twos-complement representation (this is normally undefined according to the C++ spec).
+add_compile_options(-fwrapv)
+
+add_definitions(-DDISABLE_CONTRACTS)
+# The -ferror-limit is helpful during the porting, it makes sure the compiler doesn't stop
+# after hitting just about 20 errors.
+add_compile_options(-ferror-limit=4096)
+
+# All warnings that are not explicitly disabled are reported as errors
+add_compile_options(-Werror)
+
+# Disabled warnings
+add_compile_options(-Wno-unused-private-field)
+add_compile_options(-Wno-unused-variable)
+# Explicit constructor calls are not supported by clang (this->ClassName::ClassName())
+add_compile_options(-Wno-microsoft)
+# This warning is caused by comparing 'this' to NULL
+add_compile_options(-Wno-tautological-compare)
+# There are constants of type BOOL used in a condition. But BOOL is defined as int
+# and so the compiler thinks that there is a mistake.
+add_compile_options(-Wno-constant-logical-operand)
+
+add_compile_options(-Wno-unknown-warning-option)
+
+#These seem to indicate real issues
+add_compile_options(-Wno-invalid-offsetof)
+# The following warning indicates that an attribute __attribute__((__ms_struct__)) was applied
+# to a struct or a class that has virtual members or a base class. In that case, clang
+# may not generate the same object layout as MSVC.
+add_compile_options(-Wno-incompatible-ms-struct)
+
+# Some architectures (e.g., ARM) assume char type is unsigned while CoreCLR assumes char is signed
+# as x64 does. It has been causing issues in ARM (https://github.com/dotnet/coreclr/issues/4746)
+add_compile_options(-fsigned-char)
+
+if(CLR_CMAKE_PLATFORM_UNIX_ARM)
+ # Because we don't use CMAKE_C_COMPILER/CMAKE_CXX_COMPILER to use clang
+ # we have to set the triple by adding a compiler argument
+ add_compile_options(-mthumb)
+ add_compile_options(-mfpu=vfpv3)
+ if(ARM_SOFTFP)
+ add_definitions(-DARM_SOFTFP)
+ add_compile_options(-mfloat-abi=softfp)
+ add_compile_options(-target armv7-linux-gnueabi)
+ else()
+ add_compile_options(-target armv7-linux-gnueabihf)
+ endif(ARM_SOFTFP)
+endif(CLR_CMAKE_PLATFORM_UNIX_ARM)
--- /dev/null
+add_definitions(-DPAL_STDCPP_COMPAT)
+
+include_directories(
+ BEFORE
+ .
+ config
+ info
+ misc
+ stacktrace
+ storage
+ sync
+ trace
+)
+
+if(CLR_CMAKE_TARGET_ARCH_ARM)
+ include_directories("${CLR_SRC_DIR}/src/pal/src/include")
+endif()
+
+set(CLR_INCLUDE_DIR
+ ${CLR_BIN_DIR}/inc
+ ${CLR_SRC_DIR}/src/pal/inc/rt
+ ${CLR_SRC_DIR}/src/pal/prebuilt/inc
+ ${CLR_SRC_DIR}/src/pal/inc
+ ${CLR_SRC_DIR}/src/inc)
+
+include_directories(AFTER ${CLR_INCLUDE_DIR})
+
+set(PROFILER_SOURCES_COMMON
+ classfactory.cpp
+ config/commonconfigconversions.cpp
+ config/environmentconfigprovider.cpp
+ config/loggerconfig.cpp
+ config/loggerconfigconversions.cpp
+ config/profilerconfig.cpp
+ config/profilerconfigconversions.cpp
+ config/tracelogconfig.cpp
+ config/tracelogconfigconversions.cpp
+ dllmain.cpp
+ info/classinfo.cpp
+ info/functioninfo.cpp
+ misc/localtime.cpp
+ misc/sigaction.cpp
+ profiler.cpp
+ profilerinfo.cpp
+ profilermanager.cpp
+ sync/shared_mutex.cpp
+ trace/basetrace.cpp
+ trace/commontrace.cpp
+ trace/cputrace.cpp
+ trace/executiontrace.cpp
+ trace/eventchannel.cpp
+ trace/memorytrace.cpp
+ tracelog.cpp
+)
+
+if(CLR_CMAKE_TARGET_ARCH_AMD64)
+ set(PROFILER_SOURCES_ASM
+ ${ARCH_SOURCES_DIR}/asmhelpers.S
+ ${ARCH_SOURCES_DIR}/archhelpers.cpp
+ )
+elseif(CLR_CMAKE_TARGET_ARCH_ARM)
+ set(PROFILER_SOURCES_ASM
+ ${ARCH_SOURCES_DIR}/asmhelpers.S
+ ${ARCH_SOURCES_DIR}/archhelpers.cpp
+ )
+elseif(CLR_CMAKE_TARGET_ARCH_I386)
+ set(PROFILER_SOURCES_ASM
+ ${ARCH_SOURCES_DIR}/asmhelpers.S
+ ${ARCH_SOURCES_DIR}/archhelpers.cpp
+ )
+endif()
+
+add_library(coreprof
+ SHARED
+ ${PROFILER_SOURCES_COMMON}
+ ${PROFILER_SOURCES_ASM}
+)
+
+set(PROFILER_LINK_LIBRARIES
+ ${CLR_BIN_DIR}/lib/libcorguids.a
+ # utilcodestaticnohost
+ # gcinfo
+)
+
+if(CLR_CMAKE_PLATFORM_UNIX)
+ list(APPEND PROFILER_LINK_LIBRARIES
+ # mscorrc_debug
+ ${CLR_BIN_DIR}/lib/libcoreclrpal.a
+ ${CLR_BIN_DIR}/lib/libpalrt.a
+ )
+endif(CLR_CMAKE_PLATFORM_UNIX)
+
+target_link_libraries(coreprof
+ ${PROFILER_LINK_LIBRARIES}
+)
+
+if(LIB64)
+ set(LIBSUFFIX 64)
+else(LIB64)
+ set(LIBSUFFIX "")
+endif()
+
+install(TARGETS coreprof DESTINATION ${CMAKE_INSTALL_PREFIX}/lib${LIBSUFFIX})
--- /dev/null
+#include <sys/ucontext.h>
+
+#include <cor.h>
+#include <corhdr.h>
+#include <corprof.h>
+
+HRESULT ContextToStackSnapshotContext(
+ const void *context, CONTEXT *winContext) noexcept
+{
+ _ASSERTE(context != nullptr && winContext != nullptr);
+
+ *winContext = {CONTEXT_INTEGER};
+ const mcontext_t *mc =
+ &(reinterpret_cast<const ucontext_t*>(context))->uc_mcontext;
+
+ {
+ winContext->Rax = mc->gregs[REG_RAX];
+ winContext->Rbx = mc->gregs[REG_RBX];
+ winContext->Rcx = mc->gregs[REG_RCX];
+ winContext->Rdx = mc->gregs[REG_RDX];
+ winContext->Rsi = mc->gregs[REG_RSI];
+ winContext->Rdi = mc->gregs[REG_RDI];
+ winContext->Rbp = mc->gregs[REG_RBP];
+ winContext->Rsp = mc->gregs[REG_RSP];
+ winContext->R8 = mc->gregs[REG_R8];
+ winContext->R9 = mc->gregs[REG_R9];
+ winContext->R10 = mc->gregs[REG_R10];
+ winContext->R11 = mc->gregs[REG_R11];
+ winContext->R12 = mc->gregs[REG_R12];
+ winContext->R13 = mc->gregs[REG_R13];
+ winContext->R14 = mc->gregs[REG_R14];
+ winContext->R15 = mc->gregs[REG_R15];
+ winContext->EFlags = mc->gregs[REG_EFL];
+ winContext->Rip = mc->gregs[REG_RIP];
+ winContext->SegCs = (*((WORD *)mc->gregs[REG_CSGSFS] + 0));
+ winContext->SegGs = (*((WORD *)mc->gregs[REG_CSGSFS] + 1));
+ winContext->SegFs = (*((WORD *)mc->gregs[REG_CSGSFS] + 2));
+ }
+
+ return S_OK;
+}
\ No newline at end of file
--- /dev/null
+.intel_syntax noprefix
+#include "unixasmmacros.inc"
+
+//
+// EXTERN_C void EnterNaked3(FunctionIDOrClientID functionIDOrClientID);
+//
+NESTED_ENTRY EnterNaked3, _TEXT, NoHandler
+ push rbp
+ mov rbp, rsp
+ push rax
+ push rbx
+ push rcx
+ push rdx
+ push rsi
+ push rdi
+ push r8
+ push r9
+ push r10
+ push r11
+ push r12
+ push r13
+ push r14
+ push r15
+ sub rsp, 16*8 /* xmm0-xmm7 */
+ and rsp, (-16) /* must be alligned */
+ movdqa [rsp+0x00], xmm0
+ movdqa [rsp+0x10], xmm1
+ movdqa [rsp+0x20], xmm2
+ movdqa [rsp+0x30], xmm3
+ movdqa [rsp+0x40], xmm4
+ movdqa [rsp+0x50], xmm5
+ movdqa [rsp+0x60], xmm6
+ movdqa [rsp+0x70], xmm7
+ mov rdi, r14
+ call EXTERNAL_C_FUNC(EnterStub)
+ movdqa xmm0, [rsp+0x00]
+ movdqa xmm1, [rsp+0x10]
+ movdqa xmm2, [rsp+0x20]
+ movdqa xmm3, [rsp+0x30]
+ movdqa xmm4, [rsp+0x40]
+ movdqa xmm5, [rsp+0x50]
+ movdqa xmm6, [rsp+0x60]
+ movdqa xmm7, [rsp+0x70]
+ lea rsp, [rbp - (14 * 8)]
+ pop r15
+ pop r14
+ pop r13
+ pop r12
+ pop r11
+ pop r10
+ pop r9
+ pop r8
+ pop rdi
+ pop rsi
+ pop rdx
+ pop rcx
+ pop rbx
+ pop rax
+ pop rbp
+ ret
+NESTED_END EnterNaked3, _TEXT
+
+//
+// EXTERN_C void LeaveNaked3(FunctionIDOrClientID functionIDOrClientID);
+//
+NESTED_ENTRY LeaveNaked3, _TEXT, NoHandler
+ push rbp
+ mov rbp, rsp
+ push rax
+ push rbx
+ push rcx
+ push rdx
+ push rsi
+ push rdi
+ push r8
+ push r9
+ push r10
+ push r11
+ push r12
+ push r13
+ push r14
+ push r15
+ sub rsp, 16*8 /* xmm0-xmm7 */
+ and rsp, (-16) /* must be alligned */
+ movdqa [rsp+0x00], xmm0
+ movdqa [rsp+0x10], xmm1
+ movdqa [rsp+0x20], xmm2
+ movdqa [rsp+0x30], xmm3
+ movdqa [rsp+0x40], xmm4
+ movdqa [rsp+0x50], xmm5
+ movdqa [rsp+0x60], xmm6
+ movdqa [rsp+0x70], xmm7
+
+ call EXTERNAL_C_FUNC(LeaveStub)
+ movdqa xmm0, [rsp+0x00]
+ movdqa xmm1, [rsp+0x10]
+ movdqa xmm2, [rsp+0x20]
+ movdqa xmm3, [rsp+0x30]
+ movdqa xmm4, [rsp+0x40]
+ movdqa xmm5, [rsp+0x50]
+ movdqa xmm6, [rsp+0x60]
+ movdqa xmm7, [rsp+0x70]
+ lea rsp, [rbp - (14 * 8)]
+ pop r15
+ pop r14
+ pop r13
+ pop r12
+ pop r11
+ pop r10
+ pop r9
+ pop r8
+ pop rdi
+ pop rsi
+ pop rdx
+ pop rcx
+ pop rbx
+ pop rax
+ pop rbp
+ ret
+NESTED_END LeaveNaked3, _TEXT
+
+//
+// EXTERN_C void TailcallNaked3(FunctionIDOrClientID functionIDOrClientID);
+//
+NESTED_ENTRY TailcallNaked3, _TEXT, NoHandler
+ push rbp
+ mov rbp, rsp
+ push rax
+ push rbx
+ push rcx
+ push rdx
+ push rsi
+ push rdi
+ push r8
+ push r9
+ push r10
+ push r11
+ push r12
+ push r13
+ push r14
+ push r15
+ sub rsp, 16*8 /* xmm0-xmm7 */
+ and rsp, (-16) /* must be alligned */
+ movdqa [rsp+0x00], xmm0
+ movdqa [rsp+0x10], xmm1
+ movdqa [rsp+0x20], xmm2
+ movdqa [rsp+0x30], xmm3
+ movdqa [rsp+0x40], xmm4
+ movdqa [rsp+0x50], xmm5
+ movdqa [rsp+0x60], xmm6
+ movdqa [rsp+0x70], xmm7
+
+ call EXTERNAL_C_FUNC(TailcallStub)
+ movdqa xmm0, [rsp+0x00]
+ movdqa xmm1, [rsp+0x10]
+ movdqa xmm2, [rsp+0x20]
+ movdqa xmm3, [rsp+0x30]
+ movdqa xmm4, [rsp+0x40]
+ movdqa xmm5, [rsp+0x50]
+ movdqa xmm6, [rsp+0x60]
+ movdqa xmm7, [rsp+0x70]
+ lea rsp, [rbp - (14 * 8)]
+ pop r15
+ pop r14
+ pop r13
+ pop r12
+ pop r11
+ pop r10
+ pop r9
+ pop r8
+ pop rdi
+ pop rsi
+ pop rdx
+ pop rcx
+ pop rbx
+ pop rax
+ pop rbp
+ ret
+NESTED_END TailcallNaked3, _TEXT
--- /dev/null
+#include <sys/ucontext.h>
+
+#include <cor.h>
+#include <corhdr.h>
+#include <corprof.h>
+
+HRESULT ContextToStackSnapshotContext(
+ const void *context, CONTEXT *winContext) noexcept
+{
+ _ASSERTE(context != nullptr && winContext != nullptr);
+
+ *winContext = {CONTEXT_INTEGER};
+ const mcontext_t *mc =
+ &(reinterpret_cast<const ucontext_t*>(context))->uc_mcontext;
+
+ {
+ winContext->R0 = mc->arm_r0;
+ winContext->R1 = mc->arm_r1;
+ winContext->R2 = mc->arm_r2;
+ winContext->R3 = mc->arm_r3;
+ winContext->R4 = mc->arm_r4;
+ winContext->R5 = mc->arm_r5;
+ winContext->R6 = mc->arm_r6;
+ winContext->R7 = mc->arm_r7;
+ winContext->R8 = mc->arm_r8;
+ winContext->R9 = mc->arm_r9;
+ winContext->R10 = mc->arm_r10;
+ winContext->R11 = mc->arm_fp;
+ winContext->R12 = mc->arm_ip;
+ winContext->Sp = mc->arm_sp;
+ winContext->Lr = mc->arm_lr;
+ winContext->Pc = mc->arm_pc;
+ }
+
+ return S_OK;
+}
--- /dev/null
+#include "unixasmmacros.inc"
+
+.syntax unified
+.thumb
+
+//
+// EXTERN_C void EnterNaked3(FunctionIDOrClientID functionIDOrClientID);
+//
+NESTED_ENTRY EnterNaked3, _TEXT, NoHandler
+ push {r0-r7, lr}
+ .save {r0-r7, lr}
+ bl C_FUNC(EnterStub)
+ pop {r0-r7, pc}
+NESTED_END EnterNaked3, _TEXT
+
+//
+// EXTERN_C void LeaveNaked3(FunctionIDOrClientID functionIDOrClientID);
+//
+NESTED_ENTRY LeaveNaked3, _TEXT, NoHandler
+ push {r0-r7, lr}
+ .save {r0-r7, lr}
+ bl C_FUNC(LeaveStub)
+ pop {r0-r7, pc}
+NESTED_END LeaveNaked3, _TEXT
+
+//
+// EXTERN_C void TailcallNaked3(FunctionIDOrClientID functionIDOrClientID);
+//
+NESTED_ENTRY TailcallNaked3, _TEXT, NoHandler
+ push {r0-r7, lr}
+ .save {r0-r7, lr}
+ bl C_FUNC(TailcallStub)
+ pop {r0-r7, pc}
+NESTED_END TailcallNaked3, _TEXT
+
+NESTED_ENTRY getPrevPC, _TEXT, NoHandler
+ ldr r0, [r11, #4]
+ bx lr
+NESTED_END getPrevPC, _TEXT
--- /dev/null
+#include "classfactory.h"
+
+CClassFactory::CClassFactory(PFN_CREATE_OBJ pfnCreateObject)
+ : m_cRef(1)
+ , m_pfnCreateObject(pfnCreateObject)
+{
+}
+
+CClassFactory::~CClassFactory()
+{
+}
+
+HRESULT STDMETHODCALLTYPE CClassFactory::QueryInterface(
+ REFIID riid,
+ void **ppvObject)
+{
+ if (ppvObject == nullptr)
+ return E_POINTER;
+
+ // Pick the right v-table based on the IID passed in.
+ if (riid == IID_IClassFactory)
+ {
+ *ppvObject = static_cast<IClassFactory*>(this);
+ }
+ else if (riid == IID_IUnknown)
+ {
+ *ppvObject = static_cast<IUnknown*>(this);
+ }
+ else
+ {
+ *ppvObject = nullptr;
+ return E_NOINTERFACE;
+ }
+
+ // If successful, add a reference for out pointer and return.
+ this->AddRef();
+
+ return S_OK;
+}
+
+ULONG STDMETHODCALLTYPE CClassFactory::AddRef(void)
+{
+ return InterlockedIncrement(&m_cRef);
+}
+
+ULONG STDMETHODCALLTYPE CClassFactory::Release(void)
+{
+ LONG cRef = InterlockedDecrement(&m_cRef);
+ if (cRef == 0)
+ delete this;
+
+ return cRef;
+}
+
+HRESULT STDMETHODCALLTYPE CClassFactory::CreateInstance(
+ IUnknown *pUnkOuter,
+ REFIID riid,
+ void **ppvObject)
+{
+ // Avoid confusion.
+ *ppvObject = NULL;
+
+ // Aggregation is not supported by these objects.
+ if (pUnkOuter != NULL)
+ return CLASS_E_NOAGGREGATION;
+
+ // Ask the object to create an instance of itself, and check the iid.
+ return (*m_pfnCreateObject)(riid, ppvObject);
+}
+
+HRESULT STDMETHODCALLTYPE CClassFactory::LockServer(BOOL fLock)
+{
+ // NOTE: not need to implement.
+ return E_FAIL;
+}
--- /dev/null
+#ifndef _CLASS_FACTORY_H_
+#define _CLASS_FACTORY_H_
+
+#include <unknwn.h>
+
+// This typedef is for a function which will create a new instance of an object.
+typedef HRESULT (*PFN_CREATE_OBJ)(REFIID riid, void **ppvObject);
+
+// One class factory object satifies all of our clsid's, to reduce overall
+// code bloat.
+class CClassFactory : public IClassFactory
+{
+public:
+ CClassFactory(PFN_CREATE_OBJ pfnCreateObject);
+
+private:
+ CClassFactory(); // Can't use without data.
+
+ virtual ~CClassFactory(); // Factory should be destroyed through public API.
+
+public:
+ //
+ // IUnknown methods.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(
+ REFIID riid,
+ void **ppvObject);
+
+ virtual ULONG STDMETHODCALLTYPE AddRef();
+
+ virtual ULONG STDMETHODCALLTYPE Release();
+
+ //
+ // IClassFactory methods.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE CreateInstance(
+ IUnknown *pUnkOuter,
+ REFIID riid,
+ void **ppvObject);
+
+ virtual HRESULT STDMETHODCALLTYPE LockServer(
+ BOOL fLock);
+
+private:
+ LONG m_cRef; // Reference count.
+ PFN_CREATE_OBJ m_pfnCreateObject; // Creation function for an instance.
+};
+
+#endif // _CLASS_FACTORY_H_
--- /dev/null
+#ifndef _COMMON_CONFIG_H_
+#define _COMMON_CONFIG_H_
+
+#include <stdexcept>
+
+//
+// Exceptions of type bad_conversion are thrown when value of some type
+// can't be converted to another type. Exception should contain description
+// of reason why convertion can't be performed.
+//
+class bad_conversion : public std::runtime_error {
+ using std::runtime_error::runtime_error;
+};
+
+//
+// Exceptions of type config_error are thrown during configuration is updated
+// to prevent data inconsistency. It reports about errors in update process.
+// Exception should contain description of the problem that causes update
+// interruption.
+//
+// Also this type of exceptions is used in validation process.
+//
+class config_error : public std::runtime_error {
+ using std::runtime_error::runtime_error;
+};
+
+//
+// Common declaration of convert() template function specialized for various
+// types. Function returns its argument converted to Target type that should
+// be specified as template parameter.
+//
+template<typename Target, typename Source>
+Target convert(Source);
+
+#endif // _COMMON_CONFIG_H_
--- /dev/null
+#include <string>
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <strings.h>
+
+#include "commonconfigconversions.h"
+
+template<>
+bool convert(const char *str)
+{
+ if (
+ strcmp (str, "1") == 0 ||
+ strcasecmp(str, "true") == 0 ||
+ strcasecmp(str, "on") == 0 ||
+ strcasecmp(str, "yes") == 0 ||
+ strcasecmp(str, "enabled") == 0 ||
+ strcasecmp(str, "") == 0
+ )
+ {
+ return true;
+ }
+ else if (
+ strcmp (str, "0") == 0 ||
+ strcasecmp(str, "false") == 0 ||
+ strcasecmp(str, "off") == 0 ||
+ strcasecmp(str, "no") == 0 ||
+ strcasecmp(str, "disabled") == 0
+ )
+ {
+ return false;
+ }
+ else
+ {
+ throw bad_conversion("incorrect value for type bool");
+ }
+}
+
+template<>
+unsigned long convert(const char *str)
+{
+ unsigned long value;
+ char *str_end;
+
+ errno = 0;
+ value = strtoul(str, &str_end, 0);
+
+ if (errno == ERANGE)
+ {
+ throw bad_conversion("is out of range for unsigned long");
+ }
+
+ if (str == str_end)
+ {
+ throw bad_conversion("incorrect value for type unsigned long");
+ }
+
+ if (*str_end != '\0')
+ {
+ throw bad_conversion("contains not number symbols");
+ }
+
+ return value;
+}
+
+template<>
+std::string convert(const char *str)
+{
+ return str;
+}
+
+template<>
+const char *convert(bool value)
+{
+ return value ? "T" : "F";
+}
--- /dev/null
+#ifndef _COMMON_CONFIG_CONVERSIONS_H_
+#define _COMMON_CONFIG_CONVERSIONS_H_
+
+#include "commonconfig.h"
+
+template<>
+bool convert(const char *str);
+
+template<>
+unsigned long convert(const char *str);
+
+template<>
+std::string convert(const char *str);
+
+template<>
+const char *convert(bool value);
+
+#endif // _COMMON_CONFIG_CONVERSIONS_H_
--- /dev/null
+#include <stdexcept>
+#include <sstream>
+
+#include <stdlib.h>
+
+#include "commonconfig.h"
+#include "commonconfigconversions.h"
+#include "loggerconfigconversions.h"
+#include "tracelogconfigconversions.h"
+#include "profilerconfigconversions.h"
+#include "environmentconfigprovider.h"
+
+template<typename T>
+bool EnvironmentConfigProvider::FetchValue(const char *name, T &value) const
+{
+ const char *env = getenv(name);
+ if (env)
+ {
+ try
+ {
+ value = convert<T>(env);
+ }
+ catch (const std::runtime_error &e)
+ {
+ std::stringstream ss;
+ ss << "variable " << name << "=" << env <<
+ " can't be parsed: " << e.what();
+ throw config_error(ss.str());
+ }
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void EnvironmentConfigProvider::FetchConfig(LoggerConfig &config) const
+{
+ // Save current configuration to temporary.
+ LoggerConfig new_config(config);
+
+ //
+ // Fetching from the environment can cause exceptions.
+ //
+
+ FetchValue("PROF_LOG_LEVEL", new_config.Level);
+
+ bool file_name_specified =
+ FetchValue("PROF_LOG_FILENAME", new_config.FileName);
+ if (file_name_specified)
+ {
+ new_config.OutputStream = LoggerOutputStream::File;
+ }
+
+ FetchValue("PROF_LOG_STREAM", new_config.OutputStream);
+
+ // Apply changes to the current configuration.
+ config = new_config;
+}
+
+void EnvironmentConfigProvider::FetchConfig(TraceLogConfig &config) const
+{
+ // Save current configuration to temporary.
+ TraceLogConfig new_config(config);
+
+ //
+ // Fetching from the environment can cause exceptions.
+ //
+
+ bool file_name_specified =
+ FetchValue("PROF_TRACE_FILENAME", new_config.FileName);
+ if (file_name_specified)
+ {
+ new_config.OutputStream = TraceLogOutputStream::File;
+ }
+
+ FetchValue("PROF_TRACE_STREAM", new_config.OutputStream);
+
+ // Apply changes to the current configuration.
+ config = new_config;
+}
+
+void EnvironmentConfigProvider::FetchConfig(ProfilerConfig &config) const
+{
+ // Save current configuration to temporary.
+ ProfilerConfig new_config(config);
+
+ //
+ // Fetching from the environment can cause exceptions.
+ //
+ // We don't check whether the environment variables override the
+ // configuration or not.
+ //
+
+ FetchValue("PROF_COLLECT_METHOD", new_config.CollectionMethod);
+ if (new_config.CollectionMethod != CollectionMethod::Sampling)
+ {
+ new_config.SamplingTimeoutMs = 0;
+ new_config.HighGranularityEnabled = false;
+ }
+ if (FetchValue("PROF_SAMPLING_TIMEOUT", new_config.SamplingTimeoutMs))
+ {
+ new_config.CpuTraceTimeoutMs = new_config.SamplingTimeoutMs;
+ }
+ FetchValue("PROF_HIGH_GRAN", new_config.HighGranularityEnabled);
+ FetchValue("PROF_DELAYED_START", new_config.TracingSuspendedOnStart);
+ FetchValue("PROF_LINE_TRACE", new_config.LineTraceEnabled);
+
+ bool CpuTraceEnabled;
+ if (FetchValue("PROF_CPU_TRACE", CpuTraceEnabled))
+ {
+ new_config.CpuTraceProcessEnabled = CpuTraceEnabled;
+ new_config.CpuTraceThreadEnabled = CpuTraceEnabled;
+ }
+ FetchValue("PROF_CPU_TRACE_PROC", new_config.CpuTraceProcessEnabled);
+ FetchValue("PROF_CPU_TRACE_THREAD", new_config.CpuTraceThreadEnabled);
+ if (!new_config.CpuTraceProcessEnabled && !new_config.CpuTraceThreadEnabled)
+ {
+ new_config.CpuTraceTimeoutMs = 0;
+ }
+ FetchValue("PROF_CPU_TRACE_TIMEOUT", new_config.CpuTraceTimeoutMs);
+
+ FetchValue("PROF_EXECUTION_TRACE", new_config.ExecutionTraceEnabled);
+ FetchValue("PROF_MEMORY_TRACE", new_config.MemoryTraceEnabled);
+ if (!new_config.MemoryTraceEnabled)
+ {
+ new_config.StackTrackingEnabled = false;
+ }
+
+ FetchValue("PROF_STACK_TRACK", new_config.StackTrackingEnabled);
+
+ // Apply changes to the current configuration.
+ config = new_config;
+}
--- /dev/null
+#ifndef _ENVIRONMENT_CONFIG_PROVIDER_H_
+#define _ENVIRONMENT_CONFIG_PROVIDER_H_
+
+#include "loggerconfig.h"
+#include "tracelogconfig.h"
+#include "profilerconfig.h"
+
+class EnvironmentConfigProvider
+{
+private:
+ // Internal template method that takes name of the environment variable and
+ // tries to fetch it and convert to type T. If the value of the variable
+ // is incorrect, bad_conversion exception should be thrown.
+ //
+ // FetchValue() will return true on success convertion or false if specified
+ // variable is not presented.
+ template<typename T>
+ bool FetchValue(const char *name, T &value) const;
+
+public:
+ //
+ // FetchConfig() overrides the configuration with values fetched from the
+ // environment. Other values are not changed. The configuration wouldn't
+ // chang if exception were thrown. Error messages of runtime exceptions are
+ // complemented by information about the variable that caused the problem.
+ // config_error class is used for this exceptions.
+ //
+
+ void FetchConfig(LoggerConfig &config) const;
+
+ void FetchConfig(TraceLogConfig &config) const;
+
+ void FetchConfig(ProfilerConfig &config) const;
+};
+
+#endif // _ENVIRONMENT_CONFIG_PROVIDER_H_
--- /dev/null
+#include "commonconfig.h"
+#include "loggerconfig.h"
+
+//
+// Configuration parameters should be assigned to its defaults here.
+//
+LoggerConfig::LoggerConfig()
+ : Level(LogLevel::Warn)
+ , OutputStream(LoggerOutputStream::Stdout)
+ , FileName()
+{}
+
+void LoggerConfig::Validate()
+{
+ if (OutputStream == LoggerOutputStream::File && FileName.empty())
+ {
+ throw config_error("file name is required for file output");
+ }
+}
+
+std::vector<std::string> LoggerConfig::Verify()
+{
+ std::vector<std::string> warnings;
+
+ if (!FileName.empty() && OutputStream != LoggerOutputStream::File)
+ {
+ warnings.push_back("file name is ignored for non-file output");
+ }
+
+ return warnings;
+}
+
+const char *LoggerConfig::Name()
+{
+ return "Logger configuration";
+}
--- /dev/null
+#ifndef _LOGGER_CONFIG_H_
+#define _LOGGER_CONFIG_H_
+
+#include <vector>
+#include <string>
+
+#include "log.h"
+
+//
+// The LoggerConfig structure describes configuration of the Logger.
+//
+
+enum class LoggerOutputStream
+{
+ Stdout,
+ Stderr,
+ File,
+};
+
+struct LoggerConfig
+{
+ // Creates configuration with default values.
+ LoggerConfig();
+
+ //
+ // Common options.
+ //
+
+ // The level of the Logger verbosity. Can be: None, Fatal, Error, Warn,
+ // Info, Debug, Trace, All. Each next level of verbosity enables all
+ // previous levels.
+ LogLevel Level;
+
+ // An output stream where the Logger puts information. Can be: Stdout,
+ // Stderr, File.
+ LoggerOutputStream OutputStream;
+
+ //
+ // File Output options.
+ //
+
+ // The name of the file or path to which is used by the Logger to store
+ // information when File output stream is used.
+ std::string FileName;
+
+ // TODO: other settings for File Output.
+
+ //
+ // Validation and verification.
+ //
+
+ void Validate();
+
+ std::vector<std::string> Verify();
+
+ const char *Name();
+};
+
+#endif // _LOGGER_CONFIG_H_
--- /dev/null
+#include <stdlib.h>
+#include <errno.h>
+
+#include <strings.h>
+
+#include "loggerconfigconversions.h"
+
+template<>
+LogLevel convert(const char *str)
+{
+ if (strcasecmp(str, "None") == 0)
+ {
+ return LogLevel::None;
+ }
+ else if (strcasecmp(str, "Fatal") == 0)
+ {
+ return LogLevel::Fatal;
+ }
+ else if (strcasecmp(str, "Error") == 0)
+ {
+ return LogLevel::Error;
+ }
+ else if (strcasecmp(str, "Warn") == 0)
+ {
+ return LogLevel::Warn;
+ }
+ else if (strcasecmp(str, "Info") == 0)
+ {
+ return LogLevel::Info;
+ }
+ else if (strcasecmp(str, "Debug") == 0)
+ {
+ return LogLevel::Debug;
+ }
+ else if (strcasecmp(str, "Trace") == 0)
+ {
+ return LogLevel::Trace;
+ }
+ else if (strcasecmp(str, "All") == 0)
+ {
+ return LogLevel::All;
+ }
+
+ // Trying number values.
+
+ long value;
+ char *str_end;
+
+ errno = 0;
+ value = strtol(str, &str_end, 0);
+
+ if (errno == ERANGE)
+ {
+ throw bad_conversion("is out of range");
+ }
+
+ if (str == str_end)
+ {
+ throw bad_conversion("incorrect value for type LogLevel");
+ }
+
+ if (*str_end != '\0')
+ {
+ throw bad_conversion("contains not number symbols");
+ }
+
+ if (value < static_cast<long>(LogLevel::None) ||
+ value > static_cast<long>(LogLevel::All))
+ {
+ throw bad_conversion("is out of range");
+ }
+
+ return static_cast<LogLevel>(value);
+}
+
+template<>
+LoggerOutputStream convert(const char *str)
+{
+ if (strcasecmp(str, "Stdout") == 0)
+ {
+ return LoggerOutputStream::Stdout;
+ }
+ else if (strcasecmp(str, "Stderr") == 0)
+ {
+ return LoggerOutputStream::Stderr;
+ }
+ else if (strcasecmp(str, "File") == 0)
+ {
+ return LoggerOutputStream::File;
+ }
+ else
+ {
+ throw bad_conversion("incorrect value for type LoggerOutputStream");
+ }
+}
--- /dev/null
+#ifndef _LOGGER_CONFIG_CONVERSIONS_H_
+#define _LOGGER_CONFIG_CONVERSIONS_H_
+
+#include "loggerconfig.h"
+#include "commonconfig.h"
+
+template<>
+LogLevel convert(const char *str);
+
+template<>
+LoggerOutputStream convert(const char *str);
+
+#endif // _LOGGER_CONFIG_CONVERSIONS_H_
--- /dev/null
+#include <assert.h>
+
+#include "commonconfig.h"
+#include "profilerconfig.h"
+
+//
+// Configuration parameters should be assigned to its defaults here.
+//
+ProfilerConfig::ProfilerConfig()
+ : CollectionMethod(CollectionMethod::None)
+ , SamplingTimeoutMs(10)
+ , HighGranularityEnabled(true)
+ , TracingSuspendedOnStart(false)
+ , LineTraceEnabled(false)
+ , CpuTraceProcessEnabled(false)
+ , CpuTraceThreadEnabled(false)
+ , CpuTraceTimeoutMs(10)
+ , ExecutionTraceEnabled(false)
+ , MemoryTraceEnabled(false)
+ , StackTrackingEnabled(true)
+{}
+
+void ProfilerConfig::Validate()
+{
+ if (CollectionMethod == CollectionMethod::Sampling)
+ {
+ if (SamplingTimeoutMs == 0)
+ {
+ throw config_error("sampling timeout should be non-zero");
+ }
+ }
+
+ if (CpuTraceProcessEnabled || CpuTraceThreadEnabled)
+ {
+ if (CpuTraceTimeoutMs == 0)
+ {
+ throw config_error("CPU tracing timeout should be non-zero");
+ }
+ }
+}
+
+std::vector<std::string> ProfilerConfig::Verify()
+{
+ std::vector<std::string> warnings;
+
+ if (CollectionMethod != CollectionMethod::Instrumentation)
+ {
+ // Instrumentation specific options verification.
+ }
+ else
+ {
+ if (StackTrackingEnabled)
+ {
+ warnings.push_back(
+ "stack tracking option is redundant for instrumentation");
+ }
+ }
+
+ if (CollectionMethod != CollectionMethod::Sampling)
+ {
+ // Sampling specific options verification.
+
+ if (SamplingTimeoutMs != 0)
+ {
+ warnings.push_back(
+ "sampling timeout specification requires sampling");
+ }
+
+ if (HighGranularityEnabled)
+ {
+ // We don't show this message if sampling have been required for
+ // line tracing above.
+ warnings.push_back("hight granularity option requires sampling");
+ }
+ }
+
+ if (CollectionMethod == CollectionMethod::None)
+ {
+ // Common options verification.
+
+ if (LineTraceEnabled)
+ {
+ warnings.push_back(
+ "line tracing requires sampling or instrumentation");
+ }
+ }
+
+ if (!CpuTraceProcessEnabled && !CpuTraceThreadEnabled)
+ {
+ // CPU Trace specific options verification.
+
+ if (CpuTraceTimeoutMs != 0)
+ {
+ warnings.push_back(
+ "CPU tracing timeout specified when tracing disabled");
+ }
+ }
+
+ if (!ExecutionTraceEnabled && !MemoryTraceEnabled)
+ {
+ // When all traces are disabled.
+
+ if (CollectionMethod != CollectionMethod::None)
+ {
+ warnings.push_back(
+ "collection method specification requires execution or "
+ "memory tracing");
+ }
+
+ if (LineTraceEnabled)
+ {
+ warnings.push_back(
+ "line tracing requires execution or memory tracing");
+ }
+ }
+
+ if (!MemoryTraceEnabled)
+ {
+ if (StackTrackingEnabled)
+ {
+ warnings.push_back("stack tracking is memory tracing option");
+ }
+ }
+
+ return warnings;
+}
+
+const char *ProfilerConfig::Name()
+{
+ return "Profiler configuration";
+}
--- /dev/null
+#ifndef _PROFILER_CONFIG_H_
+#define _PROFILER_CONFIG_H_
+
+#include <vector>
+#include <string>
+
+//
+// The ProfilerConfig structure describes configuration of the Profiler.
+//
+// NOTE: structure of the configuration can be changed when appropriate logic
+// will be implemented.
+//
+// TODO: configuration parameters should be described after they will be used
+// in the implementation.
+//
+
+enum class CollectionMethod
+{
+ None,
+ Instrumentation,
+ Sampling,
+};
+
+struct ProfilerConfig
+{
+ // Creates configuration with default values.
+ ProfilerConfig();
+
+ //
+ // Common Trace features.
+ //
+
+ CollectionMethod CollectionMethod;
+ unsigned long SamplingTimeoutMs;
+ bool HighGranularityEnabled;
+ bool TracingSuspendedOnStart;
+ bool LineTraceEnabled;
+
+ //
+ // CPU Trace features.
+ //
+
+ bool CpuTraceProcessEnabled;
+ bool CpuTraceThreadEnabled;
+ unsigned long CpuTraceTimeoutMs;
+
+ //
+ // Execution Trace features.
+ //
+
+ bool ExecutionTraceEnabled;
+
+ //
+ // Memory Trace features.
+ //
+
+ bool MemoryTraceEnabled;
+ bool StackTrackingEnabled;
+
+ //
+ // Validation and verification.
+ //
+
+ void Validate();
+
+ std::vector<std::string> Verify();
+
+ const char *Name();
+};
+
+#endif // _PROFILER_CONFIG_H_
--- /dev/null
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+
+#include "profilerconfigconversions.h"
+
+template<>
+CollectionMethod convert(const char *str)
+{
+ if (strcasecmp(str, "None") == 0 || strlen(str) == 0)
+ {
+ return CollectionMethod::None;
+ }
+ else if (strcasecmp(str, "Instrumentation") == 0)
+ {
+ return CollectionMethod::Instrumentation;
+ }
+ else if (strcasecmp(str, "Sampling") == 0)
+ {
+ return CollectionMethod::Sampling;
+ }
+ else
+ {
+ throw bad_conversion("incorrect value for type CollectionMethod");
+ }
+}
+
+template<>
+const char *convert(CollectionMethod method)
+{
+ switch (method)
+ {
+ case CollectionMethod::None:
+ return "None";
+
+ case CollectionMethod::Instrumentation:
+ return "Instrumentation";
+
+ case CollectionMethod::Sampling:
+ return "Sampling";
+
+ default:
+ assert(!"Unreachable");
+ return "UNKNOWN";
+ }
+}
--- /dev/null
+#ifndef _PROFILER_CONFIG_CONVERSIONS_H_
+#define _PROFILER_CONFIG_CONVERSIONS_H_
+
+#include "profilerconfig.h"
+#include "commonconfig.h"
+
+template<>
+CollectionMethod convert(const char *str);
+
+template<>
+const char *convert(CollectionMethod method);
+
+#endif // _PROFILER_CONFIG_CONVERSIONS_H_
--- /dev/null
+#include "commonconfig.h"
+#include "tracelogconfig.h"
+
+//
+// Configuration parameters should be assigned to its defaults here.
+//
+TraceLogConfig::TraceLogConfig()
+ : OutputStream(TraceLogOutputStream::Stdout)
+ , FileName()
+{}
+
+void TraceLogConfig::Validate()
+{
+ if (OutputStream == TraceLogOutputStream::File && FileName.empty())
+ {
+ throw config_error("file name is required for file output");
+ }
+}
+
+std::vector<std::string> TraceLogConfig::Verify()
+{
+ std::vector<std::string> warnings;
+
+ if (!FileName.empty() && OutputStream != TraceLogOutputStream::File)
+ {
+ warnings.push_back("file name is ignored for non-file output");
+ }
+
+ return warnings;
+}
+
+const char *TraceLogConfig::Name()
+{
+ return "TraceLog configuration";
+}
--- /dev/null
+#ifndef _TRACE_LOG_CONFIG_H_
+#define _TRACE_LOG_CONFIG_H_
+
+#include <vector>
+#include <string>
+
+//
+// The TraceLogConfig structure describes configuration of the TraceLog.
+//
+
+enum class TraceLogOutputStream
+{
+ Stdout,
+ Stderr,
+ File,
+};
+
+struct TraceLogConfig
+{
+ // Creates configuration with default values.
+ TraceLogConfig();
+
+ //
+ // Common options.
+ //
+
+ // An output stream where the TraceLog puts information. Can be: Stdout,
+ // Stderr, File.
+ TraceLogOutputStream OutputStream;
+
+ //
+ // File Output options.
+ //
+
+ // The name of the file or path to which is used by the TraceLog to store
+ // information when File output stream is used.
+ std::string FileName;
+
+ // TODO: other settings for File Output.
+
+ //
+ // Validation and verification.
+ //
+
+ void Validate();
+
+ std::vector<std::string> Verify();
+
+ const char *Name();
+};
+
+#endif // _TRACE_LOG_CONFIG_H_
--- /dev/null
+#include <strings.h>
+
+#include "tracelogconfigconversions.h"
+
+template<>
+TraceLogOutputStream convert(const char *str)
+{
+ if (strcasecmp(str, "Stdout") == 0)
+ {
+ return TraceLogOutputStream::Stdout;
+ }
+ else if (strcasecmp(str, "Stderr") == 0)
+ {
+ return TraceLogOutputStream::Stderr;
+ }
+ else if (strcasecmp(str, "File") == 0)
+ {
+ return TraceLogOutputStream::File;
+ }
+ else
+ {
+ throw bad_conversion("incorrect value for type TraceLogOutputStream");
+ }
+}
--- /dev/null
+#ifndef _TRACE_LOG_CONFIG_CONVERSIONS_H_
+#define _TRACE_LOG_CONFIG_CONVERSIONS_H_
+
+#include "tracelogconfig.h"
+#include "commonconfig.h"
+
+template<>
+TraceLogOutputStream convert(const char *str);
+
+#endif // _TRACE_LOG_CONFIG_CONVERSIONS_H_
--- /dev/null
+#include <new>
+
+#include <windows.h>
+
+#include "classfactory.h"
+#include "profilermanager.h"
+#include "profiler.h"
+
+#define INITGUID
+#include "guid.h"
+#undef INITGUID
+
+// This structure is used to declare a global list of coclasses. The class
+// factory object is created with a pointer to the correct one of these, so
+// that when create instance is called, it can be created.
+struct COCLASS_REGISTER
+{
+ const GUID *pClsid; // Class ID of the coclass.
+ LPCWSTR szProgID; // Prog ID of the class.
+ PFN_CREATE_OBJ pfnCreateObject; // Creation function to create instance.
+};
+
+// This map contains the list of coclasses which are exported from this module.
+const COCLASS_REGISTER g_CoClasses[] =
+{
+// pClsid szProgID pfnCreateObject
+ {&CLSID_PROFILER, W("Profiler"), Profiler::CreateObject},
+ {NULL, NULL, NULL }
+};
+
+// An optional entry point into this DLL.
+EXTERN_C
+BOOL WINAPI DllMain(
+ HINSTANCE hinstDLL, // A handle to the DLL module.
+ DWORD fdwReason, // The reason code that indicates
+ // why the DLL entry-point function is being called.
+ LPVOID lpvReserved) // The reserved parameter provides additional info
+ // when the DLL is being loaded/unloaded.
+{
+ switch (fdwReason)
+ {
+ // The DLL is being loaded into the virtual address space of the current
+ // process. The lpReserved parameter indicates whether the DLL is being
+ // loaded statically (non-NULL) or dynamically (NULL).
+ case DLL_PROCESS_ATTACH:
+ // Disables the DLL_THREAD_ATTACH and DLL_THREAD_DETACH
+ // notifications for this DLL.
+ DisableThreadLibraryCalls(hinstDLL);
+ break;
+
+ // The DLL is being unloaded from the virtual address space of the calling
+ // process. The lpReserved parameter indicates whether the DLL is being
+ // unloaded as a result of a FreeLibrary call (NULL), a failure to load
+ // (NULL), or process termination (non-NULL).
+ case DLL_PROCESS_DETACH:
+ // Notify the Profiler Manager about detach event.
+ ProfilerManager::Instance().DllDetachShutdown();
+ break;
+
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+// Retrieves the class object from a DLL object handler or object application.
+EXTERN_C HRESULT __stdcall DllGetClassObject(
+ REFCLSID rclsid, // The class to desired.
+ REFIID riid, // An interface wanted on the class factory.
+ LPVOID *ppv) // Return the interface pointer here.
+{
+ HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
+ CClassFactory *pClassFactory; // To create class factory object.
+ const COCLASS_REGISTER *pCoClass; // Loop control.
+
+ // Scan for the right one.
+ for (pCoClass = g_CoClasses; pCoClass->pClsid != NULL; pCoClass++)
+ {
+ if (*pCoClass->pClsid == rclsid)
+ {
+ // Allocate the new factory object.
+ pClassFactory = new (std::nothrow) CClassFactory(
+ pCoClass->pfnCreateObject);
+ if (!pClassFactory)
+ return E_OUTOFMEMORY;
+
+ // Pick the v-table based on the caller's request.
+ hr = pClassFactory->QueryInterface(riid, ppv);
+
+ // Always release the local reference, if QI failed it will be
+ // the only one and the object gets freed.
+ pClassFactory->Release();
+ break;
+ }
+ }
+
+ return hr;
+}
--- /dev/null
+#include <guiddef.h>
+
+DEFINE_GUID(CLSID_PROFILER, 0x101DA8FE, 0xFDCA, 0x4D0E, 0x97, 0x12, 0x76, 0x39, 0xCD, 0xE4, 0x8E, 0xBA);
--- /dev/null
+#include <sys/ucontext.h>
+
+#include <cor.h>
+#include <corhdr.h>
+#include <corprof.h>
+
+HRESULT ContextToStackSnapshotContext(
+ const void *context, CONTEXT *winContext) noexcept
+{
+ _ASSERTE(context != nullptr && winContext != nullptr);
+
+ *winContext = {CONTEXT_INTEGER};
+ const mcontext_t *mc =
+ &(reinterpret_cast<const ucontext_t*>(context))->uc_mcontext;
+
+ {
+ winContext->Eax = mc->gregs[REG_EAX];
+ winContext->Ebx = mc->gregs[REG_EBX];
+ winContext->Ecx = mc->gregs[REG_ECX];
+ winContext->Edx = mc->gregs[REG_EDX];
+ winContext->Esi = mc->gregs[REG_ESI];
+ winContext->Edi = mc->gregs[REG_EDI];
+ winContext->Ebp = mc->gregs[REG_EBP];
+ winContext->Esp = mc->gregs[REG_ESP];
+ winContext->SegSs = mc->gregs[REG_SS];
+ winContext->EFlags = mc->gregs[REG_EFL];
+ winContext->Eip = mc->gregs[REG_EIP];
+ winContext->SegCs = mc->gregs[REG_CS];
+ }
+
+ return S_OK;
+}
--- /dev/null
+.intel_syntax noprefix
+#include "unixasmmacros.inc"
+
+//
+// EXTERN_C void EnterNaked3(FunctionIDOrClientID functionIDOrClientID);
+//
+NESTED_ENTRY EnterNaked3, _TEXT, NoHandler
+ PROLOG_BEG
+ PROLOG_PUSH eax
+ PROLOG_PUSH ebx
+ PROLOG_PUSH ecx
+ PROLOG_PUSH edx
+ PROLOG_END
+
+ mov ebx, [ebp+0x8]
+ push ebx
+ call C_FUNC(EnterStub)
+
+ EPILOG_BEG
+ EPILOG_POP edx
+ EPILOG_POP ecx
+ EPILOG_POP ebx
+ EPILOG_POP eax
+ EPILOG_END
+ ret 4
+NESTED_END EnterNaked3, _TEXT
+
+//
+// EXTERN_C void LeaveNaked3(FunctionIDOrClientID functionIDOrClientID);
+//
+NESTED_ENTRY LeaveNaked3, _TEXT, NoHandler
+ PROLOG_BEG
+ PROLOG_PUSH eax
+ PROLOG_PUSH ebx
+ PROLOG_PUSH ecx
+ PROLOG_PUSH edx
+ PROLOG_END
+
+ mov ebx, [ebp+0x8]
+ push ebx
+ call C_FUNC(LeaveStub)
+
+ EPILOG_BEG
+ EPILOG_POP edx
+ EPILOG_POP ecx
+ EPILOG_POP ebx
+ EPILOG_POP eax
+ EPILOG_END
+ ret 4
+NESTED_END LeaveNaked3, _TEXT
+
+//
+// EXTERN_C void TailcallNaked3(FunctionIDOrClientID functionIDOrClientID);
+//
+NESTED_ENTRY TailcallNaked3, _TEXT, NoHandler
+ PROLOG_BEG
+ PROLOG_PUSH eax
+ PROLOG_PUSH ebx
+ PROLOG_PUSH ecx
+ PROLOG_PUSH edx
+ PROLOG_END
+
+ mov ebx, [ebp+0x8]
+ push ebx
+ call C_FUNC(TailcallStub)
+
+ EPILOG_BEG
+ EPILOG_POP edx
+ EPILOG_POP ecx
+ EPILOG_POP ebx
+ EPILOG_POP eax
+ EPILOG_END
+ ret 4
+NESTED_END TailcallNaked3, _TEXT
--- /dev/null
+#ifndef _BASE_INFO_H_
+#define _BASE_INFO_H_
+
+#include <stddef.h>
+
+// As struct we can use this type for overloading.
+struct InternalID
+{
+ size_t id;
+};
+
+struct BaseInfo
+{
+ InternalID internalId;
+};
+
+#endif // _BASE_INFO_H_
--- /dev/null
+#include <utility>
+#include <array>
+#include <vector>
+#include <exception>
+#include <stdexcept>
+
+#include "profiler.h"
+#include "classstorage.h"
+#include "default_delete.h"
+#include "classinfo.h"
+
+// static
+HRESULT ClassInfo::GetClassNameFromMetaData(
+ const Profiler &profiler,
+ IMetaDataImport *pMDImport,
+ mdToken classToken,
+ String &className,
+ ULONG32 *typeArgsCount) noexcept
+{
+ HRESULT hr = S_OK;
+
+ if (IsNilToken(classToken))
+ {
+ className = W("<UNKNOWN>");
+ return hr;
+ }
+
+ try
+ {
+ std::vector<WCHAR> classNameBuffer;
+ ULONG classNameSize;
+ if (TypeFromToken(classToken) == mdtTypeDef)
+ {
+ DWORD dwTypeDefFlags;
+
+ hr = pMDImport->GetTypeDefProps(
+ /* [in] type token */ classToken,
+ /* [out] name buffer */ nullptr,
+ /* [in] buffer size */ 0,
+ /* [out] name length */ &classNameSize,
+ /* [out] type flags */ &dwTypeDefFlags,
+ /* [out] base type */ nullptr
+ );
+ if (SUCCEEDED(hr))
+ {
+ classNameBuffer.resize(classNameSize);
+ // classNameBuffer.data() can be used safety now.
+ hr = pMDImport->GetTypeDefProps(
+ /* [in] type token */ classToken,
+ /* [out] name buffer */ classNameBuffer.data(),
+ /* [in] buffer size */ classNameSize,
+ /* [out] name length */ &classNameSize,
+ /* [out] type flags */ nullptr,
+ /* [out] base type */ nullptr
+ );
+ }
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "ClassInfo::GetClassNameFromMetaData(): "
+ "GetTypeDefProps()", hr
+ );
+ }
+
+ if (IsTdNested(dwTypeDefFlags))
+ {
+ mdTypeDef enclosingClass;
+ hr = pMDImport->GetNestedClassProps(classToken, &enclosingClass);
+ if (SUCCEEDED(hr))
+ {
+ hr = ClassInfo::GetClassNameFromMetaData(
+ profiler, pMDImport, enclosingClass, className,
+ typeArgsCount);
+ }
+ else
+ {
+ throw HresultException(
+ "ClassInfo::GetClassNameFromMetaData(): "
+ "GetNestedClassProps()", hr
+ );
+ }
+ className.append(1, '.').append(classNameBuffer.data());
+ }
+ else
+ {
+ className = classNameBuffer.data();
+ }
+ }
+ else if (TypeFromToken(classToken) == mdtTypeRef)
+ {
+ mdToken scopeToken;
+
+ hr = pMDImport->GetTypeRefProps(
+ /* [in] type token */ classToken,
+ /* [out] scope token */ &scopeToken,
+ /* [out] name buffer */ nullptr,
+ /* [in] buffer size */ 0,
+ /* [out] name length */ &classNameSize
+ );
+ if (SUCCEEDED(hr))
+ {
+ classNameBuffer.resize(classNameSize);
+ // classNameBuffer.data() can be used safety now.
+ hr = pMDImport->GetTypeRefProps(
+ /* [in] type token */ classToken,
+ /* [out] scope token */ nullptr,
+ /* [out] name buffer */ classNameBuffer.data(),
+ /* [in] buffer size */ classNameSize,
+ /* [out] name length */ &classNameSize
+ );
+ }
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "ClassInfo::GetClassNameFromMetaData(): "
+ "GetTypeRefProps()", hr
+ );
+ }
+
+ if (TypeFromToken(scopeToken) == mdtTypeRef)
+ {
+ hr = ClassInfo::GetClassNameFromMetaData(
+ profiler, pMDImport, scopeToken, className, typeArgsCount);
+ className.append(1, '.').append(classNameBuffer.data());
+ }
+ else
+ {
+ className = classNameBuffer.data();
+ }
+ }
+ else
+ {
+ throw std::logic_error(
+ "ClassInfo::GetClassNameFromMetaData(): Unexpected token type");
+ }
+
+ String::size_type pos = className.find_last_of('`');
+ if (pos != String::npos)
+ {
+ if (typeArgsCount)
+ {
+ ULONG32 count = PAL_wcstoul(
+ className.data() + pos + 1, nullptr, 10);
+ *typeArgsCount += count;
+ }
+ className.erase(pos);
+ }
+ if (className.empty())
+ {
+ className = W("<EMPTY>");
+ }
+ }
+ catch (const std::exception &e)
+ {
+ className = W("<UNKNOWN>");
+ hr = profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+// static
+ClassInfo::String ClassInfo::TypeArgName(
+ ULONG argIndex,
+ bool methodFormalArg)
+{
+ char argStart = methodFormalArg ? 'M' : 'T';
+ if (argIndex <= 6)
+ {
+ // The first 7 parameters are printed as M, N, O, P, Q, R, S
+ // or as T, U, V, W, X, Y, Z.
+ return String(1, argStart + argIndex);
+ }
+ else
+ {
+ // Everything after that as M7, M8, ... or T7, T8, ...
+ std::array<WCHAR, 4> argName;
+ _snwprintf_s(
+ argName.data(), argName.size(), _TRUNCATE, W("%c%u"), argStart, argIndex);
+ return argName.data();
+ }
+}
+
+// static
+void ClassInfo::AppendTypeArgNames(
+ String &str,
+ const std::vector<ClassInfo*> &typeArgs,
+ bool methodFormalArg)
+{
+ _ASSERTE(!typeArgs.empty());
+
+ str.append(1, W('<'));
+ for (ULONG i = 0; i < typeArgs.size(); i++)
+ {
+ if (i != 0)
+ {
+ str.append(W(", "));
+ }
+ str.append(ClassInfo::TypeArgName(i, methodFormalArg));
+ if (typeArgs[i])
+ {
+ str.append(W("=")).append(typeArgs[i]->name);
+ }
+ }
+ str.append(1, W('>'));
+}
+
+// static
+__forceinline ClassInfo::String ClassInfo::GetNameFromElementType(
+ CorElementType elementType) noexcept
+{
+ switch (elementType)
+ {
+ case ELEMENT_TYPE_VOID:
+ return W("System.Void");
+
+ case ELEMENT_TYPE_BOOLEAN:
+ return W("System.Boolean");
+
+ case ELEMENT_TYPE_CHAR:
+ return W("System.Char");
+
+ case ELEMENT_TYPE_I1:
+ return W("System.SByte");
+
+ case ELEMENT_TYPE_U1:
+ return W("System.Byte");
+
+ case ELEMENT_TYPE_I2:
+ return W("System.Int16");
+
+ case ELEMENT_TYPE_U2:
+ return W("System.UInt16");
+
+ case ELEMENT_TYPE_I4:
+ return W("System.Int32");
+
+ case ELEMENT_TYPE_U4:
+ return W("System.UInt32");
+
+ case ELEMENT_TYPE_I8:
+ return W("System.Int64");
+
+ case ELEMENT_TYPE_U8:
+ return W("System.UInt64");
+
+ case ELEMENT_TYPE_R4:
+ return W("System.Single");
+
+ case ELEMENT_TYPE_R8:
+ return W("System.Double");
+
+ case ELEMENT_TYPE_STRING:
+ return W("System.String");
+
+ case ELEMENT_TYPE_PTR:
+ return W("<UNKNOWN>*");
+
+ case ELEMENT_TYPE_BYREF:
+ return W("ref <UNKNOWN>");
+
+ case ELEMENT_TYPE_VALUETYPE:
+ return W("System.ValueType");
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_OBJECT:
+ return W("System.Object");
+
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ return W("System.Array");
+
+ case ELEMENT_TYPE_TYPEDBYREF:
+ return W("System.TypedReference");
+
+ case ELEMENT_TYPE_I:
+ return W("System.IntPtr");
+
+ case ELEMENT_TYPE_U:
+ return W("System.UIntPtr");
+
+ default:
+ return W("<UNKNOWN>");
+ }
+}
+
+__forceinline HRESULT ClassInfo::InitializeArrayClass(
+ const Profiler &profiler,
+ ClassStorage &storage,
+ ClassID realClassID,
+ CorElementType elementType) noexcept
+{
+ HRESULT hr = S_OK;
+
+ try
+ {
+ if (realClassID != 0 &&
+ (
+ elementType != ELEMENT_TYPE_PTR &&
+ elementType != ELEMENT_TYPE_BYREF &&
+ elementType != ELEMENT_TYPE_FNPTR
+ )
+ )
+ {
+ ClassInfo &realClass = storage.Place(realClassID).first;
+ hr = realClass.Initialize(profiler, storage);
+ // Class name of array class can contains type arguments
+ // of real class. Array brackets is calculated separately.
+ if (realClass.arrayBrackets.empty())
+ {
+ this->name = realClass.fullName;
+ }
+ else
+ {
+ this->name = realClass.name;
+ }
+ this->arrayBrackets = realClass.arrayBrackets.c_str();
+ }
+ else
+ {
+ this->name = ClassInfo::GetNameFromElementType(elementType);
+ }
+
+ _ASSERTE(this->rank >= 1);
+ this->arrayBrackets.insert(
+ 0, String(1, W('['))
+ .append(this->rank - 1, W(','))
+ .append(1, W(']'))
+ );
+ }
+ catch (const std::exception &e)
+ {
+ this->name = W("<UNKNOWN>");
+ this->arrayBrackets = W("[?]");
+ hr = profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+__forceinline HRESULT ClassInfo::InitializeRegularClassName(
+ const Profiler &profiler,
+ const ProfilerInfo &info) noexcept
+{
+ HRESULT hr = S_OK;
+
+ try
+ {
+ IUnknown *pUnknown;
+ hr = info.v1()->GetModuleMetaData(
+ this->moduleId, ofRead, IID_IMetaDataImport, &pUnknown);
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "ClassInfo::InitializeRegularClassName(): "
+ "GetModuleMetaData()", hr
+ );
+ }
+ std::unique_ptr<IUnknown> pUnknownHolder(pUnknown);
+ IMetaDataImport *pMDImport = dynamic_cast<IMetaDataImport*>(pUnknown);
+
+ ULONG32 typeArgsSize = 0;
+ hr = ClassInfo::GetClassNameFromMetaData(
+ profiler, pMDImport, this->classToken, this->name, &typeArgsSize);
+ if (this->typeArgs.empty())
+ {
+ this->typeArgs.resize(typeArgsSize);
+ }
+ }
+ catch (const std::exception &e)
+ {
+ this->name = W("<UNKNOWN>");
+ hr = profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+__forceinline HRESULT ClassInfo::InitializeTypeArgs(
+ const Profiler &profiler,
+ ClassStorage &storage,
+ const ProfilerInfo &info,
+ ULONG32 typeArgsSize) noexcept
+{
+ HRESULT hrReturn = S_OK;
+ HRESULT hr;
+
+ try
+ {
+ if (typeArgsSize > 0)
+ {
+ this->typeArgs.resize(typeArgsSize);
+ std::vector<ClassID> typeArgIds(typeArgsSize);
+ // typeArgIds.data() can be used safety now.
+ hr = info.v2()->GetClassIDInfo2(
+ /* [in] class ID */ this->id,
+ /* [out] module ID */ nullptr,
+ /* [out] class token */ nullptr,
+ /* [out] parent class ID */ nullptr,
+ /* [in] type args buffer size */ typeArgsSize,
+ /* [out] number of type args */ nullptr,
+ /* [out] type args buffer */ typeArgIds.data()
+ );
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "ClassInfo::InitializeTypeArgs(): "
+ "GetClassIDInfo2()", hr
+ );
+ }
+
+ for (ULONG32 i = 0; i < typeArgsSize; i++)
+ {
+ try
+ {
+ if (typeArgIds[i] != 0)
+ {
+ this->typeArgs[i] = &storage.Place(typeArgIds[i]).first;
+ hr = this->typeArgs[i]->Initialize(profiler, storage);
+ }
+ }
+ catch (const std::exception &e)
+ {
+ hr = profiler.HandleException(e);
+ }
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+ }
+ }
+ }
+ catch (const std::exception &e)
+ {
+ hr = profiler.HandleException(e);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+ }
+
+ return hrReturn;
+}
+
+HRESULT ClassInfo::Initialize(
+ const Profiler &profiler,
+ ClassStorage &storage) noexcept
+{
+ HRESULT hrReturn = S_OK;
+ HRESULT hr;
+
+ if (this->isInitialized)
+ {
+ return hrReturn;
+ }
+
+ _ASSERTE(this->id != 0);
+ const ProfilerInfo &info = profiler.GetProfilerInfo();
+
+ try
+ {
+ //
+ // Get Common Info.
+ //
+
+ ClassID realClassID;
+ CorElementType elementType;
+ hr = info.v1()->IsArrayClass(
+ this->id, &elementType, &realClassID, &this->rank);
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "ClassInfo::Initialize(): IsArrayClass()", hr);
+ }
+
+ if (hr == S_OK)
+ {
+ //
+ // Array class handling.
+ //
+
+ if (rank == 0)
+ {
+ throw std::logic_error(
+ "ClassInfo::Initialize(): IsArrayClass(): "
+ "Zero rank for array class"
+ );
+ }
+
+ hr = this->InitializeArrayClass(
+ profiler, storage, realClassID, elementType);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+ }
+ else if (hr == S_FALSE)
+ {
+ //
+ // Regular class handling.
+ //
+
+ if (rank != 0)
+ {
+ throw std::logic_error(
+ "ClassInfo::Initialize(): IsArrayClass(): "
+ "Non-zero rank for regular class"
+ );
+ }
+
+ ULONG32 typeArgsSize;
+ _ASSERTE(info.version() >= 2);
+ hr = info.v2()->GetClassIDInfo2(
+ /* [in] class ID */ this->id,
+ /* [out] module ID */ &this->moduleId,
+ /* [out] class token */ &this->classToken,
+ /* [out] parent class ID */ nullptr,
+ /* [in] type args buffer size */ 0,
+ /* [out] number of type args */ &typeArgsSize,
+ /* [out] type args buffer */ nullptr
+ );
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "ClassInfo::Initialize(): GetClassIDInfo2()", hr);
+ }
+
+ //
+ // Get Type Arguments.
+ //
+
+ hr = this->InitializeTypeArgs(
+ profiler, storage, info, typeArgsSize);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+
+ //
+ // Get Class Name.
+ //
+
+ hr = this->InitializeRegularClassName(profiler, info);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+ }
+ else
+ {
+ throw std::logic_error(
+ "ClassInfo::Initialize(): IsArrayClass(): Unexpected HRESULT");
+ }
+ }
+ catch (const std::exception &e)
+ {
+ this->name = W("<UNKNOWN>");
+ hr = profiler.HandleException(e);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+ }
+
+ try
+ {
+ this->fullName = this->name;
+ _ASSERTE(this->arrayBrackets.empty() || this->typeArgs.empty());
+ if (!this->arrayBrackets.empty())
+ {
+ //
+ // Array class handling.
+ //
+
+ this->fullName.append(this->arrayBrackets);
+ }
+ else if (!this->typeArgs.empty())
+ {
+ //
+ // Generic class handling.
+ //
+
+ ClassInfo::AppendTypeArgNames(
+ this->fullName, this->typeArgs, false);
+ }
+ }
+ catch (const std::exception &e)
+ {
+ this->fullName = W("<UNKNOWN>");
+ hr = profiler.HandleException(e);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+ }
+
+ this->isInitialized = true;
+ return hrReturn;
+}
+
+HRESULT ClassInfo::InitializeFromToken(
+ const Profiler &profiler,
+ ModuleID moduleId,
+ mdTypeDef classToken) noexcept
+{
+ HRESULT hrReturn = S_OK;
+ HRESULT hr;
+
+ if (this->isInitialized)
+ {
+ return hrReturn;
+ }
+
+ _ASSERTE(this->id == 0);
+ _ASSERTE(this->rank == 0);
+ _ASSERTE(this->arrayBrackets.empty());
+ const ProfilerInfo &info = profiler.GetProfilerInfo();
+
+ try
+ {
+ this->moduleId = moduleId;
+ this->classToken = classToken;
+
+ //
+ // Get Class Name.
+ //
+
+ hr = this->InitializeRegularClassName(profiler, info);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ this->name = W("<UNKNOWN>");
+ hr = profiler.HandleException(e);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+ }
+
+ try
+ {
+ this->fullName = this->name;
+ if (!this->typeArgs.empty())
+ {
+ ClassInfo::AppendTypeArgNames(
+ this->fullName, this->typeArgs, false);
+ }
+ }
+ catch (const std::exception &e)
+ {
+ this->fullName = W("<UNKNOWN>");
+ hr = profiler.HandleException(e);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+ }
+
+ this->isInitialized = true;
+ return hrReturn;
+}
--- /dev/null
+#ifndef _CLASS_INFO_
+#define _CLASS_INFO_
+
+#include <vector>
+#include <string>
+
+#include <cor.h>
+#include <corhdr.h>
+#include <corprof.h>
+
+#include "mappedinfo.h"
+
+class Profiler;
+
+class ProfilerInfo;
+
+class ClassStorage;
+
+struct ClassInfo : public MappedInfo<ClassID>
+{
+ typedef std::basic_string<WCHAR> String;
+
+ ModuleID moduleId;
+ mdTypeDef classToken = mdTypeDefNil;
+ std::vector<ClassInfo*> typeArgs;
+ ULONG rank;
+ String name;
+ String arrayBrackets;
+ String fullName;
+ bool isInitialized;
+ bool isNamePrinted;
+
+public:
+ static HRESULT GetClassNameFromMetaData(
+ const Profiler &profiler,
+ IMetaDataImport *pMDImport,
+ mdToken classToken,
+ String &className,
+ ULONG32 *typeArgsCount) noexcept;
+
+ static String TypeArgName(
+ ULONG argIndex,
+ bool methodFormalArg);
+
+ static void AppendTypeArgNames(
+ String &str,
+ const std::vector<ClassInfo*> &typeArgs,
+ bool methodFormalArg);
+
+private:
+ static String GetNameFromElementType(
+ CorElementType elementType);
+
+ HRESULT InitializeArrayClass(
+ const Profiler &profiler,
+ ClassStorage &storage,
+ ClassID realClassID,
+ CorElementType elementType) noexcept;
+
+ HRESULT InitializeRegularClassName(
+ const Profiler &profiler,
+ const ProfilerInfo &info) noexcept;
+
+ HRESULT InitializeTypeArgs(
+ const Profiler &profiler,
+ ClassStorage &storage,
+ const ProfilerInfo &info,
+ ULONG32 typeArgsSize) noexcept;
+
+public:
+ HRESULT Initialize(
+ const Profiler &profiler,
+ ClassStorage &storage) noexcept;
+
+ HRESULT InitializeFromToken(
+ const Profiler &profiler,
+ ModuleID moduleId,
+ mdTypeDef classToken) noexcept;
+};
+
+#endif // _CLASS_INFO_
--- /dev/null
+#include <utility>
+#include <vector>
+#include <exception>
+#include <stdexcept>
+
+//#include <corhlpr.h>
+
+#include "profiler.h"
+#include "classstorage.h"
+#include "classinfo.h"
+#include "default_delete.h"
+#include "functioninfo.h"
+
+inline bool isCallConv(unsigned sigByte, CorCallingConvention conv)
+{
+ return ((sigByte & IMAGE_CEE_CS_CALLCONV_MASK) == (unsigned) conv);
+}
+
+static __forceinline ULONG CorSigUncompressDataWrapper(
+ PCCOR_SIGNATURE &sigBlob, ULONG &sigBlobSize)
+{
+ HRESULT hr;
+ ULONG dataOut;
+ ULONG dataLen;
+
+ hr = CorSigUncompressData(sigBlob, sigBlobSize, &dataOut, &dataLen);
+ if (FAILED(hr))
+ {
+ throw HresultException("CorSigUncompressData()", hr);
+ }
+ sigBlob += dataLen;
+ sigBlobSize -= dataLen;
+
+ return dataOut;
+}
+
+static __forceinline mdToken CorSigUncompressTokenWrapper(
+ PCCOR_SIGNATURE &sigBlob, ULONG &sigBlobSize)
+{
+ HRESULT hr;
+ mdToken token;
+ ULONG tokenLen;
+
+ hr = CorSigUncompressToken(sigBlob, sigBlobSize, &token, &tokenLen);
+ if (FAILED(hr))
+ {
+ throw HresultException("CorSigUncompressToken()", hr);
+ }
+ sigBlob += tokenLen;
+ sigBlobSize -= tokenLen;
+
+ return token;
+}
+
+static __forceinline ULONG CorSigUncompressCallingConvWrapper(
+ PCCOR_SIGNATURE &sigBlob, ULONG &sigBlobSize)
+{
+ HRESULT hr;
+ ULONG callConv;
+
+ hr = CorSigUncompressCallingConv(sigBlob, sigBlobSize, &callConv);
+ if (FAILED(hr))
+ {
+ throw HresultException("CorSigUncompressCallingConv()", hr);
+ }
+ sigBlob++;
+ sigBlobSize--;
+
+ return callConv;
+}
+
+static __forceinline CorElementType CorSigUncompressElementTypeWrapper(
+ PCCOR_SIGNATURE &sigBlob, ULONG &sigBlobSize)
+{
+ if (sigBlobSize > 0)
+ {
+ sigBlobSize--;
+ return CorSigUncompressElementType(sigBlob);
+ }
+ else
+ {
+ throw HresultException(
+ "CorSigUncompressElementType()", META_E_BAD_SIGNATURE);
+ }
+}
+
+// static
+void FunctionInfo::ParseElementType(
+ const Profiler &profiler,
+ IMetaDataImport *pMDImport,
+ PCCOR_SIGNATURE &sigBlob,
+ ULONG &sigBlobSize,
+ String &str,
+ String &arrayBrackets)
+{
+ bool reiterate;
+ ULONG n;
+ bool methodFormalArg;
+ String appendix;
+
+ do {
+ reiterate = false;
+ switch (CorSigUncompressElementTypeWrapper(sigBlob, sigBlobSize))
+ {
+ case ELEMENT_TYPE_VOID:
+ str.append(W("void"));
+ break;
+
+ case ELEMENT_TYPE_BOOLEAN:
+ str.append(W("bool"));
+ break;
+
+ case ELEMENT_TYPE_CHAR:
+ str.append(W("char"));
+ break;
+
+ case ELEMENT_TYPE_I1:
+ str.append(W("sbyte"));
+ break;
+
+ case ELEMENT_TYPE_U1:
+ str.append(W("byte"));
+ break;
+
+ case ELEMENT_TYPE_I2:
+ str.append(W("short"));
+ break;
+
+ case ELEMENT_TYPE_U2:
+ str.append(W("ushort"));
+ break;
+
+ case ELEMENT_TYPE_I4:
+ str.append(W("int"));
+ break;
+
+ case ELEMENT_TYPE_U4:
+ str.append(W("uint"));
+ break;
+
+ case ELEMENT_TYPE_I8:
+ str.append(W("long"));
+ break;
+
+ case ELEMENT_TYPE_U8:
+ str.append(W("ulong"));
+ break;
+
+ case ELEMENT_TYPE_R4:
+ str.append(W("float"));
+ break;
+
+ case ELEMENT_TYPE_R8:
+ str.append(W("double"));
+ break;
+
+ case ELEMENT_TYPE_STRING:
+ str.append(W("string"));
+ break;
+
+ case ELEMENT_TYPE_TYPEDBYREF:
+ str.append(W("System.TypedReference"));
+ break;
+
+ case ELEMENT_TYPE_I:
+ str.append(W("System.IntPtr"));
+ break;
+
+ case ELEMENT_TYPE_U:
+ str.append(W("System.UIntPtr"));
+ break;
+
+ case ELEMENT_TYPE_OBJECT:
+ str.append(W("object"));
+ break;
+
+ case ELEMENT_TYPE_VALUETYPE:
+ case ELEMENT_TYPE_CLASS:
+ {
+ mdToken token = CorSigUncompressTokenWrapper(
+ sigBlob, sigBlobSize);
+ String tmp;
+ ClassInfo::GetClassNameFromMetaData(
+ profiler, pMDImport, token, tmp, nullptr);
+ str.append(tmp);
+ }
+ break;
+
+ case ELEMENT_TYPE_VAR:
+ methodFormalArg = false;
+ goto TYPE_ARG;
+
+ case ELEMENT_TYPE_MVAR:
+ methodFormalArg = true;
+ goto TYPE_ARG;
+
+ TYPE_ARG:
+ n = CorSigUncompressDataWrapper(sigBlob, sigBlobSize);
+ str.append(ClassInfo::TypeArgName(n, methodFormalArg));
+ break;
+
+ case ELEMENT_TYPE_GENERICINST:
+ {
+ String arrayBrackets;
+ FunctionInfo::ParseElementType(
+ profiler, pMDImport, sigBlob, sigBlobSize,
+ str, arrayBrackets);
+ if (!arrayBrackets.empty())
+ {
+ throw std::logic_error(
+ "FunctionInfo::ParseElementType(): "
+ "Parsing error: Can't parse generic instantiation: "
+ "Instantiation of array class"
+ );
+ }
+ str.append(1, '<');
+ n = CorSigUncompressDataWrapper(sigBlob, sigBlobSize);
+ for (ULONG i = 0; i < n; i++)
+ {
+ if (i != 0)
+ {
+ str.append(W(", "));
+ }
+ String arrayBrackets;
+ FunctionInfo::ParseElementType(
+ profiler, pMDImport, sigBlob, sigBlobSize,
+ str, arrayBrackets);
+ str.append(arrayBrackets);
+ }
+ str.append(1, '>');
+ }
+ break;
+
+ case ELEMENT_TYPE_FNPTR:
+ {
+ String returnType;
+ String signature;
+ FunctionInfo::ParseSignature(
+ profiler, pMDImport, sigBlob, sigBlobSize,
+ returnType, signature);
+ str.append(returnType).append(W(" *")).append(signature);
+ }
+ break;
+
+ case ELEMENT_TYPE_ARRAY:
+ {
+ FunctionInfo::ParseElementType(
+ profiler, pMDImport, sigBlob, sigBlobSize,
+ str, arrayBrackets);
+ ULONG rank = CorSigUncompressDataWrapper(sigBlob, sigBlobSize);
+ if (rank == 0)
+ {
+ throw std::logic_error(
+ "FunctionInfo::ParseElementType(): "
+ "Parsing error: Can't parse array class: "
+ "Zero rank of array class"
+ );
+ }
+
+ // Skip array sizes.
+ n = CorSigUncompressDataWrapper(sigBlob, sigBlobSize);
+ if (n > rank)
+ {
+ throw std::logic_error(
+ "FunctionInfo::ParseElementType(): "
+ "Parsing error: Can't parse array class: "
+ "Too many sizes"
+ );
+ }
+ for(ULONG i = 0; i < n; i++)
+ {
+ CorSigUncompressDataWrapper(sigBlob, sigBlobSize);
+ }
+
+ // Skip array lower bounds.
+ n = CorSigUncompressDataWrapper(sigBlob, sigBlobSize);
+ if (n > rank)
+ {
+ throw std::logic_error(
+ "FunctionInfo::ParseElementType(): "
+ "Parsing error: Can't parse array class: "
+ "Too many lower bounds"
+ );
+ }
+ for(ULONG i = 0; i < n; i++)
+ {
+ CorSigUncompressDataWrapper(sigBlob, sigBlobSize);
+ }
+
+ arrayBrackets.insert(
+ 0, String(1, W('['))
+ .append(rank - 1, W(','))
+ .append(1, W(']'))
+ );
+ }
+ break;
+
+ case ELEMENT_TYPE_SZARRAY:
+ FunctionInfo::ParseElementType(
+ profiler, pMDImport, sigBlob, sigBlobSize,
+ str, arrayBrackets);
+ arrayBrackets.insert(0, W("[]"));
+ break;
+
+ case ELEMENT_TYPE_BYREF:
+ str.append(W("ref "));
+ goto REITERATE;
+
+ case ELEMENT_TYPE_PTR:
+ appendix.insert(0, W("*"));
+ goto REITERATE;
+
+ case ELEMENT_TYPE_CMOD_OPT:
+ case ELEMENT_TYPE_CMOD_REQD:
+ // Skip class token.
+ CorSigUncompressTokenWrapper(sigBlob, sigBlobSize);
+ case ELEMENT_TYPE_PINNED:
+ goto REITERATE;
+
+ default:
+ throw std::logic_error(
+ "FunctionInfo::ParseElementType(): "
+ "Parsing error: Can't parse unknown element type"
+ );
+ break;
+
+ REITERATE:
+ reiterate = true;
+ break;
+ }
+ } while(reiterate);
+ str.append(appendix);
+}
+
+// static
+void FunctionInfo::ParseSignature(
+ const Profiler &profiler,
+ IMetaDataImport *pMDImport,
+ PCCOR_SIGNATURE &sigBlob,
+ ULONG &sigBlobSize,
+ String &returnType,
+ String &signature)
+{
+ ULONG argNum = 0;
+ ULONG argCount = 0;
+ bool argCountDetermined = false;
+ bool openBracketAppended = false;
+ bool closeBracketAppended = false;
+
+ try
+ {
+ // Get the calling convention out.
+ ULONG callConv = CorSigUncompressCallingConvWrapper(
+ sigBlob, sigBlobSize);
+
+ // Should not be a local variable, field or generic instantiation
+ // signature.
+ if (
+ isCallConv(callConv, IMAGE_CEE_CS_CALLCONV_LOCAL_SIG) ||
+ isCallConv(callConv, IMAGE_CEE_CS_CALLCONV_FIELD) ||
+ isCallConv(callConv, IMAGE_CEE_CS_CALLCONV_GENERICINST)
+ )
+ {
+ throw std::logic_error(
+ "FunctionInfo::ParseSignature(): "
+ "Parsing error: Can't parse signature: "
+ "Unexpected calling convention"
+ );
+ }
+
+ // Skip the number of method type arguments for generic methods.
+ if (callConv & IMAGE_CEE_CS_CALLCONV_GENERIC)
+ {
+ CorSigUncompressDataWrapper(sigBlob, sigBlobSize);
+ }
+
+ // Get the number of arguments.
+ argCount = CorSigUncompressDataWrapper(sigBlob, sigBlobSize);
+ argCountDetermined = true;
+
+ // Get the return type.
+ String type;
+ String arrayBrackets;
+ FunctionInfo::ParseElementType(
+ profiler, pMDImport, sigBlob, sigBlobSize, type, arrayBrackets);
+ returnType.append(type + arrayBrackets);
+
+ // Calculate signature.
+ signature.append(1, W('('));
+ openBracketAppended = true;
+ while(argNum < argCount)
+ {
+ String delimeter = argNum != 0 ? W(", ") : W("");
+
+ if (sigBlobSize == 0)
+ {
+ throw HresultException(
+ "CorSigUncompressElementType()", META_E_BAD_SIGNATURE);
+ }
+ else if (*sigBlob == ELEMENT_TYPE_SENTINEL)
+ {
+ CorSigUncompressElementTypeWrapper(sigBlob, sigBlobSize);
+ signature.append(delimeter + W("..."));
+ }
+ else
+ {
+ String type;
+ String arrayBrackets;
+ FunctionInfo::ParseElementType(
+ profiler, pMDImport, sigBlob, sigBlobSize,
+ type, arrayBrackets);
+ signature.append(delimeter + type + arrayBrackets);
+ argNum++;
+ }
+ }
+ signature.append(1, W(')'));
+ closeBracketAppended = true;
+ }
+ catch (const std::exception &e)
+ {
+ if (returnType.empty())
+ {
+ returnType = W("<UNKNOWN>");
+ }
+
+ try
+ {
+ if (!openBracketAppended)
+ {
+ signature.append(1, W('('));
+ }
+ if (argCountDetermined)
+ {
+ for(; argNum < argCount; argNum++)
+ {
+ if (argNum != 0)
+ {
+ signature.append(W(", <UNKNOWN>"));
+ }
+ else
+ {
+ signature.append(W("<UNKNOWN>"));
+ }
+ }
+ }
+ else
+ {
+ signature.append(W("?"));
+ }
+ if (!closeBracketAppended)
+ {
+ signature.append(1, W(')'));
+ }
+ }
+ catch (const std::exception &e)
+ {
+ signature = W("(?)");
+ }
+
+ throw;
+ }
+}
+
+__forceinline HRESULT FunctionInfo::InitializeCodeInfo(
+ const Profiler &profiler,
+ const ProfilerInfo &info) noexcept
+{
+ HRESULT hr = S_OK;
+
+ try
+ {
+ _ASSERTE(info.version() >= 2);
+
+ ULONG32 size;
+ hr = info.v2()->GetCodeInfo2(this->id, 0, &size, nullptr);
+ if (SUCCEEDED(hr) && size > 0)
+ {
+ this->codeInfo.resize(size);
+ // codeInfo.data() can be used safety now.
+ hr = info.v2()->GetCodeInfo2(
+ this->id, size, nullptr, this->codeInfo.data());
+ }
+
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "FunctionInfo::InitializeCodeInfo(): GetCodeInfo2()", hr);
+ }
+ }
+ catch (const std::exception &e)
+ {
+ this->codeInfo.clear();
+ this->codeInfo.shrink_to_fit();
+ hr = profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+__forceinline HRESULT FunctionInfo::InitializeILToNativeMapping(
+ const Profiler &profiler,
+ const ProfilerInfo &info) noexcept
+{
+ HRESULT hr;
+
+ try
+ {
+ ULONG32 size;
+ hr = info.v1()->GetILToNativeMapping(this->id, 0, &size, nullptr);
+ if (SUCCEEDED(hr) && size > 0)
+ {
+ this->ILToNativeMapping.resize(size);
+ // ILToNativeMapping.data() can be used safety now.
+ hr = info.v1()->GetILToNativeMapping(
+ this->id, size, &size, this->ILToNativeMapping.data());
+ }
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "FunctionInfo::InitializeILToNativeMapping(): "
+ "GetILToNativeMapping()", hr
+ );
+ }
+ else if (this->ILToNativeMapping.size() != size)
+ {
+ throw std::logic_error(
+ "FunctionInfo::InitializeILToNativeMapping(): "
+ "GetILToNativeMapping(): Unexpected map size"
+ );
+ }
+ }
+ catch (const std::exception &e)
+ {
+ this->ILToNativeMapping.clear();
+ this->ILToNativeMapping.shrink_to_fit();
+ hr = profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+__forceinline HRESULT FunctionInfo::InitializeFunctionName(
+ const Profiler &profiler,
+ IMetaDataImport *pMDImport,
+ ULONG funcNameSize) noexcept
+{
+ HRESULT hr = S_OK;
+
+ try
+ {
+ std::vector<WCHAR> funcNameBuffer(funcNameSize);
+ // funcNameBuffer.data() can be used safety now.
+ hr = pMDImport->GetMethodProps(
+ /* [in] method token */ this->funcToken,
+ /* [out] class token */ nullptr,
+ /* [out] name buffer */ funcNameBuffer.data(),
+ /* [in] buffer size */ funcNameSize,
+ /* [out] name length */ nullptr,
+ /* [out] method flags */ nullptr,
+ /* [out] signature blob */ nullptr,
+ /* [out] size of blob */ nullptr,
+ /* [out] RVA pointer */ nullptr,
+ /* [out] impl. flags */ nullptr
+ );
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "FunctionInfo::InitializeFunctionName(): "
+ "GetMethodProps()", hr
+ );
+ }
+ this->name = funcNameBuffer.data();
+ }
+ catch (const std::exception &e)
+ {
+ this->name = W("<UNKNOWN>");
+ hr = profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+__forceinline HRESULT FunctionInfo::InitializeOwnerClassFromClassId(
+ const Profiler &profiler,
+ ClassStorage &storage) noexcept
+{
+ HRESULT hr = S_OK;
+
+ try
+ {
+ this->ownerClass = &storage.Place(this->classId).first;
+ hr = this->ownerClass->Initialize(profiler, storage);
+ }
+ catch (const std::exception &e)
+ {
+ hr = profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+__forceinline HRESULT FunctionInfo::InitializeOwnerClassFromClassToken(
+ const Profiler &profiler,
+ ClassStorage &storage,
+ mdTypeDef classToken) noexcept
+{
+ HRESULT hr = S_OK;
+
+ try
+ {
+ this->ownerClass = &storage.Add();
+ hr = this->ownerClass->InitializeFromToken(
+ profiler, this->moduleId, classToken);
+ }
+ catch (const std::exception &e)
+ {
+ hr = profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+__forceinline HRESULT FunctionInfo::InitializeTypeArgs(
+ const Profiler &profiler,
+ ClassStorage &storage,
+ const ProfilerInfo &info,
+ ULONG32 typeArgsSize) noexcept
+{
+ HRESULT hrReturn = S_OK;
+ HRESULT hr;
+
+ try
+ {
+ if (typeArgsSize > 0)
+ {
+ this->typeArgs.resize(typeArgsSize);
+ std::vector<ClassID> typeArgIds(typeArgsSize);
+ // typeArgIds.data() can be used safety now.
+ hr = info.v2()->GetFunctionInfo2(
+ /* [in] function ID */ this->id,
+ /* [in] frame info */ 0,
+ /* [in] class ID */ nullptr,
+ /* [out] module ID */ nullptr,
+ /* [out] function token */ nullptr,
+ /* [in] type args buffer size */ typeArgsSize,
+ /* [out] number of type args */ nullptr,
+ /* [out] type args buffer */ typeArgIds.data()
+ );
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "FunctionInfo::InitializeTypeArgs(): "
+ "GetFunctionInfo2()", hr
+ );
+ }
+
+ for (ULONG32 i = 0; i < typeArgsSize; i++)
+ {
+ try
+ {
+ if (typeArgIds[i] != 0)
+ {
+ this->typeArgs[i] = &storage.Place(typeArgIds[i]).first;
+ hr = this->typeArgs[i]->Initialize(profiler, storage);
+ }
+ }
+ catch (const std::exception &e)
+ {
+ hr = profiler.HandleException(e);
+ }
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+ }
+ }
+ }
+ catch (const std::exception &e)
+ {
+ hr = profiler.HandleException(e);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+ }
+
+ return hrReturn;
+}
+
+__forceinline HRESULT FunctionInfo::InitializeSignature(
+ const Profiler &profiler,
+ IMetaDataImport *pMDImport,
+ PCCOR_SIGNATURE &sigBlob,
+ ULONG &sigBlobSize) noexcept
+{
+ HRESULT hr = S_OK;
+
+ try
+ {
+ FunctionInfo::ParseSignature(
+ profiler, pMDImport, sigBlob, sigBlobSize,
+ this->returnType, this->signature);
+ if (sigBlobSize != 0)
+ {
+ profiler.LOG().Warn() <<
+ "FunctionInfo::InitializeSignature(): Ambiguous signature blob";
+ }
+ }
+ catch (const std::exception &e)
+ {
+ hr = profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+HRESULT FunctionInfo::Initialize(
+ const Profiler &profiler,
+ ClassStorage &storage) noexcept
+{
+ HRESULT hrReturn = S_OK;
+ HRESULT hr;
+
+ if (this->isInitialized)
+ {
+ return hrReturn;
+ }
+
+ _ASSERTE(this->id != 0);
+ const ProfilerInfo &info = profiler.GetProfilerInfo();
+
+ hr = FunctionInfo::InitializeCodeInfo(profiler, info);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+
+ hr = FunctionInfo::InitializeILToNativeMapping(profiler, info);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+
+ try
+ {
+ //
+ // Get Common Info.
+ //
+
+ ULONG32 typeArgsSize;
+ _ASSERTE(info.version() >= 2);
+ hr = info.v2()->GetFunctionInfo2(
+ /* [in] function ID */ this->id,
+ /* [in] frame info */ 0,
+ /* [in] class ID */ &this->classId,
+ /* [out] module ID */ &this->moduleId,
+ /* [out] function token */ &this->funcToken,
+ /* [in] type args buffer size */ 0,
+ /* [out] number of type args */ &typeArgsSize,
+ /* [out] type args buffer */ nullptr
+ );
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "FunctionInfo::Initialize(): GetFunctionInfo2()", hr);
+ }
+ else if (TypeFromToken(this->funcToken) != mdtMethodDef)
+ {
+ throw std::logic_error(
+ "FunctionInfo::Initialize(): "
+ "GetTokenAndMetaDataFromFunction(): Unexpected method token"
+ );
+ }
+
+ IUnknown *pUnknown;
+ hr = info.v1()->GetModuleMetaData(
+ this->moduleId, ofRead, IID_IMetaDataImport, &pUnknown);
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "FunctionInfo::Initialize(): GetModuleMetaData()", hr);
+ }
+ std::unique_ptr<IUnknown> pUnknownHolder(pUnknown);
+ IMetaDataImport *pMDImport = dynamic_cast<IMetaDataImport*>(pUnknown);
+
+ ULONG funcNameSize;
+ mdTypeDef classToken;
+ DWORD funcAttr;
+ PCCOR_SIGNATURE sigBlob;
+ ULONG sigBlobSize;
+ hr = pMDImport->GetMethodProps(
+ /* [in] method token */ this->funcToken,
+ /* [out] class token */ &classToken,
+ /* [out] name buffer */ nullptr,
+ /* [in] buffer size */ 0,
+ /* [out] name length */ &funcNameSize,
+ /* [out] method flags */ &funcAttr,
+ /* [out] signature blob */ &sigBlob,
+ /* [out] size of blob */ &sigBlobSize,
+ /* [out] RVA pointer */ nullptr,
+ /* [out] impl. flags */ nullptr
+ );
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "FunctionInfo::Initialize(): GetMethodProps()", hr);
+ }
+
+ //
+ // Get Function Name.
+ //
+
+ hr = this->InitializeFunctionName(profiler, pMDImport, funcNameSize);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+
+ //
+ // Get Owner Class.
+ //
+
+ if (this->classId != 0)
+ {
+ hr = this->InitializeOwnerClassFromClassId(profiler, storage);
+ }
+ else
+ {
+ hr = this->InitializeOwnerClassFromClassToken(
+ profiler, storage, classToken);
+ }
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+
+ //
+ // Get Type Arguments.
+ //
+
+ hr = this->InitializeTypeArgs(
+ profiler, storage, info, typeArgsSize);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+
+ //
+ // Get Return Type and Signature.
+ //
+
+ hr = FunctionInfo::InitializeSignature(
+ profiler, pMDImport, sigBlob, sigBlobSize);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ this->name = W("<UNKNOWN>");
+ this->returnType = W("<UNKNOWN>");
+ this->signature = W("(?)");
+ hr = profiler.HandleException(e);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+ }
+
+ try
+ {
+ if (this->ownerClass)
+ {
+ this->fullName = this->ownerClass->fullName;
+ }
+ else
+ {
+ this->fullName = W("<UNKNOWN>");
+ }
+ this->fullName.append(W("::")).append(this->name);
+ if (!this->typeArgs.empty())
+ {
+ ClassInfo::AppendTypeArgNames(
+ this->fullName, this->typeArgs, true);
+ }
+ }
+ catch (const std::exception &e)
+ {
+ this->fullName = W("<UNKNOWN>");
+ hr = profiler.HandleException(e);
+ if (FAILED(hr) && SUCCEEDED(hrReturn))
+ {
+ hrReturn = hr;
+ }
+ }
+
+ this->isInitialized = true;
+ return hr;
+}
--- /dev/null
+#ifndef _FUNCTION_INFO_
+#define _FUNCTION_INFO_
+
+#include <string>
+#include <vector>
+
+#include <cor.h>
+#include <corhdr.h>
+#include <corprof.h>
+
+#include "classinfo.h"
+#include "mappedinfo.h"
+
+class Profiler;
+
+class ProfilerInfo;
+
+class ClassStorage;
+
+class ExecutionTrace;
+
+struct FunctionInfo : public MappedInfo<FunctionID>
+{
+ typedef std::basic_string<WCHAR> String;
+
+ ExecutionTrace *executionTrace;
+ std::vector<COR_PRF_CODE_INFO> codeInfo;
+ std::vector<COR_DEBUG_IL_TO_NATIVE_MAP> ILToNativeMapping;
+ ModuleID moduleId;
+ ModuleID classId;
+ mdMethodDef funcToken = mdMethodDefNil;
+ ClassInfo* ownerClass;
+ std::vector<ClassInfo*> typeArgs;
+ String name;
+ String fullName;
+ String returnType;
+ String signature;
+ bool isInitialized;
+ bool isNamePrinted;
+
+private:
+
+ static void ParseElementType(
+ const Profiler &profiler,
+ IMetaDataImport *pMDImport,
+ PCCOR_SIGNATURE &sigBlob,
+ ULONG &sigBlobSize,
+ String &str,
+ String &arrayBrackets);
+
+ static void ParseSignature(
+ const Profiler &profiler,
+ IMetaDataImport *pMDImport,
+ PCCOR_SIGNATURE &sigBlob,
+ ULONG &sigBlobSize,
+ String &returnType,
+ String &signature);
+
+ HRESULT InitializeCodeInfo(
+ const Profiler &profiler,
+ const ProfilerInfo &info) noexcept;
+
+ HRESULT InitializeILToNativeMapping(
+ const Profiler &profiler,
+ const ProfilerInfo &info) noexcept;
+
+ HRESULT InitializeFunctionName(
+ const Profiler &profiler,
+ IMetaDataImport *pMDImport,
+ ULONG funcNameSize) noexcept;
+
+ HRESULT InitializeOwnerClassFromClassId(
+ const Profiler &profiler,
+ ClassStorage &storage) noexcept;
+
+ HRESULT InitializeOwnerClassFromClassToken(
+ const Profiler &profiler,
+ ClassStorage &storage,
+ mdTypeDef classToken) noexcept;
+
+ HRESULT InitializeTypeArgs(
+ const Profiler &profiler,
+ ClassStorage &storage,
+ const ProfilerInfo &info,
+ ULONG32 typeArgsSize) noexcept;
+
+ HRESULT InitializeSignature(
+ const Profiler &profiler,
+ IMetaDataImport *pMDImport,
+ PCCOR_SIGNATURE &sigBlob,
+ ULONG &sigBlobSize) noexcept;
+
+public:
+ HRESULT Initialize(
+ const Profiler &profiler,
+ ClassStorage &storage) noexcept;
+};
+
+#endif // _FUNCTION_INFO_
--- /dev/null
+#ifndef _MAPPED_INFO_H_
+#define _MAPPED_INFO_H_
+
+#include "baseinfo.h"
+
+template<typename ID>
+struct MappedInfo : public BaseInfo
+{
+ ID id;
+};
+
+#endif // _MAPPED_INFO_H_
--- /dev/null
+#ifndef _THREAD_INFO_H_
+#define _THREAD_INFO_H_
+
+#include <atomic>
+
+#include <signal.h>
+#include <pthread.h>
+
+#include <cor.h>
+#include <corhdr.h>
+#include <corprof.h>
+
+#include "eventchannel.h"
+#include "mappedinfo.h"
+
+struct ThreadInfo : public MappedInfo<ThreadID>
+{
+ DWORD osThreadId;
+ pthread_t nativeHandle;
+
+ DWORD64 lastUserTime;
+
+ EventChannel eventChannel;
+ std::atomic_ulong genTicks;
+ ULONG fixTicks;
+ volatile sig_atomic_t interruptible;
+ size_t maxRestoreIpIdx;
+
+ ThreadInfo()
+ : osThreadId(0)
+ , nativeHandle()
+ , lastUserTime(0)
+ , eventChannel()
+ , genTicks(0)
+ , fixTicks(0)
+ , interruptible(false)
+ , maxRestoreIpIdx(0)
+ {}
+};
+
+#endif // _THREAD_INFO_H_
--- /dev/null
+#ifndef _LOG_H_
+#define _LOG_H_
+
+#include <iostream>
+#include <fstream>
+
+enum class LogLevel
+{
+ None,
+ Fatal,
+ Error,
+ Warn,
+ Info,
+ Debug,
+ Trace,
+ All
+};
+
+static inline const char *LogLevelName(LogLevel level)
+{
+ switch (level)
+ {
+ case LogLevel::None:
+ return "NONE";
+ case LogLevel::Fatal:
+ return "FATAL";
+ case LogLevel::Error:
+ return "ERROR";
+ case LogLevel::Warn:
+ return "WARN";
+ case LogLevel::Info:
+ return "INFO";
+ case LogLevel::Debug:
+ return "DEBUG";
+ case LogLevel::Trace:
+ return "TRACE";
+ case LogLevel::All:
+ return "ALL";
+ default:
+ return "";
+ }
+}
+
+#ifdef _DEBUG
+
+#include <utility>
+
+class Log
+{
+private:
+ class LogLine
+ {
+ public:
+ LogLine(LogLevel level, std::ostream *stream = nullptr)
+ : m_stream(stream)
+ {
+ if (m_stream)
+ {
+ *m_stream << "[" << LogLevelName(level) << "]\t";
+ }
+ }
+
+ LogLine(const LogLine&) = delete;
+
+ LogLine &operator=(const LogLine&) = delete;
+
+ LogLine(LogLine &&other)
+ {
+ m_stream = other.m_stream;
+ other.m_stream = nullptr;
+ }
+
+ LogLine &operator=(LogLine&&) = delete;
+
+ ~LogLine()
+ {
+ if (m_stream)
+ {
+ *m_stream << std::endl;
+ }
+ }
+
+ template<typename T>
+ LogLine &operator<<(T value)
+ {
+ if (m_stream)
+ {
+ *m_stream << value;
+ }
+ return *this;
+ }
+
+ private:
+ std::ostream *m_stream;
+ };
+
+public:
+ Log(LogLevel level, const std::string &filename)
+ : m_level(level)
+ , m_stream(new std::ofstream())
+ , m_stream_owner(true)
+ {
+ try
+ {
+ m_stream->exceptions(m_stream->exceptions() | std::ios::failbit);
+ static_cast<std::ofstream*>(m_stream)->open(filename);
+ }
+ catch (...)
+ {
+ delete m_stream;
+ throw;
+ }
+ }
+
+ explicit Log(const std::string &filename)
+ : Log(LogLevel::Warn, filename)
+ {}
+
+ Log(LogLevel level, std::ostream &stream)
+ : m_level(level)
+ , m_stream(&stream)
+ , m_stream_owner(false)
+ {}
+
+ explicit Log(LogLevel level)
+ : Log(level, std::cout)
+ {}
+
+ explicit Log(std::ostream &stream)
+ : Log(LogLevel::Warn, stream)
+ {}
+
+ Log()
+ : Log(LogLevel::Warn, std::cout)
+ {}
+
+ Log(const Log&) = delete;
+
+ Log &operator=(const Log&) = delete;
+
+ Log(Log &&other)
+ {
+ m_level = other.m_level;
+ m_stream = other.m_stream;
+ m_stream_owner = other.m_stream_owner;
+
+ other.m_level = LogLevel::None;
+ other.m_stream = nullptr;
+ other.m_stream_owner = false;
+ }
+
+ Log &operator=(Log &&other)
+ {
+ Log(std::move(other)).swap(*this);
+ return *this;
+ }
+
+ ~Log()
+ {
+ if (m_stream_owner)
+ {
+ delete m_stream;
+ }
+ }
+
+ void swap(Log &other)
+ {
+ std::swap(m_level, other.m_level);
+ std::swap(m_stream, other.m_stream);
+ std::swap(m_stream_owner, other.m_stream_owner);
+ }
+
+ std::ostream::iostate exceptions() const
+ {
+ return m_stream->exceptions();
+ }
+
+ void exceptions(std::ostream::iostate except)
+ {
+ m_stream->exceptions(except);
+ }
+
+ LogLine Fatal()
+ {
+ return DoLog<LogLevel::Fatal>();
+ }
+
+ LogLine Error()
+ {
+ return DoLog<LogLevel::Error>();
+ }
+
+ LogLine Warn()
+ {
+ return DoLog<LogLevel::Warn>();
+ }
+
+ LogLine Info()
+ {
+ return DoLog<LogLevel::Info>();
+ }
+
+ LogLine Debug()
+ {
+ return DoLog<LogLevel::Debug>();
+ }
+
+ LogLine Trace()
+ {
+ return DoLog<LogLevel::Trace>();
+ }
+
+private:
+ LogLevel m_level;
+ std::ostream *m_stream;
+ bool m_stream_owner;
+
+ template<LogLevel L>
+ LogLine DoLog()
+ {
+ // With RVO optimization LogLine destructor will be called only once.
+ // Otherwise with overloaded move constructor only last destructor call
+ // will print std::endl.
+ if (m_level >= L)
+ {
+ return LogLine(L, m_stream);
+ }
+ else
+ {
+ return LogLine(L);
+ }
+ }
+};
+
+#else // !_DEBUG
+
+class Log
+{
+private:
+ class LogLine
+ {
+ public:
+ LogLine() {}
+
+ LogLine(const LogLine&) = delete;
+
+ LogLine &operator=(const LogLine&) = delete;
+
+ LogLine(LogLine&&) = default;
+
+ LogLine &operator=(LogLine&&) = delete;
+
+ template<typename T>
+ LogLine &operator<<(T value)
+ {
+ return *this;
+ }
+ };
+
+public:
+ Log(LogLevel, const std::string&) {}
+
+ explicit Log(const std::string&) {}
+
+ Log(LogLevel, std::ostream&) {}
+
+ explicit Log(LogLevel) {}
+
+ explicit Log(std::ostream&) {}
+
+ Log() {}
+
+ Log(const Log&) = delete;
+
+ Log &operator=(const Log&) = delete;
+
+ Log(Log&&) = default;
+
+ Log &operator=(Log&&) = default;
+
+ void swap(Log &other) {}
+
+ std::ostream::iostate exceptions() const
+ {
+ return std::ostream::goodbit;
+ }
+
+ void exceptions(std::ostream::iostate except) {}
+
+ LogLine Fatal()
+ {
+ return LogLine();
+ }
+
+ LogLine Error()
+ {
+ return LogLine();
+ }
+
+ LogLine Warn()
+ {
+ return LogLine();
+ }
+
+ LogLine Info()
+ {
+ return LogLine();
+ }
+
+ LogLine Debug()
+ {
+ return LogLine();
+ }
+
+ LogLine Trace()
+ {
+ return LogLine();
+ }
+};
+
+#endif // !_DEBUG
+
+#endif // _LOG_H_
--- /dev/null
+#include <utility>
+
+#include <cor.h>
+
+namespace std
+{
+ template<>
+ struct default_delete<IUnknown> {
+ void operator()(IUnknown* pUnknown)
+ {
+ pUnknown->Release();
+ }
+ };
+}
--- /dev/null
+#ifndef _INTERVAL_SPLITTER_H_
+#define _INTERVAL_SPLITTER_H_
+
+#include <assert.h>
+#include <math.h>
+
+class IntervalSplitter
+{
+public:
+ IntervalSplitter() noexcept = default;
+
+ explicit IntervalSplitter(unsigned long length) noexcept
+ : m_length(length)
+ {}
+
+ IntervalSplitter(unsigned long length, unsigned long count) noexcept
+ : m_length(length)
+ , m_count(count)
+ {}
+
+ void Reset(unsigned long count) noexcept
+ {
+ m_count = count;
+ m_current = 0;
+ m_index = 0;
+ }
+
+ void Reset(unsigned long length, unsigned long count) noexcept
+ {
+ m_length = length;
+ m_count = count;
+ m_current = 0;
+ m_index = 0;
+ }
+
+ bool HasNext() noexcept
+ {
+ return m_index < m_count;
+ }
+
+ unsigned long GetNext() noexcept
+ {
+ assert(this->HasNext());
+ unsigned long prev = m_current;
+ m_current = llround(m_length * (++m_index / m_count));
+ return m_current - prev;
+ }
+
+private:
+ unsigned long m_length = 0;
+ double m_count = 0;
+ unsigned long m_current = 0;
+ unsigned long m_index = 0;
+};
+
+#endif // _INTERVAL_SPLITTER_H_
--- /dev/null
+#ifndef _ITERATOR_RANGE_H_
+#define _ITERATOR_RANGE_H_
+
+#include <iterator>
+
+template<typename Iterator>
+class iterator_range
+{
+public:
+ //
+ // Types.
+ //
+
+ typedef typename std::iterator_traits<Iterator>::iterator_category
+ iterator_category;
+ typedef typename std::iterator_traits<Iterator>::value_type
+ value_type;
+ typedef typename std::iterator_traits<Iterator>::difference_type
+ difference_type;
+ typedef typename std::iterator_traits<Iterator>::reference
+ reference;
+ typedef typename std::iterator_traits<Iterator>::pointer
+ pointer;
+
+ //
+ // Constructors.
+ //
+
+ iterator_range() = default;
+
+ iterator_range(Iterator begin, Iterator end)
+ : m_begin(begin)
+ , m_end(end)
+ {}
+
+ //
+ // Iterator access.
+ //
+
+ Iterator begin() const
+ {
+ return m_begin;
+ }
+
+ Iterator end() const
+ {
+ return m_end;
+ }
+
+private:
+ Iterator m_begin;
+ Iterator m_end;
+};
+
+// Deducing constructor wrappers.
+template<typename Iterator>
+inline iterator_range<Iterator>
+make_iterator_range(Iterator begin, Iterator end)
+{
+ return iterator_range<Iterator>(begin, end);
+}
+
+#endif // _ITERATOR_RANGE_H_
--- /dev/null
+#include <system_error>
+
+#include <time.h>
+#include <sys/time.h>
+#include <errno.h>
+
+#include "localtime.h"
+
+VOID
+PALAPI
+GetLocalTime(OUT LPSYSTEMTIME lpLocalTime)
+{
+ time_t tt;
+ struct tm ut;
+ struct tm *utPtr;
+ struct timeval timeval;
+ int timeofday_retval;
+
+ tt = time(NULL);
+
+ /* We can't get millisecond resolution from time(), so we get it from
+ gettimeofday() */
+ timeofday_retval = gettimeofday(&timeval, NULL);
+
+ utPtr = &ut;
+ if (localtime_r(&tt, utPtr) == NULL)
+ {
+ throw std::system_error(errno, std::system_category(),
+ "localtime_r() failed");
+ }
+
+ lpLocalTime->wYear = 1900 + utPtr->tm_year;
+ lpLocalTime->wMonth = utPtr->tm_mon + 1;
+ lpLocalTime->wDayOfWeek = utPtr->tm_wday;
+ lpLocalTime->wDay = utPtr->tm_mday;
+ lpLocalTime->wHour = utPtr->tm_hour;
+ lpLocalTime->wMinute = utPtr->tm_min;
+ lpLocalTime->wSecond = utPtr->tm_sec;
+
+ if(-1 == timeofday_retval)
+ {
+ lpLocalTime->wMilliseconds = 0;
+ throw std::system_error(errno, std::system_category(),
+ "gettimeofday() failed");
+ }
+ else
+ {
+ int old_seconds;
+ int new_seconds;
+
+ lpLocalTime->wMilliseconds = timeval.tv_usec / 1000;
+
+ old_seconds = utPtr->tm_sec;
+ new_seconds = timeval.tv_sec%60;
+
+ /* just in case we reached the next second in the interval between
+ time() and gettimeofday() */
+ if(old_seconds != new_seconds)
+ {
+ lpLocalTime->wMilliseconds = 999;
+ }
+ }
+}
--- /dev/null
+#include <pal.h>
+
+PALIMPORT
+VOID
+PALAPI
+GetLocalTime(OUT LPSYSTEMTIME lpLocalTime);
--- /dev/null
+#ifndef _SHARED_ITERATOR_RANGE_H_
+#define _SHARED_ITERATOR_RANGE_H_
+
+#include <utility>
+
+#include "iterator_range.h"
+
+template<typename Iterator, typename Lock>
+class shared_iterator_range : public iterator_range<Iterator>
+{
+public:
+ shared_iterator_range(Iterator begin, Iterator end, Lock &&lock)
+ : iterator_range<Iterator>(begin, end)
+ , m_lock(std::forward<Lock>(lock))
+ {}
+
+private:
+ Lock m_lock;
+};
+
+// Deducing constructor wrappers.
+template<typename Iterator, typename Lock>
+inline shared_iterator_range<Iterator, Lock>
+make_shared_iterator_range(Iterator begin, Iterator end, Lock &&lock)
+{
+ return shared_iterator_range<Iterator, Lock>(
+ begin, end, std::forward<Lock>(lock));
+}
+
+#endif // _SHARED_ITERATOR_RANGE_H_
--- /dev/null
+#include <utility>
+#include <system_error>
+#include <exception>
+#include <stdexcept>
+
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include "sigaction.h"
+
+// static
+void SigAction::SwapActions(
+ int signum,
+ const struct sigaction &newAction,
+ const struct sigaction &oldAction,
+ bool doFirstCheck)
+{
+ struct sigaction curAction;
+
+ if (doFirstCheck)
+ {
+ if (sigaction(signum, nullptr, &curAction))
+ {
+ throw std::system_error(errno, std::system_category(),
+ "SigAction::SwapActions(): sigaction()");
+ }
+
+ if (curAction.sa_handler != oldAction.sa_handler ||
+ curAction.sa_sigaction != oldAction.sa_sigaction)
+ {
+ throw std::runtime_error(
+ "SigAction::SwapActions(): Signal handler was changed");
+ }
+ }
+
+ if (sigaction(signum, &newAction, &curAction)) {
+ throw std::system_error(errno, std::system_category(),
+ "SigAction::SwapActions(): sigaction()");
+ }
+ // NOTE: double-check of old action to avoid some race conditions.
+ if (curAction.sa_handler != oldAction.sa_handler ||
+ curAction.sa_sigaction != oldAction.sa_sigaction)
+ {
+ sigaction(signum, &curAction, nullptr);
+ throw std::runtime_error(
+ "SigAction::SwapActions(): Signal handler was changed");
+ }
+}
+
+SigAction::SigAction()
+ : m_haveAction(false)
+{}
+
+SigAction::SigAction(int signum, const struct sigaction &action)
+ : m_haveAction(true)
+ , m_signum(signum)
+ , m_action(action)
+{
+ if (sigaction(signum, nullptr, &m_oldAction))
+ {
+ throw std::system_error(errno, std::system_category(),
+ "SigAction::SigAction(): sigaction()");
+ }
+
+ if (m_oldAction.sa_handler != SIG_DFL &&
+ m_oldAction.sa_handler != SIG_IGN)
+ {
+ throw std::runtime_error(
+ "SigAction::SigAction(): Signal handler was changed");
+ }
+
+ SigAction::SwapActions(m_signum, m_action, m_oldAction, false);
+}
+
+SigAction::SigAction(SigAction &&other) noexcept
+ : m_haveAction(false)
+{
+ *this = std::move(other);
+}
+
+SigAction::~SigAction()
+{
+ try
+ {
+ this->Release();
+ }
+ catch (...) {}
+}
+
+SigAction &SigAction::operator=(SigAction &&other)
+{
+ assert(this != &other);
+ this->Release();
+
+ m_haveAction = other.m_haveAction;
+ m_signum = other.m_signum;
+ m_action = other.m_action;
+ m_oldAction = other.m_oldAction;
+
+ other.m_haveAction = false;
+ return *this;
+}
+
+SigAction::operator bool() const noexcept
+{
+ return m_haveAction;
+}
+
+void SigAction::Release()
+{
+ if (!m_haveAction)
+ {
+ return;
+ }
+
+ m_haveAction = false;
+ SigAction::SwapActions(m_signum, m_oldAction, m_action);
+}
--- /dev/null
+#ifndef _SIG_ACTION_H_
+#define _SIG_ACTION_H_
+
+#include <signal.h>
+
+class SigAction
+{
+private:
+ static void SwapActions(
+ int signum,
+ const struct sigaction &newAction,
+ const struct sigaction &oldAction,
+ bool doFirstCheck = true);
+
+public:
+ SigAction();
+
+ SigAction(int signum, const struct sigaction &action);
+
+ SigAction(const SigAction &) = delete;
+
+ SigAction(SigAction &&other) noexcept;
+
+ ~SigAction();
+
+ SigAction &operator=(const SigAction&) = delete;
+
+ SigAction &operator=(SigAction &&other);
+
+ explicit operator bool() const noexcept;
+
+ void Release();
+
+private:
+ bool m_haveAction;
+ int m_signum;
+ struct sigaction m_action;
+ struct sigaction m_oldAction;
+};
+
+#endif // _SIG_ACTION_H_
--- /dev/null
+#include <stdexcept>
+#include <system_error>
+#include <new>
+#include <iostream>
+#include <memory>
+
+#include <errno.h>
+#include <string.h>
+
+#include "localtime.h"
+#include "profilermanager.h"
+#include "profiler.h"
+
+// static
+HRESULT Profiler::CreateObject(
+ REFIID riid,
+ void **ppInterface) noexcept
+{
+ //
+ // We should perform some prechecks to avoid unnecessary initialization.
+ //
+
+ if (ppInterface == nullptr)
+ return E_POINTER;
+
+ *ppInterface = nullptr;
+
+ if (
+ (riid != IID_ICorProfilerCallback3) &&
+ (riid != IID_ICorProfilerCallback2) &&
+ (riid != IID_ICorProfilerCallback) &&
+ (riid != IID_IUnknown)
+ )
+ {
+ return E_NOINTERFACE;
+ }
+
+ // This profiler implements the "profile-first" alternative of dealing
+ // with multiple in-process side-by-side CLR instances. First CLR
+ // to try to load us into this process wins.
+ // TODO: remove this when Global Profiler Manager will be implemented
+ // (see https://blogs.msdn.microsoft.com/davbr/2010/08/25/profilers-in-process-side-by-side-clr-instances-and-a-free-test-harness/).
+ {
+ static volatile LONG s_nFirstTime = 1;
+ if (s_nFirstTime == 0)
+ {
+ // Someone beat us to it.
+ return CORPROF_E_PROFILER_CANCEL_ACTIVATION;
+ }
+
+ // Dirty-read says this is the first load. Double-check that
+ // with a clean-read.
+ if (InterlockedCompareExchange(&s_nFirstTime, 0, 1) == 0)
+ {
+ // Someone beat us to it.
+ return CORPROF_E_PROFILER_CANCEL_ACTIVATION;
+ }
+ }
+
+ //
+ // Profiler instantiation.
+ //
+
+ Profiler *pProfiler;
+ try
+ {
+ pProfiler = new (std::nothrow) Profiler();
+ if (!pProfiler)
+ return E_OUTOFMEMORY;
+ }
+ catch (...)
+ {
+ // Exceptions in the Profiler's constructor.
+ return E_FAIL;
+ }
+
+ //
+ // Profiler registration.
+ //
+
+ HRESULT hr = S_OK;
+ try
+ {
+ ProfilerManager::Instance().RegisterProfiler(pProfiler);
+ }
+ catch (const std::exception &e)
+ {
+ hr = pProfiler->HandleException(e);
+ delete pProfiler;
+ return hr;
+ }
+
+ //
+ // Preparing results for returning.
+ //
+
+ hr = pProfiler->QueryInterface(riid, ppInterface);
+ if (FAILED(hr))
+ {
+ pProfiler->HandleHresult("Profiler::CreateObject()", hr);
+ }
+ // Profiler already had 1 on reference counter so we need to call
+ // Release() after QueryInterface() to retrieve it to 1.
+ pProfiler->Release();
+
+ return hr;
+}
+
+// static
+void Profiler::RemoveObject(
+ Profiler *pProfiler) noexcept
+{
+ //
+ // We should unregister profiler, destruct it and remove it from the memory.
+ //
+ _ASSERTE(ProfilerManager::Instance().IsProfilerRegistered(pProfiler));
+ ProfilerManager::Instance().UnregisterProfiler(pProfiler);
+ delete pProfiler;
+}
+
+Profiler::Profiler()
+ : m_cRef(1)
+ , m_logger()
+ , m_initialized(false)
+ , m_shutdowned(false)
+ , m_loggerConfig()
+ , m_traceLogConfig()
+ , m_profConfig()
+ , m_info()
+ , m_traceLog()
+ , m_commonTrace(*this) // Should be after m_logger, m_info and
+ // m_traceLog.
+ , m_cpuTrace(*this) // Should be after m_commonTrace.
+ , m_executionTrace(*this) // Should be after m_commonTrace.
+ , m_memoryTrace(*this) // Should be after m_executionTrace.
+ , m_firstTickCount(0)
+{
+}
+
+Profiler::~Profiler()
+{
+}
+
+Log &Profiler::LOG() const noexcept
+{
+ return const_cast<Log&>(m_logger);
+}
+
+ITraceLog &Profiler::TRACE() const noexcept
+{
+ // NOTE: default-constructed TraceLog object should not be used for output!
+ _ASSERTE(m_traceLog != nullptr);
+ return const_cast<ITraceLog&>(*m_traceLog);
+}
+
+DWORD Profiler::GetTickCountFromInit() const noexcept
+{
+ return GetTickCount() - m_firstTickCount;
+}
+
+HRESULT Profiler::HandleException(const std::exception &e) const noexcept
+{
+ // Find type of exception.
+
+ const HresultException* pHresultException =
+ dynamic_cast<const HresultException*>(&e);
+
+ const std::system_error* p_system_error =
+ dynamic_cast<const std::system_error*>(&e);
+
+ const std::ios_base::failure* p_ios_base_failure =
+ dynamic_cast<const std::ios_base::failure*>(&e);
+
+ const std::bad_alloc* p_bad_alloc =
+ dynamic_cast<const std::bad_alloc*>(&e);
+
+ const std::logic_error* p_logic_error =
+ dynamic_cast<const std::logic_error*>(&e);
+
+ // Send information about the exception to the log.
+
+ try
+ {
+ if (pHresultException)
+ {
+ LOG().Error() << "Exception: " << pHresultException->what()
+ << " (HR = " << pHresultException->hresult() << ")";
+ }
+ else if (p_system_error)
+ {
+ LOG().Error() << "Exception: " << p_system_error->what()
+ << " (EC = " << p_system_error->code() << ")";
+ }
+ else if (p_ios_base_failure)
+ {
+ // std::ios_base::failure should be inherited from std::system_error
+ // for C++11. This workaround applied if it is not true.
+ LOG().Error() << "Exception: " << p_ios_base_failure->what() << ": "
+ << strerror(errno);
+ }
+ else
+ {
+ LOG().Error() << "Exception: " << e.what();
+ }
+ }
+ catch (...)
+ {
+ // We can do nothing with information about exception if logging failed.
+ }
+
+ // Return appropriate for the exception HRESULT.
+
+ if (pHresultException)
+ {
+ return pHresultException->hresult();
+ }
+ else if (p_bad_alloc)
+ {
+ return E_OUTOFMEMORY;
+ }
+ else if (p_logic_error)
+ {
+ return E_UNEXPECTED;
+ }
+ else
+ {
+ return E_FAIL;
+ }
+}
+
+void Profiler::HandleSysErr(
+ const std::string& what_arg, int ev) const noexcept
+{
+ this->HandleException(
+ std::system_error(ev, std::system_category(), what_arg)
+ );
+}
+
+void Profiler::HandleHresult(
+ const std::string& what_arg, HRESULT hr) const noexcept
+{
+ this->HandleException(HresultException(what_arg, hr));
+}
+
+ProfilerConfig &Profiler::GetConfig() noexcept
+{
+ return m_profConfig;
+}
+
+const ProfilerInfo &Profiler::GetProfilerInfo() const noexcept
+{
+ return m_info;
+}
+
+CommonTrace &Profiler::GetCommonTrace() noexcept
+{
+ return m_commonTrace;
+}
+
+CpuTrace &Profiler::GetCpuTrace() noexcept
+{
+ return m_cpuTrace;
+}
+
+ExecutionTrace &Profiler::GetExecutionTrace() noexcept
+{
+ return m_executionTrace;
+}
+
+MemoryTrace &Profiler::GetMemoryTrace() noexcept
+{
+ return m_memoryTrace;
+}
+
+void Profiler::SetupLogging(LoggerConfig &config)
+{
+ if (config.OutputStream == LoggerOutputStream::Stdout)
+ {
+ m_logger = Log(config.Level, std::cout);
+ }
+ else if (config.OutputStream == LoggerOutputStream::Stderr)
+ {
+ m_logger = Log(config.Level, std::cerr);
+ }
+ else if (config.OutputStream == LoggerOutputStream::File)
+ {
+ m_logger = Log(config.Level, config.FileName);
+ }
+
+ // Disabling exceptions so Logger can be used without exceptions checking.
+ m_logger.exceptions(std::ostream::goodbit);
+}
+
+void Profiler::SetupTraceLog(TraceLogConfig &config)
+{
+ if (config.OutputStream == TraceLogOutputStream::Stdout)
+ {
+ m_traceLog.reset(ITraceLog::Create(ITraceLog::StdOutStream));
+ }
+ else if (config.OutputStream == TraceLogOutputStream::Stderr)
+ {
+ m_traceLog.reset(ITraceLog::Create(ITraceLog::StdErrStream));
+ }
+ else if (config.OutputStream == TraceLogOutputStream::File)
+ {
+ m_traceLog.reset(
+ ITraceLog::Create(ITraceLog::FileStream, config.FileName));
+ }
+}
+
+void Profiler::ProcessConfig(ProfilerConfig &config)
+{
+ // Check method is called during initialization phase.
+ _ASSERTE(m_initialized == false);
+
+ // Ensure the Profiler Info is initialized.
+ _ASSERTE(m_info.v1() != nullptr);
+
+ m_commonTrace.ProcessConfig(config);
+ m_cpuTrace.ProcessConfig(config);
+ m_executionTrace.ProcessConfig(config);
+ m_memoryTrace.ProcessConfig(config);
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::QueryInterface(
+ REFIID riid,
+ void **ppvObject)
+{
+ if (ppvObject == nullptr)
+ return E_POINTER;
+
+ // Pick the right v-table based on the IID passed in.
+ if (riid == IID_ICorProfilerCallback3)
+ {
+ *ppvObject = static_cast<ICorProfilerCallback3*>(this);
+ }
+ else if (riid == IID_ICorProfilerCallback2)
+ {
+ *ppvObject = static_cast<ICorProfilerCallback2*>(this);
+ }
+ else if (riid == IID_ICorProfilerCallback)
+ {
+ *ppvObject = static_cast<ICorProfilerCallback*>(this);
+ }
+ else if (riid == IID_IUnknown)
+ {
+ *ppvObject = static_cast<IUnknown*>(this);
+ }
+ else
+ {
+ *ppvObject = nullptr;
+ return E_NOINTERFACE;
+ }
+
+ // If successful, add a reference for out pointer and return.
+ this->AddRef();
+
+ return S_OK;
+}
+
+ULONG STDMETHODCALLTYPE Profiler::AddRef()
+{
+ return InterlockedIncrement(&m_cRef);
+}
+
+ULONG STDMETHODCALLTYPE Profiler::Release()
+{
+ LONG result = InterlockedDecrement(&m_cRef);
+ if (result == 0)
+ {
+ // Notify the Global Area that the profiler instance is not longer used.
+ Profiler::RemoveObject(this);
+ }
+
+ return result;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::Initialize(
+ IUnknown *pICorProfilerInfoUnk)
+{
+ // Check method is called only once.
+ _ASSERTE(m_initialized == false);
+
+ SYSTEMTIME systime;
+ GetLocalTime(&systime);
+ m_firstTickCount = GetTickCount();
+
+ HRESULT hr = S_OK;
+
+ try {
+ //
+ // Fetching the Configuration and setup logging.
+ //
+
+ m_loggerConfig = ProfilerManager::Instance().FetchLoggerConfig(this);
+ this->SetupLogging(m_loggerConfig);
+
+ m_traceLogConfig =
+ ProfilerManager::Instance().FetchTraceLogConfig(this);
+ m_profConfig = ProfilerManager::Instance().FetchProfilerConfig(this);
+
+ //
+ // Applying the Configuration to the TraceLog.
+ //
+
+ this->SetupTraceLog(m_traceLogConfig);
+
+ //
+ // Announce start time.
+ //
+
+ TRACE().DumpStartTime(systime);
+
+ //
+ // Initializing the Profiler Info.
+ //
+
+ hr = m_info.Initialize(pICorProfilerInfoUnk);
+ if (FAILED(hr))
+ {
+ throw HresultException("ProfilerInfo::Initialize()", hr);
+ }
+ _ASSERTE(m_info.version() > 0);
+
+ //
+ // Applying the Configuration to the Profiler.
+ //
+
+ this->ProcessConfig(m_profConfig);
+ TRACE().DumpProfilerConfig(m_profConfig);
+
+ //
+ // Initialization completion.
+ //
+
+ m_initialized = true;
+ }
+ catch (const std::exception &e)
+ {
+ this->Shutdown();
+ hr = this->HandleException(e);
+ }
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::Shutdown()
+{
+ // Check method is called only once.
+ _ASSERTE(m_shutdowned == false);
+
+ m_memoryTrace.Shutdown();
+ m_executionTrace.Shutdown();
+ m_cpuTrace.Shutdown();
+ m_commonTrace.Shutdown();
+
+ // ProfilerInfo can't be used after Shutdown event.
+ m_info.Reset();
+
+ m_shutdowned = true;
+
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::AppDomainCreationStarted(
+ AppDomainID appDomainId)
+{
+ LOG().Trace() << "AppDomainCreationStarted()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::AppDomainCreationFinished(
+ AppDomainID appDomainId,
+ HRESULT hrStatus)
+{
+ LOG().Trace() << "AppDomainCreationFinished()";
+
+ HRESULT hr;
+ hr = m_commonTrace.AppDomainCreationFinished(appDomainId, hrStatus);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::AppDomainShutdownStarted(
+ AppDomainID appDomainId)
+{
+ LOG().Trace() << "AppDomainShutdownStarted()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::AppDomainShutdownFinished(
+ AppDomainID appDomainId,
+ HRESULT hrStatus)
+{
+ LOG().Trace() << "AppDomainShutdownFinished()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::AssemblyLoadStarted(
+ AssemblyID assemblyId)
+{
+ LOG().Trace() << "AssemblyLoadStarted()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::AssemblyLoadFinished(
+ AssemblyID assemblyId,
+ HRESULT hrStatus)
+{
+ LOG().Trace() << "AssemblyLoadFinished()";
+
+ HRESULT hr;
+ hr = m_commonTrace.AssemblyLoadFinished(assemblyId, hrStatus);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::AssemblyUnloadStarted(
+ AssemblyID assemblyId)
+{
+ LOG().Trace() << "AssemblyUnloadStarted()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::AssemblyUnloadFinished(
+ AssemblyID assemblyId,
+ HRESULT hrStatus)
+{
+ LOG().Trace() << "AssemblyUnloadFinished()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ModuleLoadStarted(
+ ModuleID moduleId)
+{
+ LOG().Trace() << "ModuleLoadStarted()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ModuleLoadFinished(
+ ModuleID moduleId,
+ HRESULT hrStatus)
+{
+ LOG().Trace() << "ModuleLoadFinished()";
+
+ HRESULT hr;
+ hr = m_commonTrace.ModuleLoadFinished(moduleId, hrStatus);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ModuleUnloadStarted(
+ ModuleID moduleId)
+{
+ LOG().Trace() << "ModuleUnloadStarted()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ModuleUnloadFinished(
+ ModuleID moduleId,
+ HRESULT hrStatus)
+{
+ LOG().Trace() << "ModuleUnloadFinished()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ModuleAttachedToAssembly(
+ ModuleID moduleId,
+ AssemblyID assemblyId)
+{
+ LOG().Trace() << "ModuleAttachedToAssembly()";
+
+ HRESULT hr;
+ hr = m_commonTrace.ModuleAttachedToAssembly(moduleId, assemblyId);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ClassLoadStarted(
+ ClassID classId)
+{
+ LOG().Trace() << "ClassLoadStarted()";
+
+ HRESULT hr;
+ hr = m_commonTrace.ClassLoadStarted(classId);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ClassLoadFinished(
+ ClassID classId,
+ HRESULT hrStatus)
+{
+ LOG().Trace() << "ClassLoadFinished()";
+
+ HRESULT hr;
+ hr = m_commonTrace.ClassLoadFinished(classId, hrStatus);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ClassUnloadStarted(
+ ClassID classId)
+{
+ LOG().Trace() << "ClassUnloadStarted()";
+
+ HRESULT hr;
+ hr = m_commonTrace.ClassUnloadStarted(classId);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ClassUnloadFinished(
+ ClassID classId,
+ HRESULT hrStatus)
+{
+ LOG().Trace() << "ClassUnloadFinished()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::FunctionUnloadStarted(
+ FunctionID functionId)
+{
+ LOG().Trace() << "FunctionUnloadStarted()";
+
+ HRESULT hr;
+ hr = m_executionTrace.FunctionUnloadStarted(functionId);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::JITCompilationStarted(
+ FunctionID functionId,
+ BOOL fIsSafeToBlock)
+{
+ LOG().Trace() << "JITCompilationStarted()";
+
+ HRESULT hr;
+ hr = m_executionTrace.JITCompilationStarted(functionId, fIsSafeToBlock);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::JITCompilationFinished(
+ FunctionID functionId,
+ HRESULT hrStatus, BOOL fIsSafeToBlock)
+{
+ LOG().Trace() << "JITCompilationFinished()";
+
+ HRESULT hr;
+ hr = m_executionTrace.JITCompilationFinished(
+ functionId, hrStatus, fIsSafeToBlock);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::JITCachedFunctionSearchStarted(
+ FunctionID functionId,
+ BOOL *pbUseCachedFunction)
+{
+ LOG().Trace() << "JITCachedFunctionSearchStarted()";
+
+ HRESULT hr;
+ hr = m_executionTrace.JITCachedFunctionSearchStarted(
+ functionId, pbUseCachedFunction);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::JITCachedFunctionSearchFinished(
+ FunctionID functionId,
+ COR_PRF_JIT_CACHE result)
+{
+ LOG().Trace() << "JITCachedFunctionSearchFinished()";
+
+ HRESULT hr;
+ hr = m_executionTrace.JITCachedFunctionSearchFinished(functionId, result);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::JITFunctionPitched(
+ FunctionID functionId)
+{
+ LOG().Trace() << "JITFunctionPitched()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::JITInlining(
+ FunctionID callerId,
+ FunctionID calleeId,
+ BOOL *pfShouldInline)
+{
+ LOG().Trace() << "JITInlining()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ThreadCreated(
+ ThreadID threadId)
+{
+ LOG().Trace() << "ThreadCreated()";
+
+ HRESULT hr;
+ hr = m_commonTrace.ThreadCreated(threadId);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ThreadDestroyed(
+ ThreadID threadId)
+{
+ LOG().Trace() << "ThreadDestroyed()";
+
+ HRESULT hr;
+ hr = m_commonTrace.ThreadDestroyed(threadId);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ThreadAssignedToOSThread(
+ ThreadID managedThreadId,
+ DWORD osThreadId)
+{
+ LOG().Trace() << "ThreadAssignedToOSThread()";
+
+ HRESULT hr;
+ hr = m_commonTrace.ThreadAssignedToOSThread(managedThreadId, osThreadId);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ThreadNameChanged(
+ ThreadID threadId,
+ ULONG cchName,
+ _In_reads_opt_(cchName) WCHAR name[])
+{
+ LOG().Trace() << "ThreadNameChanged()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RemotingClientInvocationStarted()
+{
+ LOG().Trace() << "RemotingClientInvocationStarted()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RemotingClientSendingMessage(
+ GUID *pCookie,
+ BOOL fIsAsync)
+{
+ LOG().Trace() << "RemotingClientSendingMessage()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RemotingClientReceivingReply(
+ GUID *pCookie,
+ BOOL fIsAsync)
+{
+ LOG().Trace() << "RemotingClientReceivingReply()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RemotingClientInvocationFinished()
+{
+ LOG().Trace() << "RemotingClientInvocationFinished()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RemotingServerReceivingMessage(
+ GUID *pCookie,
+ BOOL fIsAsync)
+{
+ LOG().Trace() << "RemotingServerReceivingMessage()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RemotingServerInvocationStarted()
+{
+ LOG().Trace() << "RemotingServerInvocationStarted()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RemotingServerInvocationReturned()
+{
+ LOG().Trace() << "RemotingServerInvocationReturned()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RemotingServerSendingReply(
+ GUID *pCookie,
+ BOOL fIsAsync)
+{
+ LOG().Trace() << "RemotingServerSendingReply()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::UnmanagedToManagedTransition(
+ FunctionID functionId,
+ COR_PRF_TRANSITION_REASON reason)
+{
+ LOG().Trace() << "UnmanagedToManagedTransition()";
+
+ HRESULT hr;
+ hr = m_executionTrace.UnmanagedToManagedTransition(functionId, reason);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ManagedToUnmanagedTransition(
+ FunctionID functionId,
+ COR_PRF_TRANSITION_REASON reason)
+{
+ LOG().Trace() << "ManagedToUnmanagedTransition()";
+
+ HRESULT hr;
+ hr = m_executionTrace.ManagedToUnmanagedTransition(functionId, reason);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RuntimeSuspendStarted(
+ COR_PRF_SUSPEND_REASON suspendReason)
+{
+ LOG().Trace() << "RuntimeSuspendStarted()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RuntimeSuspendFinished()
+{
+ LOG().Trace() << "RuntimeSuspendFinished()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RuntimeSuspendAborted()
+{
+ LOG().Trace() << "RuntimeSuspendAborted()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RuntimeResumeStarted()
+{
+ LOG().Trace() << "RuntimeResumeStarted()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RuntimeResumeFinished()
+{
+ LOG().Trace() << "RuntimeResumeFinished()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RuntimeThreadSuspended(
+ ThreadID threadId)
+{
+ LOG().Trace() << "RuntimeThreadSuspended()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RuntimeThreadResumed(
+ ThreadID threadId)
+{
+ LOG().Trace() << "RuntimeThreadResumed()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::MovedReferences(
+ ULONG cMovedObjectIDRanges,
+ ObjectID oldObjectIDRangeStart[],
+ ObjectID newObjectIDRangeStart[],
+ ULONG cObjectIDRangeLength[])
+{
+ LOG().Trace() << "MovedReferences()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ObjectAllocated(
+ ObjectID objectId,
+ ClassID classId)
+{
+ LOG().Trace() << "ObjectAllocated()";
+
+ HRESULT hr;
+ hr = m_memoryTrace.ObjectAllocated(objectId, classId);
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ObjectsAllocatedByClass(
+ ULONG cClassCount,
+ ClassID classIds[],
+ ULONG cObjects[])
+{
+ LOG().Trace() << "ObjectsAllocatedByClass()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ObjectReferences(
+ ObjectID objectId,
+ ClassID classId,
+ ULONG cObjectRefs,
+ ObjectID objectRefIds[])
+{
+ LOG().Trace() << "ObjectReferences()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RootReferences(
+ ULONG cRootRefs,
+ ObjectID rootRefIds[])
+{
+ LOG().Trace() << "RootReferences()";
+ return S_OK;
+}
+
+
+HRESULT STDMETHODCALLTYPE Profiler::GarbageCollectionStarted(
+ int cGenerations,
+ BOOL generationCollected[],
+ COR_PRF_GC_REASON reason)
+{
+ LOG().Trace() << "GarbageCollectionStarted()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::SurvivingReferences(
+ ULONG cSurvivingObjectIDRanges,
+ ObjectID objectIDRangeStart[],
+ ULONG cObjectIDRangeLength[])
+{
+ LOG().Trace() << "SurvivingReferences()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::GarbageCollectionFinished()
+{
+ LOG().Trace() << "GarbageCollectionFinished()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::FinalizeableObjectQueued(
+ DWORD finalizerFlags,
+ ObjectID objectID)
+{
+ LOG().Trace() << "FinalizeableObjectQueued()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::RootReferences2(
+ ULONG cRootRefs,
+ ObjectID rootRefIds[],
+ COR_PRF_GC_ROOT_KIND rootKinds[],
+ COR_PRF_GC_ROOT_FLAGS rootFlags[],
+ UINT_PTR rootIds[])
+{
+ LOG().Trace() << "RootReferences2()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::HandleCreated(
+ GCHandleID handleId,
+ ObjectID initialObjectId)
+{
+ LOG().Trace() << "HandleCreated()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::HandleDestroyed(
+ GCHandleID handleId)
+{
+ LOG().Trace() << "HandleDestroyed()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ExceptionThrown(
+ ObjectID thrownObjectId)
+{
+ LOG().Trace() << "ExceptionThrown()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ExceptionSearchFunctionEnter(
+ FunctionID functionId)
+{
+ LOG().Trace() << "ExceptionSearchFunctionEnter()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ExceptionSearchFunctionLeave()
+{
+ LOG().Trace() << "ExceptionSearchFunctionLeave()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ExceptionSearchFilterEnter(
+ FunctionID functionId)
+{
+ LOG().Trace() << "ExceptionSearchFilterEnter()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ExceptionSearchFilterLeave()
+{
+ LOG().Trace() << "ExceptionSearchFilterLeave()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ExceptionSearchCatcherFound(
+ FunctionID functionId)
+{
+ LOG().Trace() << "ExceptionSearchCatcherFound()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ExceptionOSHandlerEnter(
+ UINT_PTR __unused)
+{
+ LOG().Trace() << "ExceptionOSHandlerEnter()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ExceptionOSHandlerLeave(
+ UINT_PTR __unused)
+{
+ LOG().Trace() << "ExceptionOSHandlerLeave()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ExceptionUnwindFunctionEnter(
+ FunctionID functionId)
+{
+ LOG().Trace() << "ExceptionUnwindFunctionEnter()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ExceptionUnwindFunctionLeave()
+{
+ LOG().Trace() << "ExceptionUnwindFunctionLeave()";
+
+ HRESULT hr;
+ hr = m_executionTrace.ExceptionUnwindFunctionLeave();
+
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ExceptionUnwindFinallyEnter(
+ FunctionID functionId)
+{
+ LOG().Trace() << "ExceptionUnwindFinallyEnter()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ExceptionUnwindFinallyLeave()
+{
+ LOG().Trace() << "ExceptionUnwindFinallyLeave()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ExceptionCatcherEnter(
+ FunctionID functionId,
+ ObjectID objectId)
+{
+ LOG().Trace() << "ExceptionCatcherEnter()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ExceptionCatcherLeave()
+{
+ LOG().Trace() << "ExceptionCatcherLeave()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::COMClassicVTableCreated(
+ ClassID wrappedClassId,
+ REFGUID implementedIID,
+ void *pVTable, ULONG cSlots)
+{
+ LOG().Trace() << "COMClassicVTableCreated()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::COMClassicVTableDestroyed(
+ ClassID wrappedClassId,
+ REFGUID implementedIID,
+ void *pVTable)
+{
+ LOG().Trace() << "COMClassicVTableDestroyed()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::InitializeForAttach(
+ IUnknown *pCorProfilerInfoUnk,
+ void *pvClientData,
+ UINT cbClientData)
+{
+ LOG().Trace() << "InitializeForAttach()";
+ // TODO: implement attaching functionality.
+ return CORPROF_E_PROFILER_NOT_ATTACHABLE;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ProfilerAttachComplete()
+{
+ LOG().Trace() << "ProfilerAttachComplete()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ProfilerDetachSucceeded()
+{
+ LOG().Trace() << "ProfilerDetachSucceeded()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ExceptionCLRCatcherFound()
+{
+ LOG().Trace() << "ExceptionCLRCatcherFound()";
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE Profiler::ExceptionCLRCatcherExecute()
+{
+ LOG().Trace() << "ExceptionCLRCatcherExecute()";
+ return S_OK;
+}
--- /dev/null
+#ifndef _PROFILER_H_
+#define _PROFILER_H_
+
+#include <exception>
+#include <memory>
+
+#include <cor.h>
+#include <corhdr.h>
+#include <corprof.h>
+
+#include "log.h"
+
+#include "loggerconfig.h"
+#include "tracelogconfig.h"
+#include "profilerconfig.h"
+#include "profilerinfo.h"
+
+#include "tracelog.h"
+
+#include "commontrace.h"
+#include "cputrace.h"
+#include "executiontrace.h"
+#include "memorytrace.h"
+
+// TODO: port to the std::system_error approach.
+class HresultException : public std::runtime_error
+{
+public:
+ HresultException(const std::string& what_arg, HRESULT hr)
+ : std::runtime_error(what_arg + ": UNKNOWN")
+ , m_hresult(hr)
+ {}
+
+ HRESULT hresult() const
+ {
+ return m_hresult;
+ }
+
+private:
+ HRESULT m_hresult;
+};
+
+class Profiler final : public ICorProfilerCallback3
+{
+public:
+ // Instantiate an instance of the callback interface in the Global Area.
+ static HRESULT CreateObject(
+ REFIID riid,
+ void **ppInterface) noexcept;
+
+ // Remove an instance of the callback interface from the Global Area.
+ static void RemoveObject(
+ Profiler *pProfiler) noexcept;
+
+private:
+ //
+ // Profiler should be created and destroyed through public API.
+ //
+
+ Profiler();
+
+ virtual ~Profiler();
+
+public:
+ // Returns mutable reference to the Logger even for a constant
+ // reference to the Profiler.
+ Log &LOG() const noexcept;
+
+ // Returns mutable reference to the TraceLog even for a constant
+ // reference to the Profiler.
+ ITraceLog &TRACE() const noexcept;
+
+ // Retrieves the number of milliseconds that have elapsed since the Profiler
+ // was initialized.
+ DWORD GetTickCountFromInit() const noexcept;
+
+ // Check type of the exception, send corresponding information to the log
+ // and return HRESULT related to this exception.
+ HRESULT HandleException(const std::exception &e) const noexcept;
+
+ // Wrap error code ev with std::system_error using std::system_category()
+ // and call to HandleException() to handle it.
+ void HandleSysErr(const std::string& what_arg, int ev) const noexcept;
+
+ // Wrap HRESULT with HresultException and call to HandleException()
+ // to handle it.
+ void HandleHresult(const std::string& what_arg, HRESULT hr) const noexcept;
+
+ //
+ // Simple Getters.
+ //
+
+ ProfilerConfig &GetConfig() noexcept;
+
+ const ProfilerInfo &GetProfilerInfo() const noexcept;
+
+ CommonTrace &GetCommonTrace() noexcept;
+
+ CpuTrace &GetCpuTrace() noexcept;
+
+ ExecutionTrace &GetExecutionTrace() noexcept;
+
+ MemoryTrace &GetMemoryTrace() noexcept;
+
+private:
+ //
+ // Various useful instance methods.
+ //
+
+ // Apply the configuration to the Logger.
+ void SetupLogging(LoggerConfig &config);
+
+ // Apply the configuration to the TraceLog.
+ void SetupTraceLog(TraceLogConfig &config);
+
+ // Apply the configuration to the Profiler.
+ void ProcessConfig(ProfilerConfig &config);
+
+public:
+ //
+ // IUnknown methods.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(
+ REFIID riid,
+ void **ppvObject) override;
+
+ virtual ULONG STDMETHODCALLTYPE AddRef() override;
+
+ virtual ULONG STDMETHODCALLTYPE Release() override;
+
+ //
+ // Startup/shutdown events.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE Initialize(
+ IUnknown *pICorProfilerInfoUnk) override;
+
+ virtual HRESULT STDMETHODCALLTYPE Shutdown() override;
+
+ //
+ // Application domain events.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE AppDomainCreationStarted(
+ AppDomainID appDomainId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE AppDomainCreationFinished(
+ AppDomainID appDomainId,
+ HRESULT hrStatus) override;
+
+ virtual HRESULT STDMETHODCALLTYPE AppDomainShutdownStarted(
+ AppDomainID appDomainId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE AppDomainShutdownFinished(
+ AppDomainID appDomainId,
+ HRESULT hrStatus) override;
+
+ //
+ // Assembly events.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE AssemblyLoadStarted(
+ AssemblyID assemblyId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE AssemblyLoadFinished(
+ AssemblyID assemblyId,
+ HRESULT hrStatus) override;
+
+ virtual HRESULT STDMETHODCALLTYPE AssemblyUnloadStarted(
+ AssemblyID assemblyId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE AssemblyUnloadFinished(
+ AssemblyID assemblyId,
+ HRESULT hrStatus) override;
+
+ //
+ // Module events.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE ModuleLoadStarted(
+ ModuleID moduleId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ModuleLoadFinished(
+ ModuleID moduleId,
+ HRESULT hrStatus) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ModuleUnloadStarted(
+ ModuleID moduleId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ModuleUnloadFinished(
+ ModuleID moduleId,
+ HRESULT hrStatus) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ModuleAttachedToAssembly(
+ ModuleID moduleId,
+ AssemblyID assemblyId) override;
+
+ //
+ // Class events.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE ClassLoadStarted(
+ ClassID classId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ClassLoadFinished(
+ ClassID classId,
+ HRESULT hrStatus) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ClassUnloadStarted(
+ ClassID classId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ClassUnloadFinished(
+ ClassID classId,
+ HRESULT hrStatus) override;
+
+ virtual HRESULT STDMETHODCALLTYPE FunctionUnloadStarted(
+ FunctionID functionId) override;
+
+ //
+ // Jit events.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE JITCompilationStarted(
+ FunctionID functionId,
+ BOOL fIsSafeToBlock) override;
+
+ virtual HRESULT STDMETHODCALLTYPE JITCompilationFinished(
+ FunctionID functionId,
+ HRESULT hrStatus,
+ BOOL fIsSafeToBlock) override;
+
+ virtual HRESULT STDMETHODCALLTYPE JITCachedFunctionSearchStarted(
+ FunctionID functionId,
+ BOOL *pbUseCachedFunction) override;
+
+ virtual HRESULT STDMETHODCALLTYPE JITCachedFunctionSearchFinished(
+ FunctionID functionId,
+ COR_PRF_JIT_CACHE result) override;
+
+ virtual HRESULT STDMETHODCALLTYPE JITFunctionPitched(
+ FunctionID functionId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE JITInlining(
+ FunctionID callerId,
+ FunctionID calleeId,
+ BOOL *pfShouldInline) override;
+
+ //
+ // Thread events.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE ThreadCreated(
+ ThreadID threadId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ThreadDestroyed(
+ ThreadID threadId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ThreadAssignedToOSThread(
+ ThreadID managedThreadId,
+ DWORD osThreadId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ThreadNameChanged(
+ ThreadID threadId,
+ ULONG cchName,
+ _In_reads_opt_(cchName) WCHAR name[]) override;
+
+ //
+ // Remoting events.
+ //
+
+ // Client-side events.
+
+ virtual HRESULT STDMETHODCALLTYPE RemotingClientInvocationStarted() override;
+
+ virtual HRESULT STDMETHODCALLTYPE RemotingClientSendingMessage(
+ GUID *pCookie,
+ BOOL fIsAsync) override;
+
+ virtual HRESULT STDMETHODCALLTYPE RemotingClientReceivingReply(
+ GUID *pCookie,
+ BOOL fIsAsync) override;
+
+ virtual HRESULT STDMETHODCALLTYPE RemotingClientInvocationFinished() override;
+
+ // Server-side events.
+
+ virtual HRESULT STDMETHODCALLTYPE RemotingServerReceivingMessage(
+ GUID *pCookie,
+ BOOL fIsAsync) override;
+
+ virtual HRESULT STDMETHODCALLTYPE RemotingServerInvocationStarted() override;
+
+ virtual HRESULT STDMETHODCALLTYPE RemotingServerInvocationReturned() override;
+
+ virtual HRESULT STDMETHODCALLTYPE RemotingServerSendingReply(
+ GUID *pCookie,
+ BOOL fIsAsync) override;
+
+ //
+ // Transition events.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE UnmanagedToManagedTransition(
+ FunctionID functionId,
+ COR_PRF_TRANSITION_REASON reason) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ManagedToUnmanagedTransition(
+ FunctionID functionId,
+ COR_PRF_TRANSITION_REASON reason) override;
+
+ //
+ // Runtime suspension events.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE RuntimeSuspendStarted(
+ COR_PRF_SUSPEND_REASON suspendReason) override;
+
+ virtual HRESULT STDMETHODCALLTYPE RuntimeSuspendFinished() override;
+
+ virtual HRESULT STDMETHODCALLTYPE RuntimeSuspendAborted() override;
+
+ virtual HRESULT STDMETHODCALLTYPE RuntimeResumeStarted() override;
+
+ virtual HRESULT STDMETHODCALLTYPE RuntimeResumeFinished() override;
+
+ virtual HRESULT STDMETHODCALLTYPE RuntimeThreadSuspended(
+ ThreadID threadId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE RuntimeThreadResumed(
+ ThreadID threadId) override;
+
+ //
+ // GC events.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE MovedReferences(
+ ULONG cMovedObjectIDRanges,
+ ObjectID oldObjectIDRangeStart[],
+ ObjectID newObjectIDRangeStart[],
+ ULONG cObjectIDRangeLength[]) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ObjectAllocated(
+ ObjectID objectId,
+ ClassID classId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ObjectsAllocatedByClass(
+ ULONG cClassCount,
+ ClassID classIds[],
+ ULONG cObjects[]) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ObjectReferences(
+ ObjectID objectId,
+ ClassID classId,
+ ULONG cObjectRefs,
+ ObjectID objectRefIds[]) override;
+
+ virtual HRESULT STDMETHODCALLTYPE RootReferences(
+ ULONG cRootRefs,
+ ObjectID rootRefIds[]) override;
+
+ virtual HRESULT STDMETHODCALLTYPE GarbageCollectionStarted(
+ int cGenerations,
+ BOOL generationCollected[],
+ COR_PRF_GC_REASON reason) override;
+
+ virtual HRESULT STDMETHODCALLTYPE SurvivingReferences(
+ ULONG cSurvivingObjectIDRanges,
+ ObjectID objectIDRangeStart[],
+ ULONG cObjectIDRangeLength[]) override;
+
+ virtual HRESULT STDMETHODCALLTYPE GarbageCollectionFinished() override;
+
+ virtual HRESULT STDMETHODCALLTYPE FinalizeableObjectQueued(
+ DWORD finalizerFlags,
+ ObjectID objectId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE RootReferences2(
+ ULONG cRootRefs,
+ ObjectID rootRefIds[],
+ COR_PRF_GC_ROOT_KIND rootKinds[],
+ COR_PRF_GC_ROOT_FLAGS rootFlags[],
+ UINT_PTR rootIds[]) override;
+
+ virtual HRESULT STDMETHODCALLTYPE HandleCreated(
+ GCHandleID handleId,
+ ObjectID initialObjectId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE HandleDestroyed(
+ GCHandleID handleId) override;
+
+ //
+ // Exception events.
+ //
+
+ // Exception creation.
+
+ virtual HRESULT STDMETHODCALLTYPE ExceptionThrown(
+ ObjectID thrownObjectId) override;
+
+ // Search phase.
+
+ virtual HRESULT STDMETHODCALLTYPE ExceptionSearchFunctionEnter(
+ FunctionID functionId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ExceptionSearchFunctionLeave() override;
+
+ virtual HRESULT STDMETHODCALLTYPE ExceptionSearchFilterEnter(
+ FunctionID functionId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ExceptionSearchFilterLeave() override;
+
+ virtual HRESULT STDMETHODCALLTYPE ExceptionSearchCatcherFound(
+ FunctionID functionId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ExceptionOSHandlerEnter(
+ UINT_PTR __unused) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ExceptionOSHandlerLeave(
+ UINT_PTR __unused) override;
+
+ // Unwind phase.
+
+ virtual HRESULT STDMETHODCALLTYPE ExceptionUnwindFunctionEnter(
+ FunctionID functionId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ExceptionUnwindFunctionLeave() override;
+
+ virtual HRESULT STDMETHODCALLTYPE ExceptionUnwindFinallyEnter(
+ FunctionID functionId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ExceptionUnwindFinallyLeave() override;
+
+ virtual HRESULT STDMETHODCALLTYPE ExceptionCatcherEnter(
+ FunctionID functionId, ObjectID objectId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ExceptionCatcherLeave() override;
+
+ //
+ // COM classic wrapper.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE COMClassicVTableCreated(
+ ClassID wrappedClassId,
+ REFGUID implementedIID,
+ void *pVTable,
+ ULONG cSlots) override;
+
+ virtual HRESULT STDMETHODCALLTYPE COMClassicVTableDestroyed(
+ ClassID wrappedClassId,
+ REFGUID implementedIID,
+ void *pVTable) override;
+
+ //
+ // Attach events.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE InitializeForAttach(
+ IUnknown *pCorProfilerInfoUnk,
+ void *pvClientData,
+ UINT cbClientData) override;
+
+ virtual HRESULT STDMETHODCALLTYPE ProfilerAttachComplete() override;
+
+ virtual HRESULT STDMETHODCALLTYPE ProfilerDetachSucceeded() override;
+
+ //
+ // DEPRECATED. These callbacks are no longer delivered.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE ExceptionCLRCatcherFound() override;
+
+ virtual HRESULT STDMETHODCALLTYPE ExceptionCLRCatcherExecute() override;
+
+private:
+ LONG m_cRef;
+
+ Log m_logger;
+
+ BOOL m_initialized;
+ BOOL m_shutdowned;
+
+ LoggerConfig m_loggerConfig;
+ TraceLogConfig m_traceLogConfig;
+ ProfilerConfig m_profConfig;
+ ProfilerInfo m_info;
+
+ std::unique_ptr<ITraceLog> m_traceLog;
+
+ CommonTrace m_commonTrace;
+ CpuTrace m_cpuTrace;
+ ExecutionTrace m_executionTrace;
+ MemoryTrace m_memoryTrace;
+
+ DWORD m_firstTickCount;
+};
+
+#endif // _PROFILER_H_
--- /dev/null
+#include "profilerinfo.h"
+
+ProfilerInfo::ProfilerInfo() noexcept
+ : m_pProfilerInfo (nullptr)
+ , m_pProfilerInfo2(nullptr)
+ , m_pProfilerInfo3(nullptr)
+ , m_pProfilerInfo4(nullptr)
+ , m_pProfilerInfo5(nullptr)
+ , m_pProfilerInfo6(nullptr)
+ , m_pProfilerInfo7(nullptr)
+ , m_version(0)
+{
+}
+
+ProfilerInfo::~ProfilerInfo()
+{
+ if (m_pProfilerInfo != nullptr)
+ m_pProfilerInfo->Release();
+}
+
+HRESULT ProfilerInfo::Initialize(IUnknown *pICorProfilerInfoUnk) noexcept
+{
+ this->Reset(); // Ensure ProfilerInfo is in initial state.
+
+ HRESULT hr;
+
+ if (m_pProfilerInfo7 == nullptr)
+ {
+ hr = pICorProfilerInfoUnk->QueryInterface(
+ IID_ICorProfilerInfo7,
+ (void**)&m_pProfilerInfo7);
+ if (SUCCEEDED(hr)) {
+ _ASSERTE(m_version == 0);
+ m_version = 7;
+
+ m_pProfilerInfo6 = static_cast<ICorProfilerInfo6*>(
+ m_pProfilerInfo7);
+ m_pProfilerInfo5 = static_cast<ICorProfilerInfo5*>(
+ m_pProfilerInfo7);
+ m_pProfilerInfo4 = static_cast<ICorProfilerInfo4*>(
+ m_pProfilerInfo7);
+ m_pProfilerInfo3 = static_cast<ICorProfilerInfo3*>(
+ m_pProfilerInfo7);
+ m_pProfilerInfo2 = static_cast<ICorProfilerInfo2*>(
+ m_pProfilerInfo7);
+ m_pProfilerInfo = static_cast<ICorProfilerInfo* >(
+ m_pProfilerInfo7);
+ }
+ }
+
+ if (m_pProfilerInfo6 == nullptr)
+ {
+ hr = pICorProfilerInfoUnk->QueryInterface(
+ IID_ICorProfilerInfo6,
+ (void**)&m_pProfilerInfo6);
+ if (SUCCEEDED(hr)) {
+ _ASSERTE(m_version == 0);
+ m_version = 6;
+
+ m_pProfilerInfo5 = static_cast<ICorProfilerInfo5*>(
+ m_pProfilerInfo6);
+ m_pProfilerInfo4 = static_cast<ICorProfilerInfo4*>(
+ m_pProfilerInfo6);
+ m_pProfilerInfo3 = static_cast<ICorProfilerInfo3*>(
+ m_pProfilerInfo6);
+ m_pProfilerInfo2 = static_cast<ICorProfilerInfo2*>(
+ m_pProfilerInfo6);
+ m_pProfilerInfo = static_cast<ICorProfilerInfo* >(
+ m_pProfilerInfo6);
+ }
+ }
+
+ if (m_pProfilerInfo5 == nullptr)
+ {
+ hr = pICorProfilerInfoUnk->QueryInterface(
+ IID_ICorProfilerInfo5,
+ (void**)&m_pProfilerInfo5);
+ if (SUCCEEDED(hr)) {
+ _ASSERTE(m_version == 0);
+ m_version = 5;
+
+ m_pProfilerInfo4 = static_cast<ICorProfilerInfo4*>(
+ m_pProfilerInfo5);
+ m_pProfilerInfo3 = static_cast<ICorProfilerInfo3*>(
+ m_pProfilerInfo5);
+ m_pProfilerInfo2 = static_cast<ICorProfilerInfo2*>(
+ m_pProfilerInfo5);
+ m_pProfilerInfo = static_cast<ICorProfilerInfo* >(
+ m_pProfilerInfo5);
+ }
+ }
+
+ if (m_pProfilerInfo4 == nullptr)
+ {
+ hr = pICorProfilerInfoUnk->QueryInterface(
+ IID_ICorProfilerInfo4,
+ (void**)&m_pProfilerInfo4);
+ if (SUCCEEDED(hr)) {
+ _ASSERTE(m_version == 0);
+ m_version = 4;
+
+ m_pProfilerInfo3 = static_cast<ICorProfilerInfo3*>(
+ m_pProfilerInfo4);
+ m_pProfilerInfo2 = static_cast<ICorProfilerInfo2*>(
+ m_pProfilerInfo4);
+ m_pProfilerInfo = static_cast<ICorProfilerInfo* >(
+ m_pProfilerInfo4);
+ }
+ }
+
+ if (m_pProfilerInfo3 == nullptr)
+ {
+ hr = pICorProfilerInfoUnk->QueryInterface(
+ IID_ICorProfilerInfo3,
+ (void**)&m_pProfilerInfo3);
+ if (SUCCEEDED(hr)) {
+ _ASSERTE(m_version == 0);
+ m_version = 3;
+
+ m_pProfilerInfo2 = static_cast<ICorProfilerInfo2*>(
+ m_pProfilerInfo3);
+ m_pProfilerInfo = static_cast<ICorProfilerInfo* >(
+ m_pProfilerInfo3);
+ }
+ }
+
+ if (m_pProfilerInfo2 == nullptr)
+ {
+ hr = pICorProfilerInfoUnk->QueryInterface(
+ IID_ICorProfilerInfo2,
+ (void**)&m_pProfilerInfo2);
+ if (SUCCEEDED(hr)) {
+ _ASSERTE(m_version == 0);
+ m_version = 2;
+
+ m_pProfilerInfo = static_cast<ICorProfilerInfo*>(
+ m_pProfilerInfo2);
+ }
+ }
+
+ if (m_pProfilerInfo == nullptr)
+ {
+ hr = pICorProfilerInfoUnk->QueryInterface(
+ IID_ICorProfilerInfo,
+ (void**)&m_pProfilerInfo);
+ if (SUCCEEDED(hr)) {
+ _ASSERTE(m_version == 0);
+ m_version = 1;
+ }
+ }
+
+ _ASSERTE(m_version < 7 || m_pProfilerInfo7 != nullptr);
+ _ASSERTE(m_version < 6 || m_pProfilerInfo6 != nullptr);
+ _ASSERTE(m_version < 5 || m_pProfilerInfo5 != nullptr);
+ _ASSERTE(m_version < 4 || m_pProfilerInfo4 != nullptr);
+ _ASSERTE(m_version < 3 || m_pProfilerInfo3 != nullptr);
+ _ASSERTE(m_version < 2 || m_pProfilerInfo2 != nullptr);
+ _ASSERTE(m_version < 1 || m_pProfilerInfo != nullptr);
+
+ return hr;
+}
+
+void ProfilerInfo::Reset() noexcept
+{
+ if (m_version != 0)
+ {
+ _ASSERTE(m_pProfilerInfo != nullptr);
+ m_pProfilerInfo->Release();
+
+ m_pProfilerInfo = nullptr;
+ m_pProfilerInfo2 = nullptr;
+ m_pProfilerInfo3 = nullptr;
+ m_pProfilerInfo4 = nullptr;
+ m_pProfilerInfo5 = nullptr;
+ m_pProfilerInfo6 = nullptr;
+ m_pProfilerInfo7 = nullptr;
+ m_version = 0;
+ }
+}
+
+unsigned int ProfilerInfo::version() const noexcept
+{
+ return m_version;
+}
+
+ICorProfilerInfo *ProfilerInfo::v1() const noexcept
+{
+ _ASSERTE(m_version >= 1);
+ return m_pProfilerInfo;
+}
+
+ICorProfilerInfo2 *ProfilerInfo::v2() const noexcept
+{
+ _ASSERTE(m_version >= 2);
+ return m_pProfilerInfo2;
+}
+
+ICorProfilerInfo3 *ProfilerInfo::v3() const noexcept
+{
+ _ASSERTE(m_version >= 3);
+ return m_pProfilerInfo3;
+}
+
+ICorProfilerInfo4 *ProfilerInfo::v4() const noexcept
+{
+ _ASSERTE(m_version >= 4);
+ return m_pProfilerInfo4;
+}
+
+ICorProfilerInfo5 *ProfilerInfo::v5() const noexcept
+{
+ _ASSERTE(m_version >= 5);
+ return m_pProfilerInfo5;
+}
+
+ICorProfilerInfo6 *ProfilerInfo::v6() const noexcept
+{
+ _ASSERTE(m_version >= 6);
+ return m_pProfilerInfo6;
+}
+
+ICorProfilerInfo7 *ProfilerInfo::v7() const noexcept
+{
+ _ASSERTE(m_version >= 7);
+ return m_pProfilerInfo7;
+}
--- /dev/null
+#ifndef _PROFILER_INFO_H_
+#define _PROFILER_INFO_H_
+
+#include <cor.h>
+#include <corhdr.h>
+#include <corprof.h>
+
+class ProfilerInfo final
+{
+public:
+ ProfilerInfo() noexcept;
+
+ ~ProfilerInfo();
+
+ // Initialize ProfilerInfo with specific pointer to Profiler Info interface.
+ HRESULT Initialize(IUnknown *pICorProfilerInfoUnk) noexcept;
+
+ // Reset ProfilerInfo to initial state.
+ void Reset() noexcept;
+
+ // Get version of the Profiler Info API. Zero value means that no API
+ // versions is supported.
+ unsigned int version() const noexcept;
+
+ //
+ // These methods provide access to a specific version of the Profiler Info
+ // interface. You should be sure that the requested version is supported.
+ // Requesting of unsupported interface version invokes undefined behavior.
+ //
+ ICorProfilerInfo *v1() const noexcept;
+ ICorProfilerInfo2 *v2() const noexcept;
+ ICorProfilerInfo3 *v3() const noexcept;
+ ICorProfilerInfo4 *v4() const noexcept;
+ ICorProfilerInfo5 *v5() const noexcept;
+ ICorProfilerInfo6 *v6() const noexcept;
+ ICorProfilerInfo7 *v7() const noexcept;
+
+private:
+ // Pointers to the implementation of the ProfilerInfo interface(s).
+ ICorProfilerInfo *m_pProfilerInfo;
+ ICorProfilerInfo2 *m_pProfilerInfo2;
+ ICorProfilerInfo3 *m_pProfilerInfo3;
+ ICorProfilerInfo4 *m_pProfilerInfo4;
+ ICorProfilerInfo5 *m_pProfilerInfo5;
+ ICorProfilerInfo6 *m_pProfilerInfo6;
+ ICorProfilerInfo7 *m_pProfilerInfo7;
+
+ // Version of the Profiler Info API.
+ unsigned int m_version;
+};
+
+#endif // _PROFILER_INFO_H_
--- /dev/null
+#include <assert.h>
+
+#include "profiler.h"
+#include "environmentconfigprovider.h"
+#include "profilermanager.h"
+
+// NOTE: currently only one instance of the Profiler can be registered in the
+// Profiler Manager. We use "hidden" global reference since this limitation
+// shouldn't be demonstrated in the class private section. We can use a static
+// global variable because the Profiler Manager instance is a singleton.
+static Profiler *g_pProfilerObject = nullptr;
+
+// static
+ProfilerManager &ProfilerManager::Instance() noexcept
+{
+ static ProfilerManager s_ProfilerManagerInstance;
+ return s_ProfilerManagerInstance;
+}
+
+ProfilerManager::ProfilerManager() noexcept
+{
+}
+
+ProfilerManager::~ProfilerManager()
+{
+ if (g_pProfilerObject != nullptr)
+ {
+ DllDetachShutdown();
+ }
+ // We should ensure that the DllDetachShutdown() method was called before
+ // singleton destruction.
+ assert(g_pProfilerObject == nullptr);
+}
+
+template <typename T>
+T ProfilerManager::FetchConfig(const Profiler *pProfiler)
+{
+ // Ensure method is called for the global profiler instance;
+ assert(g_pProfilerObject == pProfiler);
+
+ T config;
+
+ // Currently only environment is used as source of the configuration.
+ EnvironmentConfigProvider().FetchConfig(config);
+
+ config.Validate();
+
+ auto warnings = config.Verify();
+ if (!warnings.empty())
+ {
+ auto logLine = pProfiler->LOG().Warn();
+ logLine << "Some errors detected in " << config.Name() << ":";
+ for (const auto &warning : warnings)
+ {
+ logLine << "\n\t\t" << warning;
+ }
+ }
+
+ return config;
+}
+
+bool ProfilerManager::IsProfilerRegistered(
+ const Profiler *pProfiler) const noexcept
+{
+ return pProfiler != nullptr && g_pProfilerObject == pProfiler;
+}
+
+void ProfilerManager::RegisterProfiler(const Profiler *pProfiler)
+{
+ // NOTE: potential race condition should be avoided outside of this class.
+ if (g_pProfilerObject == nullptr)
+ {
+ g_pProfilerObject = const_cast<Profiler*>(pProfiler);
+ }
+ else
+ {
+ // Ensure method is called for the global profiler instance;
+ assert(g_pProfilerObject == pProfiler);
+ }
+}
+
+void ProfilerManager::UnregisterProfiler(const Profiler *pProfiler)
+{
+ if (g_pProfilerObject == pProfiler)
+ {
+ g_pProfilerObject = nullptr;
+ }
+}
+
+LoggerConfig ProfilerManager::FetchLoggerConfig(const Profiler *pProfiler)
+{
+ return FetchConfig<LoggerConfig>(pProfiler);
+}
+
+TraceLogConfig ProfilerManager::FetchTraceLogConfig(const Profiler *pProfiler)
+{
+ return FetchConfig<TraceLogConfig>(pProfiler);
+}
+
+ProfilerConfig ProfilerManager::FetchProfilerConfig(const Profiler *pProfiler)
+{
+ return FetchConfig<ProfilerConfig>(pProfiler);
+}
+
+void ProfilerManager::DllDetachShutdown() noexcept
+{
+ //
+ // Since this function is called when DLL ends up its lifetime, we don't
+ // worry about profiler's reference counter.
+ //
+ if (IsProfilerRegistered(g_pProfilerObject))
+ {
+ Profiler::RemoveObject(g_pProfilerObject);
+ }
+}
--- /dev/null
+#ifndef _PROFILER_MANAGER_H_
+#define _PROFILER_MANAGER_H_
+
+#include "loggerconfig.h"
+#include "tracelogconfig.h"
+#include "profilerconfig.h"
+
+class Profiler; // Forward declaration instead of the header inclusion.
+
+class ProfilerManager
+{
+public:
+ // Get the instance of the singleton. It will be instantiated at first call.
+ static ProfilerManager &Instance() noexcept;
+
+private:
+ // Signleton can be instantiated only by the public Instance() static
+ // member function.
+ ProfilerManager() noexcept;
+
+ // Singleton can be destroyed only during process termination.
+ ~ProfilerManager();
+
+ template<typename T>
+ T FetchConfig(const Profiler *pProfiler);
+
+public:
+ // Check if the Profiler is registered in the Profiler Manager.
+ bool IsProfilerRegistered(const Profiler *pProfiler) const noexcept;
+
+ // Register the Profiler in the Profiler Manager.
+ void RegisterProfiler(const Profiler *pProfiler);
+
+ // Remove the Profiler from the Profiler Manager.
+ void UnregisterProfiler(const Profiler *pProfiler);
+
+ // Get logger configuration from the Global Area for the specified Profiler.
+ LoggerConfig FetchLoggerConfig(const Profiler *pProfiler);
+
+ // Get trace configuration from the Global Area for the specified Profiler.
+ TraceLogConfig FetchTraceLogConfig(const Profiler *pProfiler);
+
+ // Get configuration from the Global Area for the specified Profiler.
+ ProfilerConfig FetchProfilerConfig(const Profiler *pProfiler);
+
+ // This function is called when DLL is detached and we should perform
+ // cleanup of the Global Area.
+ void DllDetachShutdown() noexcept;
+
+ //
+ // Singleton is a noncopyable object.
+ //
+
+ ProfilerManager(const ProfilerManager&) = delete;
+
+ ProfilerManager &operator=(const ProfilerManager&) = delete;
+};
+
+#endif // _PROFILER_MANAGER_H_
--- /dev/null
+#ifndef _BASE_STORAGE_H_
+#define _BASE_STORAGE_H_
+
+#include <type_traits>
+#include <deque>
+
+#include "baseinfo.h"
+
+template<typename INFO>
+class BaseStorage
+{
+public:
+ typedef std::deque<INFO> Container;
+ typedef typename Container::iterator iterator;
+ typedef typename Container::const_iterator const_iterator;
+
+ static_assert(std::is_base_of<BaseInfo, INFO>::value,
+ "INFO not derived from BaseInfo");
+
+ bool HasValue(InternalID id) const noexcept
+ {
+ return id.id >= m_storage.size();
+ }
+
+ INFO &Get(InternalID id)
+ {
+ return const_cast<INFO&>(
+ const_cast<const BaseStorage<INFO>&>(*this).Get(id));
+ }
+
+ const INFO &Get(InternalID id) const
+ {
+ _ASSERTE(this->HasValue(id));
+ return m_storage[id.id];
+ }
+
+ INFO &Add()
+ {
+ m_storage.emplace_back();
+ INFO &info = m_storage.back();
+ info.internalId.id = m_storage.size() - 1;
+ return info;
+ }
+
+ iterator begin() noexcept
+ {
+ return m_storage.begin();
+ }
+
+ const_iterator begin() const noexcept
+ {
+ return m_storage.begin();
+ }
+
+ iterator end() noexcept
+ {
+ return m_storage.end();
+ }
+
+ const_iterator end() const noexcept
+ {
+ return m_storage.end();
+ }
+
+protected:
+ Container m_storage;
+};
+
+#endif // _BASE_STORAGE_H_
--- /dev/null
+#ifndef _CLASS_STORAGE_H_
+#define _CLASS_STORAGE_H_
+
+#include "mappedstorage.h"
+#include "classinfo.h"
+
+class ClassStorage : public MappedStorage<ClassID, ClassInfo>
+{};
+
+#endif // _CLASS_STORAGE_H_
--- /dev/null
+#ifndef _FUNCTION_STORAGE_H_
+#define _FUNCTION_STORAGE_H_
+
+#include "mappedstorage.h"
+#include "functioninfo.h"
+
+class FunctionStorage : public MappedStorage<FunctionID, FunctionInfo>
+{
+private:
+ using Base = MappedStorage<FunctionID, FunctionInfo>;
+
+public:
+ FunctionStorage(ExecutionTrace *pExecutionTrace)
+ : Base()
+ , m_pExecutionTrace(pExecutionTrace)
+ {}
+
+ std::pair<FunctionInfo&, bool>
+ Place(FunctionID id)
+ {
+ auto res = this->Base::Place(id);
+ res.first.executionTrace = m_pExecutionTrace;
+ return std::make_pair(std::ref(res.first), res.second);
+ }
+
+ // Add new function info without mapping to FunctionID. It is useful for
+ // internal pseudo-functions.
+ FunctionInfo &Add()
+ {
+ FunctionInfo &res = this->Base::Add();
+ res.executionTrace = m_pExecutionTrace;
+ return std::ref(res);
+ }
+
+protected:
+ ExecutionTrace *m_pExecutionTrace;
+};
+
+#endif // _FUNCTION_STORAGE_H_
--- /dev/null
+#ifndef _LIVE_STORAGE_H_
+#define _LIVE_STORAGE_H_
+
+#include <set>
+
+#include "mappedstorage.h"
+#include "iterator_range.h"
+
+template<typename ID, typename INFO>
+class LiveStorage : public MappedStorage<ID, INFO>
+{
+private:
+ using Base = MappedStorage<ID, INFO>;
+
+ struct less_info
+ {
+ bool operator()(const INFO &lhs, const INFO &rhs) const
+ {
+ return lhs.internalId.id < rhs.internalId.id;
+ }
+ };
+
+public:
+ typedef std::set<std::reference_wrapper<INFO>, less_info> LiveContainer;
+ typedef typename LiveContainer::iterator live_iterator;
+ typedef typename LiveContainer::const_iterator const_live_iterator;
+ typedef iterator_range<live_iterator> live_iterator_range;
+ typedef iterator_range<const_live_iterator> const_live_iterator_range;
+
+ std::pair<INFO&, bool>
+ Place(ID id)
+ {
+ auto res = this->Base::Place(id);
+ if (res.second)
+ {
+ m_liveStorage.insert(std::ref(res.first));
+ }
+ return std::make_pair(std::ref(res.first), res.second);
+ }
+
+ INFO &Unlink(ID id)
+ {
+ auto &res = this->Base::Unlink(id);
+ m_liveStorage.erase(std::ref(res));
+ return res;
+ }
+
+ live_iterator_range GetLiveRange()
+ {
+ return live_iterator_range(m_liveStorage.begin(), m_liveStorage.end());
+ }
+
+ const_live_iterator_range GetLiveRange() const
+ {
+ return const_live_iterator_range(
+ m_liveStorage.begin(), m_liveStorage.end());
+ }
+
+ LiveContainer GetLiveContainer() const
+ {
+ return m_liveStorage;
+ }
+
+protected:
+ LiveContainer m_liveStorage;
+};
+
+#endif // _LIVE_STORAGE_H_
--- /dev/null
+#ifndef _MAPPED_STORAGE_H_
+#define _MAPPED_STORAGE_H_
+
+#include <unordered_map>
+#include <utility>
+#include <functional>
+
+#include "basestorage.h"
+#include "mappedinfo.h"
+
+template<typename ID, typename INFO>
+class MappedStorage : public BaseStorage<INFO>
+{
+public:
+ static_assert(std::is_base_of<MappedInfo<ID>, INFO>::value,
+ "INFO not derived from MappedInfo<ID>");
+
+ using BaseStorage<INFO>::HasValue;
+
+ bool HasValue(ID id) const
+ {
+ return m_toInternal.find(id) != m_toInternal.end();
+ }
+
+ using BaseStorage<INFO>::Get;
+
+ INFO &Get(ID id)
+ {
+ return const_cast<INFO&>(
+ const_cast<const MappedStorage<ID, INFO>&>(*this).Get(id));
+ }
+
+ const INFO &Get(ID id) const
+ {
+ auto it = m_toInternal.find(id);
+ _ASSERTE(it != m_toInternal.end());
+ return m_storage[it->second.id];
+ }
+
+ // Add and/or get info by ID. Second value in pair denoting whether
+ // the insertion took place.
+ std::pair<INFO&, bool> Place(ID id)
+ {
+ auto it = m_toInternal.find(id);
+ if (it != m_toInternal.end())
+ {
+ return std::make_pair(std::ref(m_storage[it->second.id]), false);
+ }
+ else
+ {
+ INFO &info = this->Add();
+ try
+ {
+ info.id = id;
+ m_toInternal[id] = info.internalId;
+ }
+ catch (...)
+ {
+ m_storage.pop_back(); // New value is always appended.
+ throw;
+ }
+ return std::make_pair(std::ref(info), true);
+ }
+ }
+
+ // Remap object accessible from iid to another ID. Returns old ID.
+ // Should not be called if storage doesn't have iid.
+ ID Link(ID id, InternalID iid)
+ {
+ _ASSERTE(this->HasValue(iid));
+ INFO &info = this->Get(iid);
+ ID old_id = info.id;
+ m_toInternal[id] = iid;
+ info.id = id;
+ return old_id;
+ }
+
+ // Remove ID from storage, so this ID can be used for another object later.
+ // Only ID is removed. Associated object stays accessible from internal ID.
+ // Function returns reference to this object.
+ // Should not be called if storage doesn't have ID.
+ INFO &Unlink(ID id)
+ {
+ auto it = m_toInternal.find(id);
+ _ASSERTE(it != m_toInternal.end());
+ INFO &info = m_storage[it->second.id];
+ m_toInternal.erase(it);
+ info.id = ID{};
+ return info;
+ }
+
+protected:
+ using BaseStorage<INFO>::m_storage;
+ std::unordered_map<ID, InternalID> m_toInternal;
+};
+
+#endif // _MAPPED_STORAGE_H_
--- /dev/null
+#ifndef _RING_BUFFER_H_
+#define _RING_BUFFER_H_
+
+#include <atomic>
+#include <limits>
+#include <stdexcept>
+#include <new>
+#include <utility>
+#include <iterator>
+
+#include <stdlib.h>
+#include <assert.h>
+
+template<typename T>
+class ring_buffer
+{
+public:
+ explicit ring_buffer(size_t capacity = 0)
+ {
+ initialize(capacity);
+ }
+
+ ring_buffer(const ring_buffer &rb)
+ {
+ initialize(rb.m_cap);
+ try
+ {
+ copy_from(rb);
+ }
+ catch (...)
+ {
+ ~ring_buffer();
+ throw;
+ }
+ }
+
+ ring_buffer(ring_buffer &&rb)
+ {
+ initialize();
+ swap(rb);
+ }
+
+ ~ring_buffer()
+ {
+ clear();
+ free(m_buf);
+ }
+
+ ring_buffer& operator=(const ring_buffer& other)
+ {
+ if (this == &other)
+ return *this;
+
+ ring_buffer tmp(other);
+ swap(tmp);
+ return *this;
+ }
+
+ ring_buffer& operator=(ring_buffer&& other)
+ {
+ swap(other);
+ return *this;
+ }
+
+ T &front() noexcept
+ {
+ assert(!empty());
+ return *m_begin;
+ }
+
+ const T &front() const noexcept
+ {
+ assert(!empty());
+ return const_cast<const T&>(*m_begin);
+ }
+
+ T &back() noexcept
+ {
+ assert(!empty());
+ return const_cast<T&>(const_cast<const ring_buffer<T>&>(*this).back());
+ }
+
+ const T &back() const noexcept
+ {
+ assert(!empty());
+
+ const T *end = m_end;
+ if (end == m_buf)
+ end = m_buf + m_cap;
+ --end;
+ return *end;
+ }
+
+ bool empty() const noexcept
+ {
+ return m_size == 0;
+ }
+
+ bool full() const noexcept
+ {
+ return m_size == m_cap;
+ }
+
+ size_t size() const noexcept
+ {
+ return m_size;
+ }
+
+ size_t max_size() const noexcept
+ {
+ static size_t max_size = std::numeric_limits<size_t>::max() / sizeof(T);
+ return max_size;
+ }
+
+ void reserve(size_t new_capacity)
+ {
+ if (new_capacity <= m_cap)
+ return;
+
+ ring_buffer tmp(new_capacity);
+ tmp.move_from(std::move(*this));
+ swap(tmp);
+ }
+
+ size_t capacity() const noexcept
+ {
+ return m_cap;
+ }
+
+ void clear() noexcept
+ {
+ size_t size = m_size;
+ while (size > 0)
+ {
+ m_begin->~T();
+ ++m_begin;
+ if (m_begin == m_buf + m_cap)
+ m_begin = m_buf;
+ --size;
+ }
+ m_size = 0;
+ }
+
+ void push_back(const T &item)
+ {
+ push_back_imp(item);
+ }
+
+ void push_back(T &&item)
+ {
+ push_back_imp(std::move(item));
+ }
+
+ void push_front(const T &item)
+ {
+ push_front_imp(item);
+ }
+
+ void push_front(T &&item)
+ {
+ push_front_imp(std::move(item));
+ }
+
+ void pop_back()
+ {
+ if (m_size == 0)
+ return;
+
+ if (m_end == m_buf)
+ m_end = m_buf + m_cap;
+ --m_end;
+ m_end->~T();
+ m_size--;
+ }
+
+ void pop_front()
+ {
+ if (m_size == 0)
+ return;
+
+ m_begin->~T();
+ ++m_begin;
+ if (m_begin == m_buf + m_cap)
+ m_begin = m_buf;
+ m_size--;
+ }
+
+ template <class InputIterator>
+ void append(InputIterator first, InputIterator last)
+ {
+ size_t c = std::distance(first, last);
+ if (c > m_cap - m_size)
+ throw std::out_of_range("ring_buffer capacity is exhausted");
+
+ size_t c1 = c;
+ size_t c2 = 0;
+ if (m_end > m_buf + m_cap - c) // m_end + c > m_buf + m_cap
+ {
+ c2 = m_end - (m_buf + m_cap - c); // (m_end + c) - (m_buf + m_cap);
+ c1 -= c2;
+ }
+ assert(c1 + c2 == std::distance(first, last));
+
+ while (c1-- > 0)
+ {
+ assert(first != last);
+ new (m_end++) T(*first++);
+ }
+
+ assert(m_end <= m_buf + m_cap);
+ if (m_end == m_buf + m_cap)
+ m_end = m_buf;
+
+ assert(c2 == 0 || m_end == m_buf); // c2 > 0 => m_end == m_buf
+ while (c2-- > 0)
+ {
+ assert(first != last);
+ new (m_end++) T(*first++);
+ }
+
+ assert(first == last);
+ m_size += c;
+ }
+
+ void swap(ring_buffer &rb) noexcept
+ {
+ std::swap(m_buf, rb.m_buf);
+ std::swap(m_begin, rb.m_begin);
+ std::swap(m_end, rb.m_end);
+ std::swap(m_cap, rb.m_cap);
+ rb.m_size = m_size.exchange(rb.m_size);
+ }
+
+private:
+ void initialize() noexcept
+ {
+#ifdef _TARGET_AMD64_
+ //assert(m_size.is_lock_free()); // NOTE: With C++17 it can be checked
+ // staticaly.
+#endif // _TARGET_AMD64_
+ m_begin = m_end = m_buf = nullptr;
+ m_size = m_cap = 0;
+ }
+
+ void initialize(size_t capacity)
+ {
+ initialize();
+
+ if (capacity == 0)
+ return;
+ else if (capacity > max_size())
+ throw std::length_error("capacity exceeds the maximum size");
+
+ m_buf = reinterpret_cast<T*>(malloc(capacity * sizeof(T)));
+
+ if (m_buf == nullptr)
+ throw std::bad_alloc();
+
+ m_cap = capacity;
+ m_begin = m_end = m_buf;
+ }
+
+ void copy_from(const ring_buffer &rb)
+ {
+ assert(
+ m_begin == m_buf &&
+ m_end == m_buf &&
+ m_size == 0 &&
+ m_cap >= rb.m_cap
+ );
+
+ T *begin = rb.m_begin;
+ size_t size = 0;
+ while (size != rb.m_size)
+ {
+ new (m_end) T(*begin);
+ ++m_end;
+ ++begin;
+ if (begin == rb.m_buf + rb.m_cap)
+ begin = rb.m_buf;
+ ++size;
+ }
+ m_size = size;
+ }
+
+ void move_from(ring_buffer &&rb)
+ {
+ assert(
+ m_begin == m_buf &&
+ m_end == m_buf &&
+ m_size == 0 &&
+ m_cap >= rb.m_cap
+ );
+
+ T *begin = rb.m_begin;
+ size_t size = 0;
+ while (size != rb.m_size)
+ {
+ new (m_end) T(std::move(*begin));
+ ++m_end;
+ ++begin;
+ if (begin == rb.m_buf + rb.m_cap)
+ begin = rb.m_buf;
+ ++size;
+ }
+ m_size = size;
+ }
+
+ template <typename ValT>
+ void push_back_imp(ValT &&item)
+ {
+ if (full())
+ throw std::out_of_range("ring_buffer capacity is exhausted");
+
+ new (m_end) T(std::forward<ValT>(item));
+ ++m_end;
+ if (m_end == m_buf + m_cap)
+ m_end = m_buf;
+ m_size++;
+ }
+
+ template <typename ValT>
+ void push_front_impl(ValT &&item)
+ {
+ if (full())
+ throw std::out_of_range("ring_buffer capacity is exhausted");
+
+ T *begin = m_begin;
+ if (begin == m_buf)
+ begin = m_buf + m_cap;
+ --begin;
+ new (begin) T(std::forward<ValT>(item));
+ m_begin = begin;
+ m_size++;
+ }
+
+private:
+ T *m_buf;
+ T *m_begin;
+ T *m_end;
+ std::atomic_size_t m_size;
+ size_t m_cap;
+};
+
+#endif /* _RING_BUFFER_H_ */
--- /dev/null
+#ifndef _THREAD_STORAGE_H_
+#define _THREAD_STORAGE_H_
+
+#include "livestorage.h"
+#include "threadinfo.h"
+
+class ThreadStorage : public LiveStorage<ThreadID, ThreadInfo>
+{};
+
+#endif // _THREAD_STORAGE_H_
--- /dev/null
+#ifndef _BINARY_SEMAPHORE_H_
+#define _BINARY_SEMAPHORE_H_
+
+#include <mutex>
+#include <condition_variable>
+
+template <typename Mutex, typename CondVar>
+class basic_binary_semaphore {
+public:
+ basic_binary_semaphore();
+
+ explicit basic_binary_semaphore(bool init);
+
+ void notify();
+
+ void wait();
+
+ bool try_wait();
+
+private:
+ Mutex m_mutex;
+ CondVar m_cv;
+ bool m_val;
+};
+
+using binary_semaphore =
+ basic_binary_semaphore<std::mutex, std::condition_variable>;
+
+template <typename Mutex, typename CondVar>
+basic_binary_semaphore<Mutex, CondVar>::basic_binary_semaphore()
+ : m_val(false)
+{}
+
+template <typename Mutex, typename CondVar>
+basic_binary_semaphore<Mutex, CondVar>::basic_binary_semaphore(bool init)
+ : m_val(init)
+{}
+
+template <typename Mutex, typename CondVar>
+void basic_binary_semaphore<Mutex, CondVar>::notify()
+{
+ std::lock_guard<Mutex> lock(m_mutex);
+ m_val = true;;
+ m_cv.notify_one();
+}
+
+template <typename Mutex, typename CondVar>
+void basic_binary_semaphore<Mutex, CondVar>::wait()
+{
+ std::unique_lock<Mutex> lock(m_mutex);
+ while (!m_val)
+ {
+ m_cv.wait(lock);
+ }
+ m_val = false;
+}
+
+template <typename Mutex, typename CondVar>
+bool basic_binary_semaphore<Mutex, CondVar>::try_wait()
+{
+ std::lock_guard<Mutex> lock(m_mutex);
+ if (m_val)
+ {
+ m_val = false;
+ return true;
+ }
+ return false;
+}
+
+#endif // _BINARY_SEMAPHORE_H_
--- /dev/null
+#include <system_error>
+
+#include <pthread.h>
+#include <errno.h>
+
+#include "shared_mutex.h"
+
+struct shared_mutex::impl
+{
+ pthread_rwlock_t rwlock;
+};
+
+shared_mutex::shared_mutex()
+ : pimpl(new impl())
+{
+ if (pthread_rwlock_init(&pimpl->rwlock, NULL))
+ {
+ throw std::system_error(errno, std::system_category(),
+ "can't create shared_mutex");
+ }
+}
+
+shared_mutex::~shared_mutex()
+{
+ pthread_rwlock_destroy(&pimpl->rwlock);
+}
+
+void shared_mutex::lock()
+{
+ if (pthread_rwlock_wrlock(&pimpl->rwlock))
+ {
+ throw std::system_error(errno, std::system_category(),
+ "can't lock shared_mutex");
+ }
+}
+
+bool shared_mutex::try_lock()
+{
+ int err = pthread_rwlock_trywrlock(&pimpl->rwlock);
+
+ if (err && err != EBUSY)
+ {
+ throw std::system_error(errno, std::system_category(),
+ "can't exclusively lock shared_mutex");
+ }
+
+ return err == 0;
+}
+
+void shared_mutex::unlock()
+{
+ if (pthread_rwlock_unlock(&pimpl->rwlock))
+ {
+ throw std::system_error(errno, std::system_category(),
+ "can't exclusively unlock shared_mutex");
+ }
+}
+
+void shared_mutex::lock_shared()
+{
+ if (pthread_rwlock_rdlock(&pimpl->rwlock))
+ {
+ throw std::system_error(errno, std::system_category(),
+ "can't shared lock shared_mutex");
+ }
+}
+
+bool shared_mutex::try_lock_shared()
+{
+ int err = pthread_rwlock_tryrdlock(&pimpl->rwlock);
+
+ if (err && err != EBUSY)
+ {
+ throw std::system_error(errno, std::system_category(),
+ "can't shared lock shared_mutex");
+ }
+
+ return err == 0;
+}
+
+void shared_mutex::unlock_shared()
+{
+ if (pthread_rwlock_unlock(&pimpl->rwlock))
+ {
+ throw std::system_error(errno, std::system_category(),
+ "can't shared unlock shared_mutex");
+ }
+}
--- /dev/null
+#ifndef _SHARED_MUTEX_H_
+#define _SHARED_MUTEX_H_
+
+#include <memory>
+
+class shared_mutex
+{
+public:
+ shared_mutex();
+
+ shared_mutex(const shared_mutex&) = delete;
+
+ ~shared_mutex();
+
+ shared_mutex &operator=(const shared_mutex&) = delete;
+
+ void lock();
+
+ bool try_lock();
+
+ void unlock();
+
+ void lock_shared();
+
+ bool try_lock_shared();
+
+ void unlock_shared();
+
+private:
+ struct impl;
+ std::unique_ptr<impl> pimpl;
+};
+
+#endif // _SHARED_MUTEX_H_
--- /dev/null
+#ifndef _SHARED_RESOURCE_H_
+#define _SHARED_RESOURCE_H_
+
+#include "shared_mutex.h"
+
+template<typename T, typename Mutex = shared_mutex>
+class SharedResource
+{
+private:
+ template<class SR>
+ class AccessorBase
+ {
+ public:
+ ~AccessorBase() = default;
+
+ AccessorBase(const AccessorBase&) = delete;
+
+ AccessorBase &operator=(const AccessorBase&) = delete;
+
+ AccessorBase(AccessorBase &&other) :
+ m_shared_resource(other.m_shared_resource)
+ {
+ other.m_shared_resource = nullptr;
+ }
+
+ AccessorBase &operator=(AccessorBase &&other)
+ {
+ if (&other != this)
+ {
+ m_shared_resource = other.m_shared_resource;
+ other.m_shared_resource = nullptr;
+ }
+ return *this;
+ }
+
+ bool isValid() const noexcept
+ {
+ return m_shared_resource != nullptr;
+ }
+
+ protected:
+ SR *m_shared_resource; // Mutable or constant pointer to SharedResource.
+
+ AccessorBase(SR *resource)
+ : m_shared_resource(resource)
+ {}
+ };
+
+ template<class SR>
+ class ExclusiveAccessor : public AccessorBase<SR>
+ {
+ public:
+ ~ExclusiveAccessor()
+ {
+ if (this->isValid())
+ {
+ this->m_shared_resource->m_mutex.unlock();
+ }
+ }
+
+ ExclusiveAccessor(ExclusiveAccessor &&other) = default;
+
+ ExclusiveAccessor &operator=(ExclusiveAccessor &&other) = default;
+
+ protected:
+ ExclusiveAccessor(SR *resource)
+ : AccessorBase<SR>(resource)
+ {
+ this->m_shared_resource->m_mutex.lock();
+ }
+ };
+
+ template<class SR>
+ class SharedAccessor : public AccessorBase<SR>
+ {
+ public:
+ ~SharedAccessor()
+ {
+ if (this->isValid())
+ {
+ this->m_shared_resource->m_mutex.unlock_shared();
+ }
+ }
+
+ SharedAccessor(SharedAccessor &&other) = default;
+
+ SharedAccessor &operator=(SharedAccessor &&other) = default;
+
+ protected:
+ SharedAccessor(SR *resource)
+ : AccessorBase<SR>(resource)
+ {
+ this->m_shared_resource->m_mutex.lock_shared();
+ }
+ };
+
+public:
+ template<class A>
+ class MutableAccessor : public A
+ {
+ friend class SharedResource<T, Mutex>;
+
+ public:
+ T *operator->()
+ {
+ return &this->m_shared_resource->m_resource;
+ }
+
+ T &operator*()
+ {
+ return this->m_shared_resource->m_resource;
+ }
+
+ using A::A; // Protected constructor.
+ };
+
+ template<class A>
+ class ConstAccessor : public A
+ {
+ friend class SharedResource<T, Mutex>;
+
+ public:
+ const T *operator->() const
+ {
+ return &this->m_shared_resource->m_resource;
+ }
+
+ const T &operator*() const
+ {
+ return this->m_shared_resource->m_resource;
+ }
+
+ using A::A; // Protected constructor.
+ };
+
+ template<typename ...Args>
+ SharedResource(Args&& ...args)
+ : m_resource(std::forward<Args>(args)...)
+ {}
+
+ ~SharedResource() = default;
+
+ SharedResource(const SharedResource&) = delete;
+
+ SharedResource(SharedResource&&) = delete;
+
+ SharedResource &operator=(const SharedResource&) = delete;
+
+ SharedResource &operator=(SharedResource&&) = delete;
+
+ // Should not be used in concurent environment.
+ T *get() const noexcept
+ {
+ return const_cast<T*>(&m_resource);
+ }
+
+ auto lock() ->
+ MutableAccessor<ExclusiveAccessor<SharedResource<T, Mutex>>>
+ {
+ // Implicit conversion to accessor with mutable exclusive lock.
+ return this;
+ }
+
+ auto lock_const() const ->
+ ConstAccessor<ExclusiveAccessor<const SharedResource<T, Mutex>>>
+ {
+ // Implicit conversion to accessor with constant exclusive lock.
+ return this;
+ }
+
+ auto lock_shared() const ->
+ ConstAccessor<SharedAccessor<const SharedResource<T, Mutex>>>
+ {
+ // Implicit conversion to accessor with constant shared lock.
+ return this;
+ }
+
+private:
+ T m_resource;
+ mutable Mutex m_mutex;
+};
+
+#endif //_SHARED_RESOURCE_H_
--- /dev/null
+#include "profiler.h"
+#include "profilerinfo.h"
+#include "basetrace.h"
+
+BaseTrace::BaseTrace(Profiler &profiler)
+ : m_disabled(true)
+ , m_profiler(profiler)
+ , m_info(profiler.GetProfilerInfo())
+{
+}
+
+BaseTrace::~BaseTrace()
+{
+}
+
+Log &BaseTrace::LOG() const noexcept
+{
+ return m_profiler.LOG();
+}
+
+ITraceLog &BaseTrace::TRACE() const noexcept
+{
+ return m_profiler.TRACE();
+}
+
+bool BaseTrace::IsEnabled() const noexcept
+{
+ return !m_disabled;
+}
--- /dev/null
+#ifndef _BASE_TRACE_H_
+#define _BASE_TRACE_H_
+
+class Profiler;
+
+class ProfilerInfo;
+
+class Log;
+
+class ITraceLog;
+
+class BaseTrace
+{
+protected:
+ BaseTrace(Profiler &profiler);
+
+ ~BaseTrace();
+
+ Log &LOG() const noexcept;
+
+ ITraceLog &TRACE() const noexcept;
+
+public:
+ bool IsEnabled() const noexcept;
+
+protected:
+ bool m_disabled;
+
+ Profiler &m_profiler;
+ const ProfilerInfo &m_info;
+};
+
+#endif // _BASE_TRACE_H_
--- /dev/null
+#include <memory>
+#include <utility>
+#include <system_error>
+#include <exception>
+#include <stdexcept>
+
+#include <string.h>
+#include <signal.h>
+#include <pthread.h>
+#include <errno.h>
+
+#include <winerror.h>
+
+#include "profiler.h"
+#include "intervalsplitter.h"
+#include "commontrace.h"
+
+#define CONTROL_SIGNAL_MIN (SIGRTMIN + 4)
+#define LOG_SIGNAL (CONTROL_SIGNAL_MIN + 0)
+#define LOG_SIGNAL_STOP (CONTROL_SIGNAL_MIN + 1)
+#define SAMPLE_SIGNAL (CONTROL_SIGNAL_MIN + 2)
+#define SAMPLING_PAUSE (CONTROL_SIGNAL_MIN + 3)
+#define SAMPLING_RESUME (CONTROL_SIGNAL_MIN + 4)
+#define SAMPLING_EVENT (CONTROL_SIGNAL_MIN + 5)
+#define SAMPLING_STOP (CONTROL_SIGNAL_MIN + 6)
+#define CONTROL_SIGNAL_END (CONTROL_SIGNAL_MIN + 7)
+#define CONTROL_SIGNAL_MAX (CONTROL_SIGNAL_END - 1)
+
+// NOTE: currently only one instance of the CommonTrace can exist at each
+// moment, so global variable can be used.
+static CommonTrace *g_pCommonTraceObject = nullptr;
+
+static void SampleHandlerStub(
+ int code, siginfo_t *siginfo, void *context)
+{
+ if (code != SAMPLE_SIGNAL)
+ {
+ return;
+ }
+
+ CommonTrace *trace =
+ reinterpret_cast<CommonTrace*>(siginfo->si_value.sival_ptr);
+
+ if (trace != nullptr && trace->IsEnabled())
+ {
+ int terrno = errno;
+ trace->HandleSample(context);
+ errno = terrno;
+ }
+}
+
+static void SamplingPauseResumeHandlerStub(int code)
+{
+ bool shouldPause;
+
+ if (code == SAMPLING_PAUSE)
+ {
+ shouldPause = true;
+ }
+ else if (code == SAMPLING_RESUME)
+ {
+ shouldPause = false;
+ }
+ else
+ {
+ return;
+ }
+
+ if (g_pCommonTraceObject != nullptr && g_pCommonTraceObject->IsEnabled())
+ {
+ int terrno = errno;
+ g_pCommonTraceObject->HandleSamplingPauseResume(shouldPause);
+ errno = terrno;
+ }
+}
+
+static struct timespec MsToTS(unsigned long ms)
+{
+ return { ms / 1000, ms % 1000 * 1000000 };
+}
+
+CommonTrace::CommonTrace(Profiler &profiler)
+ : BaseTrace(profiler)
+ , m_tlsThreadInfoIndex(TLS_OUT_OF_INDEXES)
+ , m_threadStorage()
+ , m_classStorage()
+ , m_pauseAction()
+ , m_resumeAction()
+ , m_sampleAction()
+ , m_logThread()
+ , m_samplingThread()
+ , m_samplingSuspended(true)
+{
+ _ASSERTE(g_pCommonTraceObject == nullptr);
+ g_pCommonTraceObject = this;
+}
+
+CommonTrace::~CommonTrace()
+{
+ // NOTE: we are dealing with a partially destroyed m_profiler!
+ this->Shutdown();
+
+ if (m_tlsThreadInfoIndex != TLS_OUT_OF_INDEXES)
+ {
+ if (!TlsFree(m_tlsThreadInfoIndex))
+ {
+ m_profiler.HandleHresult(
+ "CommonTrace::~CommonTrace(): TlsFree()",
+ HRESULT_FROM_WIN32(GetLastError())
+ );
+ }
+ }
+
+ _ASSERTE(g_pCommonTraceObject == this);
+ g_pCommonTraceObject = nullptr;
+}
+
+void CommonTrace::ProcessConfig(ProfilerConfig &config)
+{
+ //
+ // Check activation condition.
+ //
+
+ if (config.ExecutionTraceEnabled || config.MemoryTraceEnabled)
+ {
+ m_disabled = false;
+ }
+ else
+ {
+ return;
+ }
+
+ //
+ // Performe runtime checks.
+ //
+
+ if (CONTROL_SIGNAL_MAX > SIGRTMAX)
+ {
+ throw std::runtime_error(
+ "CommonTrace::ProcessConfig(): Not enought real-time signals");
+ }
+
+ //
+ // Line Tracing.
+ //
+
+//#if !defined(_TARGET_ARM_) && !defined(_TARGET_X86_)
+ if (config.LineTraceEnabled)
+ {
+ config.LineTraceEnabled = false;
+ LOG().Warn() <<
+ "Line tracing currently is not supported at this platform";
+ }
+//#endif // _TARGET_ARM_ or _TARGET_X86_
+
+ //
+ // Initializing thread local storage.
+ //
+
+ m_tlsThreadInfoIndex = TlsAlloc();
+ if (m_tlsThreadInfoIndex == TLS_OUT_OF_INDEXES)
+ {
+ throw HresultException(
+ "CommonTrace::ProcessConfig(): TlsAlloc()",
+ HRESULT_FROM_WIN32(GetLastError())
+ );
+ }
+
+ //
+ // Setup signal handlers.
+ //
+
+ try
+ {
+ struct sigaction action;
+ memset(&action, 0, sizeof(struct sigaction));
+ action.sa_handler = SamplingPauseResumeHandlerStub;
+ sigemptyset(&action.sa_mask);
+ sigaddset(&action.sa_mask, SAMPLING_PAUSE);
+ sigaddset(&action.sa_mask, SAMPLING_RESUME);
+ action.sa_flags = SA_RESTART;
+
+ SigAction pauseAction ( SAMPLING_PAUSE, action );
+ SigAction resumeAction ( SAMPLING_RESUME, action );
+
+ m_pauseAction = std::move(pauseAction);
+ m_resumeAction = std::move(resumeAction);
+ }
+ catch (const std::exception &e)
+ {
+ m_profiler.HandleException(e);
+ LOG().Warn() << "Tracing pause/resume functionality is disabled";
+ }
+
+ if (config.HighGranularityEnabled)
+ {
+ try
+ {
+ struct sigaction action;
+ memset(&action, 0, sizeof(struct sigaction));
+ action.sa_sigaction = SampleHandlerStub;
+ sigemptyset(&action.sa_mask);
+ action.sa_flags = SA_RESTART | SA_SIGINFO;
+ m_sampleAction = SigAction(SAMPLE_SIGNAL, action);
+ }
+ catch (const std::exception &e)
+ {
+ m_profiler.HandleException(e);
+ config.HighGranularityEnabled = false;
+ LOG().Warn() << "Hight granularity option is disabled";
+ }
+ }
+
+ //
+ // Starting service threads.
+ //
+
+ m_samplingSuspended = config.TracingSuspendedOnStart;
+
+ {
+ binary_semaphore threadInitializedSem;
+ m_logThread = std::thread(
+ &CommonTrace::LogThread, this,
+ &threadInitializedSem
+ );
+ threadInitializedSem.wait();
+
+ if (config.CollectionMethod == CollectionMethod::Sampling)
+ {
+ m_samplingThread = std::thread(
+ &CommonTrace::SamplingThread, this,
+ &threadInitializedSem
+ );
+ threadInitializedSem.wait();
+ }
+ }
+
+ //
+ // Event Mask calculation.
+ //
+
+ HRESULT hr;
+ DWORD events;
+ hr = m_info.v1()->GetEventMask(&events);
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "CommonTrace::ProcessConfig(): GetEventMask()", hr);
+ }
+
+ events = events
+ | COR_PRF_MONITOR_APPDOMAIN_LOADS
+ | COR_PRF_MONITOR_ASSEMBLY_LOADS
+ | COR_PRF_MONITOR_MODULE_LOADS
+ | COR_PRF_MONITOR_CLASS_LOADS
+ | COR_PRF_MONITOR_THREADS;
+
+ // This events are required for tracing of call stack dynamics.
+
+ if (config.LineTraceEnabled)
+ {
+ events |= COR_PRF_ENABLE_STACK_SNAPSHOT;
+ }
+
+ hr = m_info.v1()->SetEventMask(events);
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "CommonTrace::ProcessConfig(): SetEventMask()", hr);
+ }
+}
+
+void CommonTrace::Shutdown() noexcept
+{
+ m_disabled = true;
+
+ // Ensure service threads are joined before this object will be destroyed.
+ if (m_samplingThread.joinable())
+ {
+ this->SendStopSampling();
+ m_samplingThread.join();
+ }
+ if (m_logThread.joinable())
+ {
+ this->SendStopLog();
+ m_logThread.join();
+ }
+
+ // Restore signal handlers to defaults.
+ m_pauseAction . Release();
+ m_resumeAction . Release();
+ m_sampleAction . Release();
+}
+
+__forceinline void CommonTrace::SendDoSample(ThreadInfo &thrInfo) noexcept
+{
+ union sigval val;
+ val.sival_ptr = this;
+ int ev = pthread_sigqueue(thrInfo.nativeHandle, SAMPLE_SIGNAL, val);
+ // It is OK if the limit of signals which may be queued has been reached.
+ if (ev && ev != EAGAIN)
+ {
+ m_profiler.HandleSysErr(
+ "CommonTrace::SendDoStackTraceSample(): pthread_sigqueue()", ev);
+ }
+}
+
+__forceinline void CommonTrace::SendDoLog(ThreadInfo &thrInfo) noexcept
+{
+ _ASSERTE(!m_disabled);
+ _ASSERTE(m_logThread.joinable());
+
+ union sigval val;
+ val.sival_ptr = &thrInfo;
+ int ev = pthread_sigqueue(m_logThread.native_handle(), LOG_SIGNAL, val);
+ // It is OK if the limit of signals which may be queued has been reached.
+ if (ev && ev != EAGAIN)
+ {
+ m_profiler.HandleSysErr(
+ "CommonTrace::SendDoLog(): pthread_sigqueue()", ev);
+ }
+}
+
+__forceinline void CommonTrace::SendStopLog() noexcept
+{
+ _ASSERTE(m_logThread.joinable());
+
+ int ev = pthread_kill(m_logThread.native_handle(), LOG_SIGNAL_STOP);
+ if (ev)
+ {
+ m_profiler.HandleSysErr(
+ "CommonTrace::SendStopLog(): pthread_kill()", ev);
+ }
+}
+
+__forceinline void CommonTrace::SendSamplingEvent(SamplingEvent event) noexcept
+{
+ _ASSERTE(!m_disabled);
+ _ASSERTE(m_samplingThread.joinable());
+
+ union sigval val;
+ val.sival_int = static_cast<int>(event);
+ int ev = pthread_sigqueue(
+ m_samplingThread.native_handle(), SAMPLING_EVENT, val);
+ // It is OK if the limit of signals which may be queued has been reached.
+ if (ev && ev != EAGAIN)
+ {
+ m_profiler.HandleSysErr(
+ "CommonTrace::SendSamplingEvent(): pthread_sigqueue()", ev);
+ }
+}
+
+__forceinline void CommonTrace::SendStopSampling() noexcept
+{
+ _ASSERTE(m_samplingThread.joinable());
+
+ int ev = pthread_kill(m_samplingThread.native_handle(), SAMPLING_STOP);
+ if (ev)
+ {
+ m_profiler.HandleSysErr(
+ "CommonTrace::SendStopSampling(): pthread_kill()", ev);
+ }
+}
+
+void CommonTrace::LogThread(binary_semaphore *pInitialized) noexcept
+{
+ try
+ {
+ //
+ // Initialization.
+ //
+
+ int ev;
+ sigset_t set;
+ try
+ {
+ sigemptyset(&set);
+ sigaddset(&set, LOG_SIGNAL);
+ sigaddset(&set, LOG_SIGNAL_STOP);
+ ev = pthread_sigmask(SIG_BLOCK, &set, NULL);
+ if (ev)
+ {
+ throw std::system_error(ev, std::system_category(),
+ "CommonTrace::LogThread(): pthread_sigmask()");
+ }
+
+ pInitialized->notify();
+ // NOTE: semaphore can be destroyed after notification.
+ pInitialized = nullptr;
+ }
+ catch (const std::exception &e)
+ {
+ pInitialized->notify();
+ m_profiler.HandleException(e);
+ return;
+ }
+
+ //
+ // Working loop.
+ //
+
+ for (;;)
+ {
+ siginfo_t siginfo;
+ sigwaitinfo(&set, &siginfo);
+ if (siginfo.si_signo == LOG_SIGNAL_STOP)
+ {
+ break;
+ }
+ else if (siginfo.si_signo != LOG_SIGNAL)
+ {
+ continue;
+ }
+
+ _ASSERTE(siginfo.si_signo == LOG_SIGNAL);
+
+ ThreadInfo *pThreadInfo =
+ reinterpret_cast<ThreadInfo*>(siginfo.si_value.sival_ptr);
+ for (
+ // Local copy of volatile data.
+ size_t count = pThreadInfo->eventChannel.GetEventSummaryCount();
+ count > 0; --count)
+ {
+ const EventSummary &summary =
+ pThreadInfo->eventChannel.GetCurrentEventSummary();
+ TRACE().DumpSample(pThreadInfo->internalId, summary);
+ pThreadInfo->eventChannel.NextEventSummary();
+ }
+ }
+ }
+ catch (const std::exception &e)
+ {
+ m_profiler.HandleException(e);
+ }
+}
+
+void CommonTrace::SamplingThread(binary_semaphore *pInitialized) noexcept
+{
+ try
+ {
+ //
+ // Initialization.
+ //
+
+ int ev;
+ sigset_t set;
+ try
+ {
+ sigemptyset(&set);
+ sigaddset(&set, SAMPLING_EVENT);
+ sigaddset(&set, SAMPLING_STOP);
+ ev = pthread_sigmask(SIG_BLOCK, &set, NULL);
+ if (ev)
+ {
+ throw std::system_error(ev, std::system_category(),
+ "CommonTrace::SamplingThread(): pthread_sigmask()");
+ }
+
+ pInitialized->notify();
+ // NOTE: semaphore can be destroyed after notification.
+ pInitialized = nullptr;
+ }
+ catch (const std::exception &e)
+ {
+ pInitialized->notify();
+ m_profiler.HandleException(e);
+ return;
+ }
+
+ //
+ // Working loop.
+ //
+
+ IntervalSplitter splitter(m_profiler.GetConfig().SamplingTimeoutMs);
+ ThreadStorage::LiveContainer liveThreads;
+ ThreadStorage::LiveContainer::iterator itThrInfo = liveThreads.begin();
+ ThreadStorage::LiveContainer::iterator endThrInfo = liveThreads.end();
+
+ for (;;)
+ {
+ siginfo_t siginfo;
+ int rv;
+ if (!m_samplingSuspended)
+ {
+ struct timespec ts;
+
+ if (itThrInfo == endThrInfo)
+ {
+ {
+ auto storage_lock = this->GetThreadStorage();
+ liveThreads = storage_lock->GetLiveContainer();
+ }
+ itThrInfo = liveThreads.begin();
+ endThrInfo = liveThreads.end();
+ splitter.Reset(liveThreads.size());
+ }
+
+ if (!liveThreads.empty())
+ {
+ auto storage_lock = this->GetThreadStorage();
+ ThreadInfo &thrInfo = *itThrInfo++;
+
+ // We update all live threads if they are attached to OS
+ // threads.
+ if (thrInfo.id != 0 && thrInfo.nativeHandle != 0)
+ {
+ thrInfo.genTicks++; // OK with unsigned overflows.
+ if (m_profiler.GetConfig().HighGranularityEnabled)
+ {
+ this->SendDoSample(thrInfo);
+ }
+ }
+
+ ts = MsToTS(splitter.GetNext());
+ }
+ else
+ {
+ ts = MsToTS(m_profiler.GetConfig().SamplingTimeoutMs);
+ }
+
+ // NOTE: Sleep() function has better precision so we use it
+ // for short pauses.
+ if (ts.tv_sec == 0)
+ {
+ Sleep(ts.tv_nsec / 1000000);
+ ts.tv_nsec = 0;
+ }
+ rv = sigtimedwait(&set, &siginfo, &ts);
+ }
+ else
+ {
+ rv = sigwaitinfo(&set, &siginfo);
+ }
+
+ if (rv == -1 && errno == EAGAIN)
+ {
+ continue;
+ }
+ else if (rv == SAMPLING_EVENT)
+ {
+ SamplingEvent event = static_cast<SamplingEvent>(
+ siginfo.si_value.sival_int);
+ switch (event)
+ {
+ case SamplingEvent::SAMPLING_EVENT_PAUSE:
+ if (m_samplingSuspended == false)
+ {
+ TRACE().DumpProfilerTracingPause(
+ m_profiler.GetTickCountFromInit());
+ }
+ m_samplingSuspended = true;
+ break;
+
+ case SamplingEvent::SAMPLING_EVENT_RESUME:
+ if (m_samplingSuspended == true)
+ {
+ TRACE().DumpProfilerTracingResume(
+ m_profiler.GetTickCountFromInit());
+ // Should restart threads round.
+ itThrInfo = endThrInfo;
+ }
+ m_samplingSuspended = false;
+ break;
+ }
+
+ }
+ else if (rv == SAMPLING_STOP)
+ {
+ break; // End of loop!
+ }
+ else
+ {
+ m_profiler.HandleSysErr(
+ "CommonTrace::SamplingThread(): sigtimedwait()", errno);
+ }
+ }
+ }
+ catch (const std::exception &e)
+ {
+ m_profiler.HandleException(e);
+ }
+}
+
+__forceinline void CommonTrace::DoSampleWithAction(
+ ThreadInfo &thrInfo,
+ SamplingAction action,
+ SamplingSharedState &state) noexcept
+{
+ _ASSERTE(!thrInfo.interruptible);
+
+ ExecutionTrace &executionTrace = m_profiler.GetExecutionTrace();
+ MemoryTrace &memoryTrace = m_profiler.GetMemoryTrace();
+
+ state.genTicks = thrInfo.genTicks; // Local copy of volatile data.
+ bool needSample = executionTrace . NeedSample(thrInfo, state) ||
+ memoryTrace . NeedSample(thrInfo, state);
+
+ if (needSample)
+ {
+ executionTrace . PrepareSample(thrInfo, state);
+ // memoryTrace . PrepareSample(thrInfo, state);
+ }
+
+ if (action)
+ {
+ action(thrInfo, state);
+ }
+
+ if (needSample)
+ {
+ state.isSampleSucceeds = thrInfo.eventChannel.Sample(
+ m_profiler.GetTickCountFromInit(),
+ // OK with unsigned overflows in ticks.
+ state.genTicks - thrInfo.fixTicks);
+ if (state.isSampleSucceeds)
+ {
+ this->SendDoLog(thrInfo);
+ }
+ executionTrace . AfterSample(thrInfo, state);
+ // memoryTrace . AfterSample(thrInfo, state);
+ thrInfo.fixTicks = state.genTicks;
+ }
+}
+
+__forceinline void CommonTrace::DoSampleFromHandler(
+ ThreadInfo &thrInfo, void *context) noexcept
+{
+ _ASSERTE(thrInfo.interruptible);
+
+ SamplingSharedState state = {};
+ state.context = context;
+
+ ExecutionTrace &executionTrace = m_profiler.GetExecutionTrace();
+ MemoryTrace &memoryTrace = m_profiler.GetMemoryTrace();
+
+ state.genTicks = thrInfo.genTicks; // Local copy of volatile data.
+ bool needSample = executionTrace . NeedSample(thrInfo, state) ||
+ memoryTrace . NeedSample(thrInfo, state);
+
+ if (needSample)
+ {
+ executionTrace . PrepareSample(thrInfo, state);
+ // memoryTrace . PrepareSample(thrInfo, state);
+
+ state.isSampleSucceeds = thrInfo.eventChannel.Sample(
+ m_profiler.GetTickCountFromInit(),
+ // OK with unsigned overflows in ticks.
+ state.genTicks - thrInfo.fixTicks,
+ // NOTE: we can't reallocate memory from signal handler.
+ ChanCanRealloc::NO);
+ if (state.isSampleSucceeds)
+ {
+ this->SendDoLog(thrInfo);
+ }
+
+ executionTrace . AfterSample(thrInfo, state);
+ // memoryTrace . AfterSample(thrInfo, state);
+ thrInfo.fixTicks = state.genTicks;
+ }
+}
+
+ThreadInfo *CommonTrace::GetThreadInfo() noexcept
+{
+ try {
+ //
+ // Try to get thread info from the local storage.
+ //
+
+ ThreadInfo *threadInfo = reinterpret_cast<ThreadInfo*>(
+ TlsGetValue(m_tlsThreadInfoIndex));
+
+ if (threadInfo == nullptr)
+ {
+ DWORD lastError = GetLastError();
+ if (lastError != ERROR_SUCCESS)
+ {
+ m_profiler.HandleHresult(
+ "CommonTrace::GetThreadInfo(): TlsGetValue()",
+ HRESULT_FROM_WIN32(lastError)
+ );
+ }
+ }
+
+ HRESULT hr;
+
+ //
+ // Fast check if current thread is changed.
+ //
+
+ ThreadID threadId = 0;
+ hr = m_info.v1()->GetCurrentThreadID(&threadId);
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "CommonTrace::GetThreadInfo(): GetCurrentThreadID()", hr);
+ }
+
+ if (threadInfo == nullptr || threadInfo->id != threadId)
+ {
+ //
+ // We should update thread info.
+ //
+
+ // Get or create thread info for current thread ID.
+ ThreadInfo *oldThreadInfo = threadInfo;
+ auto storage_lock = m_threadStorage.lock();
+ threadInfo = &storage_lock->Place(threadId).first;
+
+ // Get current OS thread ID.
+ DWORD osThreadId = 0;
+ hr = m_info.v1()->GetThreadInfo(threadId, &osThreadId);
+ // This is OK if we can't obtain osThreadId in some special cases.
+ if (FAILED(hr) && hr != CORPROF_E_UNSUPPORTED_CALL_SEQUENCE)
+ {
+ m_profiler.HandleHresult(
+ "CommonTrace::GetThreadInfo(): GetThreadInfo()", hr);
+ }
+
+ // Check if OS thread ID changed and update it.
+ if (oldThreadInfo != nullptr &&
+ oldThreadInfo->osThreadId == osThreadId)
+ {
+ oldThreadInfo->osThreadId = 0;
+ oldThreadInfo->nativeHandle = 0;
+ }
+ threadInfo->osThreadId = osThreadId;
+ threadInfo->nativeHandle = pthread_self();
+
+ //
+ // Save new thead info to the local storage.
+ //
+
+ if (!TlsSetValue(m_tlsThreadInfoIndex, threadInfo))
+ {
+ m_profiler.HandleHresult(
+ "CommonTrace::GetThreadInfo(): TlsSetValue()",
+ HRESULT_FROM_WIN32(GetLastError())
+ );
+ }
+ }
+
+ return threadInfo;
+ }
+ catch (const std::exception &e)
+ {
+ m_profiler.HandleException(e);
+ return nullptr;
+ }
+}
+
+ThreadInfo *CommonTrace::GetThreadInfoR() const noexcept
+{
+ //
+ // Try to get thread info from the local storage.
+ //
+
+ ThreadInfo *threadInfo = reinterpret_cast<ThreadInfo*>(
+ TlsGetValue(m_tlsThreadInfoIndex));
+
+#ifdef _TARGET_AMD64_
+ if (threadInfo == nullptr)
+ {
+ return nullptr;
+ }
+
+ //
+ // Fast check if current thread is changed.
+ //
+
+ HRESULT hr;
+ ThreadID threadId = 0;
+ hr = m_info.v1()->GetCurrentThreadID(&threadId);
+ if (FAILED(hr) || threadInfo->id != threadId)
+ {
+ return nullptr;
+ }
+#endif // _TARGET_AMD64_
+
+ return threadInfo;
+}
+
+void CommonTrace::InterruptSampling(
+ SamplingSharedState &state,
+ SamplingAction beforeAction,
+ SamplingAction action,
+ SamplingAction afterAction) noexcept
+{
+ ThreadInfo *pThreadInfo = m_profiler.GetCommonTrace().GetThreadInfo();
+ if (pThreadInfo != nullptr)
+ {
+ pThreadInfo->interruptible = false;
+
+ this->DoSampleWithAction(*pThreadInfo, beforeAction, state);
+
+ if (action)
+ {
+ action(*pThreadInfo, state);
+ }
+
+ this->DoSampleWithAction(*pThreadInfo, afterAction, state);
+
+ pThreadInfo->interruptible = true;
+ }
+}
+
+__forceinline void CommonTrace::HandleSample(void *context) noexcept
+{
+ _ASSERTE(!m_disabled);
+ ThreadInfo *pThreadInfo = this->GetThreadInfoR();
+ if (pThreadInfo && pThreadInfo->interruptible)
+ {
+ DoSampleFromHandler(*pThreadInfo, context);
+ }
+}
+
+__forceinline void CommonTrace::HandleSamplingPauseResume(
+ bool shouldPause) noexcept
+{
+ _ASSERTE(!m_disabled);
+ if (m_profiler.GetConfig().CollectionMethod == CollectionMethod::Sampling)
+ {
+ this->SendSamplingEvent(
+ shouldPause ? SamplingEvent::SAMPLING_EVENT_PAUSE :
+ SamplingEvent::SAMPLING_EVENT_RESUME
+ );
+ }
+ else if (m_profiler.GetConfig().CollectionMethod ==
+ CollectionMethod::Instrumentation)
+ {
+ m_samplingSuspended = shouldPause;
+ }
+}
+
+bool CommonTrace::IsSamplingSuspended() const noexcept
+{
+ return m_samplingSuspended;
+}
+
+HRESULT CommonTrace::AppDomainCreationFinished(
+ AppDomainID appDomainId,
+ HRESULT hrStatus) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ HRESULT hr = S_OK;
+ try
+ {
+ ULONG size = 0;
+ ProcessID processId = 0;
+
+ hr = m_info.v1()->GetAppDomainInfo(
+ appDomainId, 0, &size, nullptr, &processId);
+
+ std::unique_ptr<WCHAR[]> name = nullptr;
+ if (SUCCEEDED(hr))
+ {
+ name.reset(new (std::nothrow) WCHAR[size]);
+ if (name)
+ {
+ hr = m_info.v1()->GetAppDomainInfo(
+ appDomainId, size, nullptr, name.get(), nullptr);
+ }
+ }
+
+ TRACE().DumpAppDomainCreationFinished(
+ appDomainId, name.get(), processId, hrStatus);
+
+ // Do it after dump.
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "CommonTrace::AppDomainCreationFinished()", hr);
+ }
+ }
+ catch (const std::exception &e)
+ {
+ hr = m_profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+HRESULT CommonTrace::AssemblyLoadFinished(
+ AssemblyID assemblyId,
+ HRESULT hrStatus) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ HRESULT hr = S_OK;
+ try
+ {
+ ULONG size = 0;
+ AssemblyID appDomainId = 0;
+ ModuleID moduleId = 0;
+
+ hr = m_info.v1()->GetAssemblyInfo(
+ assemblyId, 0, &size, nullptr, &appDomainId, &moduleId);
+
+ std::unique_ptr<WCHAR[]> name = nullptr;
+ if (SUCCEEDED(hr))
+ {
+ name.reset(new (std::nothrow) WCHAR[size]);
+ if (name)
+ {
+ hr = m_info.v1()->GetAssemblyInfo(
+ assemblyId, size, nullptr, name.get(), nullptr, nullptr);
+ }
+ }
+
+ TRACE().DumpAssemblyLoadFinished(
+ assemblyId, name.get(), appDomainId, moduleId, hrStatus);
+
+ // Do it after dump.
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "CommonTrace::AssemblyLoadFinished(): GetAssemblyInfo()", hr);
+ }
+ }
+ catch (const std::exception &e)
+ {
+ hr = m_profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+HRESULT CommonTrace::ModuleLoadFinished(
+ ModuleID moduleId,
+ HRESULT hrStatus) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ HRESULT hr = S_OK;
+ try
+ {
+ ULONG size = 0;
+ LPCBYTE baseLoadAddress = 0;
+ AssemblyID assemblyId = 0;
+
+ hr = m_info.v1()->GetModuleInfo(
+ moduleId, &baseLoadAddress, 0, &size, nullptr, &assemblyId);
+
+ std::unique_ptr<WCHAR[]> name = nullptr;
+ if (SUCCEEDED(hr))
+ {
+ name.reset(new (std::nothrow) WCHAR[size]);
+ if (name)
+ {
+ hr = m_info.v1()->GetModuleInfo(
+ moduleId, nullptr, size, nullptr, name.get(), nullptr);
+ }
+ }
+
+ TRACE().DumpModuleLoadFinished(
+ moduleId, baseLoadAddress, name.get(), assemblyId, hrStatus);
+
+ // Do it after dump.
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "CommonTrace::ModuleLoadFinished(): GetModuleInfo()", hr);
+ }
+ }
+ catch (const std::exception &e)
+ {
+ hr = m_profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+HRESULT CommonTrace::ModuleAttachedToAssembly(
+ ModuleID moduleId,
+ AssemblyID assemblyId) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ HRESULT hr = S_OK;
+ try
+ {
+ TRACE().DumpModuleAttachedToAssembly(moduleId, assemblyId);
+ }
+ catch (const std::exception &e)
+ {
+ hr = m_profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+HRESULT CommonTrace::ClassLoadStarted(
+ ClassID classId) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ HRESULT hr = S_OK;
+ try
+ {
+ m_classStorage.lock()->Place(classId);
+ }
+ catch (const std::exception &e)
+ {
+ hr = m_profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+HRESULT CommonTrace::ClassLoadFinished(
+ ClassID classId,
+ HRESULT hrStatus) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ if (m_info.v1()->IsArrayClass(classId, nullptr, nullptr, nullptr) == S_OK)
+ {
+ LOG().Warn() << "Array class in ClassLoadFinished()";
+ }
+
+ HRESULT hr = S_OK;
+ try
+ {
+ auto storage_lock = m_classStorage.lock();
+ ClassInfo &classInfo = storage_lock->Get(classId);
+ hr = classInfo.Initialize(m_profiler, *storage_lock);
+
+ TRACE().DumpClassLoadFinished(classInfo, hrStatus);
+ if (!classInfo.isNamePrinted)
+ {
+ TRACE().DumpClassName(classInfo);
+ classInfo.isNamePrinted = true;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ hr = m_profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+HRESULT CommonTrace::ClassUnloadStarted(
+ ClassID classId) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ HRESULT hr = S_OK;
+ try
+ {
+ m_classStorage.lock()->Unlink(classId);
+ }
+ catch (const std::exception &e)
+ {
+ hr = m_profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+HRESULT CommonTrace::ThreadCreated(
+ ThreadID threadId) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ HRESULT hr = S_OK;
+ try
+ {
+ InternalID threadIid =
+ m_threadStorage.lock()->Place(threadId).first.internalId;
+ TRACE().DumpThreadCreated(threadId, threadIid);
+ }
+ catch (const std::exception &e)
+ {
+ hr = m_profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+HRESULT CommonTrace::ThreadDestroyed(
+ ThreadID threadId) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ HRESULT hr = S_OK;
+ try
+ {
+ InternalID threadIid;
+ {
+ auto storage_lock = m_threadStorage.lock();
+ ThreadInfo &thrInfo = storage_lock->Unlink(threadId);
+ thrInfo.osThreadId = 0;
+ threadIid = thrInfo.internalId;
+ }
+ TRACE().DumpThreadDestroyed(threadIid);
+ }
+ catch (const std::exception &e)
+ {
+ hr = m_profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+HRESULT CommonTrace::ThreadAssignedToOSThread(
+ ThreadID managedThreadId,
+ DWORD osThreadId) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ HRESULT hr = S_OK;
+ try
+ {
+ InternalID threadIid;
+ {
+ auto storage_lock = m_threadStorage.lock();
+ ThreadInfo &thrInfo = storage_lock->Get(managedThreadId);
+
+ if (thrInfo.osThreadId != osThreadId)
+ {
+ // Get current OS thread ID.
+ DWORD currentOsThreadId = 0;
+ hr = m_info.v1()->GetThreadInfo(
+ managedThreadId, ¤tOsThreadId);
+ // Check if we can setup OS thread ID.
+ if (SUCCEEDED(hr) && currentOsThreadId == osThreadId)
+ {
+ thrInfo.nativeHandle = pthread_self();
+ }
+ else
+ {
+ // This will be updated by GetThreadInfo() later.
+ thrInfo.nativeHandle = 0;
+ if (FAILED(hr))
+ {
+ m_profiler.HandleHresult(
+ "CommonTrace::ThreadAssignedToOSThread(): "
+ "GetThreadInfo()", hr
+ );
+ }
+ }
+ thrInfo.osThreadId = osThreadId;
+ }
+ threadIid = thrInfo.internalId;
+ }
+ TRACE().DumpThreadAssignedToOSThread(threadIid, osThreadId);
+ }
+ catch (const std::exception &e)
+ {
+ hr = m_profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+auto CommonTrace::GetThreadStorage() ->
+ decltype(m_threadStorage.lock())
+{
+ return m_threadStorage.lock();
+}
+
+auto CommonTrace::GetThreadStorage() const ->
+ decltype(m_threadStorage.lock_shared())
+{
+ return m_threadStorage.lock_shared();
+}
+
+auto CommonTrace::GetClassStorage() ->
+ decltype(m_classStorage.lock())
+{
+ return m_classStorage.lock();
+}
+
+auto CommonTrace::GetClassStorage() const ->
+ decltype(m_classStorage.lock_shared())
+{
+ return m_classStorage.lock_shared();
+}
--- /dev/null
+#ifndef _COMMON_TRACE_H_
+#define _COMMON_TRACE_H_
+
+#include <thread>
+
+#ifdef _TARGET_AMD64_
+#include <future>
+#endif // _TARGET_AMD64_
+
+#include <cor.h>
+#include <corhdr.h>
+#include <corprof.h>
+
+#include "basetrace.h"
+
+#include "sharedresource.h"
+#include "threadstorage.h"
+#include "classstorage.h"
+#include "sigaction.h"
+#include "binarysemaphore.h"
+
+class CommonTrace final : public BaseTrace
+{
+public:
+ CommonTrace(Profiler &profiler);
+
+ ~CommonTrace();
+
+ void ProcessConfig(ProfilerConfig &config);
+
+ void Shutdown() noexcept;
+
+ struct SamplingSharedState
+ {
+ ULONG genTicks;
+ bool isIpRestored;
+ bool isSampleSucceeds;
+ void *context;
+ bool stackWillBeChanged;
+ };
+
+ typedef std::function<void(ThreadInfo&, SamplingSharedState&)>
+ SamplingAction;
+
+private:
+ enum class SamplingEvent
+ {
+ SAMPLING_EVENT_PAUSE,
+ SAMPLING_EVENT_RESUME,
+ };
+
+ void SendDoSample(ThreadInfo &thrInfo) noexcept;
+
+ void SendDoLog(ThreadInfo &thrInfo) noexcept;
+
+ void SendStopLog() noexcept;
+
+ void SendSamplingEvent(SamplingEvent event) noexcept;
+
+ void SendStopSampling() noexcept;
+
+ void LogThread(binary_semaphore *pInitialized) noexcept;
+
+ void SamplingThread(binary_semaphore *pInitialized) noexcept;
+
+ void DoSampleWithAction(
+ ThreadInfo &thrInfo,
+ SamplingAction action,
+ SamplingSharedState &state) noexcept;
+
+ void DoSampleFromHandler(
+ ThreadInfo &thrInfo, void *context) noexcept;
+
+public:
+ ThreadInfo *GetThreadInfo() noexcept;
+
+ // Simple and safety version of GetThreadInfo() that can be used in signal
+ // handlers.
+ ThreadInfo *GetThreadInfoR() const noexcept;
+
+ void InterruptSampling(
+ SamplingSharedState &state,
+ SamplingAction beforeAction = {},
+ SamplingAction action = {},
+ SamplingAction afterAction = {}) noexcept;
+
+ void HandleSample(void *context) noexcept;
+
+ void HandleSamplingPauseResume(bool shouldPause) noexcept;
+
+ bool IsSamplingSuspended() const noexcept;
+
+ HRESULT AppDomainCreationFinished(
+ AppDomainID appDomainId,
+ HRESULT hrStatus) noexcept;
+
+ HRESULT AssemblyLoadFinished(
+ AssemblyID assemblyId,
+ HRESULT hrStatus) noexcept;
+
+ HRESULT ModuleLoadFinished(
+ ModuleID moduleId,
+ HRESULT hrStatus) noexcept;
+
+ HRESULT ModuleAttachedToAssembly(
+ ModuleID moduleId,
+ AssemblyID assemblyId) noexcept;
+
+ HRESULT ClassLoadStarted(
+ ClassID classId) noexcept;
+
+ HRESULT ClassLoadFinished(
+ ClassID classId,
+ HRESULT hrStatus) noexcept;
+
+ HRESULT ClassUnloadStarted(
+ ClassID classId) noexcept;
+
+ HRESULT ThreadCreated(
+ ThreadID threadId) noexcept;
+
+ HRESULT ThreadDestroyed(
+ ThreadID threadId) noexcept;
+
+ HRESULT ThreadAssignedToOSThread(
+ ThreadID managedThreadId,
+ DWORD osThreadId) noexcept;
+
+private:
+ int m_tlsThreadInfoIndex;
+
+ SharedResource<ThreadStorage> m_threadStorage;
+ SharedResource<ClassStorage> m_classStorage;
+
+ SigAction m_pauseAction;
+ SigAction m_resumeAction;
+ SigAction m_sampleAction;
+
+ std::thread m_logThread;
+ std::thread m_samplingThread;
+
+ bool m_samplingSuspended;
+
+public:
+ auto GetThreadStorage() -> decltype(m_threadStorage.lock());
+
+ auto GetThreadStorage() const -> decltype(m_threadStorage.lock_shared());
+
+ auto GetClassStorage() -> decltype(m_classStorage.lock());
+
+ auto GetClassStorage() const -> decltype(m_classStorage.lock_shared());
+};
+
+#endif // _COMMON_TRACE_H_
--- /dev/null
+#include <system_error>
+#include <exception>
+
+#include <errno.h>
+
+#include "profiler.h"
+#include "cputrace.h"
+
+CpuTrace::CpuTrace(Profiler &profiler)
+ : BaseTrace(profiler)
+ , m_logThread()
+ , lastUserTime(0)
+{
+}
+
+CpuTrace::~CpuTrace()
+{
+ // NOTE: we are dealing with a partially destroyed m_profiler!
+ this->Shutdown();
+}
+
+void CpuTrace::ProcessConfig(ProfilerConfig &config)
+{
+ //
+ // Check activation condition.
+ //
+
+ if (config.CpuTraceProcessEnabled || config.CpuTraceThreadEnabled)
+ {
+ m_disabled = false;
+ }
+ else
+ {
+ return;
+ }
+
+ //
+ // Starting service threads.
+ //
+
+ m_logThread = std::thread(&CpuTrace::LogThread, this);
+}
+
+void CpuTrace::Shutdown() noexcept
+{
+ m_disabled = true;
+ if (m_logThread.joinable())
+ {
+ m_logThread.join();
+ }
+}
+
+// static
+DWORD64 CpuTrace::GetClockTime(clockid_t clk_id)
+{
+ struct timespec ts;
+ if (clock_gettime(clk_id, &ts))
+ {
+ throw std::system_error(errno, std::system_category(),
+ "CpuTrace::GetClockTime(): clock_gettime()");
+ }
+ return (DWORD64)ts.tv_sec * 1000000 + ts.tv_nsec / 1000; // (us)
+}
+
+void CpuTrace::LogProcessTime()
+{
+ DWORD64 userTimeUs64 = CpuTrace::GetClockTime(CLOCK_PROCESS_CPUTIME_ID);
+
+ TRACE().DumpProcessTimes(
+ m_profiler.GetTickCountFromInit(),
+ userTimeUs64 - lastUserTime
+ );
+
+ lastUserTime = userTimeUs64;
+}
+
+void CpuTrace::LogThreadTime(ThreadInfo &thrInfo)
+{
+ clockid_t cid;
+ int err = pthread_getcpuclockid(thrInfo.nativeHandle, &cid);
+ if (err && err != ESRCH)
+ {
+ throw std::system_error(err, std::system_category(),
+ "CpuTrace::LogThreadTimes(): pthread_getcpuclockid()");
+ }
+ else if (err == ESRCH)
+ {
+ return;
+ }
+
+ DWORD64 userTimeUs64 = CpuTrace::GetClockTime(cid);
+
+ TRACE().DumpThreadTimes(
+ thrInfo.internalId,
+ m_profiler.GetTickCountFromInit(),
+ userTimeUs64 - thrInfo.lastUserTime
+ );
+
+ thrInfo.lastUserTime = userTimeUs64;
+}
+
+void CpuTrace::LogThread() noexcept
+{
+ try
+ {
+ lastUserTime = CpuTrace::GetClockTime(CLOCK_PROCESS_CPUTIME_ID);
+
+ while(m_disabled == false)
+ {
+ Sleep(m_profiler.GetConfig().CpuTraceTimeoutMs);
+ if (m_disabled)
+ {
+ break;
+ }
+ else if (m_profiler.GetCommonTrace().IsSamplingSuspended())
+ {
+ continue;
+ }
+
+ if (m_profiler.GetConfig().CpuTraceProcessEnabled)
+ {
+ this->LogProcessTime();
+ }
+
+ if (m_profiler.GetConfig().CpuTraceThreadEnabled)
+ {
+ auto storage_lock =
+ m_profiler.GetCommonTrace().GetThreadStorage();
+ for (ThreadInfo &thrInfo : storage_lock->GetLiveRange())
+ {
+ if (m_disabled == true)
+ break;
+
+ // We update all live threads if they are attached to OS
+ // threads.
+ if (thrInfo.id != 0 && thrInfo.nativeHandle != 0)
+ {
+ this->LogThreadTime(thrInfo);
+ }
+ }
+ }
+ }
+ }
+ catch (const std::exception &e)
+ {
+ m_profiler.HandleException(e);
+ }
+}
--- /dev/null
+#ifndef _CPU_TRACE_H_
+#define _CPU_TRACE_H_
+
+#include <thread>
+
+#include <time.h>
+
+#include <cor.h>
+#include <corhdr.h>
+#include <corprof.h>
+
+#include "basetrace.h"
+
+#include "threadinfo.h"
+
+class CpuTrace final : public BaseTrace
+{
+public:
+ CpuTrace(Profiler &profiler);
+
+ ~CpuTrace();
+
+ void ProcessConfig(ProfilerConfig &config);
+
+ void Shutdown() noexcept;
+
+private:
+ static DWORD64 GetClockTime(clockid_t clk_id);
+
+ void LogProcessTime();
+
+ void LogThreadTime(ThreadInfo &thrInfo);
+
+ void LogThread() noexcept;
+
+private:
+ std::thread m_logThread;
+
+ DWORD64 lastUserTime;
+};
+
+#endif // _CPU_TRACE_H_
--- /dev/null
+#include <assert.h>
+
+#include "eventchannel.h"
+
+#define EVENT_CHANNEL_START_CAP 128 // Should be power of 2.
+
+using Stack = EventSummary::Stack;
+
+EventSummary::EventSummary(Stack::size_type stackSize)
+ : ticks(0)
+ , count(0)
+ , matchPrefixSize(stackSize)
+ , stackSize(stackSize)
+ , ipIsChanged(false)
+ , ip(0)
+ , newFrames()
+{}
+
+bool EventSummary::HasStackSample() const noexcept
+{
+ return count > 0
+ || matchPrefixSize != stackSize
+ || ipIsChanged
+ || newFrames.size() > 0;
+}
+
+bool EventSummary::HasAllocSample() const noexcept
+{
+ return allocTable.size() > 0;
+}
+
+EventChannel::EventChannel()
+ : m_stack()
+ , m_currentState()
+ , m_buffer(EVENT_CHANNEL_START_CAP)
+ , m_mutex()
+ , m_bufferCapacityIncreaseIsPlanned(false)
+{}
+
+void EventChannel::IncreaseBufferCapacity()
+{
+ m_buffer.reserve(m_buffer.capacity() * 2);
+ m_bufferCapacityIncreaseIsPlanned = false;
+}
+
+bool EventChannel::EnsureBufferCapacity(ChanCanRealloc canRealloc)
+{
+ bool isBufferNoSpace = m_buffer.size() == m_buffer.capacity();
+ Stack::difference_type needStackSize =
+ m_stack.size() - m_currentState.matchPrefixSize;
+ bool isStackNoSpace = m_currentState.newFrames.capacity() < needStackSize;
+ switch (canRealloc)
+ {
+ case ChanCanRealloc::NO:
+ if (isBufferNoSpace)
+ {
+ this->PlanToIncreaseBufferCapacity();
+ }
+ assert(!isStackNoSpace);
+ return !isBufferNoSpace;
+
+ case ChanCanRealloc::YES:
+ if (isBufferNoSpace || m_bufferCapacityIncreaseIsPlanned)
+ {
+ std::lock_guard<decltype(m_mutex)> lock(m_mutex);
+ this->IncreaseBufferCapacity();
+ }
+ if (isStackNoSpace)
+ {
+ m_currentState.newFrames.reserve(needStackSize);
+ }
+ assert(m_buffer.capacity() != m_buffer.size());
+ assert(m_currentState.newFrames.capacity() >= needStackSize);
+ return true;
+ }
+}
+
+void EventChannel::Push(const FunctionInfo &funcInfo) noexcept
+{
+ assert(m_currentState.matchPrefixSize <= m_stack.size());
+ m_stack.push_back(Frame{&funcInfo, 0});
+
+ // XXX: exception in this call will terminate process!
+ this->EnsureBufferCapacity(); // Perform planned reallocation.
+}
+
+void EventChannel::Pop() noexcept
+{
+ assert(!m_stack.empty());
+ assert(m_currentState.matchPrefixSize <= m_stack.size());
+ m_stack.pop_back();
+ if (m_stack.size() < m_currentState.matchPrefixSize)
+ {
+ m_currentState.matchPrefixSize = m_stack.size();
+ m_currentState.ipIsChanged = false;
+ }
+
+ // XXX: exception in this call will terminate process!
+ this->EnsureBufferCapacity(); // Perform planned reallocation.
+}
+
+void EventChannel::ChIP(UINT_PTR ip, size_t idxFromTop) noexcept
+{
+ assert(idxFromTop < m_stack.size());
+ assert(m_currentState.matchPrefixSize <= m_stack.size());
+ assert(
+ m_stack.size() - idxFromTop >= m_currentState.matchPrefixSize
+ );
+
+ Frame &frame = const_cast<Frame&>(this->GetFrameFromTop(idxFromTop));
+ size_t frameIdx = m_stack.size() - idxFromTop - 1;
+ assert(&m_stack[frameIdx] == &frame);
+ if (frame.ip != ip)
+ {
+ if (frameIdx + 1 == m_currentState.matchPrefixSize)
+ {
+ m_currentState.ipIsChanged = true;
+ }
+ frame.ip = ip;
+ }
+
+ // XXX: exception in this call will terminate process!
+ this->EnsureBufferCapacity(); // Perform planned reallocation.
+}
+
+void EventChannel::Allocation(
+ ClassInfo &classInfo, SIZE_T size, UINT_PTR ip) noexcept
+{
+ AllocInfo &allocInfo =
+ m_currentState.allocTable[classInfo.internalId.id][ip];
+ allocInfo.allocCount++;
+ allocInfo.memSize += size;
+}
+
+bool EventChannel::Sample(
+ DWORD ticks, ULONG count, ChanCanRealloc canRealloc) noexcept
+{
+ assert(m_currentState.matchPrefixSize <= m_stack.size());
+ assert(m_currentState.matchPrefixSize <= m_currentState.stackSize);
+ assert(!m_currentState.ipIsChanged || m_currentState.matchPrefixSize > 0);
+
+ // XXX: exception in this call will terminate process!
+ if (!this->EnsureBufferCapacity(canRealloc))
+ {
+ // No space for new sample.
+ return false;
+ }
+
+ m_currentState.ticks = ticks;
+ m_currentState.count = count;
+
+ if (m_currentState.ipIsChanged)
+ {
+ m_currentState.ip = m_stack[m_currentState.matchPrefixSize - 1].ip;
+ }
+
+ assert(m_currentState.newFrames.size() == 0);
+ m_currentState.newFrames.assign(
+ m_stack.cbegin() + m_currentState.matchPrefixSize, m_stack.cend());
+
+ m_buffer.push_back(std::move(m_currentState));
+ m_currentState = EventSummary(m_stack.size());
+
+ return true;
+}
+
+void EventChannel::PlanToIncreaseBufferCapacity() noexcept
+{
+ m_bufferCapacityIncreaseIsPlanned = true;
+}
+
+Stack::size_type EventChannel::GetStackSize() const noexcept
+{
+ return m_stack.size();
+}
+
+bool EventChannel::HasStackSample() const noexcept
+{
+ return m_currentState.HasStackSample() ||
+ m_stack.size() > m_currentState.matchPrefixSize;
+}
+
+bool EventChannel::HasAllocSample() const noexcept
+{
+ return m_currentState.HasAllocSample();
+}
+
+const Frame &EventChannel::GetFrameFromTop(
+ Stack::size_type idxFromTop) const noexcept
+{
+ assert(idxFromTop < m_stack.size());
+ return m_stack.rbegin()[idxFromTop];
+}
+
+size_t EventChannel::GetEventSummaryCount() const noexcept
+{
+ return m_buffer.size();
+}
+
+const EventSummary &EventChannel::GetCurrentEventSummary() noexcept
+{
+ m_mutex.lock();
+ return m_buffer.front();
+}
+
+void EventChannel::NextEventSummary() noexcept
+{
+ m_buffer.pop_front();
+ m_mutex.unlock();
+}
--- /dev/null
+#ifndef _EVENT_CHANNEL_H_
+#define _EVENT_CHANNEL_H_
+
+#include <utility>
+#include <vector>
+#include <map>
+#include <mutex>
+#include <atomic>
+
+#include "functioninfo.h"
+#include "classinfo.h"
+#include "ringbuffer.h"
+
+struct Frame
+{
+ const FunctionInfo *pFuncInfo;
+ UINT_PTR ip;
+};
+
+struct AllocInfo
+{
+ SIZE_T allocCount = 0;
+ SIZE_T memSize = 0;
+};
+
+typedef std::map<ULONG, std::map<UINT_PTR, AllocInfo>> AllocTable;
+
+struct EventSummary
+{
+ typedef std::vector<Frame> Stack;
+
+ explicit EventSummary(Stack::size_type stackSize = 0);
+
+ //
+ // Sample
+ //
+
+ DWORD ticks;
+ ULONG count;
+
+ //
+ // Stack
+ //
+
+ Stack::difference_type matchPrefixSize;
+ Stack::size_type stackSize;
+ bool ipIsChanged;
+ UINT_PTR ip;
+ Stack newFrames;
+
+ bool HasStackSample() const noexcept;
+
+ //
+ // Allocations
+ //
+
+ AllocTable allocTable;
+
+ bool HasAllocSample() const noexcept;
+};
+
+enum class ChanCanRealloc
+{
+ NO,
+ YES
+};
+
+class EventChannel
+{
+public:
+ typedef EventSummary::Stack Stack;
+
+ EventChannel();
+
+private:
+ void IncreaseBufferCapacity();
+
+ bool EnsureBufferCapacity(ChanCanRealloc canRealloc = ChanCanRealloc::YES);
+
+public:
+ //
+ // Writer methods.
+ //
+
+ void Push(const FunctionInfo &funcInfo) noexcept;
+
+ void Pop() noexcept;
+
+ void ChIP(UINT_PTR ip, size_t idxFromTop = 0) noexcept;
+
+ void Allocation(
+ ClassInfo &classInfo, SIZE_T size, UINT_PTR ip = 0) noexcept;
+
+ bool Sample(
+ DWORD ticks, ULONG count,
+ ChanCanRealloc canRealloc = ChanCanRealloc::YES) noexcept;
+
+ void PlanToIncreaseBufferCapacity() noexcept;
+
+ Stack::size_type GetStackSize() const noexcept;
+
+ const Frame &GetFrameFromTop(
+ Stack::size_type idxFromTop = 0) const noexcept;
+
+ bool HasStackSample() const noexcept;
+
+ bool HasAllocSample() const noexcept;
+
+ //
+ // Reader methods.
+ //
+
+ size_t GetEventSummaryCount() const noexcept;
+
+ // Reference only valid until next call to NextEventSummary().
+ const EventSummary &GetCurrentEventSummary() noexcept;
+
+ void NextEventSummary() noexcept;
+
+private:
+ Stack m_stack;
+ EventSummary m_currentState;
+
+ ring_buffer<EventSummary> m_buffer;
+ std::mutex m_mutex;
+ bool m_bufferCapacityIncreaseIsPlanned;
+};
+
+#endif // _EVENT_CHANNEL_H_
--- /dev/null
+#include <exception>
+#include <tuple>
+
+#include "profiler.h"
+#include "executiontrace.h"
+
+EXTERN_C UINT_PTR __stdcall FunctionIDMapStub(
+ FunctionID funcId,
+ void *clientData,
+ BOOL *pbHookFunction)
+{
+ return reinterpret_cast<ExecutionTrace*>(clientData)->
+ FunctionIDMap(funcId, pbHookFunction);
+}
+
+EXTERN_C __stdcall void EnterNaked3(FunctionIDOrClientID functionIDOrClientID);
+EXTERN_C __stdcall void LeaveNaked3(FunctionIDOrClientID functionIDOrClientID);
+EXTERN_C __stdcall void TailcallNaked3(FunctionIDOrClientID functionIDOrClientID);
+
+#ifdef _TARGET_ARM_
+EXTERN_C UINT_PTR getPrevPC();
+#endif // _TARGET_ARM_
+
+EXTERN_C void __stdcall EnterStub(FunctionIDOrClientID functionIDOrClientID)
+{
+ UINT_PTR ip = 0;
+
+#ifdef _TARGET_ARM_
+ ip = getPrevPC();
+#endif // _TARGET_ARM_
+
+ FunctionInfo *funcInfo = reinterpret_cast<FunctionInfo*>(
+ functionIDOrClientID.clientID);
+ funcInfo->executionTrace->Enter(*funcInfo, ip);
+}
+
+EXTERN_C void __stdcall LeaveStub(FunctionIDOrClientID functionIDOrClientID)
+{
+ FunctionInfo *funcInfo = reinterpret_cast<FunctionInfo*>(
+ functionIDOrClientID.clientID);
+ funcInfo->executionTrace->Leave(*funcInfo);
+}
+
+EXTERN_C void __stdcall TailcallStub(FunctionIDOrClientID functionIDOrClientID)
+{
+ FunctionInfo *funcInfo = reinterpret_cast<FunctionInfo*>(
+ functionIDOrClientID.clientID);
+ funcInfo->executionTrace->Tailcall(*funcInfo);
+}
+
+ExecutionTrace::ExecutionTrace(Profiler &profiler)
+ : BaseTrace(profiler)
+ , m_functionStorage(this)
+ , m_pUnmanagedFunctionInfo(nullptr)
+ , m_pJitFunctionInfo(nullptr)
+{
+ auto storage_lock = m_functionStorage.lock();
+
+ m_pUnmanagedFunctionInfo = &storage_lock->Add();
+ m_pJitFunctionInfo = &storage_lock->Add();
+
+ m_pUnmanagedFunctionInfo->name = W("<UNMANAGED>");
+ m_pUnmanagedFunctionInfo->fullName = m_pUnmanagedFunctionInfo->name;
+ m_pJitFunctionInfo->name = W("<JIT>");
+ m_pJitFunctionInfo->fullName = m_pJitFunctionInfo->name;
+}
+
+ExecutionTrace::~ExecutionTrace()
+{
+ // NOTE: we are dealing with a partially destroyed m_profiler!
+ this->Shutdown();
+}
+
+void ExecutionTrace::ProcessConfig(ProfilerConfig &config)
+{
+ //
+ // Check activation condition.
+ //
+
+ if (config.ExecutionTraceEnabled || config.MemoryTraceEnabled)
+ {
+ m_disabled = false;
+ }
+ else
+ {
+ return;
+ }
+
+ //
+ // Announce names of the special functions.
+ //
+
+ TRACE().DumpJITFunctionName(*m_pUnmanagedFunctionInfo);
+ TRACE().DumpJITFunctionName(*m_pJitFunctionInfo);
+
+ //
+ // Event Mask calculation.
+ //
+
+ HRESULT hr;
+ DWORD events;
+ hr = m_info.v1()->GetEventMask(&events);
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "ExecutionTrace::ProcessConfig(): GetEventMask()", hr);
+ }
+
+ // This events are common for execution tracing.
+ events = events
+ | COR_PRF_MONITOR_JIT_COMPILATION
+ | COR_PRF_MONITOR_CACHE_SEARCHES
+ | COR_PRF_MONITOR_FUNCTION_UNLOADS;
+
+ if (config.CollectionMethod == CollectionMethod::Instrumentation ||
+ config.CollectionMethod == CollectionMethod::Sampling)
+ {
+ if (m_info.version() < 3)
+ {
+ LOG().Warn() <<
+ "ICorProfilerInfo3 is required for current configuration";
+ goto next_stage;
+ }
+
+ m_info.v3()->SetFunctionIDMapper2(FunctionIDMapStub, this);
+
+ hr = m_info.v3()->SetEnterLeaveFunctionHooks3(
+ EnterNaked3, LeaveNaked3, TailcallNaked3);
+ if (FAILED(hr))
+ {
+ m_profiler.HandleHresult(
+ "ExecutionTrace::ProcessConfig(): "
+ "SetEnterLeaveFunctionHooks3()", hr
+ );
+ goto next_stage;
+ }
+
+ // This events are required for tracing of call stack dynamics.
+ events = events
+ | COR_PRF_MONITOR_ENTERLEAVE
+ | COR_PRF_MONITOR_CODE_TRANSITIONS
+ | COR_PRF_MONITOR_EXCEPTIONS;
+ }
+
+next_stage:
+ //
+ // Set Event Mask.
+ //
+
+ hr = m_info.v1()->SetEventMask(events);
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "ExecutionTrace::ProcessConfig(): SetEventMask()", hr);
+ }
+}
+
+void ExecutionTrace::Shutdown() noexcept
+{
+ m_disabled = true;
+}
+
+bool ExecutionTrace::IsPseudoFunction(
+ const FunctionInfo &funcInfo) const noexcept
+{
+ _ASSERTE(m_pUnmanagedFunctionInfo != nullptr);
+ _ASSERTE(m_pUnmanagedFunctionInfo->internalId.id == 0);
+ _ASSERTE(m_pJitFunctionInfo != nullptr);
+ _ASSERTE(m_pJitFunctionInfo->internalId.id == 1);
+
+ return funcInfo.internalId.id >= 0 && funcInfo.internalId.id <= 1;
+}
+
+UINT_PTR ExecutionTrace::GetCurrentManagedIP(
+ ThreadInfo &thrInfo, CONTEXT *winContext) noexcept
+{
+ struct Snapshot {
+ FunctionID funcId;
+ UINT_PTR pc;
+
+ static HRESULT __stdcall Callback(
+ FunctionID funcId,
+ UINT_PTR ip,
+ COR_PRF_FRAME_INFO frameInfo,
+ ULONG32 contextSize,
+ BYTE context[],
+ void *clientData)
+ {
+ Snapshot *data = reinterpret_cast<Snapshot*>(clientData);
+
+ if (funcId == data->funcId)
+ {
+ // We have HIT !
+ data->pc = ip;
+ return E_FAIL; // != S_OK
+ }
+
+ return S_OK;
+ }
+ };
+
+ const FunctionInfo &funcInfo =
+ *thrInfo.eventChannel.GetFrameFromTop().pFuncInfo;
+ if (this->IsPseudoFunction(funcInfo))
+ {
+ return 0;
+ }
+
+ // XXX: prevent deadlock at DoStackSnapshot() call.
+ if (std::uncaught_exception())
+ {
+ return 0;
+ }
+
+ _ASSERTE(m_info.version() >= 2);
+ Snapshot data = {funcInfo.id, 0};
+ m_info.v2()->DoStackSnapshot(thrInfo.id, Snapshot::Callback,
+ COR_PRF_SNAPSHOT_DEFAULT, &data,
+ reinterpret_cast<BYTE*>(winContext), sizeof(winContext));
+
+ return data.pc;
+}
+
+void ExecutionTrace::RestoreManagedIP(
+ ThreadInfo &thrInfo, CONTEXT *winContext) noexcept
+{
+ struct Snapshot {
+ ExecutionTrace &execTrace;
+ EventChannel &channel;
+ size_t idxFromTop;
+ size_t maxIdxFromTop;
+ size_t maxUndIdxFromTop;
+
+ static HRESULT __stdcall Callback(
+ FunctionID funcId,
+ UINT_PTR ip,
+ COR_PRF_FRAME_INFO frameInfo,
+ ULONG32 contextSize,
+ BYTE context[],
+ void *clientData)
+ {
+ Snapshot *data = reinterpret_cast<Snapshot*>(clientData);
+
+ ExecutionTrace &execTrace = data->execTrace;
+ EventChannel &channel = data->channel;
+ size_t &idxFromTop = data->idxFromTop; // Reference!
+ size_t maxIdxFromTop = data->maxIdxFromTop;
+ size_t &maxUndIdxFromTop =
+ data->maxUndIdxFromTop; // Reference!
+
+ _ASSERTE(maxIdxFromTop < channel.GetStackSize());
+ _ASSERTE(idxFromTop <= maxIdxFromTop);
+
+ // Skip pseudo-functions.
+ const Frame *pFrame = &channel.GetFrameFromTop(idxFromTop);
+ while (execTrace.IsPseudoFunction(*pFrame->pFuncInfo))
+ {
+ if (++idxFromTop > maxIdxFromTop)
+ {
+ return E_FAIL; // Stop unwinding.
+ }
+ pFrame = &channel.GetFrameFromTop(idxFromTop);
+ }
+
+ if (funcId == pFrame->pFuncInfo->id)
+ {
+ // We have HIT !
+ if (ip != 0 || idxFromTop == 0)
+ {
+ channel.ChIP(ip, idxFromTop++);
+ }
+ else
+ {
+ maxUndIdxFromTop = idxFromTop++;
+ }
+ }
+
+ // Continue for next function on stack or stop unwinding.
+ return idxFromTop <= maxIdxFromTop ? S_OK : E_FAIL;
+ }
+ };
+
+ // XXX: prevent deadlock at DoStackSnapshot() call.
+ if (std::uncaught_exception())
+ {
+ return;
+ }
+
+ _ASSERTE(m_info.version() >= 2);
+ Snapshot data = {
+ *this,
+ thrInfo.eventChannel,
+ 0,
+ thrInfo.maxRestoreIpIdx,
+ 0,
+ };
+ HRESULT hr;
+ hr = m_info.v2()->DoStackSnapshot(thrInfo.id, Snapshot::Callback,
+ COR_PRF_SNAPSHOT_DEFAULT, &data,
+ reinterpret_cast<BYTE*>(winContext), sizeof(winContext));
+ if (FAILED(hr) && hr != CORPROF_E_STACKSNAPSHOT_ABORTED)
+ {
+ thrInfo.eventChannel.ChIP(0);
+ }
+ else if (data.idxFromTop > data.maxIdxFromTop)
+ {
+ thrInfo.maxRestoreIpIdx = data.maxUndIdxFromTop;
+ }
+}
+
+bool ExecutionTrace::NeedSample(
+ ThreadInfo &thrInfo, SamplingSharedState &state) const noexcept
+{
+ if (m_disabled || !m_profiler.GetConfig().ExecutionTraceEnabled)
+ return false;
+
+ return (thrInfo.fixTicks != state.genTicks) ||
+ (
+ thrInfo.eventChannel.HasStackSample() &&
+ (m_profiler.GetConfig().CollectionMethod ==
+ CollectionMethod::Instrumentation &&
+ !m_profiler.GetCommonTrace().IsSamplingSuspended())
+ );
+}
+
+HRESULT ContextToStackSnapshotContext(
+ const void *context, CONTEXT *winContext) noexcept;
+
+void ExecutionTrace::PrepareSample(
+ ThreadInfo &thrInfo, SamplingSharedState &state) noexcept
+{
+ if (m_profiler.GetConfig().LineTraceEnabled &&
+ thrInfo.eventChannel.GetStackSize() > 0)
+ {
+ if (state.context)
+ {
+ CONTEXT winContext;
+ if (SUCCEEDED(ContextToStackSnapshotContext(
+ state.context, &winContext)))
+ {
+ this->RestoreManagedIP(thrInfo, &winContext);
+ }
+ else
+ {
+ thrInfo.eventChannel.ChIP(0);
+ }
+ }
+ else
+ {
+ this->RestoreManagedIP(thrInfo);
+ }
+ state.isIpRestored = true;
+ }
+}
+
+void ExecutionTrace::AfterSample(
+ ThreadInfo &thrInfo, SamplingSharedState &state) noexcept
+{
+ if (state.isSampleSucceeds)
+ {
+ thrInfo.maxRestoreIpIdx = 0;
+ }
+}
+
+HRESULT ContextToStackSnapshotContext(
+ const void *context, CONTEXT *winContext) noexcept;
+
+void ExecutionTrace::UpdateCallStackPush(const FunctionInfo &funcInfo) noexcept
+{
+ SamplingSharedState state = {};
+ state.stackWillBeChanged = true;
+ m_profiler.GetCommonTrace().InterruptSampling(
+ state,
+ {},
+ [&funcInfo](ThreadInfo &thrInfo, SamplingSharedState &state)
+ {
+ EventChannel &channel = thrInfo.eventChannel;
+ if (channel.GetStackSize() > 0)
+ {
+ ++thrInfo.maxRestoreIpIdx;
+ }
+ channel.Push(funcInfo);
+ state.isIpRestored = false;
+ state.stackWillBeChanged = false;
+ }
+ );
+}
+
+void ExecutionTrace::UpdateCallStackPush(
+ const FunctionInfo &funcInfo, UINT_PTR prevIP) noexcept
+{
+ SamplingSharedState state = {};
+ state.stackWillBeChanged = true;
+ m_profiler.GetCommonTrace().InterruptSampling(
+ state,
+ [&funcInfo, prevIP](ThreadInfo &thrInfo, SamplingSharedState &state)
+ {
+ EventChannel &channel = thrInfo.eventChannel;
+ if (channel.GetStackSize() > 0)
+ {
+ channel.ChIP(prevIP);
+ }
+ },
+ [&funcInfo, prevIP](ThreadInfo &thrInfo, SamplingSharedState &state)
+ {
+ EventChannel &channel = thrInfo.eventChannel;
+ if (channel.GetStackSize() > 0)
+ {
+ if (prevIP == 0 || thrInfo.maxRestoreIpIdx > 0)
+ {
+ ++thrInfo.maxRestoreIpIdx;
+ }
+ }
+ channel.Push(funcInfo);
+ state.isIpRestored = false;
+ state.stackWillBeChanged = false;
+ }
+ );
+}
+
+void ExecutionTrace::UpdateCallStackPop() noexcept
+{
+ SamplingSharedState state = {};
+ state.stackWillBeChanged = true;
+ m_profiler.GetCommonTrace().InterruptSampling(
+ state,
+ {},
+ [](ThreadInfo &thrInfo, SamplingSharedState &state)
+ {
+ thrInfo.eventChannel.Pop();
+ if (thrInfo.maxRestoreIpIdx > 0)
+ {
+ --thrInfo.maxRestoreIpIdx;
+ }
+ state.isIpRestored = false;
+ state.stackWillBeChanged = false;
+ }
+ );
+}
+
+UINT_PTR ExecutionTrace::FunctionIDMap(
+ FunctionID funcId,
+ BOOL *pbHookFunction) noexcept
+{
+ LOG().Trace() << "FunctionIDMap()";
+
+ try
+ {
+ FunctionInfo *pFuncInfo =
+ &m_functionStorage.lock()->Place(funcId).first;
+ pFuncInfo->executionTrace = this;
+ *pbHookFunction = true;
+ // This pointer should be stable during all lifetime of the Profiler.
+ // It is important feature guaranteed by the BaseStorage class.
+ return reinterpret_cast<UINT_PTR>(pFuncInfo);
+ }
+ catch (const std::exception &e)
+ {
+ m_profiler.HandleException(e);
+ *pbHookFunction = false;
+ return reinterpret_cast<UINT_PTR>(nullptr);
+ }
+}
+
+__forceinline void ExecutionTrace::Enter(
+ const FunctionInfo &funcInfo, UINT_PTR prevIP) noexcept
+{
+ LOG().Trace() << "EnterStub()";
+ if (m_profiler.GetConfig().LineTraceEnabled)
+ {
+ this->UpdateCallStackPush(funcInfo, prevIP);
+ }
+ else
+ {
+ this->UpdateCallStackPush(funcInfo);
+ }
+}
+
+__forceinline void ExecutionTrace::Leave(const FunctionInfo &funcInfo) noexcept
+{
+ LOG().Trace() << "LeaveStub()";
+ this->UpdateCallStackPop();
+}
+
+__forceinline void ExecutionTrace::Tailcall(
+ const FunctionInfo &funcInfo) noexcept
+{
+ LOG().Trace() << "TailcallStub()";
+ this->UpdateCallStackPop();
+}
+
+HRESULT ExecutionTrace::JITStarted(
+ FunctionID functionId) noexcept
+{
+ HRESULT hr = S_OK;
+ try
+ {
+ this->UpdateCallStackPush(*m_pJitFunctionInfo);
+ m_functionStorage.lock()->Place(functionId);
+ }
+ catch (const std::exception &e)
+ {
+ hr = m_profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+HRESULT ExecutionTrace::JITFinished(
+ FunctionID functionId,
+ std::function<JITDumpFunction> dumpFunction) noexcept
+{
+ HRESULT hr = S_OK;
+ try
+ {
+ auto storage_lock = m_functionStorage.lock();
+ FunctionInfo &funcInfo = storage_lock->Get(functionId);
+
+ {
+ auto storage_lock = m_profiler.GetCommonTrace().GetClassStorage();
+ hr = funcInfo.Initialize(m_profiler, *storage_lock);
+ }
+
+ dumpFunction(funcInfo);
+ if (!funcInfo.isNamePrinted)
+ {
+ TRACE().DumpJITFunctionName(funcInfo);
+ funcInfo.isNamePrinted = true;
+ }
+
+ this->UpdateCallStackPop();
+ }
+ catch (const std::exception &e)
+ {
+ hr = m_profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+HRESULT ExecutionTrace::FunctionUnloadStarted(
+ FunctionID functionId) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ HRESULT hr = S_OK;
+ try
+ {
+ m_functionStorage.lock()->Unlink(functionId);
+ }
+ catch (const std::exception &e)
+ {
+ hr = m_profiler.HandleException(e);
+ }
+
+ return hr;
+}
+
+HRESULT ExecutionTrace::JITCompilationStarted(
+ FunctionID functionId,
+ BOOL fIsSafeToBlock) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ return this->JITStarted(functionId);
+}
+
+HRESULT ExecutionTrace::JITCompilationFinished(
+ FunctionID functionId,
+ HRESULT hrStatus, BOOL fIsSafeToBlock) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ return this->JITFinished(
+ functionId,
+ [this, hrStatus](const FunctionInfo &funcInfo)
+ {
+ TRACE().DumpJITCompilationFinished(funcInfo, hrStatus);
+ }
+ );
+}
+
+HRESULT ExecutionTrace::JITCachedFunctionSearchStarted(
+ FunctionID functionId,
+ BOOL *pbUseCachedFunction) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ *pbUseCachedFunction = TRUE;
+ return this->JITStarted(functionId);
+}
+
+HRESULT ExecutionTrace::JITCachedFunctionSearchFinished(
+ FunctionID functionId,
+ COR_PRF_JIT_CACHE result) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ if (result != COR_PRF_CACHED_FUNCTION_FOUND)
+ {
+ return S_OK;
+ }
+
+ return this->JITFinished(
+ functionId,
+ [this](const FunctionInfo &funcInfo)
+ {
+ TRACE().DumpJITCachedFunctionSearchFinished(funcInfo);
+ }
+ );
+}
+
+HRESULT ExecutionTrace::UnmanagedToManagedTransition(
+ FunctionID functionId,
+ COR_PRF_TRANSITION_REASON reason) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ if (reason == COR_PRF_TRANSITION_RETURN)
+ {
+ this->UpdateCallStackPop();
+ }
+
+ return S_OK;
+}
+
+HRESULT ExecutionTrace::ManagedToUnmanagedTransition(
+ FunctionID functionId,
+ COR_PRF_TRANSITION_REASON reason) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ if (reason == COR_PRF_TRANSITION_CALL)
+ {
+ this->UpdateCallStackPush(*m_pUnmanagedFunctionInfo);
+ }
+
+ return S_OK;
+}
+
+HRESULT ExecutionTrace::ExceptionUnwindFunctionLeave() noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ this->UpdateCallStackPop();
+
+ return S_OK;
+}
--- /dev/null
+#ifndef _EXECUTION_TRACE_H_
+#define _EXECUTION_TRACE_H_
+
+#include <functional>
+
+#include <cor.h>
+#include <corhdr.h>
+#include <corprof.h>
+
+#include "basetrace.h"
+
+#include "sharedresource.h"
+#include "threadinfo.h"
+#include "functionstorage.h"
+
+// #include "shared_iterator_range.h"
+
+class ExecutionTrace final : public BaseTrace
+{
+private:
+ using SamplingSharedState = CommonTrace::SamplingSharedState;
+
+public:
+ ExecutionTrace(Profiler &profiler);
+
+ ~ExecutionTrace();
+
+ void ProcessConfig(ProfilerConfig &config);
+
+ bool IsPseudoFunction(const FunctionInfo &funcInfo) const noexcept;
+
+ void Shutdown() noexcept;
+
+ UINT_PTR GetCurrentManagedIP(
+ ThreadInfo &thrInfo, CONTEXT *winContext = nullptr) noexcept;
+
+ void RestoreManagedIP(
+ ThreadInfo &thrInfo, CONTEXT *winContext = nullptr) noexcept;
+
+ bool NeedSample(
+ ThreadInfo &thrInfo, SamplingSharedState &state) const noexcept;
+
+ void PrepareSample(
+ ThreadInfo &thrInfo, SamplingSharedState &state) noexcept;
+
+ void AfterSample(
+ ThreadInfo &thrInfo, SamplingSharedState &state) noexcept;
+
+private:
+ //
+ // Various useful instance methods.
+ //
+
+ void UpdateCallStackPush(const FunctionInfo &funcInfo) noexcept;
+
+ void UpdateCallStackPush(
+ const FunctionInfo &funcInfo, UINT_PTR prevIP) noexcept;
+
+ void UpdateCallStackPop() noexcept;
+
+public:
+ //
+ // Function Hooks and mapper function.
+ // Used by stub function so have to be public.
+ //
+
+ UINT_PTR FunctionIDMap(
+ FunctionID funcId,
+ BOOL *pbHookFunction) noexcept;
+
+ void Enter(const FunctionInfo &funcInfo, UINT_PTR prevIP) noexcept;
+
+ void Leave(const FunctionInfo &funcInfo) noexcept;
+
+ void Tailcall(const FunctionInfo &funcInfo) noexcept;
+
+private:
+ //
+ // Events helpers.
+ //
+
+ typedef void JITDumpFunction(const FunctionInfo &funcInfo);
+
+ HRESULT JITStarted(
+ FunctionID functionId) noexcept;
+
+ HRESULT JITFinished(
+ FunctionID functionId,
+ std::function<JITDumpFunction> dumpFunction) noexcept;
+public:
+ //
+ // Events.
+ //
+
+ HRESULT FunctionUnloadStarted(
+ FunctionID functionId) noexcept;
+
+ HRESULT JITCompilationStarted(
+ FunctionID functionId,
+ BOOL fIsSafeToBlock) noexcept;
+
+ HRESULT JITCompilationFinished(
+ FunctionID functionId,
+ HRESULT hrStatus, BOOL fIsSafeToBlock) noexcept;
+
+ HRESULT JITCachedFunctionSearchStarted(
+ FunctionID functionId,
+ BOOL *pbUseCachedFunction) noexcept;
+
+ HRESULT JITCachedFunctionSearchFinished(
+ FunctionID functionId,
+ COR_PRF_JIT_CACHE result) noexcept;
+
+ HRESULT UnmanagedToManagedTransition(
+ FunctionID functionId,
+ COR_PRF_TRANSITION_REASON reason) noexcept;
+
+ HRESULT ManagedToUnmanagedTransition(
+ FunctionID functionId,
+ COR_PRF_TRANSITION_REASON reason) noexcept;
+
+ HRESULT ExceptionUnwindFunctionLeave() noexcept;
+
+private:
+ SharedResource<FunctionStorage> m_functionStorage;
+
+ FunctionInfo *m_pUnmanagedFunctionInfo;
+ FunctionInfo *m_pJitFunctionInfo;
+};
+
+#endif // _EXECUTION_TRACE_H_
--- /dev/null
+#include "profiler.h"
+#include "memorytrace.h"
+
+MemoryTrace::MemoryTrace(Profiler &profiler)
+ : BaseTrace(profiler)
+{
+}
+
+MemoryTrace::~MemoryTrace()
+{
+ // NOTE: we are dealing with a partially destroyed m_profiler!
+ this->Shutdown();
+}
+
+void MemoryTrace::ProcessConfig(ProfilerConfig &config)
+{
+ //
+ // Check activation condition.
+ //
+
+ if (config.MemoryTraceEnabled)
+ {
+ m_disabled = false;
+ }
+ else
+ {
+ return;
+ }
+
+ //
+ // Event Mask calculation.
+ //
+
+ HRESULT hr;
+ DWORD events;
+ hr = m_info.v1()->GetEventMask(&events);
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "MemoryTrace::ProcessConfig(): GetEventMask()", hr
+ );
+ }
+
+ // This events are common for memory tracing.
+ events = events
+ | COR_PRF_ENABLE_OBJECT_ALLOCATED
+ | COR_PRF_MONITOR_OBJECT_ALLOCATED;
+
+ //
+ // Set Event Mask.
+ //
+
+ hr = m_info.v1()->SetEventMask(events);
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "MemoryTrace::ProcessConfig(): SetEventMask()", hr);
+ }
+}
+
+void MemoryTrace::Shutdown() noexcept
+{
+ m_disabled = true;
+}
+
+bool MemoryTrace::NeedSample(
+ ThreadInfo &thrInfo, SamplingSharedState &state) const noexcept
+{
+ if (m_disabled)
+ return false;
+
+ return thrInfo.eventChannel.HasAllocSample() &&
+ (
+ (thrInfo.fixTicks != state.genTicks) ||
+ (m_profiler.GetConfig().CollectionMethod ==
+ CollectionMethod::Instrumentation) ||
+ (m_profiler.GetConfig().StackTrackingEnabled &&
+ state.stackWillBeChanged)
+ );
+}
+
+HRESULT MemoryTrace::ObjectAllocated(
+ ObjectID objectId,
+ ClassID classId) noexcept
+{
+ if (m_disabled)
+ return S_OK;
+
+ if (m_profiler.GetCommonTrace().IsSamplingSuspended())
+ return S_OK;
+
+ HRESULT hr = S_OK;
+ try
+ {
+ auto storage_lock = m_profiler.GetCommonTrace().GetClassStorage();
+ ClassInfo &classInfo = storage_lock->Place(classId).first;
+ classInfo.Initialize(m_profiler, *storage_lock);
+ if (!classInfo.isNamePrinted)
+ {
+ TRACE().DumpClassName(classInfo);
+ classInfo.isNamePrinted = true;
+ }
+
+ SIZE_T objectSize = 0;
+ if (m_info.version() >= 4)
+ {
+ hr = m_info.v4()->GetObjectSize2(objectId, &objectSize);
+ }
+ else
+ {
+ ULONG size = 0;
+ hr = m_info.v1()->GetObjectSize(objectId, &size);
+ objectSize = size;
+ }
+ if (FAILED(hr))
+ {
+ throw HresultException(
+ "MemoryTrace::ObjectAllocated(): GetObjectSize()", hr);
+ }
+
+ UINT_PTR ip = 0;
+ SamplingSharedState state = {};
+ m_profiler.GetCommonTrace().InterruptSampling(
+ state,
+ [this, &classInfo, &objectSize, &ip]
+ (ThreadInfo &thrInfo, SamplingSharedState &state)
+ {
+ EventChannel &channel = thrInfo.eventChannel;
+ if (m_profiler.GetConfig().LineTraceEnabled &&
+ channel.GetStackSize() > 0)
+ {
+ if (state.isIpRestored)
+ {
+ ip = channel.GetFrameFromTop().ip;
+ }
+ else
+ {
+ ip = m_profiler.GetExecutionTrace().
+ GetCurrentManagedIP(thrInfo);
+ }
+ }
+ channel.Allocation(classInfo, objectSize, ip);
+ }
+ );
+ }
+ catch (const std::exception &e)
+ {
+ hr = m_profiler.HandleException(e);
+ }
+
+ return hr;
+}
--- /dev/null
+#ifndef _MEMORY_TRACE_H_
+#define _MEMORY_TRACE_H_
+
+#include <cor.h>
+#include <corhdr.h>
+#include <corprof.h>
+
+#include "basetrace.h"
+
+class MemoryTrace : public BaseTrace
+{
+private:
+ using SamplingSharedState = CommonTrace::SamplingSharedState;
+
+public:
+ MemoryTrace(Profiler &profiler);
+
+ ~MemoryTrace();
+
+ void ProcessConfig(ProfilerConfig &config);
+
+ void Shutdown() noexcept;
+
+ bool NeedSample(
+ ThreadInfo &thrInfo, SamplingSharedState &state) const noexcept;
+
+ // void PrepareSample(
+ // ThreadInfo &thrInfo, SamplingSharedState &state) noexcept;
+
+ // void AfterSample(
+ // ThreadInfo &thrInfo, SamplingSharedState &state) noexcept;
+
+private:
+
+public:
+ //
+ // Events.
+ //
+
+ HRESULT ObjectAllocated(
+ ObjectID objectId,
+ ClassID classId) noexcept;
+};
+
+#endif // _MEMORY_TRACE_H_
--- /dev/null
+#include <system_error>
+#include <utility>
+#include <mutex>
+
+#include <errno.h>
+
+#include <pal.h>
+
+#include "commonconfigconversions.h"
+#include "profilerconfigconversions.h"
+#include "tracelog.h"
+
+class TraceLog final : public ITraceLog
+{
+public:
+ TraceLog(StdOutStream_t)
+ : m_pStream(PAL_stdout)
+ , m_bIsOwner(false)
+ {}
+
+ TraceLog(StdErrStream_t)
+ : m_pStream(PAL_stderr)
+ , m_bIsOwner(false)
+ {}
+
+ TraceLog(FileStream_t, const std::string &filename)
+ {
+ m_pStream = PAL_fopen(filename.c_str(), "w");
+ if (m_pStream == nullptr)
+ {
+ throw std::system_error(errno, std::system_category(),
+ "can't create TraceLog object");
+ }
+ m_bIsOwner = true;
+ }
+
+ virtual ~TraceLog()
+ {
+ if (m_bIsOwner)
+ PAL_fclose(m_pStream);
+ }
+
+ virtual void DumpStartTime(
+ const SYSTEMTIME &systime) override
+ {
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(
+ m_pStream, "prf stm %04hu-%02hu-%02hu %02hu:%02hu:%02hu.%03hu\n",
+ systime.wYear, systime.wMonth, systime.wDay,
+ systime.wHour, systime.wMinute, systime.wSecond,
+ systime.wMilliseconds
+ );
+ }
+
+ virtual void DumpProfilerConfig(
+ const ProfilerConfig &config) override
+ {
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(
+ m_pStream, "prf cfg CollectionMethod %s\n",
+ convert<LPCSTR>(config.CollectionMethod)
+ );
+ PAL_fprintf(
+ m_pStream, "prf cfg SamplingTimeoutMs %lu\n",
+ config.SamplingTimeoutMs
+ );
+ PAL_fprintf(
+ m_pStream, "prf cfg HighGranularityEnabled %s\n",
+ convert<LPCSTR>(config.HighGranularityEnabled)
+ );
+ PAL_fprintf(
+ m_pStream, "prf cfg TracingSuspendedOnStart %s\n",
+ convert<LPCSTR>(config.TracingSuspendedOnStart)
+ );
+ PAL_fprintf(
+ m_pStream, "prf cfg LineTraceEnabled %s\n",
+ convert<LPCSTR>(config.LineTraceEnabled)
+ );
+ PAL_fprintf(
+ m_pStream, "prf cfg CpuTraceProcessEnabled %s\n",
+ convert<LPCSTR>(config.CpuTraceProcessEnabled)
+ );
+ PAL_fprintf(
+ m_pStream, "prf cfg CpuTraceThreadEnabled %s\n",
+ convert<LPCSTR>(config.CpuTraceThreadEnabled)
+ );
+ PAL_fprintf(
+ m_pStream, "prf cfg CpuTraceTimeoutMs %lu\n",
+ config.CpuTraceTimeoutMs
+ );
+ PAL_fprintf(
+ m_pStream, "prf cfg ExecutionTraceEnabled %s\n",
+ convert<LPCSTR>(config.ExecutionTraceEnabled)
+ );
+ PAL_fprintf(
+ m_pStream, "prf cfg MemoryTraceEnabled %s\n",
+ convert<LPCSTR>(config.MemoryTraceEnabled)
+ );
+ PAL_fprintf(
+ m_pStream, "prf cfg StackTrackingEnabled %s\n",
+ convert<LPCSTR>(config.StackTrackingEnabled)
+ );
+ }
+
+ virtual void DumpProfilerTracingPause(
+ DWORD ticks) override
+ {
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(m_pStream, "prf tps %d\n", ticks);
+ }
+
+ virtual void DumpProfilerTracingResume(
+ DWORD ticks) override
+ {
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(m_pStream, "prf trs %d\n", ticks);
+ }
+
+ virtual void DumpProcessTimes(
+ DWORD ticksFromStart,
+ DWORD64 userTime) override
+ {
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(m_pStream, "prc cpu %d %I64u\n",
+ ticksFromStart, userTime);
+ }
+
+ virtual void DumpAppDomainCreationFinished(
+ AppDomainID appDomainId,
+ LPCWCH appDomainName,
+ ProcessID processId,
+ HRESULT hrStatus) override
+ {
+ if (appDomainName == nullptr)
+ appDomainName = W("UNKNOWN");
+
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(
+ m_pStream, "apd crf 0x%p 0x%p 0x%08x \"%S\"\n",
+ appDomainId, processId, hrStatus, appDomainName
+ );
+ }
+
+ virtual void DumpAssemblyLoadFinished(
+ AssemblyID assemblyId,
+ LPCWCH assemblyName,
+ AppDomainID appDomainId,
+ ModuleID moduleId,
+ HRESULT hrStatus) override
+ {
+ if (assemblyName == nullptr)
+ assemblyName = W("UNKNOWN");
+
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(
+ m_pStream, "asm ldf 0x%p 0x%p 0x%p 0x%08x \"%S\"\n",
+ assemblyId, appDomainId, moduleId, hrStatus, assemblyName
+ );
+ }
+
+ virtual void DumpModuleLoadFinished(
+ ModuleID moduleId,
+ LPCBYTE baseLoadAddress,
+ LPCWCH moduleName,
+ AssemblyID assemblyId,
+ HRESULT hrStatus) override
+ {
+ if (moduleName == nullptr)
+ moduleName = W("UNKNOWN");
+
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(
+ m_pStream, "mod ldf 0x%p 0x%p 0x%p 0x%08x \"%S\"\n",
+ moduleId, baseLoadAddress, assemblyId, hrStatus, moduleName
+ );
+ }
+
+ virtual void DumpModuleAttachedToAssembly(
+ ModuleID moduleId,
+ AssemblyID assemblyId) override
+ {
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(
+ m_pStream, "mod ata 0x%p 0x%p\n", moduleId, assemblyId
+ );
+ }
+
+ virtual void DumpClassLoadFinished(
+ const ClassInfo &info,
+ HRESULT hrStatus) override
+ {
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(
+ m_pStream, "cls ldf 0x%p 0x%08x 0x%p 0x%08x 0x%08x\n",
+ info.id, info.internalId.id, info.moduleId, info.classToken,
+ hrStatus
+ );
+ }
+
+ virtual void DumpClassName(
+ const ClassInfo &info) override
+ {
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(
+ m_pStream, "cls nam 0x%08x \"%S\"\n",
+ info.internalId.id, info.fullName.c_str()
+ );
+ }
+
+ virtual void DumpJITCompilationFinished(
+ const FunctionInfo &info,
+ HRESULT hrStatus) override
+ {
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(
+ m_pStream, "fun cmf 0x%p 0x%08x 0x%p 0x%p 0x%08x 0x%08x",
+ info.id, info.internalId.id, info.classId, info.moduleId,
+ info.funcToken, hrStatus
+ );
+ DumpFunctionInfo(info);
+ PAL_fprintf(m_pStream, "\n");
+ }
+
+ virtual void DumpJITCachedFunctionSearchFinished(
+ const FunctionInfo &info) override
+ {
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(
+ m_pStream, "fun csf 0x%p 0x%08x 0x%p 0x%p 0x%08x",
+ info.id, info.internalId.id, info.classId, info.moduleId,
+ info.funcToken
+ );
+ DumpFunctionInfo(info);
+ PAL_fprintf(m_pStream, "\n");
+ }
+
+ virtual void DumpJITFunctionName(
+ const FunctionInfo &info) override
+ {
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(
+ m_pStream, "fun nam 0x%08x \"%S\" \"%S\" \"%S\"\n",
+ info.internalId.id, info.fullName.c_str(),
+ info.returnType.c_str(), info.signature.c_str()
+ );
+ }
+
+ virtual void DumpThreadCreated(
+ ThreadID threadId,
+ InternalID threadIid) override
+ {
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(m_pStream, "thr crt 0x%p 0x%08x\n", threadId, threadIid.id);
+ }
+
+ virtual void DumpThreadDestroyed(
+ InternalID threadIid) override
+ {
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(m_pStream, "thr dst 0x%08x\n", threadIid.id);
+ }
+
+ virtual void DumpThreadAssignedToOSThread(
+ InternalID managedThreadIid,
+ DWORD osThreadId) override
+ {
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(
+ m_pStream, "thr aos 0x%08x %d\n", managedThreadIid.id, osThreadId
+ );
+ }
+
+ virtual void DumpThreadTimes(
+ InternalID threadIid,
+ DWORD ticksFromStart,
+ DWORD64 userTime) override
+ {
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+ PAL_fprintf(m_pStream, "thr cpu 0x%08x %d %I64u\n",
+ threadIid.id, ticksFromStart, userTime);
+ }
+
+ virtual void DumpSample(
+ InternalID threadIid,
+ const EventSummary &summary) override
+ {
+ std::lock_guard<std::mutex> streamLock(m_mStream);
+
+ if (summary.HasStackSample())
+ {
+ PAL_fprintf(
+ m_pStream, "sam str 0x%08x %d %lu",
+ threadIid.id, summary.ticks, summary.count
+ );
+ PAL_fprintf(m_pStream, " %d:%d",
+ summary.matchPrefixSize, summary.stackSize);
+ if (summary.ipIsChanged)
+ {
+ PAL_fprintf(m_pStream, summary.ip != 0 ? ":%p" : ":?", summary.ip);
+ }
+ for (const auto &frame : summary.newFrames)
+ {
+ PAL_fprintf(m_pStream, frame.ip != 0 ? " 0x%x:%p" : " 0x%x",
+ frame.pFuncInfo->internalId.id, frame.ip);
+ }
+ PAL_fprintf(m_pStream, "\n");
+ }
+
+ if (summary.HasAllocSample())
+ {
+ PAL_fprintf(
+ m_pStream, "sam mem 0x%08x %d", threadIid.id, summary.ticks
+ );
+ for (const auto &classIdIpAllocInfo : summary.allocTable)
+ {
+ for (const auto &IpAllocInfo : classIdIpAllocInfo.second)
+ {
+ PAL_fprintf(
+ m_pStream,
+ IpAllocInfo.first != 0 ?
+ " 0x%x:%Iu:%Iu:%p" : " 0x%x:%Iu:%Iu",
+ classIdIpAllocInfo.first,
+ IpAllocInfo.second.allocCount,
+ IpAllocInfo.second.memSize,
+ IpAllocInfo.first
+ );
+ }
+ }
+ PAL_fprintf(m_pStream, "\n");
+ }
+ }
+
+private:
+ PAL_FILE *m_pStream;
+ std::mutex m_mStream;
+ bool m_bIsOwner;
+
+ void DumpFunctionInfo(const FunctionInfo &info)
+ {
+ for (const auto &ci : info.codeInfo)
+ {
+ PAL_fprintf(m_pStream, " 0x%p:0x%x",
+ ci.startAddress, ci.size);
+ }
+
+ for (const auto &m : info.ILToNativeMapping)
+ {
+ PAL_fprintf(m_pStream, " 0x%x:0x%x:0x%x",
+ m.ilOffset, m.nativeStartOffset, m.nativeEndOffset);
+ }
+ }
+};
+
+// static
+ITraceLog *ITraceLog::Create(StdOutStream_t StdOutStream)
+{
+ return new TraceLog(StdOutStream);
+}
+
+// static
+ITraceLog *ITraceLog::Create(StdErrStream_t StdErrStream)
+{
+ return new TraceLog(StdErrStream);
+}
+
+// static
+ITraceLog *ITraceLog::Create(
+ FileStream_t FileStream, const std::string &filename)
+{
+ return new TraceLog(FileStream, filename);
+}
--- /dev/null
+#ifndef _TRACE_LOG_H_
+#define _TRACE_LOG_H_
+
+#include <windows.h>
+
+#include <cor.h>
+#include <corhdr.h>
+#include <corprof.h>
+
+#include "profilerconfig.h"
+#include "functioninfo.h"
+#include "classinfo.h"
+#include "eventchannel.h"
+
+class ITraceLog
+{
+protected:
+ class StdOutStream_t {};
+
+ class StdErrStream_t {};
+
+ class FileStream_t {};
+
+public:
+ static StdOutStream_t StdOutStream;
+
+ static StdErrStream_t StdErrStream;
+
+ static FileStream_t FileStream;
+
+ ITraceLog() = default;
+
+ ITraceLog(const ITraceLog&) = delete;
+
+ ITraceLog &operator=(const ITraceLog&) = delete;
+
+ virtual ~ITraceLog() = default;
+
+ static ITraceLog *Create(StdOutStream_t);
+
+ static ITraceLog *Create(StdErrStream_t);
+
+ static ITraceLog *Create(FileStream_t, const std::string &filename);
+
+ // TODO: different methods to dump information.
+
+ virtual void DumpStartTime(
+ const SYSTEMTIME &systime) = 0;
+
+ virtual void DumpProfilerConfig(
+ const ProfilerConfig &config) = 0;
+
+ virtual void DumpProfilerTracingPause(
+ DWORD ticks) = 0;
+
+ virtual void DumpProfilerTracingResume(
+ DWORD ticks) = 0;
+
+ virtual void DumpProcessTimes(
+ DWORD ticksFromStart,
+ DWORD64 userTime) = 0;
+
+ virtual void DumpAppDomainCreationFinished(
+ AppDomainID appDomainId,
+ LPCWCH appDomainName,
+ ProcessID processId,
+ HRESULT hrStatus) = 0;
+
+ virtual void DumpAssemblyLoadFinished(
+ AssemblyID assemblyId,
+ LPCWCH assemblyName,
+ AppDomainID appDomainId,
+ ModuleID moduleId,
+ HRESULT hrStatus) = 0;
+
+ virtual void DumpModuleLoadFinished(
+ ModuleID moduleId,
+ LPCBYTE baseLoadAddress,
+ LPCWCH moduleName,
+ AssemblyID assemblyId,
+ HRESULT hrStatus) = 0;
+
+ virtual void DumpModuleAttachedToAssembly(
+ ModuleID moduleId,
+ AssemblyID assemblyId) = 0;
+
+ virtual void DumpClassLoadFinished(
+ const ClassInfo &info,
+ HRESULT hrStatus) = 0;
+
+ virtual void DumpClassName(
+ const ClassInfo &info) = 0;
+
+ virtual void DumpJITCompilationFinished(
+ const FunctionInfo &info,
+ HRESULT hrStatus) = 0;
+
+ virtual void DumpJITCachedFunctionSearchFinished(
+ const FunctionInfo &info) = 0;
+
+ virtual void DumpJITFunctionName(
+ const FunctionInfo &info) = 0;
+
+ virtual void DumpThreadCreated(
+ ThreadID threadId,
+ InternalID threadIid) = 0;
+
+ virtual void DumpThreadDestroyed(
+ InternalID threadIid) = 0;
+
+ virtual void DumpThreadAssignedToOSThread(
+ InternalID managedThreadIid,
+ DWORD osThreadId) = 0;
+
+ virtual void DumpThreadTimes(
+ InternalID threadIid,
+ DWORD ticksFromStart,
+ DWORD64 userTime) = 0;
+
+ virtual void DumpSample(
+ InternalID threadIid,
+ const EventSummary &summary) = 0;
+};
+
+#endif // _TRACE_LOG_H_