Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/coreclr/inc/clrconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <dir>/<assembly>/m_<token>.il). Disabled when unset.")
#endif

RETAIL_CONFIG_STRING_INFO(EXTERNAL_StartupDelayMS, W("StartupDelayMS"), "")
Expand Down
14 changes: 14 additions & 0 deletions src/coreclr/pal/inc/pal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
63 changes: 63 additions & 0 deletions src/coreclr/pal/src/misc/perfjitdump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ namespace
#endif

JIT_CODE_LOAD = 0,
JIT_CODE_DEBUG_INFO = 2,
};

static bool UseArchTimeStamp()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down
68 changes: 67 additions & 1 deletion src/coreclr/vm/perfmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#if defined(FEATURE_PERFMAP) && !defined(DACCESS_COMPILE)
#include <clrconfignocache.h>
#include "perfmap.h"
#include "debugdebugger.h"
#include "pal.h"
#include <dn-stdio.h>

Expand All @@ -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;

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<PAL_PerfJitDumpDebugEntry> 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));

Expand All @@ -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
Expand Down
Loading