From e406060ba14e532d964dbe64be23afe1b510aa6f Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Mon, 13 Apr 2015 16:18:16 -0700 Subject: [PATCH] Adds support for resolving JIT compiled managed call frames in perf_events. --- CMakeLists.txt | 3 + clr.defines.targets | 1 + src/inc/clrconfigvalues.h | 4 ++ src/vm/CMakeLists.txt | 1 + src/vm/ceemain.cpp | 13 +++++ src/vm/jitinterface.cpp | 10 +++- src/vm/jitinterface.h | 2 +- src/vm/perfmap.cpp | 141 ++++++++++++++++++++++++++++++++++++++++++++++ src/vm/perfmap.h | 49 ++++++++++++++++ src/vm/prestub.cpp | 17 +++++- 10 files changed, 238 insertions(+), 3 deletions(-) create mode 100644 src/vm/perfmap.cpp create mode 100644 src/vm/perfmap.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b2786f..ca7bc3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -455,6 +455,9 @@ if(WIN32) add_definitions(-DFEATURE_STRONGNAME_TESTKEY_ALLOWED) endif(WIN32) add_definitions(-DFEATURE_SVR_GC) +if(CLR_CMAKE_PLATFORM_LINUX) + add_definitions(-DFEATURE_PERFMAP) +endif(CLR_CMAKE_PLATFORM_LINUX) add_definitions(-DFEATURE_SYNTHETIC_CULTURES) add_definitions(-DFEATURE_VERSIONING) if(WIN32) diff --git a/clr.defines.targets b/clr.defines.targets index 0a64d55..87ec8ac 100644 --- a/clr.defines.targets +++ b/clr.defines.targets @@ -102,6 +102,7 @@ $(CDefines);FEATURE_STRONGNAME_MIGRATION $(CDefines);FEATURE_STRONGNAME_TESTKEY_ALLOWED $(CDefines);FEATURE_SVR_GC + $(CDefines);FEATURE_PERFMAP $(CDefines);FEATURE_SYNCHRONIZATIONCONTEXT_WAIT $(CDefines);FEATURE_SYNTHETIC_CULTURES $(CDefines);FEATURE_TYPEEQUIVALENCE diff --git a/src/inc/clrconfigvalues.h b/src/inc/clrconfigvalues.h index 75273cb..75836e0 100644 --- a/src/inc/clrconfigvalues.h +++ b/src/inc/clrconfigvalues.h @@ -843,6 +843,10 @@ RETAIL_CONFIG_DWORD_INFO_EX(EXTERNAL_VistaAndAboveETWEnabled, W("ETWEnabled"), 1 RETAIL_CONFIG_STRING_INFO_EX(UNSUPPORTED_ETW_ObjectAllocationEventsPerTypePerSec, W("ETW_ObjectAllocationEventsPerTypePerSec"), "Desired number of GCSampledObjectAllocation ETW events to be logged per type per second. If 0, then the default built in to the implementation for the enabled event (e.g., High, Low), will be used.", CLRConfig::REGUTIL_default) RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_ProfAPI_ValidateNGENInstrumentation, W("ProfAPI_ValidateNGENInstrumentation"), 0, "This flag enables additional validations when using the IMetaDataEmit APIs for NGEN'ed images to ensure only supported edits are made.") +#ifdef FEATURE_PERFMAP +RETAIL_CONFIG_DWORD_INFO_EX(EXTERNAL_PerfMapEnabled, W("PerfMapEnabled"), 0, "This flag is used on Linux to enable writing /tmp/perf-$pid.map. It is disabled by default", CLRConfig::REGUTIL_default) +#endif + // // Shim // diff --git a/src/vm/CMakeLists.txt b/src/vm/CMakeLists.txt index 62ec27f..c29ae78 100644 --- a/src/vm/CMakeLists.txt +++ b/src/vm/CMakeLists.txt @@ -94,6 +94,7 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON pefile.cpp peimage.cpp peimagelayout.cpp + perfmap.cpp precode.cpp prestub.cpp rejit.cpp diff --git a/src/vm/ceemain.cpp b/src/vm/ceemain.cpp index 8721b54..55909ee 100644 --- a/src/vm/ceemain.cpp +++ b/src/vm/ceemain.cpp @@ -252,6 +252,10 @@ #include #endif // FEATURE_UEF_CHAINMANAGER +#ifdef FEATURE_PERFMAP +#include "perfmap.h" +#endif + #ifdef FEATURE_IPCMAN static HRESULT InitializeIPCManager(void); static void PublishIPCManager(void); @@ -926,6 +930,10 @@ void EEStartupHelper(COINITIEE fFlags) PerfLog::PerfLogInitialize(); #endif //ENABLE_PERF_LOG +#ifdef FEATURE_PERFMAP + PerfMap::Initialize(); +#endif + STRESS_LOG0(LF_STARTUP, LL_ALWAYS, "===================EEStartup Starting==================="); #ifndef CROSSGEN_COMPILE @@ -1907,6 +1915,11 @@ void STDMETHODCALLTYPE EEShutDownHelper(BOOL fIsDllUnloading) ETW::TypeSystemLog::FlushObjectAllocationEvents(); #endif // FEATURE_EVENT_TRACE +#ifdef FEATURE_PERFMAP + // Flush and close the perf map file. + PerfMap::Destroy(); +#endif + #ifdef FEATURE_PREJIT // If we're doing basic block profiling, we need to write the log files to disk. diff --git a/src/vm/jitinterface.cpp b/src/vm/jitinterface.cpp index a50590a..8af3fd9 100644 --- a/src/vm/jitinterface.cpp +++ b/src/vm/jitinterface.cpp @@ -12533,7 +12533,7 @@ BOOL g_fAllowRel32 = TRUE; // are OK since they discard the return value of this method. PCODE UnsafeJitFunction(MethodDesc* ftn, COR_ILMETHOD_DECODER* ILHeader, - DWORD flags, DWORD flags2) + DWORD flags, DWORD flags2, ULONG * pSizeOfCode) { STANDARD_VM_CONTRACT; @@ -12785,6 +12785,14 @@ PCODE UnsafeJitFunction(MethodDesc* ftn, COR_ILMETHOD_DECODER* ILHeader, (MethodDesc*)ftn); LOG((LF_CORDB, LL_EVERYTHING, "Got through CallCompile MethodWithSEHWrapper\n")); +#if FEATURE_PERFMAP + // Save the code size so that it can be reported to the perfmap. + if (pSizeOfCode != NULL) + { + *pSizeOfCode = sizeOfCode; + } +#endif + #if defined(ENABLE_PERF_COUNTERS) LARGE_INTEGER CycleStop; QueryPerformanceCounter(&CycleStop); diff --git a/src/vm/jitinterface.h b/src/vm/jitinterface.h index 8143d48..5977b17 100644 --- a/src/vm/jitinterface.h +++ b/src/vm/jitinterface.h @@ -46,7 +46,7 @@ void InitJITHelpers1(); void InitJITHelpers2(); PCODE UnsafeJitFunction(MethodDesc* ftn, COR_ILMETHOD_DECODER* header, - DWORD flags, DWORD flags2); + DWORD flags, DWORD flags2, ULONG* sizeOfCode = NULL); void getMethodInfoHelper(MethodDesc * ftn, CORINFO_METHOD_HANDLE ftnHnd, diff --git a/src/vm/perfmap.cpp b/src/vm/perfmap.cpp new file mode 100644 index 0000000..ccb511f --- /dev/null +++ b/src/vm/perfmap.cpp @@ -0,0 +1,141 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// =========================================================================== +// File: perfmap.cpp +// + +#include "common.h" + +#ifdef FEATURE_PERFMAP +#include "perfmap.h" +#include "pal.h" + +PerfMap * PerfMap::s_Current = NULL; + +// Initialize the map for the process - called from EEStartupHelper. +void PerfMap::Initialize() +{ + LIMITED_METHOD_CONTRACT; + + // Only enable the map if requested. + if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_PerfMapEnabled)) + { + // Get the current process id. + int currentPid = GetCurrentProcessId(); + + // Create the map. + s_Current = new PerfMap(currentPid); + } +} + +// Log a method to the map. +void PerfMap::LogMethod(MethodDesc * pMethod, PCODE pCode, size_t codeSize) +{ + LIMITED_METHOD_CONTRACT; + + if (s_Current != NULL) + { + s_Current->Log(pMethod, pCode, codeSize); + } +} + +// Destroy the map for the process - called from EEShutdownHelper. +void PerfMap::Destroy() +{ + if (s_Current != NULL) + { + delete s_Current; + s_Current = NULL; + } +} + +// Construct a new map for the process. +PerfMap::PerfMap(int pid) +{ + LIMITED_METHOD_CONTRACT; + + // Initialize with no failures. + m_ErrorEncountered = false; + + // Build the path to the map file on disk. + WCHAR tempPath[MAX_PATH+1]; + if(!GetTempPathW(MAX_PATH, tempPath)) + { + return; + } + + SString path; + path.Printf("%Sperf-%d.map", &tempPath, pid); + + // Open the file stream. + m_FileStream = new (nothrow) CFileStream(); + if(m_FileStream != NULL) + { + HRESULT hr = m_FileStream->OpenForWrite(path.GetUnicode()); + if(FAILED(hr)) + { + delete m_FileStream; + m_FileStream = NULL; + } + } +} + +// Clean-up resources. +PerfMap::~PerfMap() +{ + LIMITED_METHOD_CONTRACT; + + delete m_FileStream; + m_FileStream = NULL; +} + +// Log a method to the map. +void PerfMap::Log(MethodDesc * pMethod, PCODE pCode, size_t codeSize) +{ + CONTRACTL{ + THROWS; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + PRECONDITION(pMethod != NULL); + PRECONDITION(pCode != NULL); + PRECONDITION(codeSize > 0); + } CONTRACTL_END; + + if (m_FileStream == NULL || m_ErrorEncountered) + { + // A failure occurred, do not log. + return; + } + + // Logging failures should not cause any exceptions to flow upstream. + EX_TRY + { + // Get the full method signature. + SString fullMethodSignature; + pMethod->GetFullMethodInfo(fullMethodSignature); + + // Build the map file line. + StackScratchBuffer scratch; + SString line; + line.Printf("%p %x %s\n", pCode, codeSize, fullMethodSignature.GetANSI(scratch)); + + // Write the line. + // The PAL already takes a lock when writing, so we don't need to do so here. + const char * strLine = line.GetANSI(scratch); + ULONG inCount = line.GetCount(); + ULONG outCount; + m_FileStream->Write(strLine, inCount, &outCount); + + if (inCount != outCount) + { + // This will cause us to stop writing to the file. + // The file will still remain open until shutdown so that we don't have to take a lock at this levelwhen we touch the file stream. + m_ErrorEncountered = true; + } + } + EX_CATCH{} EX_END_CATCH(SwallowAllExceptions); +} + +#endif // FEATURE_PERFMAP diff --git a/src/vm/perfmap.h b/src/vm/perfmap.h new file mode 100644 index 0000000..e053915 --- /dev/null +++ b/src/vm/perfmap.h @@ -0,0 +1,49 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// =========================================================================== +// File: perfmap.h +// +#ifndef PERFPID_H +#define PERFPID_H + +#include "sstring.h" +#include "fstream.h" + +class PerfMap +{ +private: + // The one and only PerfMap for the process. + static PerfMap * s_Current; + + // The file stream to write the map to. + CFileStream * m_FileStream; + + // Set to true if an error is encountered when writing to the file. + bool m_ErrorEncountered; + + // Construct a new map. + PerfMap(int pid); + + // Clean-up resources. + ~PerfMap(); + + // Does the actual work to log to the map. + void Log(MethodDesc * pMethod, PCODE pCode, size_t codeSize); + + // Does the actual work to close and flush the map. + void Close(); + +public: + // Initialize the map for the current process. + static void Initialize(); + + // Log a method to the map. + static void LogMethod(MethodDesc * pMethod, PCODE pCode, size_t codeSize); + + // Close the map and flush any remaining data. + static void Destroy(); +}; + +#endif // PERFPID_H diff --git a/src/vm/prestub.cpp b/src/vm/prestub.cpp index 79c34d2..e740c9a 100644 --- a/src/vm/prestub.cpp +++ b/src/vm/prestub.cpp @@ -49,6 +49,10 @@ #include "stacksampler.h" #endif +#ifdef FEATURE_PERFMAP +#include "perfmap.h" +#endif + #ifndef DACCESS_COMPILE EXTERN_C void STDCALL ThePreStub(); @@ -262,6 +266,7 @@ PCODE MethodDesc::MakeJitWorker(COR_ILMETHOD_DECODER* ILHeader, DWORD flags, DWO m_pszDebugMethodName)); PCODE pCode = NULL; + ULONG sizeOfCode = 0; #ifdef FEATURE_INTERPRETER PCODE pPreviousInterpStub = NULL; BOOL fInterpreted = FALSE; @@ -454,7 +459,7 @@ PCODE MethodDesc::MakeJitWorker(COR_ILMETHOD_DECODER* ILHeader, DWORD flags, DWO EX_TRY { - pCode = UnsafeJitFunction(this, ILHeader, flags, flags2); + pCode = UnsafeJitFunction(this, ILHeader, flags, flags2, &sizeOfCode); } EX_CATCH { @@ -514,6 +519,11 @@ PCODE MethodDesc::MakeJitWorker(COR_ILMETHOD_DECODER* ILHeader, DWORD flags, DWO { // Fire an ETW event to mark the end of JIT'ing ETW::MethodLog::MethodJitted(this, &namespaceOrClassName, &methodName, &methodSignature, pCode, 0 /* ReJITID */); + +#ifdef FEATURE_PERFMAP + // Save the JIT'd method information so that perf can resolve JIT'd call frames. + PerfMap::LogMethod(this, pCode, sizeOfCode); +#endif mcJitManager.GetMulticoreJitCodeStorage().StoreMethodCode(this, pCode); @@ -593,6 +603,11 @@ GotNewCode: { // Fire an ETW event to mark the end of JIT'ing ETW::MethodLog::MethodJitted(this, &namespaceOrClassName, &methodName, &methodSignature, pCode, 0 /* ReJITID */); + +#ifdef FEATURE_PERFMAP + // Save the JIT'd method information so that perf can resolve JIT'd call frames. + PerfMap::LogMethod(this, pCode, sizeOfCode); +#endif } -- 2.7.4