Skip to content

Commit

Permalink
Add an ETW event for reporting richer debug information back (#71263)
Browse files Browse the repository at this point in the history
This adds some richer debug information in the form of inline trees and
IL<->native mappings with inlinee information, plus an ETW event in the private
runtime provider to report it back. A COMPlus variable is used to opt-in to
generating and storing this larger debug information.

On the JIT side, we now store a few more fields of data for each inline context
in release builds:

* Its ordinal which is required to be incrementally assigned

* The actual IL offset of the IL instruction leading to the creation of the
  inline context

* The method handle of the inlinee

On the EE side we store the new debug information together with the normal debug
info, reusing the flag byte used for patchpoint information.

The hope is that the new richer format of debug info can eventually replace the
old one, but this won't happen in .NET 7 as I expect it will take some time to
come up with the right format for this data and the right set of fields to
expose. In the meantime this environment variable and event/debugging APIs are
mainly to be used as an opt-in way to start incrementally prototyping on the
tooling side without needing special checked builds of the runtime/JIT.
  • Loading branch information
jakobbotsch authored Jul 7, 2022
1 parent 641ef68 commit 527f1d1
Show file tree
Hide file tree
Showing 39 changed files with 1,065 additions and 296 deletions.
1 change: 1 addition & 0 deletions src/coreclr/inc/clrconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ RETAIL_CONFIG_DWORD_INFO(INTERNAL_MiniMdBufferCapacity, W("MiniMdBufferCapacity"
#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS

CONFIG_DWORD_INFO(INTERNAL_DbgNativeCodeBpBindsAcrossVersions, W("DbgNativeCodeBpBindsAcrossVersions"), 0, "If non-zero causes native breakpoints at offset 0 to bind in all tiered compilation versions of the given method")
RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_RichDebugInfo, W("RichDebugInfo"), 0, "If non-zero store some additional debug information for each jitted method")

///
/// Diagnostics (internal general-purpose)
Expand Down
27 changes: 27 additions & 0 deletions src/coreclr/inc/cordebuginfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -369,4 +369,31 @@ class ICorDebugInfo
uint32_t varNumber;
VarLoc loc;
};

// Represents an individual entry in the inline tree.
// This is ordinarily stored as a flat array in which [0] is the root, and
// the indices below indicate the tree structure.
struct InlineTreeNode
{
// Method handle of inlinee (or root)
CORINFO_METHOD_HANDLE Method;
// IL offset of IL instruction resulting in the inline
uint32_t ILOffset;
// Index of child in tree, 0 if no children
uint32_t Child;
// Index of sibling in tree, 0 if no sibling
uint32_t Sibling;
};

struct RichOffsetMapping
{
// Offset in emitted code
uint32_t NativeOffset;
// Index of inline tree node containing the IL offset (0 for root)
uint32_t Inlinee;
// IL offset of IL instruction in inlinee that this mapping was created from
uint32_t ILOffset;
// Source information about the IL instruction in the inlinee
SourceTypes Source;
};
};
10 changes: 10 additions & 0 deletions src/coreclr/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -2734,6 +2734,16 @@ class ICorStaticInfo
// jit allocated with allocateArray, EE frees
) = 0;

// Report inline tree and rich offset mappings to EE.
// The arrays are expected to be allocated with allocateArray
// and ownership is transferred to the EE with this call.
virtual void reportRichMappings(
ICorDebugInfo::InlineTreeNode* inlineTreeNodes, // [IN] Nodes of the inline tree
uint32_t numInlineTreeNodes, // [IN] Number of nodes in the inline tree
ICorDebugInfo::RichOffsetMapping* mappings, // [IN] Rich mappings
uint32_t numMappings // [IN] Number of rich mappings
) = 0;

/*-------------------------- Misc ---------------------------------------*/

// Used to allocate memory that needs to handed to the EE.
Expand Down
5 changes: 4 additions & 1 deletion src/coreclr/inc/eventtracebase.h
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,7 @@ namespace ETW
MethodDCEndILToNativeMap= 0x00020000,
JitMethodILToNativeMap= 0x00040000,
TypeUnload= 0x00080000,
JittedMethodRichDebugInfo= 0x00100000,

// Helpers
ModuleRangeEnabledAny = ModuleRangeLoad | ModuleRangeDCStart | ModuleRangeDCEnd | ModuleRangeLoadPrivate,
Expand Down Expand Up @@ -897,13 +898,14 @@ namespace ETW
BOOL fUnloadOrDCEnd,
BOOL fSendMethodEvent,
BOOL fSendILToNativeMapEvent,
BOOL fSendRichDebugInfoEvent,
BOOL fGetCodeIds);
static VOID SendEventsForNgenMethods(Module *pModule, DWORD dwEventOptions);
static VOID SendMethodJitStartEvent(MethodDesc *pMethodDesc, SString *namespaceOrClassName=NULL, SString *methodName=NULL, SString *methodSignature=NULL);
static VOID SendMethodILToNativeMapEvent(MethodDesc * pMethodDesc, DWORD dwEventOptions, PCODE pNativeCodeStartAddress, DWORD nativeCodeId, ReJITID ilCodeId);
static VOID SendMethodRichDebugInfo(MethodDesc * pMethodDesc, PCODE pNativeCodeStartAddress, DWORD nativeCodeId, ReJITID ilCodeId);
static VOID SendMethodEvent(MethodDesc *pMethodDesc, DWORD dwEventOptions, BOOL bIsJit, SString *namespaceOrClassName=NULL, SString *methodName=NULL, SString *methodSignature=NULL, PCODE pNativeCodeStartAddress = 0, PrepareCodeConfig *pConfig = NULL);
static VOID SendHelperEvent(ULONGLONG ullHelperStartAddress, ULONG ulHelperSize, LPCWSTR pHelperName);
static VOID SendMethodDetailsEvent(MethodDesc *pMethodDesc);
public:
typedef union _MethodStructs
{
Expand Down Expand Up @@ -934,6 +936,7 @@ namespace ETW
static VOID GetR2RGetEntryPoint(MethodDesc *pMethodDesc, PCODE pEntryPoint);
static VOID MethodJitting(MethodDesc *pMethodDesc, SString *namespaceOrClassName, SString *methodName, SString *methodSignature);
static VOID MethodJitted(MethodDesc *pMethodDesc, SString *namespaceOrClassName, SString *methodName, SString *methodSignature, PCODE pNativeCodeStartAddress, PrepareCodeConfig *pConfig);
static VOID SendMethodDetailsEvent(MethodDesc *pMethodDesc);
static VOID StubInitialized(ULONGLONG ullHelperStartAddress, LPCWSTR pHelperName);
static VOID StubsInitialized(PVOID *pHelperStartAddress, PVOID *pHelperNames, LONG ulNoOfHelpers);
static VOID MethodRestored(MethodDesc * pMethodDesc);
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/inc/icorjitinfoimpl_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,12 @@ void setVars(
uint32_t cVars,
ICorDebugInfo::NativeVarInfo* vars) override;

void reportRichMappings(
ICorDebugInfo::InlineTreeNode* inlineTreeNodes,
uint32_t numInlineTreeNodes,
ICorDebugInfo::RichOffsetMapping* mappings,
uint32_t numMappings) override;

void* allocateArray(
size_t cBytes) override;

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/ICorJitInfo_API_names.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ DEF_CLR_API(getBoundaries)
DEF_CLR_API(setBoundaries)
DEF_CLR_API(getVars)
DEF_CLR_API(setVars)
DEF_CLR_API(reportRichMappings)
DEF_CLR_API(allocateArray)
DEF_CLR_API(freeArray)
DEF_CLR_API(getArgNext)
Expand Down
11 changes: 11 additions & 0 deletions src/coreclr/jit/ICorJitInfo_API_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,17 @@ void WrapICorJitInfo::setVars(
API_LEAVE(setVars);
}

void WrapICorJitInfo::reportRichMappings(
ICorDebugInfo::InlineTreeNode* inlineTreeNodes,
uint32_t numInlineTreeNodes,
ICorDebugInfo::RichOffsetMapping* mappings,
uint32_t numMappings)
{
API_ENTER(reportRichMappings);
wrapHnd->reportRichMappings(inlineTreeNodes, numInlineTreeNodes, mappings, numMappings);
API_LEAVE(reportRichMappings);
}

void* WrapICorJitInfo::allocateArray(
size_t cBytes)
{
Expand Down
10 changes: 7 additions & 3 deletions src/coreclr/jit/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -674,11 +674,15 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
void genIPmappingAdd(IPmappingDscKind kind, const DebugInfo& di, bool isLabel);
void genIPmappingAddToFront(IPmappingDscKind kind, const DebugInfo& di, bool isLabel);
void genIPmappingGen();
void genAddRichIPMappingHere(const DebugInfo& di);

void genReportRichDebugInfo();

void genRecordRichDebugInfoInlineTree(InlineContext* context, ICorDebugInfo::InlineTreeNode* tree);

#ifdef DEBUG
void genDumpPreciseDebugInfo();
void genDumpPreciseDebugInfoInlineTree(FILE* file, InlineContext* context, bool* first);
void genAddPreciseIPMappingHere(const DebugInfo& di);
void genReportRichDebugInfoToFile();
void genReportRichDebugInfoInlineTreeToFile(FILE* file, InlineContext* context, bool* first);
#endif

void genEnsureCodeEmitted(const DebugInfo& di);
Expand Down
147 changes: 127 additions & 20 deletions src/coreclr/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2085,7 +2085,7 @@ void CodeGen::genEmitUnwindDebugGCandEH()

genIPmappingGen();

INDEBUG(genDumpPreciseDebugInfo());
genReportRichDebugInfo();

/* Finalize the Local Var info in terms of generated code */

Expand Down Expand Up @@ -7416,12 +7416,7 @@ void CodeGen::genIPmappingGen()
return;
}

#ifdef DEBUG
if (verbose)
{
printf("*************** In genIPmappingGen()\n");
}
#endif
JITDUMP("*************** In genIPmappingGen()\n");

if (compiler->genIPmappings.size() <= 0)
{
Expand Down Expand Up @@ -7560,11 +7555,20 @@ void CodeGen::genIPmappingGen()
}

#ifdef DEBUG
void CodeGen::genDumpPreciseDebugInfoInlineTree(FILE* file, InlineContext* context, bool* first)
//------------------------------------------------------------------------
// genReportRichDebugInfoInlineTreeToFile:
// Recursively process a context in the inline tree and write information about it to a file.
//
// Parameters:
// file - the file
// context - the context
// first - whether this is the first of the siblings being written out
//
void CodeGen::genReportRichDebugInfoInlineTreeToFile(FILE* file, InlineContext* context, bool* first)
{
if (context->GetSibling() != nullptr)
{
genDumpPreciseDebugInfoInlineTree(file, context->GetSibling(), first);
genReportRichDebugInfoInlineTreeToFile(file, context->GetSibling(), first);
}

if (context->IsSuccess())
Expand All @@ -7577,42 +7581,53 @@ void CodeGen::genDumpPreciseDebugInfoInlineTree(FILE* file, InlineContext* conte
*first = false;

fprintf(file, "{\"Ordinal\":%u,", context->GetOrdinal());
fprintf(file, "\"MethodID\":%lld,", (INT64)context->GetCallee());
fprintf(file, "\"MethodID\":%lld,", (int64_t)context->GetCallee());
fprintf(file, "\"ILOffset\":%u,", context->GetLocation().GetOffset());
fprintf(file, "\"LocationFlags\":%u,", (uint32_t)context->GetLocation().EncodeSourceTypes());
fprintf(file, "\"ExactILOffset\":%u,", context->GetActualCallOffset());
const char* className;
const char* methodName = compiler->eeGetMethodName(context->GetCallee(), &className);
fprintf(file, "\"MethodName\":\"%s\",", methodName);
fprintf(file, "\"Inlinees\":[");
if (context->GetChild() != nullptr)
{
bool childFirst = true;
genDumpPreciseDebugInfoInlineTree(file, context->GetChild(), &childFirst);
genReportRichDebugInfoInlineTreeToFile(file, context->GetChild(), &childFirst);
}
fprintf(file, "]}");
}
}

void CodeGen::genDumpPreciseDebugInfo()
//------------------------------------------------------------------------
// genReportRichDebugInfoToFile:
// Write rich debug info in JSON format to file specified by environment variable.
//
void CodeGen::genReportRichDebugInfoToFile()
{
if (JitConfig.JitDumpPreciseDebugInfoFile() == nullptr)
if (JitConfig.WriteRichDebugInfoFile() == nullptr)
{
return;
}

static CritSecObject s_critSect;
CritSecHolder holder(s_critSect);

FILE* file = _wfopen(JitConfig.JitDumpPreciseDebugInfoFile(), W("a"));
FILE* file = _wfopen(JitConfig.WriteRichDebugInfoFile(), W("a"));
if (file == nullptr)
{
return;
}

// MethodID in ETW events are the method handles.
fprintf(file, "{\"MethodID\":%lld,", (INT64)compiler->info.compMethodHnd);
// Print inline tree.
fprintf(file, "\"InlineTree\":");

bool first = true;
genDumpPreciseDebugInfoInlineTree(file, compiler->compInlineContext, &first);
genReportRichDebugInfoInlineTreeToFile(file, compiler->compInlineContext, &first);
fprintf(file, ",\"Mappings\":[");
first = true;
for (PreciseIPMapping& mapping : compiler->genPreciseIPmappings)
for (RichIPMapping& mapping : compiler->genRichIPmappings)
{
if (!first)
{
Expand All @@ -7631,14 +7646,106 @@ void CodeGen::genDumpPreciseDebugInfo()
fclose(file);
}

void CodeGen::genAddPreciseIPMappingHere(const DebugInfo& di)
#endif

//------------------------------------------------------------------------
// genRecordRichDebugInfoInlineTree:
// Recursively process a context in the inline tree and record information
// about it.
//
// Parameters:
// context - the inline context
// nodes - the array to record into
//
void CodeGen::genRecordRichDebugInfoInlineTree(InlineContext* context, ICorDebugInfo::InlineTreeNode* nodes)
{
if (context->IsSuccess())
{
// We expect 1 + NumInlines unique ordinals
assert(context->GetOrdinal() <= compiler->m_inlineStrategy->GetInlineCount());

ICorDebugInfo::InlineTreeNode* node = &nodes[context->GetOrdinal()];
node->Method = context->GetCallee();
node->ILOffset = context->GetActualCallOffset();
node->Child = context->GetChild() == nullptr ? 0 : context->GetChild()->GetOrdinal();
node->Sibling = context->GetSibling() == nullptr ? 0 : context->GetSibling()->GetOrdinal();
}

if (context->GetSibling() != nullptr)
{
genRecordRichDebugInfoInlineTree(context->GetSibling(), nodes);
}

if (context->GetChild() != nullptr)
{
genRecordRichDebugInfoInlineTree(context->GetChild(), nodes);
}
}

//------------------------------------------------------------------------
// genReportRichDebugInfo:
// If enabled, report rich debugging information to file and/or EE.
//
void CodeGen::genReportRichDebugInfo()
{
INDEBUG(genReportRichDebugInfoToFile());

if (JitConfig.RichDebugInfo() == 0)
{
return;
}

unsigned numContexts = 1 + compiler->m_inlineStrategy->GetInlineCount();
unsigned numRichMappings = static_cast<unsigned>(compiler->genRichIPmappings.size());

ICorDebugInfo::InlineTreeNode* inlineTree = static_cast<ICorDebugInfo::InlineTreeNode*>(
compiler->info.compCompHnd->allocateArray(numContexts * sizeof(ICorDebugInfo::InlineTreeNode)));
ICorDebugInfo::RichOffsetMapping* mappings = static_cast<ICorDebugInfo::RichOffsetMapping*>(
compiler->info.compCompHnd->allocateArray(numRichMappings * sizeof(ICorDebugInfo::RichOffsetMapping)));

memset(inlineTree, 0, numContexts * sizeof(ICorDebugInfo::InlineTreeNode));
memset(mappings, 0, numRichMappings * sizeof(ICorDebugInfo::RichOffsetMapping));

genRecordRichDebugInfoInlineTree(compiler->compInlineContext, inlineTree);

#ifdef DEBUG
for (unsigned i = 0; i < numContexts; i++)
{
assert(inlineTree[i].Method != NO_METHOD_HANDLE);
}
#endif

size_t mappingIndex = 0;
for (const RichIPMapping& richMapping : compiler->genRichIPmappings)
{
ICorDebugInfo::RichOffsetMapping* mapping = &mappings[mappingIndex];
assert(richMapping.debugInfo.IsValid());
mapping->NativeOffset = richMapping.nativeLoc.CodeOffset(GetEmitter());
mapping->Inlinee = richMapping.debugInfo.GetInlineContext()->GetOrdinal();
mapping->ILOffset = richMapping.debugInfo.GetLocation().GetOffset();
mapping->Source = richMapping.debugInfo.GetLocation().EncodeSourceTypes();

mappingIndex++;
}

compiler->info.compCompHnd->reportRichMappings(inlineTree, numContexts, mappings, numRichMappings);
}

//------------------------------------------------------------------------
// genAddRichIPMappingHere:
// Create a rich IP mapping at the current emit location using the specified
// debug information.
//
// Parameters:
// di - the debug information
//
void CodeGen::genAddRichIPMappingHere(const DebugInfo& di)
{
PreciseIPMapping mapping;
RichIPMapping mapping;
mapping.nativeLoc.CaptureLocation(GetEmitter());
mapping.debugInfo = di;
compiler->genPreciseIPmappings.push_back(mapping);
compiler->genRichIPmappings.push_back(mapping);
}
#endif

/*============================================================================
*
Expand Down
14 changes: 8 additions & 6 deletions src/coreclr/jit/codegenlinear.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -424,11 +424,13 @@ void CodeGen::genCodeForBBlist()
}
}
}

bool addPreciseMappings =
(JitConfig.JitDumpPreciseDebugInfoFile() != nullptr) || (JitConfig.JitDisasmWithDebugInfo() != 0);
#endif // DEBUG

bool addRichMappings = JitConfig.RichDebugInfo() != 0;

INDEBUG(addRichMappings |= JitConfig.JitDisasmWithDebugInfo() != 0);
INDEBUG(addRichMappings |= JitConfig.WriteRichDebugInfoFile() != nullptr);

DebugInfo currentDI;
for (GenTree* node : LIR::AsRange(block))
{
Expand All @@ -445,12 +447,12 @@ void CodeGen::genCodeForBBlist()
firstMapping = false;
}

#ifdef DEBUG
if (addPreciseMappings && ilOffset->gtStmtDI.IsValid())
if (addRichMappings && ilOffset->gtStmtDI.IsValid())
{
genAddPreciseIPMappingHere(ilOffset->gtStmtDI);
genAddRichIPMappingHere(ilOffset->gtStmtDI);
}

#ifdef DEBUG
assert(ilOffset->gtStmtLastILoffs <= compiler->info.compILCodeSize ||
ilOffset->gtStmtLastILoffs == BAD_IL_OFFSET);

Expand Down
Loading

0 comments on commit 527f1d1

Please sign in to comment.