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