Skip to content
Merged
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
16 changes: 16 additions & 0 deletions docs/design/datacontracts/ExecutionManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,16 @@ Within a range section fragment, a [nibble map](#nibblemap) structure is used to
| `Bucket` | `Keys` | `pointer` | Array of keys of `HashMapSlotsPerBucket` length |
| `Bucket` | `Values` | `pointer` | Array of values of `HashMapSlotsPerBucket` length |
| `CodeHeap` | `HeapType` | `uint8` | `uint8` discriminant identifying the concrete heap type |
| `CodeHeapListNode` | `CLRPersonalityRoutine` | `pointer` | Address of the CLR personality routine; when non-null, this is the module base for a Windows dynamic function table |
| `CodeHeapListNode` | `EndAddress` | `pointer` | End address of the used portion of the code heap |
| `CodeHeapListNode` | `HeaderMap` | `pointer` | Bit array used to find the start of methods - relative to `MapBase` |
| `CodeHeapListNode` | `Heap` | `pointer` | Pointer to the `CodeHeap` object managed by this node |
| `CodeHeapListNode` | `MapBase` | `pointer` | Start of the map - start address rounded down based on OS page size |
| `CodeHeapListNode` | `Next` | `pointer` | Next node |
| `CodeHeapListNode` | `StartAddress` | `pointer` | Start address of the used portion of the code heap |
| `CodeRangeMapRangeList` | `RangeListType` | `int32` | Integer identifying the stub code block kind for this range list |
| `DynamicFunctionTable` | `Context` | `pointer` | Tagged pointer to the owning `EEJitManager`; low bits are flags |
| `DynamicFunctionTable` | `MinimumAddress` | `pointer` | Module base address covered by the dynamic function table |
| `EEExceptionClause` | *(type size)* | `uint32` | Size of an exception clause in bytes |
| `EEExceptionClause` | `Flags` | `uint32` | Exception clause flags (`COR_ILEXCEPTION_CLAUSE_*` bit flags) |
| `EEExceptionClause` | `HandlerEndPC` | `uint32` | Native offset of the end of the handler |
Expand Down Expand Up @@ -610,6 +613,19 @@ IEnumerable<ICodeHeapInfo> IExecutionManager.GetCodeHeapInfos()
}
```

### Dynamic Function Table Entries

`GetDynamicFunctionTableEntries` returns the target addresses of the
`RUNTIME_FUNCTION` records for a Windows dynamic function table. These records describe
the unwind information for dynamically generated JIT code and are consumed by
out-of-process unwinders.

`tableAddress` identifies a target `DYNAMIC_FUNCTION_TABLE`. Its `Context` identifies
the owning `EEJitManager`, and its `MinimumAddress` identifies the associated code heap.
The result contains the entries for that heap, ordered by descending method start address
and then by ascending entry address within a method. If the table does not identify a
known code heap, or if the target is not Windows non-x86, the result is empty.

### RangeSectionMap

The range section map logically partitions the entire 32-bit or 64-bit addressable space into chunks.
Expand Down
3 changes: 3 additions & 0 deletions docs/design/datacontracts/data-descriptor-meanings.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"CMiniMdSchema.RecordCounts": "Address of the inline per-table row count array",
"CMiniMdSchema.Sorted": "Sorted-table bit mask",
"CodeHeap.HeapType": "`uint8` discriminant identifying the concrete heap type",
"CodeHeapListNode.CLRPersonalityRoutine": "Address of the CLR personality routine; when non-null, this is the module base for a Windows dynamic function table",
"CodeHeapListNode.EndAddress": "End address of the used portion of the code heap",
"CodeHeapListNode.HeaderMap": "Bit array used to find the start of methods - relative to `MapBase`",
"CodeHeapListNode.Heap": "Pointer to the `CodeHeap` object managed by this node",
Expand Down Expand Up @@ -92,6 +93,8 @@
"Delegate.MethodPtrAux": "Auxiliary method pointer",
"Delegate.Target": "Bound `this` reference for closed delegates",
"DynamicHelperFrame.DynamicHelperFrameFlags": "Flags indicating which argument registers contain GC references",
"DynamicFunctionTable.Context": "Tagged pointer to the owning `EEJitManager`; low bits are flags",
"DynamicFunctionTable.MinimumAddress": "Module base address covered by the dynamic function table",
"DynamicILBlobTable.EntryIL": "Offset of the IL pointer within each dynamic IL table entry",
"DynamicILBlobTable.EntryMethodToken": "Offset of the method token within each dynamic IL table entry",
"DynamicILBlobTable.Size": "Size in bytes of each table entry",
Expand Down
11 changes: 11 additions & 0 deletions src/coreclr/vm/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1135,8 +1135,19 @@ CDAC_TYPE_FIELD(CodeHeapListNode, T_POINTER, EndAddress, offsetof(HeapList, endA
CDAC_TYPE_FIELD(CodeHeapListNode, T_POINTER, MapBase, offsetof(HeapList, mapBase))
CDAC_TYPE_FIELD(CodeHeapListNode, T_POINTER, HeaderMap, offsetof(HeapList, pHdrMap))
CDAC_TYPE_FIELD(CodeHeapListNode, T_POINTER, Heap, offsetof(HeapList, pHeap))
#ifdef TARGET_64BIT
CDAC_TYPE_FIELD(CodeHeapListNode, T_POINTER, CLRPersonalityRoutine, offsetof(HeapList, CLRPersonalityRoutine))
#endif
CDAC_TYPE_END(CodeHeapListNode)

#if !defined(TARGET_UNIX) && !defined(TARGET_X86)
CDAC_TYPE_BEGIN(DynamicFunctionTable)
CDAC_TYPE_INDETERMINATE(DynamicFunctionTable)
CDAC_TYPE_FIELD(DynamicFunctionTable, T_POINTER, MinimumAddress, offsetof(DYNAMIC_FUNCTION_TABLE, MinimumAddress))
CDAC_TYPE_FIELD(DynamicFunctionTable, T_POINTER, Context, offsetof(DYNAMIC_FUNCTION_TABLE, Context))
CDAC_TYPE_END(DynamicFunctionTable)
#endif // !defined(TARGET_UNIX) && !defined(TARGET_X86)

CDAC_TYPE_BEGIN(CodeHeap)
CDAC_TYPE_INDETERMINATE(CodeHeap)
CDAC_TYPE_FIELD(CodeHeap, T_UINT8, HeapType, cdac_data<CodeHeap>::HeapType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public interface IExecutionManager : IContract
uint GetStackParameterSize(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
JitManagerInfo GetEEJitManagerInfo() => throw new NotImplementedException();
IEnumerable<ICodeHeapInfo> GetCodeHeapInfos() => throw new NotImplementedException();
IReadOnlyList<TargetPointer> GetDynamicFunctionTableEntries(TargetPointer tableAddress) => throw new NotImplementedException();
// Classify a code address as a known stub kind (precode, jump stub, VSD stub, etc.)
Comment thread
max-charlamb marked this conversation as resolved.
// or as managed code. Returns Unknown if the address is not recognized.
CodeKind GetCodeKind(TargetCodePointer codeAddress) => throw new NotImplementedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers;
Expand Down Expand Up @@ -159,6 +160,43 @@ private TargetPointer FindMethodCode(RangeSection rangeSection, TargetCodePointe
return _nibbleMap.FindMethodCode(heapListNode, codeAddress);
}

public List<TargetPointer> EnumerateFunctionTableEntries(Data.CodeHeapListNode heapListNode)
{
// Port of the reverse code-header walk in OutOfProcessFunctionTableCallbackEx. Starting from
// the end of the used portion of the code heap, walk backwards through the nibble map to visit
// each method, skip stub code blocks, and collect the RUNTIME_FUNCTION entries of the real code
// headers. Entries are ordered by descending method start address, ascending within a method.
uint runtimeFunctionSize = Target.GetTypeInfo(DataType.RuntimeFunction).Size!.Value;
List<TargetPointer> entries = [];

TargetCodePointer current = new(heapListNode.EndAddress.Value);
while (true)
{
TargetPointer codeStart = _nibbleMap.FindMethodCode(heapListNode, current);
if (codeStart == TargetPointer.Null)
break;

// The real code header pointer is stored immediately before the code start.
TargetPointer codeHeaderIndirect = codeStart - (ulong)Target.PointerSize;
TargetPointer codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect);

// Only real code headers (not stub code blocks) contribute unwind info entries.
if (!RangeSection.IsStubCodeBlock(Target, codeHeaderAddress))
{
Data.RealCodeHeader realCodeHeader = Target.ProcessedData.GetOrAdd<Data.RealCodeHeader>(codeHeaderAddress);
for (uint i = 0; i < realCodeHeader.NumUnwindInfos; i++)
entries.Add(realCodeHeader.UnwindInfos + (ulong)(i * runtimeFunctionSize));
}

if (codeStart.Value <= heapListNode.StartAddress.Value)
break;

current = new TargetCodePointer(codeStart.Value - 1);
}

return entries;
}

private TargetPointer GetCodeHeaderAddress(RangeSection rangeSection, TargetPointer codeStart)
{
// EEJitManager::JitCodeToMethodInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,45 @@ IEnumerable<ICodeHeapInfo> IExecutionManager.GetCodeHeapInfos()
}
}

IReadOnlyList<TargetPointer> IExecutionManager.GetDynamicFunctionTableEntries(TargetPointer tableAddress)
{
IRuntimeInfo runtimeInfo = _target.Contracts.RuntimeInfo;
if (runtimeInfo.GetTargetOperatingSystem() != RuntimeInfoOperatingSystem.Windows ||
runtimeInfo.GetTargetArchitecture() == RuntimeInfoArchitecture.X86)
{
return [];
}

// Port of the DAC's OutOfProcessFunctionTableCallbackEx (see vm/../debug/daccess/fntableaccess.cpp).
// The dynamic-function-table header identifies both the owning JIT manager (via its Context) and
// the module base (via MinimumAddress) of the code heap whose function table is requested.
Data.DynamicFunctionTable table = _target.ProcessedData.GetOrAdd<Data.DynamicFunctionTable>(tableAddress);

// The low bits of Context are flags; the remaining bits point at the owning EEJitManager.
TargetPointer jitManagerAddress = new(table.Context.Value & ~(ulong)3);
TargetPointer minimumAddress = table.MinimumAddress;

Data.EEJitManager jitManager = _target.ProcessedData.GetOrAdd<Data.EEJitManager>(jitManagerAddress);

TargetPointer nodeAddr = jitManager.AllCodeHeaps;
while (nodeAddr != TargetPointer.Null)
{
Data.CodeHeapListNode node = _target.ProcessedData.GetOrAdd<Data.CodeHeapListNode>(nodeAddr);

// HeapList::GetModuleBase - the personality routine on 64-bit targets when set,
// otherwise the map base. This matches the value used to register the function table.
TargetPointer moduleBase = node.CLRPersonalityRoutine is { } personalityRoutine && personalityRoutine != TargetPointer.Null
? personalityRoutine
: node.MapBase;
if (moduleBase == minimumAddress)
return _eeJitManager.EnumerateFunctionTableEntries(node);

nodeAddr = node.Next;
}

return [];
}

private RangeSection RangeSectionFromCodeBlockHandle(CodeBlockHandle codeInfoHandle)
{
if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ internal ExecutionManager_1(Target target)
public List<ExceptionClauseInfo> GetExceptionClauses(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetExceptionClauses(codeInfoHandle);
public JitManagerInfo GetEEJitManagerInfo() => _executionManagerCore.GetEEJitManagerInfo();
public IEnumerable<ICodeHeapInfo> GetCodeHeapInfos() => _executionManagerCore.GetCodeHeapInfos();
public IReadOnlyList<TargetPointer> GetDynamicFunctionTableEntries(TargetPointer tableAddress) => _executionManagerCore.GetDynamicFunctionTableEntries(tableAddress);
public CodeKind GetCodeKind(TargetCodePointer codeAddress) => _executionManagerCore.GetCodeKind(codeAddress);
public TargetPointer FindReadyToRunModule(TargetPointer address) => _executionManagerCore.FindReadyToRunModule(address);
public void Flush(FlushScope scope) => _executionManagerCore.Flush(scope);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ internal ExecutionManager_2(Target target)
public List<ExceptionClauseInfo> GetExceptionClauses(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetExceptionClauses(codeInfoHandle);
public JitManagerInfo GetEEJitManagerInfo() => _executionManagerCore.GetEEJitManagerInfo();
public IEnumerable<ICodeHeapInfo> GetCodeHeapInfos() => _executionManagerCore.GetCodeHeapInfos();
public IReadOnlyList<TargetPointer> GetDynamicFunctionTableEntries(TargetPointer tableAddress) => _executionManagerCore.GetDynamicFunctionTableEntries(tableAddress);
public CodeKind GetCodeKind(TargetCodePointer codeAddress) => _executionManagerCore.GetCodeKind(codeAddress);
public TargetPointer FindReadyToRunModule(TargetPointer address) => _executionManagerCore.FindReadyToRunModule(address);
public void Flush(FlushScope scope) => _executionManagerCore.Flush(scope);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ internal sealed partial class CodeHeapListNode : IData<CodeHeapListNode>
[Field] public partial TargetPointer MapBase { get; }
[Field] public partial TargetPointer HeaderMap { get; }
[Field] public partial TargetPointer Heap { get; }

// 64-bit only: jump thunk to the personality routine. Used as the module base
// when matching a dynamic function table's minimum address.
[Field] public partial TargetPointer? CLRPersonalityRoutine { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Diagnostics.DataContractReader.Data;

[CdacType(nameof(DataType.DynamicFunctionTable))]
internal sealed partial class DynamicFunctionTable : IData<DynamicFunctionTable>
{
[Field] public partial TargetPointer MinimumAddress { get; }
[Field] public partial TargetPointer Context { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ public enum DataType
CodeHeap,
LoaderCodeHeap,
HostCodeHeap,
DynamicFunctionTable,
MethodDescVersioningState,
ILCodeVersioningState,
NativeCodeVersionNode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,43 @@ int IXCLRDataProcess3.GetFunctionTable(
*bytesNeeded = 0;
*entries = 0;

return HResults.E_NOTIMPL;
try
{
IExecutionManager executionManager = _target.Contracts.ExecutionManager;
IReadOnlyList<TargetPointer> functionEntries =
executionManager.GetDynamicFunctionTableEntries(tableAddress.ToTargetPointer(_target));

uint runtimeFunctionSize = _target.GetTypeInfo(DataType.RuntimeFunction).Size!.Value;
uint count = (uint)functionEntries.Count;
ulong totalBytes = (ulong)count * runtimeFunctionSize;
if (totalBytes > uint.MaxValue)
return HResults.E_FAIL;

*entries = count;
*bytesNeeded = (uint)totalBytes;

// An empty or unmatched table reports zero entries and succeeds.
if (count == 0)
return HResults.S_OK;

// A size query (null buffer) or a buffer that is too small writes nothing and reports
// the required sizes so the caller can retry with adequate storage.
if (buffer is null || bufferSize < totalBytes)
return HResults.S_FALSE;

int entrySize = checked((int)runtimeFunctionSize);
for (int i = 0; i < functionEntries.Count; i++)
{
Span<byte> destination = new(buffer + ((nint)i * entrySize), entrySize);
_target.ReadBuffer(functionEntries[i].Value, destination);
}

return HResults.S_OK;
}
catch (System.Exception ex)
{
return ex.HResult;
}
}

int IXCLRDataProcess.Flush()
Expand Down
Loading