From: Andrey Kvochko Date: Fri, 2 Jun 2017 12:22:34 +0000 (+0300) Subject: initial commit X-Git-Tag: submit/tizen/20180619.075036~19 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=e7f3b5ec9306d93cefd39af23641ffc3594f9938;p=sdk%2Ftools%2Fcoreprofiler.git initial commit --- e7f3b5ec9306d93cefd39af23641ffc3594f9938 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..edcff25 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,33 @@ +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) diff --git a/clrdefinitions.cmake b/clrdefinitions.cmake new file mode 100644 index 0000000..e077efc --- /dev/null +++ b/clrdefinitions.cmake @@ -0,0 +1,47 @@ +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) + diff --git a/compileoptions.cmake b/compileoptions.cmake new file mode 100644 index 0000000..d872275 --- /dev/null +++ b/compileoptions.cmake @@ -0,0 +1,57 @@ +# 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) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..5c14c76 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,103 @@ +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}) diff --git a/src/amd64/archhelpers.cpp b/src/amd64/archhelpers.cpp new file mode 100644 index 0000000..45513e0 --- /dev/null +++ b/src/amd64/archhelpers.cpp @@ -0,0 +1,41 @@ +#include + +#include +#include +#include + +HRESULT ContextToStackSnapshotContext( + const void *context, CONTEXT *winContext) noexcept +{ + _ASSERTE(context != nullptr && winContext != nullptr); + + *winContext = {CONTEXT_INTEGER}; + const mcontext_t *mc = + &(reinterpret_cast(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 diff --git a/src/amd64/asmhelpers.S b/src/amd64/asmhelpers.S new file mode 100644 index 0000000..6382cc7 --- /dev/null +++ b/src/amd64/asmhelpers.S @@ -0,0 +1,179 @@ +.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 diff --git a/src/arm/archhelpers.cpp b/src/arm/archhelpers.cpp new file mode 100644 index 0000000..fb4dcc3 --- /dev/null +++ b/src/arm/archhelpers.cpp @@ -0,0 +1,36 @@ +#include + +#include +#include +#include + +HRESULT ContextToStackSnapshotContext( + const void *context, CONTEXT *winContext) noexcept +{ + _ASSERTE(context != nullptr && winContext != nullptr); + + *winContext = {CONTEXT_INTEGER}; + const mcontext_t *mc = + &(reinterpret_cast(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; +} diff --git a/src/arm/asmhelpers.S b/src/arm/asmhelpers.S new file mode 100644 index 0000000..6dcc892 --- /dev/null +++ b/src/arm/asmhelpers.S @@ -0,0 +1,39 @@ +#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 diff --git a/src/classfactory.cpp b/src/classfactory.cpp new file mode 100644 index 0000000..27c9a5e --- /dev/null +++ b/src/classfactory.cpp @@ -0,0 +1,75 @@ +#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(this); + } + else if (riid == IID_IUnknown) + { + *ppvObject = static_cast(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; +} diff --git a/src/classfactory.h b/src/classfactory.h new file mode 100644 index 0000000..ba750e9 --- /dev/null +++ b/src/classfactory.h @@ -0,0 +1,51 @@ +#ifndef _CLASS_FACTORY_H_ +#define _CLASS_FACTORY_H_ + +#include + +// 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_ diff --git a/src/config/commonconfig.h b/src/config/commonconfig.h new file mode 100644 index 0000000..75dd8f4 --- /dev/null +++ b/src/config/commonconfig.h @@ -0,0 +1,35 @@ +#ifndef _COMMON_CONFIG_H_ +#define _COMMON_CONFIG_H_ + +#include + +// +// 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 +Target convert(Source); + +#endif // _COMMON_CONFIG_H_ diff --git a/src/config/commonconfigconversions.cpp b/src/config/commonconfigconversions.cpp new file mode 100644 index 0000000..cb13b03 --- /dev/null +++ b/src/config/commonconfigconversions.cpp @@ -0,0 +1,78 @@ +#include + +#include +#include +#include + +#include + +#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"; +} diff --git a/src/config/commonconfigconversions.h b/src/config/commonconfigconversions.h new file mode 100644 index 0000000..be8f217 --- /dev/null +++ b/src/config/commonconfigconversions.h @@ -0,0 +1,18 @@ +#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_ diff --git a/src/config/environmentconfigprovider.cpp b/src/config/environmentconfigprovider.cpp new file mode 100644 index 0000000..d32aaf7 --- /dev/null +++ b/src/config/environmentconfigprovider.cpp @@ -0,0 +1,136 @@ +#include +#include + +#include + +#include "commonconfig.h" +#include "commonconfigconversions.h" +#include "loggerconfigconversions.h" +#include "tracelogconfigconversions.h" +#include "profilerconfigconversions.h" +#include "environmentconfigprovider.h" + +template +bool EnvironmentConfigProvider::FetchValue(const char *name, T &value) const +{ + const char *env = getenv(name); + if (env) + { + try + { + value = convert(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; +} diff --git a/src/config/environmentconfigprovider.h b/src/config/environmentconfigprovider.h new file mode 100644 index 0000000..9be9162 --- /dev/null +++ b/src/config/environmentconfigprovider.h @@ -0,0 +1,36 @@ +#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 + 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_ diff --git a/src/config/loggerconfig.cpp b/src/config/loggerconfig.cpp new file mode 100644 index 0000000..8599da7 --- /dev/null +++ b/src/config/loggerconfig.cpp @@ -0,0 +1,36 @@ +#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 LoggerConfig::Verify() +{ + std::vector 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"; +} diff --git a/src/config/loggerconfig.h b/src/config/loggerconfig.h new file mode 100644 index 0000000..24b9290 --- /dev/null +++ b/src/config/loggerconfig.h @@ -0,0 +1,59 @@ +#ifndef _LOGGER_CONFIG_H_ +#define _LOGGER_CONFIG_H_ + +#include +#include + +#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 Verify(); + + const char *Name(); +}; + +#endif // _LOGGER_CONFIG_H_ diff --git a/src/config/loggerconfigconversions.cpp b/src/config/loggerconfigconversions.cpp new file mode 100644 index 0000000..c9e3429 --- /dev/null +++ b/src/config/loggerconfigconversions.cpp @@ -0,0 +1,95 @@ +#include +#include + +#include + +#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(LogLevel::None) || + value > static_cast(LogLevel::All)) + { + throw bad_conversion("is out of range"); + } + + return static_cast(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"); + } +} diff --git a/src/config/loggerconfigconversions.h b/src/config/loggerconfigconversions.h new file mode 100644 index 0000000..ae0556d --- /dev/null +++ b/src/config/loggerconfigconversions.h @@ -0,0 +1,13 @@ +#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_ diff --git a/src/config/profilerconfig.cpp b/src/config/profilerconfig.cpp new file mode 100644 index 0000000..4bd262b --- /dev/null +++ b/src/config/profilerconfig.cpp @@ -0,0 +1,131 @@ +#include + +#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 ProfilerConfig::Verify() +{ + std::vector 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"; +} diff --git a/src/config/profilerconfig.h b/src/config/profilerconfig.h new file mode 100644 index 0000000..93bb604 --- /dev/null +++ b/src/config/profilerconfig.h @@ -0,0 +1,71 @@ +#ifndef _PROFILER_CONFIG_H_ +#define _PROFILER_CONFIG_H_ + +#include +#include + +// +// 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 Verify(); + + const char *Name(); +}; + +#endif // _PROFILER_CONFIG_H_ diff --git a/src/config/profilerconfigconversions.cpp b/src/config/profilerconfigconversions.cpp new file mode 100644 index 0000000..6ed0538 --- /dev/null +++ b/src/config/profilerconfigconversions.cpp @@ -0,0 +1,46 @@ +#include +#include +#include + +#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"; + } +} diff --git a/src/config/profilerconfigconversions.h b/src/config/profilerconfigconversions.h new file mode 100644 index 0000000..dba3afb --- /dev/null +++ b/src/config/profilerconfigconversions.h @@ -0,0 +1,13 @@ +#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_ diff --git a/src/config/tracelogconfig.cpp b/src/config/tracelogconfig.cpp new file mode 100644 index 0000000..f0465c7 --- /dev/null +++ b/src/config/tracelogconfig.cpp @@ -0,0 +1,35 @@ +#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 TraceLogConfig::Verify() +{ + std::vector 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"; +} diff --git a/src/config/tracelogconfig.h b/src/config/tracelogconfig.h new file mode 100644 index 0000000..716cc70 --- /dev/null +++ b/src/config/tracelogconfig.h @@ -0,0 +1,52 @@ +#ifndef _TRACE_LOG_CONFIG_H_ +#define _TRACE_LOG_CONFIG_H_ + +#include +#include + +// +// 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 Verify(); + + const char *Name(); +}; + +#endif // _TRACE_LOG_CONFIG_H_ diff --git a/src/config/tracelogconfigconversions.cpp b/src/config/tracelogconfigconversions.cpp new file mode 100644 index 0000000..f7c13d2 --- /dev/null +++ b/src/config/tracelogconfigconversions.cpp @@ -0,0 +1,24 @@ +#include + +#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"); + } +} diff --git a/src/config/tracelogconfigconversions.h b/src/config/tracelogconfigconversions.h new file mode 100644 index 0000000..a9da078 --- /dev/null +++ b/src/config/tracelogconfigconversions.h @@ -0,0 +1,10 @@ +#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_ diff --git a/src/dllmain.cpp b/src/dllmain.cpp new file mode 100644 index 0000000..62c31dc --- /dev/null +++ b/src/dllmain.cpp @@ -0,0 +1,99 @@ +#include + +#include + +#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; +} diff --git a/src/guid.h b/src/guid.h new file mode 100644 index 0000000..ec7585a --- /dev/null +++ b/src/guid.h @@ -0,0 +1,3 @@ +#include + +DEFINE_GUID(CLSID_PROFILER, 0x101DA8FE, 0xFDCA, 0x4D0E, 0x97, 0x12, 0x76, 0x39, 0xCD, 0xE4, 0x8E, 0xBA); diff --git a/src/i386/archhelpers.cpp b/src/i386/archhelpers.cpp new file mode 100644 index 0000000..90408cb --- /dev/null +++ b/src/i386/archhelpers.cpp @@ -0,0 +1,32 @@ +#include + +#include +#include +#include + +HRESULT ContextToStackSnapshotContext( + const void *context, CONTEXT *winContext) noexcept +{ + _ASSERTE(context != nullptr && winContext != nullptr); + + *winContext = {CONTEXT_INTEGER}; + const mcontext_t *mc = + &(reinterpret_cast(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; +} diff --git a/src/i386/asmhelpers.S b/src/i386/asmhelpers.S new file mode 100644 index 0000000..f8248ed --- /dev/null +++ b/src/i386/asmhelpers.S @@ -0,0 +1,74 @@ +.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 diff --git a/src/info/baseinfo.h b/src/info/baseinfo.h new file mode 100644 index 0000000..ad415b2 --- /dev/null +++ b/src/info/baseinfo.h @@ -0,0 +1,17 @@ +#ifndef _BASE_INFO_H_ +#define _BASE_INFO_H_ + +#include + +// As struct we can use this type for overloading. +struct InternalID +{ + size_t id; +}; + +struct BaseInfo +{ + InternalID internalId; +}; + +#endif // _BASE_INFO_H_ diff --git a/src/info/classinfo.cpp b/src/info/classinfo.cpp new file mode 100644 index 0000000..8bd6dff --- /dev/null +++ b/src/info/classinfo.cpp @@ -0,0 +1,665 @@ +#include +#include +#include +#include +#include + +#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(""); + return hr; + } + + try + { + std::vector 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(""); + } + } + catch (const std::exception &e) + { + className = W(""); + 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 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 &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("*"); + + case ELEMENT_TYPE_BYREF: + return W("ref "); + + 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(""); + } +} + +__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(""); + 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 pUnknownHolder(pUnknown); + IMetaDataImport *pMDImport = dynamic_cast(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(""); + 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 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(""); + 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(""); + 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(""); + 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(""); + hr = profiler.HandleException(e); + if (FAILED(hr) && SUCCEEDED(hrReturn)) + { + hrReturn = hr; + } + } + + this->isInitialized = true; + return hrReturn; +} diff --git a/src/info/classinfo.h b/src/info/classinfo.h new file mode 100644 index 0000000..f9f7dd4 --- /dev/null +++ b/src/info/classinfo.h @@ -0,0 +1,81 @@ +#ifndef _CLASS_INFO_ +#define _CLASS_INFO_ + +#include +#include + +#include +#include +#include + +#include "mappedinfo.h" + +class Profiler; + +class ProfilerInfo; + +class ClassStorage; + +struct ClassInfo : public MappedInfo +{ + typedef std::basic_string String; + + ModuleID moduleId; + mdTypeDef classToken = mdTypeDefNil; + std::vector 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 &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_ diff --git a/src/info/functioninfo.cpp b/src/info/functioninfo.cpp new file mode 100644 index 0000000..11977c1 --- /dev/null +++ b/src/info/functioninfo.cpp @@ -0,0 +1,900 @@ +#include +#include +#include +#include + +//#include + +#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(""); + } + + try + { + if (!openBracketAppended) + { + signature.append(1, W('(')); + } + if (argCountDetermined) + { + for(; argNum < argCount; argNum++) + { + if (argNum != 0) + { + signature.append(W(", ")); + } + else + { + signature.append(W("")); + } + } + } + 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 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(""); + 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 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 pUnknownHolder(pUnknown); + IMetaDataImport *pMDImport = dynamic_cast(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(""); + this->returnType = W(""); + 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(""); + } + 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(""); + hr = profiler.HandleException(e); + if (FAILED(hr) && SUCCEEDED(hrReturn)) + { + hrReturn = hr; + } + } + + this->isInitialized = true; + return hr; +} diff --git a/src/info/functioninfo.h b/src/info/functioninfo.h new file mode 100644 index 0000000..2fb4071 --- /dev/null +++ b/src/info/functioninfo.h @@ -0,0 +1,99 @@ +#ifndef _FUNCTION_INFO_ +#define _FUNCTION_INFO_ + +#include +#include + +#include +#include +#include + +#include "classinfo.h" +#include "mappedinfo.h" + +class Profiler; + +class ProfilerInfo; + +class ClassStorage; + +class ExecutionTrace; + +struct FunctionInfo : public MappedInfo +{ + typedef std::basic_string String; + + ExecutionTrace *executionTrace; + std::vector codeInfo; + std::vector ILToNativeMapping; + ModuleID moduleId; + ModuleID classId; + mdMethodDef funcToken = mdMethodDefNil; + ClassInfo* ownerClass; + std::vector 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_ diff --git a/src/info/mappedinfo.h b/src/info/mappedinfo.h new file mode 100644 index 0000000..2e66650 --- /dev/null +++ b/src/info/mappedinfo.h @@ -0,0 +1,12 @@ +#ifndef _MAPPED_INFO_H_ +#define _MAPPED_INFO_H_ + +#include "baseinfo.h" + +template +struct MappedInfo : public BaseInfo +{ + ID id; +}; + +#endif // _MAPPED_INFO_H_ diff --git a/src/info/threadinfo.h b/src/info/threadinfo.h new file mode 100644 index 0000000..e4e00af --- /dev/null +++ b/src/info/threadinfo.h @@ -0,0 +1,41 @@ +#ifndef _THREAD_INFO_H_ +#define _THREAD_INFO_H_ + +#include + +#include +#include + +#include +#include +#include + +#include "eventchannel.h" +#include "mappedinfo.h" + +struct ThreadInfo : public MappedInfo +{ + 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_ diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..5e980af --- /dev/null +++ b/src/log.h @@ -0,0 +1,323 @@ +#ifndef _LOG_H_ +#define _LOG_H_ + +#include +#include + +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 + +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 + 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(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(); + } + + LogLine Error() + { + return DoLog(); + } + + LogLine Warn() + { + return DoLog(); + } + + LogLine Info() + { + return DoLog(); + } + + LogLine Debug() + { + return DoLog(); + } + + LogLine Trace() + { + return DoLog(); + } + +private: + LogLevel m_level; + std::ostream *m_stream; + bool m_stream_owner; + + template + 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 + 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_ diff --git a/src/misc/default_delete.h b/src/misc/default_delete.h new file mode 100644 index 0000000..e511f7d --- /dev/null +++ b/src/misc/default_delete.h @@ -0,0 +1,14 @@ +#include + +#include + +namespace std +{ + template<> + struct default_delete { + void operator()(IUnknown* pUnknown) + { + pUnknown->Release(); + } + }; +} diff --git a/src/misc/intervalsplitter.h b/src/misc/intervalsplitter.h new file mode 100644 index 0000000..4bf72b8 --- /dev/null +++ b/src/misc/intervalsplitter.h @@ -0,0 +1,56 @@ +#ifndef _INTERVAL_SPLITTER_H_ +#define _INTERVAL_SPLITTER_H_ + +#include +#include + +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_ diff --git a/src/misc/iterator_range.h b/src/misc/iterator_range.h new file mode 100644 index 0000000..1b9c0c8 --- /dev/null +++ b/src/misc/iterator_range.h @@ -0,0 +1,63 @@ +#ifndef _ITERATOR_RANGE_H_ +#define _ITERATOR_RANGE_H_ + +#include + +template +class iterator_range +{ +public: + // + // Types. + // + + typedef typename std::iterator_traits::iterator_category + iterator_category; + typedef typename std::iterator_traits::value_type + value_type; + typedef typename std::iterator_traits::difference_type + difference_type; + typedef typename std::iterator_traits::reference + reference; + typedef typename std::iterator_traits::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 +inline iterator_range +make_iterator_range(Iterator begin, Iterator end) +{ + return iterator_range(begin, end); +} + +#endif // _ITERATOR_RANGE_H_ diff --git a/src/misc/localtime.cpp b/src/misc/localtime.cpp new file mode 100644 index 0000000..d733b3c --- /dev/null +++ b/src/misc/localtime.cpp @@ -0,0 +1,63 @@ +#include + +#include +#include +#include + +#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; + } + } +} diff --git a/src/misc/localtime.h b/src/misc/localtime.h new file mode 100644 index 0000000..e316556 --- /dev/null +++ b/src/misc/localtime.h @@ -0,0 +1,6 @@ +#include + +PALIMPORT +VOID +PALAPI +GetLocalTime(OUT LPSYSTEMTIME lpLocalTime); diff --git a/src/misc/shared_iterator_range.h b/src/misc/shared_iterator_range.h new file mode 100644 index 0000000..18cfc7c --- /dev/null +++ b/src/misc/shared_iterator_range.h @@ -0,0 +1,30 @@ +#ifndef _SHARED_ITERATOR_RANGE_H_ +#define _SHARED_ITERATOR_RANGE_H_ + +#include + +#include "iterator_range.h" + +template +class shared_iterator_range : public iterator_range +{ +public: + shared_iterator_range(Iterator begin, Iterator end, Lock &&lock) + : iterator_range(begin, end) + , m_lock(std::forward(lock)) + {} + +private: + Lock m_lock; +}; + +// Deducing constructor wrappers. +template +inline shared_iterator_range +make_shared_iterator_range(Iterator begin, Iterator end, Lock &&lock) +{ + return shared_iterator_range( + begin, end, std::forward(lock)); +} + +#endif // _SHARED_ITERATOR_RANGE_H_ diff --git a/src/misc/sigaction.cpp b/src/misc/sigaction.cpp new file mode 100644 index 0000000..f89a8b1 --- /dev/null +++ b/src/misc/sigaction.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include + +#include +#include +#include + +#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); +} diff --git a/src/misc/sigaction.h b/src/misc/sigaction.h new file mode 100644 index 0000000..e7f427f --- /dev/null +++ b/src/misc/sigaction.h @@ -0,0 +1,41 @@ +#ifndef _SIG_ACTION_H_ +#define _SIG_ACTION_H_ + +#include + +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_ diff --git a/src/profiler.cpp b/src/profiler.cpp new file mode 100644 index 0000000..01f574a --- /dev/null +++ b/src/profiler.cpp @@ -0,0 +1,1122 @@ +#include +#include +#include +#include +#include + +#include +#include + +#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(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(*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(&e); + + const std::system_error* p_system_error = + dynamic_cast(&e); + + const std::ios_base::failure* p_ios_base_failure = + dynamic_cast(&e); + + const std::bad_alloc* p_bad_alloc = + dynamic_cast(&e); + + const std::logic_error* p_logic_error = + dynamic_cast(&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(this); + } + else if (riid == IID_ICorProfilerCallback2) + { + *ppvObject = static_cast(this); + } + else if (riid == IID_ICorProfilerCallback) + { + *ppvObject = static_cast(this); + } + else if (riid == IID_IUnknown) + { + *ppvObject = static_cast(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; +} diff --git a/src/profiler.h b/src/profiler.h new file mode 100644 index 0000000..c84588c --- /dev/null +++ b/src/profiler.h @@ -0,0 +1,498 @@ +#ifndef _PROFILER_H_ +#define _PROFILER_H_ + +#include +#include + +#include +#include +#include + +#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 m_traceLog; + + CommonTrace m_commonTrace; + CpuTrace m_cpuTrace; + ExecutionTrace m_executionTrace; + MemoryTrace m_memoryTrace; + + DWORD m_firstTickCount; +}; + +#endif // _PROFILER_H_ diff --git a/src/profilerinfo.cpp b/src/profilerinfo.cpp new file mode 100644 index 0000000..e0ad714 --- /dev/null +++ b/src/profilerinfo.cpp @@ -0,0 +1,226 @@ +#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( + m_pProfilerInfo7); + m_pProfilerInfo5 = static_cast( + m_pProfilerInfo7); + m_pProfilerInfo4 = static_cast( + m_pProfilerInfo7); + m_pProfilerInfo3 = static_cast( + m_pProfilerInfo7); + m_pProfilerInfo2 = static_cast( + m_pProfilerInfo7); + m_pProfilerInfo = static_cast( + 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( + m_pProfilerInfo6); + m_pProfilerInfo4 = static_cast( + m_pProfilerInfo6); + m_pProfilerInfo3 = static_cast( + m_pProfilerInfo6); + m_pProfilerInfo2 = static_cast( + m_pProfilerInfo6); + m_pProfilerInfo = static_cast( + 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( + m_pProfilerInfo5); + m_pProfilerInfo3 = static_cast( + m_pProfilerInfo5); + m_pProfilerInfo2 = static_cast( + m_pProfilerInfo5); + m_pProfilerInfo = static_cast( + 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( + m_pProfilerInfo4); + m_pProfilerInfo2 = static_cast( + m_pProfilerInfo4); + m_pProfilerInfo = static_cast( + 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( + m_pProfilerInfo3); + m_pProfilerInfo = static_cast( + 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( + 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; +} diff --git a/src/profilerinfo.h b/src/profilerinfo.h new file mode 100644 index 0000000..7008b8c --- /dev/null +++ b/src/profilerinfo.h @@ -0,0 +1,52 @@ +#ifndef _PROFILER_INFO_H_ +#define _PROFILER_INFO_H_ + +#include +#include +#include + +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_ diff --git a/src/profilermanager.cpp b/src/profilermanager.cpp new file mode 100644 index 0000000..b90a6c2 --- /dev/null +++ b/src/profilermanager.cpp @@ -0,0 +1,115 @@ +#include + +#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 +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(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(pProfiler); +} + +TraceLogConfig ProfilerManager::FetchTraceLogConfig(const Profiler *pProfiler) +{ + return FetchConfig(pProfiler); +} + +ProfilerConfig ProfilerManager::FetchProfilerConfig(const Profiler *pProfiler) +{ + return FetchConfig(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); + } +} diff --git a/src/profilermanager.h b/src/profilermanager.h new file mode 100644 index 0000000..573f798 --- /dev/null +++ b/src/profilermanager.h @@ -0,0 +1,59 @@ +#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 + 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_ diff --git a/src/storage/basestorage.h b/src/storage/basestorage.h new file mode 100644 index 0000000..a7433db --- /dev/null +++ b/src/storage/basestorage.h @@ -0,0 +1,69 @@ +#ifndef _BASE_STORAGE_H_ +#define _BASE_STORAGE_H_ + +#include +#include + +#include "baseinfo.h" + +template +class BaseStorage +{ +public: + typedef std::deque Container; + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + + static_assert(std::is_base_of::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( + const_cast&>(*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_ diff --git a/src/storage/classstorage.h b/src/storage/classstorage.h new file mode 100644 index 0000000..4b171aa --- /dev/null +++ b/src/storage/classstorage.h @@ -0,0 +1,10 @@ +#ifndef _CLASS_STORAGE_H_ +#define _CLASS_STORAGE_H_ + +#include "mappedstorage.h" +#include "classinfo.h" + +class ClassStorage : public MappedStorage +{}; + +#endif // _CLASS_STORAGE_H_ diff --git a/src/storage/functionstorage.h b/src/storage/functionstorage.h new file mode 100644 index 0000000..5b6b635 --- /dev/null +++ b/src/storage/functionstorage.h @@ -0,0 +1,39 @@ +#ifndef _FUNCTION_STORAGE_H_ +#define _FUNCTION_STORAGE_H_ + +#include "mappedstorage.h" +#include "functioninfo.h" + +class FunctionStorage : public MappedStorage +{ +private: + using Base = MappedStorage; + +public: + FunctionStorage(ExecutionTrace *pExecutionTrace) + : Base() + , m_pExecutionTrace(pExecutionTrace) + {} + + std::pair + 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_ diff --git a/src/storage/livestorage.h b/src/storage/livestorage.h new file mode 100644 index 0000000..23d76be --- /dev/null +++ b/src/storage/livestorage.h @@ -0,0 +1,68 @@ +#ifndef _LIVE_STORAGE_H_ +#define _LIVE_STORAGE_H_ + +#include + +#include "mappedstorage.h" +#include "iterator_range.h" + +template +class LiveStorage : public MappedStorage +{ +private: + using Base = MappedStorage; + + struct less_info + { + bool operator()(const INFO &lhs, const INFO &rhs) const + { + return lhs.internalId.id < rhs.internalId.id; + } + }; + +public: + typedef std::set, less_info> LiveContainer; + typedef typename LiveContainer::iterator live_iterator; + typedef typename LiveContainer::const_iterator const_live_iterator; + typedef iterator_range live_iterator_range; + typedef iterator_range const_live_iterator_range; + + std::pair + 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_ diff --git a/src/storage/mappedstorage.h b/src/storage/mappedstorage.h new file mode 100644 index 0000000..0851230 --- /dev/null +++ b/src/storage/mappedstorage.h @@ -0,0 +1,97 @@ +#ifndef _MAPPED_STORAGE_H_ +#define _MAPPED_STORAGE_H_ + +#include +#include +#include + +#include "basestorage.h" +#include "mappedinfo.h" + +template +class MappedStorage : public BaseStorage +{ +public: + static_assert(std::is_base_of, INFO>::value, + "INFO not derived from MappedInfo"); + + using BaseStorage::HasValue; + + bool HasValue(ID id) const + { + return m_toInternal.find(id) != m_toInternal.end(); + } + + using BaseStorage::Get; + + INFO &Get(ID id) + { + return const_cast( + const_cast&>(*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 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::m_storage; + std::unordered_map m_toInternal; +}; + +#endif // _MAPPED_STORAGE_H_ diff --git a/src/storage/ringbuffer.h b/src/storage/ringbuffer.h new file mode 100644 index 0000000..63ae738 --- /dev/null +++ b/src/storage/ringbuffer.h @@ -0,0 +1,345 @@ +#ifndef _RING_BUFFER_H_ +#define _RING_BUFFER_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include + +template +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(*m_begin); + } + + T &back() noexcept + { + assert(!empty()); + return const_cast(const_cast&>(*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::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 + 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(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 + void push_back_imp(ValT &&item) + { + if (full()) + throw std::out_of_range("ring_buffer capacity is exhausted"); + + new (m_end) T(std::forward(item)); + ++m_end; + if (m_end == m_buf + m_cap) + m_end = m_buf; + m_size++; + } + + template + 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(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_ */ diff --git a/src/storage/threadstorage.h b/src/storage/threadstorage.h new file mode 100644 index 0000000..9ee8643 --- /dev/null +++ b/src/storage/threadstorage.h @@ -0,0 +1,10 @@ +#ifndef _THREAD_STORAGE_H_ +#define _THREAD_STORAGE_H_ + +#include "livestorage.h" +#include "threadinfo.h" + +class ThreadStorage : public LiveStorage +{}; + +#endif // _THREAD_STORAGE_H_ diff --git a/src/sync/binarysemaphore.h b/src/sync/binarysemaphore.h new file mode 100644 index 0000000..7f5a86b --- /dev/null +++ b/src/sync/binarysemaphore.h @@ -0,0 +1,70 @@ +#ifndef _BINARY_SEMAPHORE_H_ +#define _BINARY_SEMAPHORE_H_ + +#include +#include + +template +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; + +template +basic_binary_semaphore::basic_binary_semaphore() + : m_val(false) +{} + +template +basic_binary_semaphore::basic_binary_semaphore(bool init) + : m_val(init) +{} + +template +void basic_binary_semaphore::notify() +{ + std::lock_guard lock(m_mutex); + m_val = true;; + m_cv.notify_one(); +} + +template +void basic_binary_semaphore::wait() +{ + std::unique_lock lock(m_mutex); + while (!m_val) + { + m_cv.wait(lock); + } + m_val = false; +} + +template +bool basic_binary_semaphore::try_wait() +{ + std::lock_guard lock(m_mutex); + if (m_val) + { + m_val = false; + return true; + } + return false; +} + +#endif // _BINARY_SEMAPHORE_H_ diff --git a/src/sync/shared_mutex.cpp b/src/sync/shared_mutex.cpp new file mode 100644 index 0000000..1e0847d --- /dev/null +++ b/src/sync/shared_mutex.cpp @@ -0,0 +1,88 @@ +#include + +#include +#include + +#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"); + } +} diff --git a/src/sync/shared_mutex.h b/src/sync/shared_mutex.h new file mode 100644 index 0000000..d8df9ab --- /dev/null +++ b/src/sync/shared_mutex.h @@ -0,0 +1,34 @@ +#ifndef _SHARED_MUTEX_H_ +#define _SHARED_MUTEX_H_ + +#include + +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 pimpl; +}; + +#endif // _SHARED_MUTEX_H_ diff --git a/src/sync/sharedresource.h b/src/sync/sharedresource.h new file mode 100644 index 0000000..1ab7d87 --- /dev/null +++ b/src/sync/sharedresource.h @@ -0,0 +1,183 @@ +#ifndef _SHARED_RESOURCE_H_ +#define _SHARED_RESOURCE_H_ + +#include "shared_mutex.h" + +template +class SharedResource +{ +private: + template + 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 ExclusiveAccessor : public AccessorBase + { + 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(resource) + { + this->m_shared_resource->m_mutex.lock(); + } + }; + + template + class SharedAccessor : public AccessorBase + { + 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(resource) + { + this->m_shared_resource->m_mutex.lock_shared(); + } + }; + +public: + template + class MutableAccessor : public A + { + friend class SharedResource; + + 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 ConstAccessor : public A + { + friend class SharedResource; + + 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 + SharedResource(Args&& ...args) + : m_resource(std::forward(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(&m_resource); + } + + auto lock() -> + MutableAccessor>> + { + // Implicit conversion to accessor with mutable exclusive lock. + return this; + } + + auto lock_const() const -> + ConstAccessor>> + { + // Implicit conversion to accessor with constant exclusive lock. + return this; + } + + auto lock_shared() const -> + ConstAccessor>> + { + // Implicit conversion to accessor with constant shared lock. + return this; + } + +private: + T m_resource; + mutable Mutex m_mutex; +}; + +#endif //_SHARED_RESOURCE_H_ diff --git a/src/trace/basetrace.cpp b/src/trace/basetrace.cpp new file mode 100644 index 0000000..2406a8e --- /dev/null +++ b/src/trace/basetrace.cpp @@ -0,0 +1,29 @@ +#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; +} diff --git a/src/trace/basetrace.h b/src/trace/basetrace.h new file mode 100644 index 0000000..8107977 --- /dev/null +++ b/src/trace/basetrace.h @@ -0,0 +1,33 @@ +#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_ diff --git a/src/trace/commontrace.cpp b/src/trace/commontrace.cpp new file mode 100644 index 0000000..eab3713 --- /dev/null +++ b/src/trace/commontrace.cpp @@ -0,0 +1,1189 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#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(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(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(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( + 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( + 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( + 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 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 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 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(); +} diff --git a/src/trace/commontrace.h b/src/trace/commontrace.h new file mode 100644 index 0000000..9320e58 --- /dev/null +++ b/src/trace/commontrace.h @@ -0,0 +1,154 @@ +#ifndef _COMMON_TRACE_H_ +#define _COMMON_TRACE_H_ + +#include + +#ifdef _TARGET_AMD64_ +#include +#endif // _TARGET_AMD64_ + +#include +#include +#include + +#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 + 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 m_threadStorage; + SharedResource 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_ diff --git a/src/trace/cputrace.cpp b/src/trace/cputrace.cpp new file mode 100644 index 0000000..b923961 --- /dev/null +++ b/src/trace/cputrace.cpp @@ -0,0 +1,148 @@ +#include +#include + +#include + +#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); + } +} diff --git a/src/trace/cputrace.h b/src/trace/cputrace.h new file mode 100644 index 0000000..2e28d34 --- /dev/null +++ b/src/trace/cputrace.h @@ -0,0 +1,42 @@ +#ifndef _CPU_TRACE_H_ +#define _CPU_TRACE_H_ + +#include + +#include + +#include +#include +#include + +#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_ diff --git a/src/trace/eventchannel.cpp b/src/trace/eventchannel.cpp new file mode 100644 index 0000000..b13b903 --- /dev/null +++ b/src/trace/eventchannel.cpp @@ -0,0 +1,210 @@ +#include + +#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 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(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(); +} diff --git a/src/trace/eventchannel.h b/src/trace/eventchannel.h new file mode 100644 index 0000000..12aadf4 --- /dev/null +++ b/src/trace/eventchannel.h @@ -0,0 +1,129 @@ +#ifndef _EVENT_CHANNEL_H_ +#define _EVENT_CHANNEL_H_ + +#include +#include +#include +#include +#include + +#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> AllocTable; + +struct EventSummary +{ + typedef std::vector 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 m_buffer; + std::mutex m_mutex; + bool m_bufferCapacityIncreaseIsPlanned; +}; + +#endif // _EVENT_CHANNEL_H_ diff --git a/src/trace/executiontrace.cpp b/src/trace/executiontrace.cpp new file mode 100644 index 0000000..13d7743 --- /dev/null +++ b/src/trace/executiontrace.cpp @@ -0,0 +1,656 @@ +#include +#include + +#include "profiler.h" +#include "executiontrace.h" + +EXTERN_C UINT_PTR __stdcall FunctionIDMapStub( + FunctionID funcId, + void *clientData, + BOOL *pbHookFunction) +{ + return reinterpret_cast(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( + functionIDOrClientID.clientID); + funcInfo->executionTrace->Enter(*funcInfo, ip); +} + +EXTERN_C void __stdcall LeaveStub(FunctionIDOrClientID functionIDOrClientID) +{ + FunctionInfo *funcInfo = reinterpret_cast( + functionIDOrClientID.clientID); + funcInfo->executionTrace->Leave(*funcInfo); +} + +EXTERN_C void __stdcall TailcallStub(FunctionIDOrClientID functionIDOrClientID) +{ + FunctionInfo *funcInfo = reinterpret_cast( + 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(""); + m_pUnmanagedFunctionInfo->fullName = m_pUnmanagedFunctionInfo->name; + m_pJitFunctionInfo->name = W(""); + 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(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(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(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(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(pFuncInfo); + } + catch (const std::exception &e) + { + m_profiler.HandleException(e); + *pbHookFunction = false; + return reinterpret_cast(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 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; +} diff --git a/src/trace/executiontrace.h b/src/trace/executiontrace.h new file mode 100644 index 0000000..1a69cda --- /dev/null +++ b/src/trace/executiontrace.h @@ -0,0 +1,131 @@ +#ifndef _EXECUTION_TRACE_H_ +#define _EXECUTION_TRACE_H_ + +#include + +#include +#include +#include + +#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 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 m_functionStorage; + + FunctionInfo *m_pUnmanagedFunctionInfo; + FunctionInfo *m_pJitFunctionInfo; +}; + +#endif // _EXECUTION_TRACE_H_ diff --git a/src/trace/memorytrace.cpp b/src/trace/memorytrace.cpp new file mode 100644 index 0000000..a8e7a1a --- /dev/null +++ b/src/trace/memorytrace.cpp @@ -0,0 +1,152 @@ +#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; +} diff --git a/src/trace/memorytrace.h b/src/trace/memorytrace.h new file mode 100644 index 0000000..2582a58 --- /dev/null +++ b/src/trace/memorytrace.h @@ -0,0 +1,45 @@ +#ifndef _MEMORY_TRACE_H_ +#define _MEMORY_TRACE_H_ + +#include +#include +#include + +#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_ diff --git a/src/tracelog.cpp b/src/tracelog.cpp new file mode 100644 index 0000000..1c45ec2 --- /dev/null +++ b/src/tracelog.cpp @@ -0,0 +1,371 @@ +#include +#include +#include + +#include + +#include + +#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 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 streamLock(m_mStream); + PAL_fprintf( + m_pStream, "prf cfg CollectionMethod %s\n", + convert(config.CollectionMethod) + ); + PAL_fprintf( + m_pStream, "prf cfg SamplingTimeoutMs %lu\n", + config.SamplingTimeoutMs + ); + PAL_fprintf( + m_pStream, "prf cfg HighGranularityEnabled %s\n", + convert(config.HighGranularityEnabled) + ); + PAL_fprintf( + m_pStream, "prf cfg TracingSuspendedOnStart %s\n", + convert(config.TracingSuspendedOnStart) + ); + PAL_fprintf( + m_pStream, "prf cfg LineTraceEnabled %s\n", + convert(config.LineTraceEnabled) + ); + PAL_fprintf( + m_pStream, "prf cfg CpuTraceProcessEnabled %s\n", + convert(config.CpuTraceProcessEnabled) + ); + PAL_fprintf( + m_pStream, "prf cfg CpuTraceThreadEnabled %s\n", + convert(config.CpuTraceThreadEnabled) + ); + PAL_fprintf( + m_pStream, "prf cfg CpuTraceTimeoutMs %lu\n", + config.CpuTraceTimeoutMs + ); + PAL_fprintf( + m_pStream, "prf cfg ExecutionTraceEnabled %s\n", + convert(config.ExecutionTraceEnabled) + ); + PAL_fprintf( + m_pStream, "prf cfg MemoryTraceEnabled %s\n", + convert(config.MemoryTraceEnabled) + ); + PAL_fprintf( + m_pStream, "prf cfg StackTrackingEnabled %s\n", + convert(config.StackTrackingEnabled) + ); + } + + virtual void DumpProfilerTracingPause( + DWORD ticks) override + { + std::lock_guard streamLock(m_mStream); + PAL_fprintf(m_pStream, "prf tps %d\n", ticks); + } + + virtual void DumpProfilerTracingResume( + DWORD ticks) override + { + std::lock_guard streamLock(m_mStream); + PAL_fprintf(m_pStream, "prf trs %d\n", ticks); + } + + virtual void DumpProcessTimes( + DWORD ticksFromStart, + DWORD64 userTime) override + { + std::lock_guard 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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); +} diff --git a/src/tracelog.h b/src/tracelog.h new file mode 100644 index 0000000..079882c --- /dev/null +++ b/src/tracelog.h @@ -0,0 +1,125 @@ +#ifndef _TRACE_LOG_H_ +#define _TRACE_LOG_H_ + +#include + +#include +#include +#include + +#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_