diff options
author | Brian Robbins <brianrob@microsoft.com> | 2015-11-09 17:50:56 -0800 |
---|---|---|
committer | Brian Robbins <brianrob@microsoft.com> | 2015-11-12 13:04:47 -0800 |
commit | 3801b5493e9d4b8837ceec03f49707656877fff1 (patch) | |
tree | 23828a8ede5c24422479fc062aa2800b2ffa4418 /src | |
parent | 128546aed70f8f24efebd50b0f6c358f41ff9756 (diff) | |
download | coreclr-3801b5493e9d4b8837ceec03f49707656877fff1.tar.gz coreclr-3801b5493e9d4b8837ceec03f49707656877fff1.tar.bz2 coreclr-3801b5493e9d4b8837ceec03f49707656877fff1.zip |
Enable offline generation of text-based symbol tables for native images. This is needed in order to profile applications on Linux using perf_event.
This change also modifies the runtime to emit a perfmap entry when a native image is loaded so that offline trace processing tools can identify when a perfmap file for a native image is needed and generate it.
Diffstat (limited to 'src')
-rw-r--r-- | src/tools/crossgen/crossgen.cpp | 60 | ||||
-rw-r--r-- | src/vm/compile.cpp | 52 | ||||
-rw-r--r-- | src/vm/crossgen/CMakeLists.txt | 6 | ||||
-rw-r--r-- | src/vm/domainfile.cpp | 9 | ||||
-rw-r--r-- | src/vm/perfmap.cpp | 252 | ||||
-rw-r--r-- | src/vm/perfmap.h | 49 | ||||
-rw-r--r-- | src/vm/prestub.cpp | 4 |
7 files changed, 374 insertions, 58 deletions
diff --git a/src/tools/crossgen/crossgen.cpp b/src/tools/crossgen/crossgen.cpp index 9bdfdd7294..f09f3d066d 100644 --- a/src/tools/crossgen/crossgen.cpp +++ b/src/tools/crossgen/crossgen.cpp @@ -194,7 +194,12 @@ void PrintUsageHelper() W(" /CreatePDB <Dir to store PDB> [/lines [<search path for managed PDB>] ]\n") W(" When specifying /CreatePDB, the native image should be created\n") W(" first, and <assembly name> should be the path to the NI.") -#endif // NO_NGENPDB +#elif defined(FEATURE_PERFMAP) + W(" Debugging Parameters\n") + W(" /CreatePerfMap <Dir to store perf map>\n") + W(" When specifying /CreatePerfMap, the native image should be created\n") + W(" first, and <assembly name> should be the path to the NI.\n") +#endif ); } @@ -741,7 +746,58 @@ int _cdecl wmain(int argc, __in_ecount(argc) WCHAR **argv) argv--; argc++; } -#endif // !NO_NGENPDB +#endif // NO_NGENPDB +#ifdef FEATURE_PERFMAP + else if (MatchParameter(*argv, W("CreatePerfMap")) && (argc > 1)) + { + // syntax: /CreatePerfMap <directory to store perfmap> + + // Parse: /CreatePerfMap + // NOTE: We use the same underlying PDB logic. + fCreatePDB = true; + argv++; + argc--; + + // Clear the /fulltrust flag - /CreatePDB does not work with any other flags. + dwFlags = dwFlags & ~NGENWORKER_FLAGS_FULLTRUSTDOMAIN; + + // Parse: <directory to store PDB> + if (wcscpy_s( + wzDirectoryToStorePDB, + _countof(wzDirectoryToStorePDB), + argv[0]) != 0) + { + Output(W("Unable to parse output directory to store perfmap")); + exit(FAILURE_RESULT); + } + argv++; + argc--; + + // Ensure output dir ends in a backslash + if (wzDirectoryToStorePDB[wcslen(wzDirectoryToStorePDB)-1] != DIRECTORY_SEPARATOR_CHAR_W) + { + if (wcscat_s( + wzDirectoryToStorePDB, + _countof(wzDirectoryToStorePDB), + DIRECTORY_SEPARATOR_STR_W) != 0) + { + Output(W("Unable to parse output directory to store perfmap")); + exit(FAILURE_RESULT); + } + } + + if (argc == 0) + { + Output(W("The /CreatePerfMap switch requires <directory to store perfmap> and <assembly name>.\n")); + exit(FAILURE_RESULT); + } + + // Undo last arg iteration, since we do it for all cases at the bottom of + // the loop + argv--; + argc++; + } +#endif // FEATURE_PERFMAP else { if (argc == 1) diff --git a/src/vm/compile.cpp b/src/vm/compile.cpp index 846aa283ba..f9f7e5dd98 100644 --- a/src/vm/compile.cpp +++ b/src/vm/compile.cpp @@ -71,6 +71,10 @@ #include <cvinfo.h> #endif +#ifdef FEATURE_PERFMAP +#include "perfmap.h" +#endif + #ifdef MDIL #include <mdil.h> #endif @@ -2676,7 +2680,7 @@ BOOL CEECompileInfo::AreAllClassesFullyLoaded(CORINFO_MODULE_HANDLE moduleHandle // public\devdiv\inc\corsym.h and debugger\sh\symwrtr\ngenpdbwriter.h,cpp // ---------------------------------------------------------------------------- -#ifdef NO_NGENPDB +#if defined(NO_NGENPDB) && !defined(FEATURE_PERFMAP) BOOL CEECompileInfo::GetIsGeneratingNgenPDB() { return FALSE; @@ -2690,13 +2694,7 @@ BOOL IsNgenPDBCompilationProcess() { return FALSE; } - -HRESULT __stdcall CreatePdb(CORINFO_ASSEMBLY_HANDLE hAssembly, BSTR pNativeImagePath, BSTR pPdbPath, BOOL pdbLines, BSTR pManagedPdbSearchPath) -{ - return E_NOTIMPL; -} -#else // NO_NGENPDB - +#else BOOL CEECompileInfo::GetIsGeneratingNgenPDB() { LIMITED_METHOD_DAC_CONTRACT; @@ -2715,6 +2713,9 @@ BOOL IsNgenPDBCompilationProcess() return IsCompilationProcess() && g_pCEECompileInfo->GetIsGeneratingNgenPDB(); } +#endif // NO_NGENPDB && !FEATURE_PERFMAP + +#ifndef NO_NGENPDB // This is the prototype of "CreateNGenPdbWriter" exported by diasymreader.dll typedef HRESULT (__stdcall *CreateNGenPdbWriter_t)(const WCHAR *pwszNGenImagePath, const WCHAR *pwszPdbPath, void **ppvObj); @@ -4888,23 +4889,27 @@ BOOL NGenMethodLinesPdbWriter::FinalizeLinesFileBlock( return TRUE; } - - +#endif // NO_NGENPDB +#if defined(FEATURE_PERFMAP) || !defined(NO_NGENPDB) HRESULT __stdcall CreatePdb(CORINFO_ASSEMBLY_HANDLE hAssembly, BSTR pNativeImagePath, BSTR pPdbPath, BOOL pdbLines, BSTR pManagedPdbSearchPath) { STANDARD_VM_CONTRACT; + Assembly *pAssembly = reinterpret_cast<Assembly *>(hAssembly); + _ASSERTE(pAssembly); + _ASSERTE(pNativeImagePath); + _ASSERTE(pPdbPath); + +#if !defined(NO_NGENPDB) NGenPdbWriter pdbWriter( pNativeImagePath, pPdbPath, pdbLines ? kPDBLines : 0, pManagedPdbSearchPath); IfFailThrow(pdbWriter.Load()); - - Assembly *pAssembly = reinterpret_cast<Assembly *>(hAssembly); - _ASSERTE(pAssembly); - _ASSERTE(pNativeImagePath); - _ASSERTE(pPdbPath); +#elif defined(FEATURE_PERFMAP) + NativeImagePerfMap perfMap(pAssembly, pPdbPath); +#endif ModuleIterator moduleIterator = pAssembly->IterateModules(); Module *pModule = NULL; @@ -4916,7 +4921,11 @@ HRESULT __stdcall CreatePdb(CORINFO_ASSEMBLY_HANDLE hAssembly, BSTR pNativeImage if (pModule->HasNativeImage() || pModule->IsReadyToRun()) { +#if !defined(NO_NGENPDB) IfFailThrow(pdbWriter.WritePDBDataForModule(pModule)); +#elif defined(FEATURE_PERFMAP) + perfMap.LogDataForModule(pModule); +#endif fAtLeastOneNativeModuleFound = TRUE; } } @@ -4929,12 +4938,21 @@ HRESULT __stdcall CreatePdb(CORINFO_ASSEMBLY_HANDLE hAssembly, BSTR pNativeImage } GetSvcLogger()->Printf( +#if !defined(NO_NGENPDB) W("Successfully generated PDB for native assembly '%s'.\n"), +#elif defined(FEATURE_PERFMAP) + W("Successfully generated perfmap for native assembly '%s'.\n"), +#endif pNativeImagePath); + return S_OK; } - -#endif // NO_NGENPDB +#else +HRESULT __stdcall CreatePdb(CORINFO_ASSEMBLY_HANDLE hAssembly, BSTR pNativeImagePath, BSTR pPdbPath, BOOL pdbLines, BSTR pManagedPdbSearchPath) +{ + return E_NOTIMPL; +} +#endif // defined(FEATURE_PERFMAP) || !defined(NO_NGENPDB) // End of PDB writing code // ---------------------------------------------------------------------------- diff --git a/src/vm/crossgen/CMakeLists.txt b/src/vm/crossgen/CMakeLists.txt index e1fb8c7ef6..f5d2399a08 100644 --- a/src/vm/crossgen/CMakeLists.txt +++ b/src/vm/crossgen/CMakeLists.txt @@ -147,4 +147,10 @@ if (WIN32) set_source_files_properties(../mscorlib.cpp PROPERTIES COMPILE_FLAGS "/Y-") endif (WIN32) +if (CLR_CMAKE_PLATFORM_LINUX) + list(APPEND VM_CROSSGEN_SOURCES + ../perfmap.cpp + ) +endif (CLR_CMAKE_PLATFORM_LINUX) + add_library(cee_crossgen ${VM_CROSSGEN_SOURCES}) diff --git a/src/vm/domainfile.cpp b/src/vm/domainfile.cpp index c0619f49e0..ce3dff38e0 100644 --- a/src/vm/domainfile.cpp +++ b/src/vm/domainfile.cpp @@ -39,6 +39,10 @@ #endif #include "winrthelpers.h" +#ifdef FEATURE_PERFMAP +#include "perfmap.h" +#endif // FEATURE_PERFMAP + BOOL DomainAssembly::IsUnloading() { WRAPPER_NO_CONTRACT; @@ -1298,6 +1302,11 @@ void DomainFile::FinishLoad() // Inform metadata that it has been loaded from a native image // (and so there was an opportunity to check for or fix inconsistencies in the original IL metadata) m_pFile->GetMDImport()->SetVerifiedByTrustedSource(TRUE); + +#ifdef FEATURE_PERFMAP + // Notify the perfmap of the native image load. + PerfMap::LogNativeImageLoad(m_pFile); +#endif } // Are we absolutely required to use a native image? diff --git a/src/vm/perfmap.cpp b/src/vm/perfmap.cpp index f868bcf817..4b9646b70d 100644 --- a/src/vm/perfmap.cpp +++ b/src/vm/perfmap.cpp @@ -8,7 +8,7 @@ #include "common.h" -#ifdef FEATURE_PERFMAP +#if defined(FEATURE_PERFMAP) && !defined(DACCESS_COMPILE) #include "perfmap.h" #include "pal.h" @@ -30,20 +30,11 @@ void PerfMap::Initialize() } } -// 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() { + LIMITED_METHOD_CONTRACT; + if (s_Current != NULL) { delete s_Current; @@ -69,6 +60,31 @@ PerfMap::PerfMap(int pid) SString path; path.Printf("%Sperf-%d.map", &tempPath, pid); + // Open the map file for writing. + OpenFile(path); +} + +// Construct a new map without a specified file name. +// Used for offline creation of NGEN map files. +PerfMap::PerfMap() +{ + LIMITED_METHOD_CONTRACT; +} + +// Clean-up resources. +PerfMap::~PerfMap() +{ + LIMITED_METHOD_CONTRACT; + + delete m_FileStream; + m_FileStream = NULL; +} + +// Open the specified destination map file. +void PerfMap::OpenFile(SString& path) +{ + STANDARD_VM_CONTRACT; + // Open the file stream. m_FileStream = new (nothrow) CFileStream(); if(m_FileStream != NULL) @@ -82,17 +98,34 @@ PerfMap::PerfMap(int pid) } } -// Clean-up resources. -PerfMap::~PerfMap() +// Write a line to the map file. +void PerfMap::WriteLine(SString& line) { - LIMITED_METHOD_CONTRACT; + STANDARD_VM_CONTRACT; - delete m_FileStream; - m_FileStream = NULL; + EX_TRY + { + // Write the line. + // The PAL already takes a lock when writing, so we don't need to do so here. + StackScratchBuffer scratch; + 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 level when we touch the file stream. + m_ErrorEncountered = true; + } + + } + EX_CATCH{} EX_END_CATCH(SwallowAllExceptions); } // Log a method to the map. -void PerfMap::Log(MethodDesc * pMethod, PCODE pCode, size_t codeSize) +void PerfMap::LogMethod(MethodDesc * pMethod, PCODE pCode, size_t codeSize) { CONTRACTL{ THROWS; @@ -122,20 +155,181 @@ void PerfMap::Log(MethodDesc * pMethod, PCODE pCode, size_t codeSize) 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); + WriteLine(line); + } + EX_CATCH{} EX_END_CATCH(SwallowAllExceptions); +} - if (inCount != outCount) +// Log a native image to the map. +void PerfMap::LogNativeImage(PEFile * pFile) +{ + CONTRACTL{ + THROWS; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + PRECONDITION(pFile != NULL); + } 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 native image name. + LPCUTF8 lpcSimpleName = pFile->GetSimpleName(); + + // Get the native image signature. + WCHAR wszSignature[39]; + GetNativeImageSignature(pFile, wszSignature, lengthof(wszSignature)); + + SString strNativeImageSymbol; + strNativeImageSymbol.Printf("%s.ni.%S", lpcSimpleName, wszSignature); + + // Get the base addess of the native image. + SIZE_T baseAddress = (SIZE_T)pFile->GetLoaded()->GetBase(); + + // Get the image size + COUNT_T imageSize = pFile->GetLoaded()->GetVirtualSize(); + + // Log baseAddress imageSize strNativeImageSymbol + StackScratchBuffer scratch; + SString line; + line.Printf("%p %x %s\n", baseAddress, imageSize, strNativeImageSymbol.GetANSI(scratch)); + + // Write the line. + WriteLine(line); + } + EX_CATCH{} EX_END_CATCH(SwallowAllExceptions); +} + +// Log a native image load to the map. +void PerfMap::LogNativeImageLoad(PEFile * pFile) +{ + STANDARD_VM_CONTRACT; + + if (s_Current != NULL) + { + s_Current->LogNativeImage(pFile); + } +} + +// Log a method to the map. +void PerfMap::LogJITCompiledMethod(MethodDesc * pMethod, PCODE pCode, size_t codeSize) +{ + LIMITED_METHOD_CONTRACT; + + if (s_Current != NULL) + { + s_Current->LogMethod(pMethod, pCode, codeSize); + } +} + +void PerfMap::GetNativeImageSignature(PEFile * pFile, WCHAR * pwszSig, unsigned int nSigSize) +{ + CONTRACTL{ + PRECONDITION(pFile != NULL); + PRECONDITION(pFile->HasNativeImage()); + PRECONDITION(pwszSig != NULL); + PRECONDITION(nSigSize >= 39); + } CONTRACTL_END; + + PEImageHolder pNativeImage(pFile->GetNativeImageWithRef()); + CORCOMPILE_VERSION_INFO * pVersionInfo = pNativeImage->GetLoadedLayout()->GetNativeVersionInfo(); + _ASSERTE(pVersionInfo); + CORCOMPILE_NGEN_SIGNATURE * pSignature = &pVersionInfo->signature; + if(!StringFromGUID2(*pSignature, pwszSig, nSigSize)) + { + pwszSig[0] = '\0'; + } +} + +// Create a new native image perf map. +NativeImagePerfMap::NativeImagePerfMap(Assembly * pAssembly, BSTR pDestPath) + : PerfMap() +{ + STANDARD_VM_CONTRACT; + + // Generate perfmap path. + + // Get the assembly simple name. + LPCUTF8 lpcSimpleName = pAssembly->GetSimpleName(); + + // Get the native image signature (GUID). + // Used to ensure that we match symbols to the correct NGEN image. + WCHAR wszSignature[39]; + GetNativeImageSignature(pAssembly->GetManifestFile(), wszSignature, lengthof(wszSignature)); + + // Build the path to the perfmap file, which consists of <inputpath><imagesimplename>.ni.<signature>.map. + // Example: /tmp/mscorlib.ni.{GUID}.map + SString sDestPerfMapPath; + sDestPerfMapPath.Printf("%S%s.ni.%S.map", pDestPath, lpcSimpleName, wszSignature); + + // Open the perf map file. + OpenFile(sDestPerfMapPath); +} + +// Log data to the perfmap for the specified module. +void NativeImagePerfMap::LogDataForModule(Module * pModule) +{ + STANDARD_VM_CONTRACT; + + PEImageLayout * pLoadedLayout = pModule->GetFile()->GetLoaded(); + _ASSERTE(pLoadedLayout != NULL); + + SIZE_T baseAddr = (SIZE_T)pLoadedLayout->GetBase(); + +#ifdef FEATURE_READYTORUN_COMPILER + if (pLoadedLayout->HasReadyToRunHeader()) + { + ReadyToRunInfo::MethodIterator mi(pModule->GetReadyToRunInfo()); + while (mi.Next()) { - // 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; + MethodDesc *hotDesc = mi.GetMethodDesc(); + + LogPreCompiledMethod(hotDesc, mi.GetMethodStartAddress(), baseAddr); } } - EX_CATCH{} EX_END_CATCH(SwallowAllExceptions); + else +#endif // FEATURE_READYTORUN_COMPILER + { + MethodIterator mi((PTR_Module)pModule); + while (mi.Next()) + { + MethodDesc *hotDesc = mi.GetMethodDesc(); + hotDesc->CheckRestore(); + + LogPreCompiledMethod(hotDesc, mi.GetMethodStartAddress(), baseAddr); + } + } +} + +// Log a pre-compiled method to the perfmap. +void NativeImagePerfMap::LogPreCompiledMethod(MethodDesc * pMethod, PCODE pCode, SIZE_T baseAddr) +{ + STANDARD_VM_CONTRACT; + + // Get information about the NGEN'd method code. + EECodeInfo codeInfo(pCode); + _ASSERTE(codeInfo.IsValid()); + + IJitManager::MethodRegionInfo methodRegionInfo; + codeInfo.GetMethodRegionInfo(&methodRegionInfo); + + // NGEN can split code between hot and cold sections which are separate in memory. + // Emit an entry for each section if it is used. + if (methodRegionInfo.hotSize > 0) + { + LogMethod(pMethod, (PCODE)methodRegionInfo.hotStartAddress - baseAddr, methodRegionInfo.hotSize); + } + + if (methodRegionInfo.coldSize > 0) + { + LogMethod(pMethod, (PCODE)methodRegionInfo.coldStartAddress - baseAddr, methodRegionInfo.coldSize); + } } -#endif // FEATURE_PERFMAP +#endif // FEATURE_PERFMAP && !DACCESS_COMPILE diff --git a/src/vm/perfmap.h b/src/vm/perfmap.h index e0539150fc..f35084d4c4 100644 --- a/src/vm/perfmap.h +++ b/src/vm/perfmap.h @@ -11,6 +11,7 @@ #include "sstring.h" #include "fstream.h" +// Generates a perfmap file. class PerfMap { private: @@ -23,27 +24,59 @@ private: // Set to true if an error is encountered when writing to the file. bool m_ErrorEncountered; - // Construct a new map. + // Construct a new map for the specified pid. PerfMap(int pid); + // Write a line to the map file. + void WriteLine(SString & line); + +protected: + // Construct a new map without a specified file name. + // Used for offline creation of NGEN map files. + PerfMap(); + // Clean-up resources. ~PerfMap(); - // Does the actual work to log to the map. - void Log(MethodDesc * pMethod, PCODE pCode, size_t codeSize); + // Open the perf map file for write. + void OpenFile(SString& path); + + // Does the actual work to log a method to the map. + void LogMethod(MethodDesc * pMethod, PCODE pCode, size_t codeSize); + + // Does the actual work to log a native image load to the map. + void LogNativeImage(PEFile * pFile); - // Does the actual work to close and flush the map. - void Close(); + // Get the native image signature and store it as a string. + static void GetNativeImageSignature(PEFile * pFile, WCHAR * pwszSig, unsigned int nSigSize); 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); - + // Log a native image load to the map. + static void LogNativeImageLoad(PEFile * pFile); + + // Log a JIT compiled method to the map. + static void LogJITCompiledMethod(MethodDesc * pMethod, PCODE pCode, size_t codeSize); + // Close the map and flush any remaining data. static void Destroy(); }; +// Generates a perfmap file for a native image by running crossgen. +class NativeImagePerfMap : PerfMap +{ +private: + // Log a pre-compiled method to the map. + void LogPreCompiledMethod(MethodDesc * pMethod, PCODE pCode, SIZE_T baseAddr); + +public: + // Construct a new map for a native image. + NativeImagePerfMap(Assembly * pAssembly, BSTR pDestPath); + + // Log method information for each module. + void LogDataForModule(Module * pModule); +}; + #endif // PERFPID_H diff --git a/src/vm/prestub.cpp b/src/vm/prestub.cpp index 06d1967313..1889fbcec2 100644 --- a/src/vm/prestub.cpp +++ b/src/vm/prestub.cpp @@ -522,7 +522,7 @@ PCODE MethodDesc::MakeJitWorker(COR_ILMETHOD_DECODER* ILHeader, DWORD flags, DWO #ifdef FEATURE_PERFMAP // Save the JIT'd method information so that perf can resolve JIT'd call frames. - PerfMap::LogMethod(this, pCode, sizeOfCode); + PerfMap::LogJITCompiledMethod(this, pCode, sizeOfCode); #endif mcJitManager.GetMulticoreJitCodeStorage().StoreMethodCode(this, pCode); @@ -606,7 +606,7 @@ GotNewCode: #ifdef FEATURE_PERFMAP // Save the JIT'd method information so that perf can resolve JIT'd call frames. - PerfMap::LogMethod(this, pCode, sizeOfCode); + PerfMap::LogJITCompiledMethod(this, pCode, sizeOfCode); #endif } |