#develop (short for SharpDevelop) is a free IDE for .NET programming languages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

506 lines
16 KiB

// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
#include "main.h"
#include "global.h"
#include "Profiler.h"
#include "windows.h"
#include "CircularBuffer.h"
#include "allocator.h"
#include "FunctionInfo.h"
#include "LightweightList.h"
#include "Callback.h"
#include <cassert>
#include <intrin.h>
#include "ProfilerMetaData.h"
STDMETHODIMP_(ULONG) CProfiler::AddRef() {
return 1;
}
STDMETHODIMP_(ULONG) CProfiler::Release() {
return 1;
}
HRESULT CProfiler::QueryInterface(const IID &riid, LPVOID *ppv) {
*ppv = nullptr;
if(IsEqualIID(riid, IID_IUnknown) ||
IsEqualIID(riid, IID_ICorProfilerCallback) ||
IsEqualIID(riid, IID_ICorProfilerCallback2)) {
*ppv = this;
return S_OK;
}
return E_NOINTERFACE;
}
// ---- CALLBACK FUNCTIONS ------------------
__declspec(noinline) void FunctionEnterCreateNewRoot(ThreadLocalData *data, const ULONGLONG &tsc) {
DebugWriteLine(L"Creating new root");
// first function call on this thread
// create a new root node for this thread
data->stack.push(StackEntry(profiler.CreateNewRoot(), tsc));
DebugWriteLine(L"Created new root");
}
void CProfiler::EnterLock(ThreadLocalData *data) {
data->inLock = 1;
_ReadWriteBarrier(); _mm_mfence();
while (sharedMemoryHeader->ExclusiveAccess == 1)
{
data->inLock = 0;
this->accessEventHandle->Wait();
data->inLock = 1;
_ReadWriteBarrier(); _mm_mfence();
}
}
ASSEMBLER_CALLBACK FunctionEnterGlobal(int functionID) {
ThreadLocalData *data = getThreadLocalData();
if (data == nullptr) {
data = AttachToThread();
profiler.EnterLock(data);
FunctionEnterCreateNewRoot(data, __rdtsc());
} else
profiler.EnterLock(data);
if (!data->active)
goto EXIT;
// this call allows GetOrAddChild to update the value at the top of the stack
// if the FunctionInfo is resized
FunctionInfo *f = data->stack.top().function;
if ((sharedMemoryHeader->doNotProfileDotnetInternals && functionID < 0 && f->Id < 0) || (sharedMemoryHeader->combineRecursiveFunction && functionID == f->Id)) {
data->stack.top().frameCount++;
} else {
//DebugWriteLine(L"FunctionEnterGlobal %d, current stack top=%d", functionID, f->Id);
FunctionInfo *newParent = nullptr;
FunctionInfo *child = f->GetOrAddChild(functionID, newParent);
if (newParent != nullptr) {
// f was moved to newParent
// update stack:
data->stack.top().function = newParent;
// update parent of f:
if (data->stack.hasAtLeastTwoElements()) {
//DebugWriteLine("Updating parent of parent");
data->stack.belowTop().function->AddOrUpdateChild(newParent);
data->stack.belowTop().function->Check();
} else {
DebugWriteLine(L"Updating parent of parent (root)");
profiler.MovedRootChild(newParent);
}
FreeFunctionInfo(f);
}
// Set the stats for this function
child->CallCount++;
data->stack.push(StackEntry(child, __rdtsc()));
}
EXIT:
data->inLock = 0;
}
void DetachFromThread(ThreadLocalData *data) {
if (data != nullptr) {
DebugWriteLine(L"DetachFromThread %d", data->threadID);
ULONGLONG tsc = __rdtsc();
while (!data->stack.empty()) {
StackEntry &stackTop = data->stack.top();
stackTop.function->TimeSpent += (tsc - stackTop.startTime);
data->stack.pop();
}
}
}
ASSEMBLER_CALLBACK FunctionLeaveGlobal() {
ThreadLocalData *data = getThreadLocalData();
profiler.EnterLock(data);
if (!data->active)
goto EXIT;
if (data->stack.empty()) {
//DebugWriteLine(L"FunctionLeaveGlobal (but stack was empty)");
} else {
StackEntry &stackTop = data->stack.top();
//DebugWriteLine(L"FunctionLeaveGlobal %d", stackTop.function->Id);
stackTop.frameCount--;
if (stackTop.frameCount == 0) {
stackTop.function->TimeSpent += (__rdtsc() - stackTop.startTime);
data->stack.pop();
}
}
EXIT:
data->inLock = 0;
}
ASSEMBLER_CALLBACK FunctionTailcallGlobal() {
DebugWriteLine(L"FunctionTailcallGlobal");
// handle tail calls A->B as leave A, enter B, ...
FunctionLeaveGlobal();
// FunctionTailcallGlobal call will be followed by FunctionEnterGlobal for new function
}
volatile LONG nextPosFunctionID = 0;
int getNewPosFunctionID() {
const int step = 5;
// Simple continuous assignment of IDs leads to problems with the
// linear probing in FunctionInfo, so we use multiples of 'step' as IDs.
// The step should not be a multiple of 2 (otherwise parts of the hash table could not be used)!
LONG oldID;
LONG newID;
do {
oldID = nextPosFunctionID;
newID = ((oldID + step) & 0x7FFFFFFF); // x % 2^31
} while (InterlockedCompareExchange(&nextPosFunctionID, newID, oldID) != oldID);
return newID;
}
// this function is called by the CLR when a function has been mapped to an ID
UINT_PTR CProfiler::FunctionMapper(FunctionID functionID, BOOL *) {
return profiler.MapFunction(functionID, nullptr);
}
UINT_PTR CProfiler::MapFunction(FunctionID functionID, const WCHAR **sigOutput) {
mapFunctionCriticalSection.Enter();
int clientData = 0;
TFunctionIDMap::iterator it = this->functionIDMap.find(functionID);
if (it == this->functionIDMap.end()) {
DebugWriteLine(L"Creating new ID");
if (sigReader->IsNetInternal(functionID))
clientData = -getNewPosFunctionID(); // negative series
else
clientData = getNewPosFunctionID(); // positive series
this->functionIDMap.insert(TFunctionIDPair(functionID, clientData));
// send to host
std::wstring signature = sigReader->Parse(functionID);
if (sigOutput != nullptr)
*sigOutput = sigReader->fullName.c_str();
LogString(L"map %d %Id %s", clientData, functionID, signature.c_str());
} else {
DebugWriteLine(L"using old ID");
clientData = it->second;
}
mapFunctionCriticalSection.Leave();
return clientData;
}
FunctionInfo *CProfiler::CreateNewRoot() {
rootElementCriticalSection.Enter();
FunctionInfo *oldRoot = sharedMemoryHeader->RootFuncInfo;
FunctionInfo *newRoot = nullptr;
FunctionInfo *newThreadRoot = oldRoot->GetOrAddChild(getNewPosFunctionID(), newRoot);
if (newRoot != nullptr) {
sharedMemoryHeader->RootFuncInfo = newRoot;
FreeFunctionInfo(oldRoot);
}
rootElementCriticalSection.Leave();
ThreadInfo *data = this->unmanagedThreadIDMap[GetCurrentThreadId()];
data->functionInfoId = newThreadRoot->Id;
LogString(L"mapthread %d 0 \"Thread#%d\" \"%s\"", newThreadRoot->Id, GetCurrentThreadId(), data->threadName.c_str());
return newThreadRoot;
}
void CProfiler::MovedRootChild(FunctionInfo *newRootChild) {
rootElementCriticalSection.Enter();
sharedMemoryHeader->RootFuncInfo->AddOrUpdateChild(newRootChild);
rootElementCriticalSection.Leave();
}
CProfiler::CProfiler() {
this->pICorProfilerInfo = nullptr;
this->pICorProfilerInfo2 = nullptr;
this->sigReader = nullptr;
}
// ---- ICorProfilerCallback IMPLEMENTATION ------------------
// called when the profiling object is created by the CLR
STDMETHODIMP CProfiler::Initialize(IUnknown *pICorProfilerInfoUnk) {
#ifdef DEBUG
MessageBox(nullptr, TEXT("CProfiler::Initialize - Attach debugger now!"), TEXT("Attach debugger"), MB_OK);
//__debugbreak();
#endif
// Have to disable the profiler, if this process starts other .NET processes (e. g. run a project in SharpDevelop)
SetEnvironmentVariable("COR_ENABLE_PROFILING", "0");
InitializeCommunication();
// log that we are initializing
LogString(L"Initializing...");
sharedMemoryHeader->mallocator.initialize((byte *)sharedMemory->GetStartPtr() + sharedMemoryHeader->HeapOffset, (byte *)sharedMemory->GetStartPtr() + sharedMemoryHeader->HeapOffset + sharedMemoryHeader->HeapLength);
stackAllocator.initialize((byte *)sharedMemory->GetStartPtr() + sharedMemoryHeader->ThreadDataOffset, (byte *)sharedMemory->GetStartPtr() + sharedMemoryHeader->ThreadDataOffset + sharedMemoryHeader->ThreadDataLength);
sharedMemoryHeader->RootFuncInfo = CreateFunctionInfo(0, 0);
// get the ICorProfilerInfo interface
HRESULT hr = pICorProfilerInfoUnk->QueryInterface(IID_ICorProfilerInfo, (LPVOID*)&pICorProfilerInfo);
if (FAILED(hr))
return E_FAIL;
// determine if this object implements ICorProfilerInfo2
hr = pICorProfilerInfoUnk->QueryInterface(IID_ICorProfilerInfo2, (LPVOID*)&pICorProfilerInfo2);
if (FAILED(hr))
{
LogString(L"FATAL ERROR: Unsupported .NET version needs to be at least 2.0!");
this->pICorProfilerInfo2 = nullptr;
return E_FAIL;
}
// Indicate which events we're interested in.
hr = SetEventMask();
if (FAILED(hr))
LogString(L"Error setting the event mask");
// set the enter, leave and tailcall hooks
hr = pICorProfilerInfo2->SetEnterLeaveFunctionHooks2(FunctionEnterNaked, FunctionLeaveNaked, FunctionTailcallNaked);
if (SUCCEEDED(hr))
hr = pICorProfilerInfo2->SetFunctionIDMapper(FunctionMapper);
// report our success or failure to the log file
if (FAILED(hr))
LogString(L"Error setting the enter, leave and tailcall hooks");
else
LogString(L"Successfully initialized profiling" );
this->sigReader = new SignatureReader(this->pICorProfilerInfo);
return S_OK;
}
// called when the profiler is being terminated by the CLR
STDMETHODIMP CProfiler::Shutdown() {
LogString(L"Shutdown...");
return S_OK;
}
int CProfiler::InitializeCommunication() {
DebugWriteLine(L"Looking for Shared Memory...");
TCHAR sharedMemName[68];
memset(sharedMemName, 0, sizeof(sharedMemName));
if (GetEnvironmentVariable("SharedMemoryName", sharedMemName, 68) == 0) {
DebugWriteLine(L"Getting environment variable failed");
if (GetLastError() == ERROR_ENVVAR_NOT_FOUND)
MessageBox(nullptr, TEXT("Could not find environment variable 'SharedMemoryName', please restart the profiler!"), TEXT("Profiler Error"), MB_OK);
else
MessageBox(nullptr, TEXT("Unknown error!"), TEXT("Profiler Error"), MB_OK);
return 1;
}
DebugWriteLine(L"Got Shared Memory...");
this->sharedMemory = new CSharedMemory(sharedMemName);
sharedMemoryHeader = (SharedMemoryHeader*)sharedMemory->GetStartPtr();
this->nativeToManagedBuffer = new CCircularBuffer((byte *)sharedMemory->GetStartPtr() + sharedMemoryHeader->NativeToManagedBufferOffset);
sharedMemoryHeader->TargetPointer = this->sharedMemory->GetStartPtr();
TCHAR mutexName[61];
memset(mutexName, 0, sizeof(mutexName));
if (GetEnvironmentVariable("MutexName", mutexName, 61) == 0) {
DebugWriteLine(L"Getting environment variable failed");
if (GetLastError() == ERROR_ENVVAR_NOT_FOUND)
MessageBox(nullptr, TEXT("Could not find environment variable 'MutexName', please restart the profiler!"), TEXT("Profiler Error"), MB_OK);
else
MessageBox(nullptr, TEXT("Unknown error!"), TEXT("Profiler Error"), MB_OK);
return 1;
}
HANDLE mutex = OpenMutex(MUTEX_ALL_ACCESS, false, mutexName);
if (mutex == nullptr) {
DebugWriteLine(L"Failed to access mutex: %d!", GetLastError());
return 1;
}
DebugWriteLine(L"Mutex opened successfully!");
allThreadLocalDatas = new LightweightList(mutex);
TCHAR accessEventName[62];
memset(accessEventName, 0, sizeof(accessEventName));
if (GetEnvironmentVariable("AccessEventName", accessEventName, 62) == 0) {
DebugWriteLine(L"Getting environment variable failed");
if (GetLastError() == ERROR_ENVVAR_NOT_FOUND)
MessageBox(nullptr, TEXT("Could not find environment variable 'AccessEventName', please restart the profiler!"), TEXT("Profiler Error"), MB_OK);
else
MessageBox(nullptr, TEXT("Unknown error!"), TEXT("Profiler Error"), MB_OK);
return 1;
}
this->accessEventHandle = new CEventWaitHandle(accessEventName);
return 0;
}
// Writes a string to the log file. Uses the same calling convention as printf.
void CProfiler::LogString(WCHAR *pszFmtString, ...) {
WCHAR szBuffer[2 * 4096];
va_list args;
va_start( args, pszFmtString );
vswprintf_s(szBuffer, 2 * 4096, pszFmtString, args );
va_end( args );
DebugWriteLine(szBuffer);
nativeToManagedCriticalSection.Enter();
nativeToManagedBuffer->WriteString(szBuffer);
nativeToManagedCriticalSection.Leave();
}
HRESULT CProfiler::SetEventMask() {
DWORD eventMask = COR_PRF_MONITOR_ENTERLEAVE | COR_PRF_MONITOR_THREADS |
COR_PRF_MONITOR_FUNCTION_UNLOADS | COR_PRF_MONITOR_CLR_EXCEPTIONS |
COR_PRF_MONITOR_EXCEPTIONS;
if (sharedMemoryHeader->trackEvents)
eventMask = eventMask | COR_PRF_MONITOR_MODULE_LOADS/* | COR_PRF_MONITOR_JIT_COMPILATION*/;
return pICorProfilerInfo->SetEventMask(eventMask);
}
// THREAD CALLBACK FUNCTIONS
STDMETHODIMP CProfiler::ThreadAssignedToOSThread(ThreadID managedThreadID, DWORD osThreadID) {
this->threadMapCriticalSection.Enter();
DebugWriteLine(L"ThreadAssignedToOSThread %d, %d", managedThreadID, osThreadID);
TThreadIDMap::iterator it = this->threadIDMap.find(managedThreadID);
TUThreadIDMap::iterator it2 = this->unmanagedThreadIDMap.find(osThreadID);
if (it != this->threadIDMap.end() && it2 == this->unmanagedThreadIDMap.end()) {
ThreadInfo *data = it->second;
data->managedThreadId = managedThreadID;
data->unmanagedThreadId = osThreadID;
this->unmanagedThreadIDMap.insert(TUThreadIDPair(osThreadID, data));
} else {
DebugWriteLine(L"Thread %d (%d) already exists in map!", managedThreadID, osThreadID);
LogString(L"error-Thread %d (%d) already exists in map!-", managedThreadID, osThreadID);
return E_FAIL;
}
this->threadMapCriticalSection.Leave();
return S_OK;
}
STDMETHODIMP CProfiler::ThreadNameChanged(ThreadID threadID, ULONG cchName, WCHAR name[]) {
this->threadMapCriticalSection.Enter();
DebugWriteLine(L"ThreadNameChanged %d, %s", threadID, name);
TThreadIDMap::iterator it = this->threadIDMap.find(threadID);
ThreadInfo *data;
if (it != this->threadIDMap.end()) {
DebugWriteLine(L"Thread %d, %s exists", threadID, name);
data = it->second;
data->managedThreadId = threadID;
data->threadName.assign(name, cchName);
} else {
DebugWriteLine(L"Thread %d, %s does not exist", threadID, name);
data = new ThreadInfo();
data->managedThreadId = threadID;
data->threadName.assign(name, cchName);
this->threadIDMap.insert(TThreadIDPair(threadID, data));
}
this->threadMapCriticalSection.Leave();
return S_OK;
}
// UNLOAD CALLBACK FUNCTIONS
STDMETHODIMP CProfiler::FunctionUnloadStarted(FunctionID functionID) {
mapFunctionCriticalSection.Enter();
DebugWriteLine(L"FunctionUnloadStarted %d", functionID);
this->functionIDMap.erase(functionID);
mapFunctionCriticalSection.Leave();
return S_OK;
}
STDMETHODIMP CProfiler::ThreadCreated(ThreadID threadID) {
this->threadMapCriticalSection.Enter();
DebugWriteLine(L"ThreadCreated %d", threadID);
TThreadIDMap::iterator it = this->threadIDMap.find(threadID);
if (it == this->threadIDMap.end()) {
ThreadInfo *data = new ThreadInfo();
data->managedThreadId = threadID;
this->threadIDMap.insert(TThreadIDPair(threadID, data));
} else {
DebugWriteLine(L"ThreadCreated %d - did not create thread (already exists)", threadID);
}
this->threadMapCriticalSection.Leave();
return S_OK;
}
STDMETHODIMP CProfiler::ThreadDestroyed(ThreadID threadID) {
this->threadMapCriticalSection.Enter();
DebugWriteLine(L"ThreadDestroyed %d", threadID);
TThreadIDMap::iterator it = this->threadIDMap.find(threadID);
if (it != this->threadIDMap.end()) {
ThreadInfo *data = it->second;
this->unmanagedThreadIDMap.erase(data->unmanagedThreadId);
this->threadIDMap.erase(threadID);
delete data;
}
this->threadMapCriticalSection.Leave();
return S_OK;
}
STDMETHODIMP CProfiler::ExceptionThrown(ObjectID) {
DebugWriteLine(L"ExceptionThrown");
return S_OK;
}
STDMETHODIMP CProfiler::ExceptionUnwindFunctionLeave() {
DebugWriteLine(L"ExceptionUnwindFunctionLeave");
FunctionLeaveGlobal();
return S_OK;
}
void CProfiler::Activate() {
ThreadLocalData *data = getThreadLocalData();
data->active = true;
}
void CProfiler::Deactivate() {
ThreadLocalData *data = getThreadLocalData();
data->active = false;
}