|
// Licensed to the .NET Foundation under one or more agreements.
// 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;
using Microsoft.Diagnostics.DataContractReader.Data;
namespace Microsoft.Diagnostics.DataContractReader.Contracts;
internal sealed partial class ExecutionManagerCore<T> : IExecutionManager
where T : INibbleMap
{
internal readonly Target _target;
// maps CodeBlockHandle.Address (which is the CodeHeaderAddress) to the CodeBlock
private readonly Dictionary<TargetPointer, CodeBlock> _codeInfos = new();
private readonly Data.RangeSectionMap _topRangeSectionMap;
private readonly ExecutionManagerHelpers.RangeSectionMap _rangeSectionMapLookup;
private readonly EEJitManager _eeJitManager;
private readonly ReadyToRunJitManager _r2rJitManager;
private readonly InterpreterJitManager _interpreterJitManager;
public ExecutionManagerCore(Target target, Data.RangeSectionMap topRangeSectionMap)
{
_target = target;
_topRangeSectionMap = topRangeSectionMap;
_rangeSectionMapLookup = ExecutionManagerHelpers.RangeSectionMap.Create(_target);
INibbleMap nibbleMap = T.Create(_target);
_eeJitManager = new EEJitManager(_target, nibbleMap);
_r2rJitManager = new ReadyToRunJitManager(_target);
_interpreterJitManager = new InterpreterJitManager(_target, nibbleMap);
}
public void Flush()
{
_codeInfos.Clear();
}
// Note, because of RelativeOffset, this code info is per code pointer, not per method
private sealed class CodeBlock
{
public TargetPointer StartAddress { get; }
public TargetPointer MethodDescAddress { get; }
public TargetPointer JitManagerAddress { get; }
public TargetNUInt RelativeOffset { get; }
public CodeBlock(TargetPointer startAddress, TargetPointer methodDesc, TargetNUInt relativeOffset, TargetPointer jitManagerAddress)
{
StartAddress = startAddress;
MethodDescAddress = methodDesc;
RelativeOffset = relativeOffset;
JitManagerAddress = jitManagerAddress;
}
public bool Valid => JitManagerAddress != TargetPointer.Null;
}
[Flags]
private enum RangeSectionFlags : int
{
CodeHeap = 0x02,
RangeList = 0x04,
Interpreter = 0x08,
}
// Mirrors the native CodeHeap::CodeHeapType enum in codeman.h.
// Used to interpret the raw byte stored in the target process.
private enum CodeHeapType : byte
{
LoaderCodeHeap = 0,
HostCodeHeap = 1,
UnknownCodeHeap = 0xff,
}
private enum ExceptionClauseFlags_1 : uint
{
Filter = 0x1,
Finally = 0x2,
Fault = 0x4,
CachedClass = 0x10000000,
}
// Mirrors StubCodeBlockKind in codeman.h
private enum StubKind : int
{
Unknown = 0,
JumpStub = 1,
DynamicHelper = 3,
StubPrecode = 4,
FixupPrecode = 5,
VSDDispatchStub = 6,
VSDResolveStub = 7,
VSDLookupStub = 8,
VSDVTableStub = 9,
CallCountingStub = 10,
}
private abstract class JitManager
{
public Target Target { get; }
protected JitManager(Target target)
{
Target = target;
}
public abstract bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info);
public abstract void GetMethodRegionInfo(
RangeSection rangeSection,
TargetCodePointer jittedCodeAddress,
out uint hotSize,
out TargetPointer coldStart,
out uint coldSize);
public abstract TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress);
public abstract TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out bool hasFlagByte);
public abstract void GetGCInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, out TargetPointer gcInfo, out uint gcVersion);
public abstract void GetExceptionClauses(RangeSection rangeSection, CodeBlockHandle codeInfoHandle, out TargetPointer startAddr, out TargetPointer endAddr);
public abstract CodeKind GetCodeKind(RangeSection rangeSection, TargetCodePointer jittedCodeAddress);
}
private sealed class RangeSection
{
public readonly Data.RangeSection? Data;
public RangeSection()
{
Data = default;
}
public RangeSection(Data.RangeSection rangeSection)
{
Data = rangeSection;
}
private bool HasFlags(RangeSectionFlags mask) => (Data!.Flags & (int)mask) != 0;
internal bool IsRangeList => HasFlags(RangeSectionFlags.RangeList);
internal bool IsCodeHeap => HasFlags(RangeSectionFlags.CodeHeap);
internal bool IsInterpreter => HasFlags(RangeSectionFlags.Interpreter);
internal bool HasR2RModule => Data!.R2RModule != TargetPointer.Null;
internal static bool IsStubCodeBlock(Target target, TargetPointer codeHeaderIndirect)
{
byte stubCodeBlockLast = target.ReadGlobal<byte>(Constants.Globals.StubCodeBlockLast);
return codeHeaderIndirect.Value <= stubCodeBlockLast;
}
internal static RangeSection Find(Target target, Data.RangeSectionMap topRangeSectionMap, ExecutionManagerHelpers.RangeSectionMap rangeSectionLookup, TargetCodePointer jittedCodeAddress)
{
TargetPointer rangeSectionFragmentPtr = rangeSectionLookup.FindFragment(target, topRangeSectionMap, jittedCodeAddress);
// The lowest level of the range section map covers a large address space which may contain multiple small fragments.
// Iterate over them to find the one that contains the jitted code address.
while (rangeSectionFragmentPtr != TargetPointer.Null)
{
Data.RangeSectionFragment curFragment = target.ProcessedData.GetOrAdd<Data.RangeSectionFragment>(rangeSectionFragmentPtr);
if (curFragment.Contains(jittedCodeAddress))
{
break;
}
rangeSectionFragmentPtr = curFragment.Next;
}
if (rangeSectionFragmentPtr == TargetPointer.Null)
{
return new RangeSection();
}
Data.RangeSectionFragment fragment = target.ProcessedData.GetOrAdd<Data.RangeSectionFragment>(rangeSectionFragmentPtr);
Data.RangeSection rangeSection = target.ProcessedData.GetOrAdd<Data.RangeSection>(fragment.RangeSection);
if (rangeSection.NextForDelete != TargetPointer.Null)
{
return new RangeSection();
}
return new RangeSection(rangeSection);
}
}
private JitManager? GetJitManager(RangeSection rangeSection)
{
if (rangeSection.IsInterpreter)
{
return _interpreterJitManager;
}
else if (rangeSection.Data!.R2RModule != TargetPointer.Null)
{
return _r2rJitManager;
}
else if (rangeSection.IsCodeHeap)
{
return _eeJitManager;
}
else
{
return null;
}
}
private CodeBlock? GetCodeBlock(TargetCodePointer jittedCodeAddress)
{
RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, jittedCodeAddress);
if (range.Data == null)
{
return null;
}
JitManager? jitManager = GetJitManager(range);
if (jitManager?.GetMethodInfo(range, jittedCodeAddress, out CodeBlock? info) == true)
{
return info;
}
else
{
return null;
}
}
CodeBlockHandle? IExecutionManager.GetCodeBlockHandle(TargetCodePointer ip)
{
TargetPointer key = ip.AsTargetPointer; // FIXME: thumb bit. It's harmless (we potentialy have 2 cache entries per IP), but we should fix it
if (_codeInfos.ContainsKey(key))
{
return new CodeBlockHandle(key);
}
CodeBlock? info = GetCodeBlock(ip);
if (info == null || !info.Valid)
{
return null;
}
_codeInfos.TryAdd(key, info);
return new CodeBlockHandle(key);
}
TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle)
{
if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info))
throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}");
return info.MethodDescAddress;
}
TargetPointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHandle)
{
if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info))
throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}");
return info.StartAddress;
}
TargetPointer IExecutionManager.GetFuncletStartAddress(CodeBlockHandle codeInfoHandle)
{
RangeSection range = RangeSectionFromCodeBlockHandle(codeInfoHandle);
if (range.Data == null)
throw new InvalidOperationException("Unable to get runtime function address");
JitManager? jitManager = GetJitManager(range);
TargetPointer runtimeFunctionPtr = jitManager?.GetUnwindInfo(range, codeInfoHandle.Address.Value) ?? TargetPointer.Null;
if (runtimeFunctionPtr == TargetPointer.Null)
throw new InvalidOperationException("Unable to get runtime function address");
Data.RuntimeFunction runtimeFunction = _target.ProcessedData.GetOrAdd<Data.RuntimeFunction>(runtimeFunctionPtr);
// TODO(cdac): EXCEPTION_DATA_SUPPORTS_FUNCTION_FRAGMENTS, implement iterating over fragments until finding
// non-fragment RuntimeFunction
return range.Data.RangeBegin + runtimeFunction.BeginAddress;
}
void IExecutionManager.GetMethodRegionInfo(CodeBlockHandle codeInfoHandle, out uint hotSize, out TargetPointer coldStart, out uint coldSize)
{
hotSize = 0;
coldStart = TargetPointer.Null;
coldSize = 0;
RangeSection range = RangeSectionFromCodeBlockHandle(codeInfoHandle);
if (range.Data == null)
throw new InvalidOperationException("Unable to get runtime function address");
JitManager? jitManager = GetJitManager(range);
jitManager?.GetMethodRegionInfo(range, codeInfoHandle.Address.Value, out hotSize, out coldStart, out coldSize);
}
TargetPointer IExecutionManager.NonVirtualEntry2MethodDesc(TargetCodePointer entrypoint)
{
if (_target.ReadGlobal<byte>(Constants.Globals.FeaturePortableEntrypoints) != 0)
{
Data.PortableEntryPoint portableEntryPoint = _target.ProcessedData.GetOrAdd<Data.PortableEntryPoint>(entrypoint.AsTargetPointer);
return portableEntryPoint.MethodDesc;
}
RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, entrypoint);
if (range.Data == null)
return TargetPointer.Null;
if (range.IsRangeList)
{
// An address may fall within a precode RangeSection without actually being a
// valid precode (e.g., a MethodDesc address that shares the same memory range).
// GetMethodDescFromStubAddress throws InvalidOperationException when the bytes
// at the address don't match any known precode type. The DAC's C++ implementation
// returns NULL in this case, so we match that behavior by returning TargetPointer.Null.
IPrecodeStubs precodeStubs = _target.Contracts.PrecodeStubs;
try
{
return precodeStubs.GetMethodDescFromStubAddress(entrypoint);
}
catch (InvalidOperationException)
{
return TargetPointer.Null;
}
}
else
{
JitManager? jitManager = GetJitManager(range);
if (jitManager?.GetMethodInfo(range, entrypoint, out CodeBlock? info) == true && info != null)
{
return info.MethodDescAddress;
}
}
return TargetPointer.Null;
}
bool IExecutionManager.IsFunclet(CodeBlockHandle codeInfoHandle)
{
// Interpreter code has no native unwind info and therefore no funclets.
TargetPointer startAddress = ((IExecutionManager)this).GetStartAddress(codeInfoHandle);
if (((IExecutionManager)this).GetCodeKind(new TargetCodePointer(startAddress.Value)) == CodeKind.Interpreter)
return false;
return startAddress != ((IExecutionManager)this).GetFuncletStartAddress(codeInfoHandle);
}
bool IExecutionManager.IsFilterFunclet(CodeBlockHandle codeInfoHandle)
{
if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info))
throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}");
IExecutionManager eman = this;
if (!eman.IsFunclet(codeInfoHandle))
return false;
TargetPointer funcletStartAddress = eman.GetFuncletStartAddress(codeInfoHandle);
uint funcletStartOffset = (uint)(funcletStartAddress - info.StartAddress);
List<ExceptionClauseInfo> clauses = eman.GetExceptionClauses(codeInfoHandle);
foreach (ExceptionClauseInfo clause in clauses)
{
if (clause.ClauseType == ExceptionClauseInfo.ExceptionClauseFlags.Filter && clause.FilterOffset == funcletStartOffset)
return true;
}
return false;
}
TargetPointer IExecutionManager.GetUnwindInfo(CodeBlockHandle codeInfoHandle)
{
RangeSection range = RangeSectionFromCodeBlockHandle(codeInfoHandle);
if (range.Data == null)
return TargetPointer.Null;
JitManager? jitManager = GetJitManager(range);
return jitManager?.GetUnwindInfo(range, codeInfoHandle.Address.Value) ?? TargetPointer.Null;
}
TargetPointer IExecutionManager.GetUnwindInfoBaseAddress(CodeBlockHandle codeInfoHandle)
{
RangeSection range = RangeSectionFromCodeBlockHandle(codeInfoHandle);
if (range.Data == null)
throw new InvalidOperationException($"{nameof(RangeSection)} not found for {codeInfoHandle.Address}");
return range.Data.RangeBegin;
}
TargetPointer IExecutionManager.GetDebugInfo(CodeBlockHandle codeInfoHandle, out bool hasFlagByte)
{
hasFlagByte = false;
RangeSection range = RangeSectionFromCodeBlockHandle(codeInfoHandle);
if (range.Data == null)
return TargetPointer.Null;
JitManager? jitManager = GetJitManager(range);
return jitManager?.GetDebugInfo(range, codeInfoHandle.Address.Value, out hasFlagByte) ?? TargetPointer.Null;
}
void IExecutionManager.GetGCInfo(CodeBlockHandle codeInfoHandle, out TargetPointer gcInfo, out uint gcVersion)
{
gcInfo = TargetPointer.Null;
gcVersion = 0;
RangeSection range = RangeSectionFromCodeBlockHandle(codeInfoHandle);
if (range.Data == null)
return;
JitManager? jitManager = GetJitManager(range);
jitManager?.GetGCInfo(range, codeInfoHandle.Address.Value, out gcInfo, out gcVersion);
}
TargetNUInt IExecutionManager.GetRelativeOffset(CodeBlockHandle codeInfoHandle)
{
if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info))
throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}");
return info.RelativeOffset;
}
TargetPointer IExecutionManager.FindReadyToRunModule(TargetPointer address)
{
// Use the range section map to find the RangeSection containing the address.
// The R2R range section covers the entire PE image (code + data), so this
// works for import section addresses used by FindGCRefMap.
TargetCodePointer codeAddr = CodePointerUtils.CodePointerFromAddress(address, _target);
RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeAddr);
if (range.Data is null)
return TargetPointer.Null;
return range.Data.R2RModule;
}
JitManagerInfo IExecutionManager.GetEEJitManagerInfo()
{
TargetPointer eeJitManagerPtr = _target.ReadGlobalPointer(Constants.Globals.EEJitManagerAddress);
TargetPointer eeJitManagerAddr = _target.ReadPointer(eeJitManagerPtr);
Data.EEJitManager jitManager = _target.ProcessedData.GetOrAdd<Data.EEJitManager>(eeJitManagerAddr);
return new JitManagerInfo
{
ManagerAddress = eeJitManagerAddr,
CodeType = 0, // miManaged | miIL
HeapListAddress = jitManager.AllCodeHeaps,
};
}
private ICodeHeapInfo GetCodeHeapInfo(TargetPointer codeHeapAddress)
{
Data.CodeHeap codeHeap = _target.ProcessedData.GetOrAdd<Data.CodeHeap>(codeHeapAddress);
return (CodeHeapType)codeHeap.HeapType switch
{
CodeHeapType.LoaderCodeHeap => new Contracts.LoaderCodeHeapInfo(codeHeapAddress,
_target.ProcessedData.GetOrAdd<Data.LoaderCodeHeap>(codeHeapAddress).LoaderHeap),
CodeHeapType.HostCodeHeap => new Contracts.HostCodeHeapInfo(codeHeapAddress,
_target.ProcessedData.GetOrAdd<Data.HostCodeHeap>(codeHeapAddress).BaseAddress,
_target.ProcessedData.GetOrAdd<Data.HostCodeHeap>(codeHeapAddress).CurrentAddress),
_ => new Contracts.UnknownCodeHeapInfo(),
};
}
IEnumerable<ICodeHeapInfo> IExecutionManager.GetCodeHeapInfos()
{
TargetPointer heapListAddress = ((IExecutionManager)this).GetEEJitManagerInfo().HeapListAddress;
TargetPointer nodeAddr = heapListAddress;
while (nodeAddr != TargetPointer.Null)
{
Data.CodeHeapListNode node = _target.ProcessedData.GetOrAdd<Data.CodeHeapListNode>(nodeAddr);
yield return GetCodeHeapInfo(node.Heap);
nodeAddr = node.Next;
}
}
private RangeSection RangeSectionFromCodeBlockHandle(CodeBlockHandle codeInfoHandle)
{
if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info))
throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}");
RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeInfoHandle.Address.Value);
return range;
}
private static ExceptionClauseInfo.ExceptionClauseFlags GetExceptionClauseFlags(uint flags)
{
if ((flags & (uint)ExceptionClauseFlags_1.Fault) != 0) return ExceptionClauseInfo.ExceptionClauseFlags.Fault;
if ((flags & (uint)ExceptionClauseFlags_1.Finally) != 0) return ExceptionClauseInfo.ExceptionClauseFlags.Finally;
if ((flags & (uint)ExceptionClauseFlags_1.Filter) != 0) return ExceptionClauseInfo.ExceptionClauseFlags.Filter;
return ExceptionClauseInfo.ExceptionClauseFlags.Typed;
}
private static bool IsFilterHandler(ExceptionClauseInfo.ExceptionClauseFlags flags) => flags == ExceptionClauseInfo.ExceptionClauseFlags.Filter;
private static bool IsTypedHandler(ExceptionClauseInfo.ExceptionClauseFlags flags) => flags == ExceptionClauseInfo.ExceptionClauseFlags.Typed;
private static bool HasCachedTypeHandle(IExceptionClauseData clause) => (clause.Flags & (uint)ExceptionClauseFlags_1.CachedClass) != 0;
private bool IsObjectType(TargetPointer moduleAddr, uint classToken)
{
ILoader loader = _target.Contracts.Loader;
ModuleHandle module = loader.GetModuleHandleFromModulePtr(moduleAddr);
ModuleLookupTables tables = loader.GetLookupTables(module);
TargetPointer resolvedMethodTable = (EcmaMetadataUtils.TokenType)(classToken & EcmaMetadataUtils.TokenTypeMask) switch
{
EcmaMetadataUtils.TokenType.mdtTypeDef => loader.GetModuleLookupMapElement(tables.TypeDefToMethodTable, classToken, out _),
EcmaMetadataUtils.TokenType.mdtTypeRef => loader.GetModuleLookupMapElement(tables.TypeRefToMethodTable, classToken, out _),
_ => TargetPointer.Null,
};
if (resolvedMethodTable == TargetPointer.Null)
return false;
TargetPointer objectMethodTable = _target.ReadPointer(
_target.ReadGlobalPointer(Constants.Globals.ObjectMethodTable));
return resolvedMethodTable == objectMethodTable;
}
List<ExceptionClauseInfo> IExecutionManager.GetExceptionClauses(CodeBlockHandle codeInfoHandle)
{
RangeSection range = RangeSectionFromCodeBlockHandle(codeInfoHandle);
if (range.Data == null)
return new List<ExceptionClauseInfo>();
JitManager? jitManager = GetJitManager(range);
if (jitManager == null)
return new List<ExceptionClauseInfo>();
jitManager.GetExceptionClauses(range, codeInfoHandle, out TargetPointer startAddr, out TargetPointer endAddr);
bool isR2R = jitManager is ReadyToRunJitManager;
DataType clauseType = isR2R ? DataType.R2RExceptionClause : DataType.EEExceptionClause;
uint clauseSize = _target.GetTypeInfo(clauseType).Size!.Value;
TargetPointer methodDescPtr = ((IExecutionManager)this).GetMethodDesc(codeInfoHandle);
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
MethodDescHandle mdHandle = rts.GetMethodDescHandle(methodDescPtr);
TargetPointer mtPtr = rts.GetMethodTable(mdHandle);
TypeHandle th = rts.GetTypeHandle(mtPtr);
TargetPointer handleModuleAddr = rts.GetModule(th);
List<ExceptionClauseInfo> exceptionClauses = new List<ExceptionClauseInfo>();
for (TargetPointer addr = startAddr; addr < endAddr; addr += clauseSize)
{
IExceptionClauseData entry = isR2R
? _target.ProcessedData.GetOrAdd<R2RExceptionClause>(addr)
: _target.ProcessedData.GetOrAdd<EEExceptionClause>(addr);
ExceptionClauseInfo.ExceptionClauseFlags flags = GetExceptionClauseFlags(entry.Flags);
uint? filterOffset = IsFilterHandler(flags) ? entry.FilterOffset : null;
TargetNUInt? typeHandle = null;
bool? isCatchAllHandler = null;
TargetPointer? moduleAddr = null;
uint? classToken = null;
if (IsTypedHandler(flags))
{
if (HasCachedTypeHandle(entry) && !isR2R) // Dynamic method path: we only have a cached type handle, no token.
{
typeHandle = ((EEExceptionClause)entry).TypeHandle;
TargetPointer objectMethodTable = _target.ReadPointer(
_target.ReadGlobalPointer(Constants.Globals.ObjectMethodTable));
isCatchAllHandler = typeHandle.Value.Value == objectMethodTable.Value;
}
else
{
isCatchAllHandler = IsObjectType(handleModuleAddr, entry.ClassToken);
moduleAddr = handleModuleAddr;
classToken = entry.ClassToken;
}
}
exceptionClauses.Add(new ExceptionClauseInfo
{
ClauseType = flags,
IsCatchAllHandler = isCatchAllHandler,
TryStartPC = entry.TryStartPC,
TryEndPC = entry.TryEndPC,
HandlerStartPC = entry.HandlerStartPC,
HandlerEndPC = entry.HandlerEndPC,
FilterOffset = filterOffset,
ClassToken = classToken,
TypeHandle = typeHandle,
ModuleAddr = moduleAddr,
});
}
return exceptionClauses;
}
private static CodeKind GetStubKind(StubKind stubKind)
{
return stubKind switch
{
StubKind.JumpStub => CodeKind.JumpStub,
StubKind.DynamicHelper => CodeKind.DynamicHelper,
StubKind.StubPrecode => CodeKind.StubPrecode,
StubKind.FixupPrecode => CodeKind.FixupPrecode,
StubKind.VSDDispatchStub => CodeKind.VSD_DispatchStub,
StubKind.VSDResolveStub => CodeKind.VSD_ResolveStub,
StubKind.VSDLookupStub => CodeKind.VSD_LookupStub,
StubKind.VSDVTableStub => CodeKind.VSD_VTableStub,
StubKind.CallCountingStub => CodeKind.CallCountingStub,
_ => CodeKind.Unknown,
};
}
public CodeKind GetCodeKind(TargetCodePointer codeAddress)
{
RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, codeAddress);
if (range.Data == null)
return CodeKind.Unknown;
// check if this is a stub
JitManager? jitManager = GetJitManager(range);
if (jitManager == null)
{
CodeRangeMapRangeList rangeList = _target.ProcessedData.GetOrAdd<Data.CodeRangeMapRangeList>(range.Data.RangeList);
return GetStubKind((StubKind)rangeList.RangeListType);
}
return jitManager.GetCodeKind(range, codeAddress);
}
}
|