diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index d6badce75704c0..fb1ca3564b557e 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -426,6 +426,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_PerfMapEnabled, W("PerfMapEnabled"), 0, "This RETAIL_CONFIG_DWORD_INFO(EXTERNAL_PerfMapIgnoreSignal, W("PerfMapIgnoreSignal"), 0, "When perf map is enabled, this option will configure the specified signal to be accepted and ignored as a marker in the perf logs. It is disabled by default") RETAIL_CONFIG_DWORD_INFO(EXTERNAL_PerfMapShowOptimizationTiers, W("PerfMapShowOptimizationTiers"), 1, "Shows optimization tiers in the perf map for methods, as part of the symbol name. Useful for seeing separate stack frames for different optimization tiers of each method.") RETAIL_CONFIG_DWORD_INFO(EXTERNAL_PerfMapStubGranularity, W("PerfMapStubGranularity"), 0, "Report stubs with varying amounts of granularity (low bit being zero indicates attempt to group all stubs of a type together) (second lowest bit being non-zero records stubs at individual allocation sites, which is more expensive, but also more accurate) (third lowest bit disables stub logging).") +RETAIL_CONFIG_STRING_INFO(EXTERNAL_PerfMapIlSourcePath, W("PerfMapIlSourcePath"), "If set (and jitdump is enabled via PerfMapEnabled), emit JIT_CODE_DEBUG_INFO jitdump records that map native code to synthetic IL source files under this directory (contract: IL instruction at offset K is on line K + 2 of //m_.il). Disabled when unset.") #endif RETAIL_CONFIG_STRING_INFO(EXTERNAL_StartupDelayMS, W("StartupDelayMS"), "") diff --git a/src/coreclr/pal/inc/pal.h b/src/coreclr/pal/inc/pal.h index 0ff5b7435ecab0..6024c2501f5af8 100644 --- a/src/coreclr/pal/inc/pal.h +++ b/src/coreclr/pal/inc/pal.h @@ -369,6 +369,20 @@ bool PALAPI PAL_PerfJitDump_IsStarted(); +typedef struct _PAL_PerfJitDumpDebugEntry +{ + ULONG64 address; // absolute native code address + INT32 line; // "line number" (IL-offset based, see PerfMap::LogJITCompiledMethod) + INT32 discrim; +} PAL_PerfJitDumpDebugEntry; + +typedef struct _PAL_PerfJitDumpDebugInfo +{ + ULONG64 nrEntries; + const char* fileName; // shared by all entries + const PAL_PerfJitDumpDebugEntry* entries; +} PAL_PerfJitDumpDebugInfo; + PALIMPORT int PALAPI diff --git a/src/coreclr/pal/src/misc/perfjitdump.cpp b/src/coreclr/pal/src/misc/perfjitdump.cpp index 3b45b8c8856d62..cc743619c30f8e 100644 --- a/src/coreclr/pal/src/misc/perfjitdump.cpp +++ b/src/coreclr/pal/src/misc/perfjitdump.cpp @@ -66,6 +66,7 @@ namespace #endif JIT_CODE_LOAD = 0, + JIT_CODE_DEBUG_INFO = 2, }; static bool UseArchTimeStamp() @@ -146,6 +147,15 @@ namespace // Null terminated name // Optional native code }; + + struct JitCodeDebugInfoRecord + { + RecordHeader header; + uint64_t code_addr; + uint64_t nr_entry; + // Followed by nr_entry variable-sized entries: + // uint64_t address; int32_t line; int32_t discrim; null-terminated file name + }; }; struct PerfJitDumpState @@ -280,6 +290,59 @@ struct PerfJitDumpState if (!enabled) goto exit; + // A JIT_CODE_DEBUG_INFO record must immediately precede the + // JitCodeLoadRecord it describes (see the perf jitdump specification). + if (debugInfo != nullptr) + { + const PAL_PerfJitDumpDebugInfo* di = (const PAL_PerfJitDumpDebugInfo*)debugInfo; + if (di->nrEntries > 0 && di->entries != nullptr && di->fileName != nullptr) + { + size_t fileNameLen = strlen(di->fileName) + 1; + size_t entrySize = sizeof(uint64_t) + 2 * sizeof(int32_t) + fileNameLen; + size_t payloadSize = sizeof(JitCodeDebugInfoRecord) + (size_t)di->nrEntries * entrySize; + char* dbgBuffer = (char*)malloc(payloadSize); + if (dbgBuffer != nullptr) + { + JitCodeDebugInfoRecord dbg; + dbg.header.id = JIT_CODE_DEBUG_INFO; + dbg.header.total_size = (uint32_t)payloadSize; + dbg.header.timestamp = record.header.timestamp; + dbg.code_addr = (uint64_t)pCode; + dbg.nr_entry = di->nrEntries; + + char* cursor = dbgBuffer; + memcpy(cursor, &dbg, sizeof(dbg)); + cursor += sizeof(dbg); + for (uint64_t i = 0; i < di->nrEntries; i++) + { + memcpy(cursor, &di->entries[i].address, sizeof(uint64_t)); + cursor += sizeof(uint64_t); + memcpy(cursor, &di->entries[i].line, sizeof(int32_t)); + cursor += sizeof(int32_t); + memcpy(cursor, &di->entries[i].discrim, sizeof(int32_t)); + cursor += sizeof(int32_t); + memcpy(cursor, di->fileName, fileNameLen); + cursor += fileNameLen; + } + + size_t dbgWritten = 0; + while (dbgWritten < payloadSize) + { + ssize_t w = write(fd, dbgBuffer + dbgWritten, payloadSize - dbgWritten); + if (w == -1) + { + if (errno == EINTR) + continue; + free(dbgBuffer); + return FatalError(); + } + dbgWritten += (size_t)w; + } + free(dbgBuffer); + } + } + } + // Increment codeIndex while locked record.code_index = ++codeIndex; diff --git a/src/coreclr/vm/perfmap.cpp b/src/coreclr/vm/perfmap.cpp index 55fa3e0a7af175..6f7adf5c4f994e 100644 --- a/src/coreclr/vm/perfmap.cpp +++ b/src/coreclr/vm/perfmap.cpp @@ -9,6 +9,7 @@ #if defined(FEATURE_PERFMAP) && !defined(DACCESS_COMPILE) #include #include "perfmap.h" +#include "debugdebugger.h" #include "pal.h" #include @@ -35,6 +36,11 @@ bool PerfMap::s_GroupStubsOfSameType = false; bool PerfMap::s_IndividualAllocationStubReporting = false; bool PerfMap::s_LogStubs = false; +// IL-as-source debug info emission (jitdump JIT_CODE_DEBUG_INFO records). +// Enabled by setting DOTNET_PerfMapIlSourcePath; disabled (zero cost) otherwise. +static bool s_IlDebugInfoEnabled = false; +static char s_IlSourcePath[512]; + unsigned PerfMap::s_StubsMapped = 0; CrstStatic PerfMap::s_csPerfMap; @@ -87,6 +93,17 @@ void PerfMap::InitializeConfiguration() s_GroupStubsOfSameType = (granularity & 1) != 1; s_IndividualAllocationStubReporting = (granularity & 2) != 0; s_LogStubs = (granularity & 4) == 0; + + CLRConfigStringHolder ilSourcePath(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_PerfMapIlSourcePath)); + if (ilSourcePath != NULL) + { + MAKE_UTF8PTR_FROMWIDE_NOTHROW(utf8IlSourcePath, ilSourcePath); + if (utf8IlSourcePath != NULL && strlen(utf8IlSourcePath) < sizeof(s_IlSourcePath)) + { + strcpy_s(s_IlSourcePath, sizeof(s_IlSourcePath), utf8IlSourcePath); + s_IlDebugInfoEnabled = true; + } + } } void PerfMap::Enable(PerfMapType type, bool sendExisting) @@ -360,6 +377,55 @@ void PerfMap::LogJITCompiledMethod(MethodDesc * pMethod, PCODE pCode, size_t cod SString line; line.Printf(FMT_CODE_ADDR " %zx %s\n", (void*)pCode, codeSize, name.GetUTF8()); + // If jitdump is active, collect the IL-to-native map and emit it as a + // JIT_CODE_DEBUG_INFO record referencing synthetic IL "source" files. + // Contract with the IL file generator: the IL instruction at offset K + // is placed on line K + 2 of the file (line 1 is a generated header). + PAL_PerfJitDumpDebugInfo palDebugInfo = {}; + PAL_PerfJitDumpDebugInfo* pPalDebugInfo = nullptr; + NewArrayHolder debugEntries; + SString ilFileName; + if (s_IlDebugInfoEnabled && PAL_PerfJitDump_IsStarted()) + { + uint32_t cMap = 0; + uint32_t* rguiNativeOffset = nullptr; + uint32_t* rguiILOffset = nullptr; + ILToNativeMapArrays context(7000); + EECodeInfo codeInfo(pCode); + if (codeInfo.IsValid()) + { + DebugInfoRequest request; + request.InitFromStartingAddr(pMethod, pCode); + codeInfo.GetJitManager()->WalkILOffsets(request, BoundsType::Uninstrumented, &context, ComputeILOffsetArrays); + context.GetArrays(&cMap, &rguiNativeOffset, &rguiILOffset); + } + if (cMap > 0) + { + debugEntries = new PAL_PerfJitDumpDebugEntry[cMap]; + uint64_t nEntries = 0; + for (uint32_t i = 0; i < cMap; i++) + { + uint32_t ilOffset = rguiILOffset[i]; + if (ilOffset < (uint32_t)ICorDebugInfo::MAX_MAPPING_VALUE) + { + debugEntries[nEntries].address = (ULONG64)pCode + rguiNativeOffset[i]; + debugEntries[nEntries].line = (INT32)(ilOffset + 2); + debugEntries[nEntries].discrim = 0; + nEntries++; + } + } + if (nEntries > 0) + { + ilFileName.Printf("%s/%s/m_%08x.il", s_IlSourcePath, + pMethod->GetModule()->GetSimpleName(), pMethod->GetMemberDef()); + palDebugInfo.nrEntries = nEntries; + palDebugInfo.fileName = ilFileName.GetUTF8(); + palDebugInfo.entries = debugEntries; + pPalDebugInfo = &palDebugInfo; + } + } + } + { CrstHolder ch(&(s_csPerfMap)); @@ -368,7 +434,7 @@ void PerfMap::LogJITCompiledMethod(MethodDesc * pMethod, PCODE pCode, size_t cod s_Current->WriteLine(line); } - PAL_PerfJitDump_LogMethod((void*)pCode, codeSize, name.GetUTF8(), nullptr, nullptr, /*reportCodeBlock*/true); + PAL_PerfJitDump_LogMethod((void*)pCode, codeSize, name.GetUTF8(), (void*)pPalDebugInfo, nullptr, /*reportCodeBlock*/true); } } EX_CATCH{} EX_END_CATCH